PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Measure/Number.php

https://github.com/mfairchild365/zf2
PHP | 414 lines | 264 code | 53 blank | 97 comment | 27 complexity | 4f91d4af0b0fb44bb7bcbfe955e9a554 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Measure
  17. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. */
  20. /**
  21. * @namespace
  22. */
  23. namespace Zend\Measure;
  24. use Zend\Locale;
  25. use Zend\Locale\Math;
  26. /**
  27. * Class for handling number conversions
  28. *
  29. * This class can only handle numbers without precision
  30. *
  31. * @uses Zend\Locale
  32. * @uses Zend\Locale\Math
  33. * @category Zend
  34. * @package Zend_Measure
  35. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  36. * @license http://framework.zend.com/license/new-bsd New BSD License
  37. */
  38. class Number extends AbstractMeasure
  39. {
  40. const STANDARD = 'DECIMAL';
  41. const BINARY = 'BINARY';
  42. const TERNARY = 'TERNARY';
  43. const QUATERNARY = 'QUATERNARY';
  44. const QUINARY = 'QUINARY';
  45. const SENARY = 'SENARY';
  46. const SEPTENARY = 'SEPTENARY';
  47. const OCTAL = 'OCTAL';
  48. const NONARY = 'NONARY';
  49. const DECIMAL = 'DECIMAL';
  50. const DUODECIMAL = 'DUODECIMAL';
  51. const HEXADECIMAL = 'HEXADECIMAL';
  52. const ROMAN = 'ROMAN';
  53. /**
  54. * Calculations for all number units
  55. *
  56. * @var array
  57. */
  58. protected $_units = array(
  59. 'BINARY' => array(2, '⑵'),
  60. 'TERNARY' => array(3, '⑶'),
  61. 'QUATERNARY' => array(4, '⑷'),
  62. 'QUINARY' => array(5, '⑸'),
  63. 'SENARY' => array(6, '⑹'),
  64. 'SEPTENARY' => array(7, '⑺'),
  65. 'OCTAL' => array(8, '⑻'),
  66. 'NONARY' => array(9, '⑼'),
  67. 'DECIMAL' => array(10, '⑽'),
  68. 'DUODECIMAL' => array(12, '⑿'),
  69. 'HEXADECIMAL' => array(16, '⒃'),
  70. 'ROMAN' => array(99, ''),
  71. 'STANDARD' => 'DECIMAL'
  72. );
  73. /**
  74. * Definition of all roman signs
  75. *
  76. * @var array $_roman
  77. */
  78. private static $_roman = array(
  79. 'I' => 1,
  80. 'A' => 4,
  81. 'V' => 5,
  82. 'B' => 9,
  83. 'X' => 10,
  84. 'E' => 40,
  85. 'L' => 50,
  86. 'F' => 90,
  87. 'C' => 100,
  88. 'G' => 400,
  89. 'D' => 500,
  90. 'H' => 900,
  91. 'M' => 1000,
  92. 'J' => 4000,
  93. 'P' => 5000,
  94. 'K' => 9000,
  95. 'Q' => 10000,
  96. 'N' => 40000,
  97. 'R' => 50000,
  98. 'W' => 90000,
  99. 'S' => 100000,
  100. 'Y' => 400000,
  101. 'T' => 500000,
  102. 'Z' => 900000,
  103. 'U' => 1000000
  104. );
  105. /**
  106. * Convertion table for roman signs
  107. *
  108. * @var array $_romanconvert
  109. */
  110. private static $_romanconvert = array(
  111. '/_V/' => '/P/',
  112. '/_X/' => '/Q/',
  113. '/_L/' => '/R/',
  114. '/_C/' => '/S/',
  115. '/_D/' => '/T/',
  116. '/_M/' => '/U/',
  117. '/IV/' => '/A/',
  118. '/IX/' => '/B/',
  119. '/XL/' => '/E/',
  120. '/XC/' => '/F/',
  121. '/CD/' => '/G/',
  122. '/CM/' => '/H/',
  123. '/M_V/'=> '/J/',
  124. '/MQ/' => '/K/',
  125. '/QR/' => '/N/',
  126. '/QS/' => '/W/',
  127. '/ST/' => '/Y/',
  128. '/SU/' => '/Z/'
  129. );
  130. /**
  131. * Zend\Measure\AbstractMeasure is an abstract class for the different measurement types
  132. *
  133. * @param integer $value Value
  134. * @param string $type (Optional) A Zend\Measure\Number Type
  135. * @param string|Zend\Locale\Locale $locale (Optional) A Zend\Locale\Locale
  136. * @throws Zend\Measure\Exception When language is unknown
  137. * @throws Zend\Measure\Exception When type is unknown
  138. */
  139. public function __construct($value, $type, $locale = null)
  140. {
  141. if (($type !== null) and (Locale\Locale::isLocale($type))) {
  142. $locale = $type;
  143. $type = null;
  144. }
  145. if ($locale === null) {
  146. $locale = new Locale\Locale();
  147. }
  148. if (!Locale\Locale::isLocale($locale, true)) {
  149. if (!Locale\Locale::isLocale($locale, false)) {
  150. throw new Exception("Language (" . (string) $locale . ") is unknown");
  151. }
  152. $locale = new Locale\Locale($locale);
  153. }
  154. $this->_locale = (string) $locale;
  155. if ($type === null) {
  156. $type = $this->_units['STANDARD'];
  157. }
  158. if (isset($this->_units[$type]) === false) {
  159. throw new Exception("Type ($type) is unknown");
  160. }
  161. $this->setValue($value, $type, $this->_locale);
  162. }
  163. /**
  164. * Set a new value
  165. *
  166. * @param integer $value Value
  167. * @param string $type (Optional) A Zend\Measure\Number Type
  168. * @param string|Zend\Locale\Locale $locale (Optional) A Zend\Locale\Locale Type
  169. * @throws Zend\Measure\Exception
  170. */
  171. public function setValue($value, $type = null, $locale = null)
  172. {
  173. if (empty($locale)) {
  174. $locale = $this->_locale;
  175. }
  176. if (empty($this->_units[$type])) {
  177. throw new Exception('unknown type of number:' . $type);
  178. }
  179. switch($type) {
  180. case 'BINARY':
  181. preg_match('/[01]+/', $value, $ergebnis);
  182. $value = $ergebnis[0];
  183. break;
  184. case 'TERNARY':
  185. preg_match('/[012]+/', $value, $ergebnis);
  186. $value = $ergebnis[0];
  187. break;
  188. case 'QUATERNARY':
  189. preg_match('/[0123]+/', $value, $ergebnis);
  190. $value = $ergebnis[0];
  191. break;
  192. case 'QUINARY':
  193. preg_match('/[01234]+/', $value, $ergebnis);
  194. $value = $ergebnis[0];
  195. break;
  196. case 'SENARY':
  197. preg_match('/[012345]+/', $value, $ergebnis);
  198. $value = $ergebnis[0];
  199. break;
  200. case 'SEPTENARY':
  201. preg_match('/[0123456]+/', $value, $ergebnis);
  202. $value = $ergebnis[0];
  203. break;
  204. case 'OCTAL':
  205. preg_match('/[01234567]+/', $value, $ergebnis);
  206. $value = $ergebnis[0];
  207. break;
  208. case 'NONARY':
  209. preg_match('/[012345678]+/', $value, $ergebnis);
  210. $value = $ergebnis[0];
  211. break;
  212. case 'DUODECIMAL':
  213. preg_match('/[0123456789AB]+/', strtoupper($value), $ergebnis);
  214. $value = $ergebnis[0];
  215. break;
  216. case 'HEXADECIMAL':
  217. preg_match('/[0123456789ABCDEF]+/', strtoupper($value), $ergebnis);
  218. $value = $ergebnis[0];
  219. break;
  220. case 'ROMAN':
  221. preg_match('/[IVXLCDM_]+/', strtoupper($value), $ergebnis);
  222. $value = $ergebnis[0];
  223. break;
  224. default:
  225. try {
  226. $value = Locale\Format::getInteger($value, array('locale' => $locale));
  227. } catch (\Exception $e) {
  228. throw new Exception($e->getMessage(), $e->getCode(), $e);
  229. }
  230. if (call_user_func(Math::$comp, $value, 0) < 0) {
  231. $value = call_user_func(Math::$sqrt, call_user_func(Math::$pow, $value, 2));
  232. }
  233. break;
  234. }
  235. $this->_value = $value;
  236. $this->_type = $type;
  237. }
  238. /**
  239. * Convert input to decimal value string
  240. *
  241. * @param integer $input Input string
  242. * @param string $type Type from which to convert to decimal
  243. * @return string
  244. */
  245. private function _toDecimal($input, $type)
  246. {
  247. $value = '';
  248. // Convert base xx values
  249. if ($this->_units[$type][0] <= 16) {
  250. $split = str_split($input);
  251. $length = strlen($input);
  252. for ($x = 0; $x < $length; ++$x) {
  253. $split[$x] = hexdec($split[$x]);
  254. $value = call_user_func(Math::$add, $value,
  255. call_user_func(Math::$mul, $split[$x],
  256. call_user_func(Math::$pow, $this->_units[$type][0], ($length - $x - 1))));
  257. }
  258. }
  259. // Convert roman numbers
  260. if ($type === 'ROMAN') {
  261. $input = strtoupper($input);
  262. $input = preg_replace(array_keys(self::$_romanconvert), array_values(self::$_romanconvert), $input);
  263. $split = preg_split('//', strrev($input), -1, PREG_SPLIT_NO_EMPTY);
  264. for ($x =0; $x < count($split); $x++) {
  265. if ($split[$x] == '/') {
  266. continue;
  267. }
  268. $num = self::$_roman[$split[$x]];
  269. if (($x > 0 and ($split[$x-1] != '/') and ($num < self::$_roman[$split[$x-1]]))) {
  270. $num -= $num;
  271. }
  272. $value += $num;
  273. }
  274. str_replace('/', '', $value);
  275. }
  276. return $value;
  277. }
  278. /**
  279. * Convert input to type value string
  280. *
  281. * @param integer $value Input string
  282. * @param string $type Type to convert to
  283. * @return string
  284. * @throws Zend\Measure\Exception When more than 200 digits are calculated
  285. */
  286. private function _fromDecimal($value, $type)
  287. {
  288. $tempvalue = $value;
  289. if ($this->_units[$type][0] <= 16) {
  290. $newvalue = '';
  291. $count = 200;
  292. $base = $this->_units[$type][0];
  293. while (call_user_func(Math::$comp, $value, 0, 25) <> 0) {
  294. $target = call_user_func(Math::$mod, $value, $base);
  295. $newvalue = strtoupper(dechex($target)) . $newvalue;
  296. $value = call_user_func(Math::$sub, $value, $target, 0);
  297. $value = call_user_func(Math::$div, $value, $base, 0);
  298. --$count;
  299. if ($count === 0) {
  300. throw new Exception("Your value '$tempvalue' cannot be processed because it extends 200 digits");
  301. }
  302. }
  303. if ($newvalue === '') {
  304. $newvalue = '0';
  305. }
  306. }
  307. if ($type === 'ROMAN') {
  308. $i = 0;
  309. $newvalue = '';
  310. $romanval = array_values(array_reverse(self::$_roman));
  311. $romankey = array_keys(array_reverse(self::$_roman));
  312. $count = 200;
  313. while (call_user_func(Math::$comp, $value, 0, 25) <> 0) {
  314. while ($value >= $romanval[$i]) {
  315. $value -= $romanval[$i];
  316. $newvalue .= $romankey[$i];
  317. if ($value < 1) {
  318. break;
  319. }
  320. --$count;
  321. if ($count === 0) {
  322. throw new Exception("Your value '$tempvalue' cannot be processed because it extends 200 digits");
  323. }
  324. }
  325. $i++;
  326. }
  327. $newvalue = str_replace('/', '', preg_replace(array_values(self::$_romanconvert), array_keys(self::$_romanconvert), $newvalue));
  328. }
  329. return $newvalue;
  330. }
  331. /**
  332. * Set a new type, and convert the value
  333. *
  334. * @param string $type New type to set
  335. * @throws Zend\Measure\Exception When a unknown type is given
  336. * @return void
  337. */
  338. public function setType($type)
  339. {
  340. if (empty($this->_units[$type]) === true) {
  341. throw new Exception('Unknown type of number:' . $type);
  342. }
  343. $value = $this->_toDecimal($this->getValue(-1), $this->getType(-1));
  344. $value = $this->_fromDecimal($value, $type);
  345. $this->_value = $value;
  346. $this->_type = $type;
  347. }
  348. /**
  349. * Alias function for setType returning the converted unit
  350. * Default is 0 as this class only handles numbers without precision
  351. *
  352. * @param string $type Type to convert to
  353. * @param integer $round (Optional) Precision to add, will always be 0
  354. * @return string
  355. */
  356. public function convertTo($type, $round = 0, $locale = null)
  357. {
  358. $this->setType($type);
  359. return $this->toString($round, $locale);
  360. }
  361. }