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