File indexing completed on 2024-12-22 05:36:52

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category  Zend
0016  * @package   Zend_Measure
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license   http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version   $Id$
0020  */
0021 
0022 /**
0023  * Implement needed classes
0024  */
0025 // require_once 'Zend/Measure/Abstract.php';
0026 // require_once 'Zend/Locale.php';
0027 
0028 /**
0029  * Class for handling number conversions
0030  *
0031  * This class can only handle numbers without precision
0032  *
0033  * @category   Zend
0034  * @package    Zend_Measure
0035  * @subpackage Zend_Measure_Number
0036  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0037  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0038  */
0039 class Zend_Measure_Number extends Zend_Measure_Abstract
0040 {
0041     const STANDARD = 'DECIMAL';
0042 
0043     const BINARY      = 'BINARY';
0044     const TERNARY     = 'TERNARY';
0045     const QUATERNARY  = 'QUATERNARY';
0046     const QUINARY     = 'QUINARY';
0047     const SENARY      = 'SENARY';
0048     const SEPTENARY   = 'SEPTENARY';
0049     const OCTAL       = 'OCTAL';
0050     const NONARY      = 'NONARY';
0051     const DECIMAL     = 'DECIMAL';
0052     const DUODECIMAL  = 'DUODECIMAL';
0053     const HEXADECIMAL = 'HEXADECIMAL';
0054     const ROMAN       = 'ROMAN';
0055 
0056     /**
0057      * Calculations for all number units
0058      *
0059      * @var array
0060      */
0061     protected $_units = array(
0062         'BINARY'      => array(2,  '⑵'),
0063         'TERNARY'     => array(3,  '⑶'),
0064         'QUATERNARY'  => array(4,  '⑷'),
0065         'QUINARY'     => array(5,  '⑸'),
0066         'SENARY'      => array(6,  '⑹'),
0067         'SEPTENARY'   => array(7,  '⑺'),
0068         'OCTAL'       => array(8,  '⑻'),
0069         'NONARY'      => array(9,  '⑼'),
0070         'DECIMAL'     => array(10, '⑽'),
0071         'DUODECIMAL'  => array(12, '⑿'),
0072         'HEXADECIMAL' => array(16, '⒃'),
0073         'ROMAN'       => array(99, ''),
0074         'STANDARD'    => 'DECIMAL'
0075     );
0076 
0077     /**
0078      * Definition of all roman signs
0079      *
0080      * @var array $_roman
0081      */
0082     private static $_roman = array(
0083         'I' => 1,
0084         'A' => 4,
0085         'V' => 5,
0086         'B' => 9,
0087         'X' => 10,
0088         'E' => 40,
0089         'L' => 50,
0090         'F' => 90,
0091         'C' => 100,
0092         'G' => 400,
0093         'D' => 500,
0094         'H' => 900,
0095         'M' => 1000,
0096         'J' => 4000,
0097         'P' => 5000,
0098         'K' => 9000,
0099         'Q' => 10000,
0100         'N' => 40000,
0101         'R' => 50000,
0102         'W' => 90000,
0103         'S' => 100000,
0104         'Y' => 400000,
0105         'T' => 500000,
0106         'Z' => 900000,
0107         'U' => 1000000
0108     );
0109 
0110     /**
0111      * Convertion table for roman signs
0112      *
0113      * @var array $_romanconvert
0114      */
0115     private static $_romanconvert = array(
0116         '/_V/' => '/P/',
0117         '/_X/' => '/Q/',
0118         '/_L/' => '/R/',
0119         '/_C/' => '/S/',
0120         '/_D/' => '/T/',
0121         '/_M/' => '/U/',
0122         '/IV/' => '/A/',
0123         '/IX/' => '/B/',
0124         '/XL/' => '/E/',
0125         '/XC/' => '/F/',
0126         '/CD/' => '/G/',
0127         '/CM/' => '/H/',
0128         '/M_V/'=> '/J/',
0129         '/MQ/' => '/K/',
0130         '/QR/' => '/N/',
0131         '/QS/' => '/W/',
0132         '/ST/' => '/Y/',
0133         '/SU/' => '/Z/'
0134     );
0135 
0136     /**
0137      * Zend_Measure_Abstract is an abstract class for the different measurement types
0138      *
0139      * @param  integer            $value  Value
0140      * @param  string             $type   (Optional) A Zend_Measure_Number Type
0141      * @param  string|Zend_Locale $locale (Optional) A Zend_Locale
0142      * @throws Zend_Measure_Exception When language is unknown
0143      * @throws Zend_Measure_Exception When type is unknown
0144      */
0145     public function __construct($value, $type, $locale = null)
0146     {
0147         if (($type !== null) and (Zend_Locale::isLocale($type, null, false))) {
0148             $locale = $type;
0149             $type = null;
0150         }
0151 
0152         if ($locale === null) {
0153             $locale = new Zend_Locale();
0154         }
0155 
0156         if (!Zend_Locale::isLocale($locale, true, false)) {
0157             if (!Zend_Locale::isLocale($locale, true, false)) {
0158                 // require_once 'Zend/Measure/Exception.php';
0159                 throw new Zend_Measure_Exception("Language (" . (string) $locale . ") is unknown");
0160             }
0161 
0162             $locale = new Zend_Locale($locale);
0163         }
0164 
0165         $this->_locale = (string) $locale;
0166 
0167         if ($type === null) {
0168             $type = $this->_units['STANDARD'];
0169         }
0170 
0171         if (isset($this->_units[$type]) === false) {
0172             // require_once 'Zend/Measure/Exception.php';
0173             throw new Zend_Measure_Exception("Type ($type) is unknown");
0174         }
0175 
0176         $this->setValue($value, $type, $this->_locale);
0177     }
0178 
0179     /**
0180      * Set a new value
0181      *
0182      * @param  integer            $value  Value
0183      * @param  string             $type   (Optional) A Zend_Measure_Number Type
0184      * @param  string|Zend_Locale $locale (Optional) A Zend_Locale Type
0185      * @throws Zend_Measure_Exception
0186      */
0187     public function setValue($value, $type = null, $locale = null)
0188     {
0189         if (empty($locale)) {
0190             $locale = $this->_locale;
0191         }
0192 
0193         if (empty($this->_units[$type])) {
0194             // require_once 'Zend/Measure/Exception.php';
0195             throw new Zend_Measure_Exception('unknown type of number:' . $type);
0196         }
0197 
0198         switch($type) {
0199             case 'BINARY':
0200                 preg_match('/[01]+/', $value, $ergebnis);
0201                 $value = $ergebnis[0];
0202                 break;
0203 
0204             case 'TERNARY':
0205                 preg_match('/[012]+/', $value, $ergebnis);
0206                 $value = $ergebnis[0];
0207                 break;
0208 
0209             case 'QUATERNARY':
0210                 preg_match('/[0123]+/', $value, $ergebnis);
0211                 $value = $ergebnis[0];
0212                 break;
0213 
0214             case 'QUINARY':
0215                 preg_match('/[01234]+/', $value, $ergebnis);
0216                 $value = $ergebnis[0];
0217                 break;
0218 
0219             case 'SENARY':
0220                 preg_match('/[012345]+/', $value, $ergebnis);
0221                 $value = $ergebnis[0];
0222                 break;
0223 
0224             case 'SEPTENARY':
0225                 preg_match('/[0123456]+/', $value, $ergebnis);
0226                 $value = $ergebnis[0];
0227                 break;
0228 
0229             case 'OCTAL':
0230                 preg_match('/[01234567]+/', $value, $ergebnis);
0231                 $value = $ergebnis[0];
0232                 break;
0233 
0234             case 'NONARY':
0235                 preg_match('/[012345678]+/', $value, $ergebnis);
0236                 $value = $ergebnis[0];
0237                 break;
0238 
0239             case 'DUODECIMAL':
0240                 preg_match('/[0123456789AB]+/', strtoupper($value), $ergebnis);
0241                 $value = $ergebnis[0];
0242                 break;
0243 
0244             case 'HEXADECIMAL':
0245                 preg_match('/[0123456789ABCDEF]+/', strtoupper($value), $ergebnis);
0246                 $value = $ergebnis[0];
0247                 break;
0248 
0249             case 'ROMAN':
0250                 preg_match('/[IVXLCDM_]+/', strtoupper($value), $ergebnis);
0251                 $value = $ergebnis[0];
0252                 break;
0253 
0254             default:
0255                 try {
0256                     $value = Zend_Locale_Format::getInteger($value, array('locale' => $locale));
0257                 } catch (Exception $e) {
0258                     // require_once 'Zend/Measure/Exception.php';
0259                     throw new Zend_Measure_Exception($e->getMessage(), $e->getCode(), $e);
0260                 }
0261                 if (call_user_func(Zend_Locale_Math::$comp, $value, 0) < 0) {
0262                     $value = call_user_func(Zend_Locale_Math::$sqrt, call_user_func(Zend_Locale_Math::$pow, $value, 2));
0263                 }
0264                 break;
0265         }
0266 
0267         $this->_value = $value;
0268         $this->_type  = $type;
0269     }
0270 
0271     /**
0272      * Convert input to decimal value string
0273      *
0274      * @param  integer $input Input string
0275      * @param  string  $type  Type from which to convert to decimal
0276      * @return string
0277      */
0278     private function _toDecimal($input, $type)
0279     {
0280         $value = '';
0281         // Convert base xx values
0282         if ($this->_units[$type][0] <= 16) {
0283             $split  = str_split($input);
0284             $length = strlen($input);
0285             for ($x = 0; $x < $length; ++$x) {
0286                 $split[$x] = hexdec($split[$x]);
0287                 $value     = call_user_func(Zend_Locale_Math::$add, $value,
0288                             call_user_func(Zend_Locale_Math::$mul, $split[$x],
0289                             call_user_func(Zend_Locale_Math::$pow, $this->_units[$type][0], ($length - $x - 1))));
0290             }
0291         }
0292 
0293         // Convert roman numbers
0294         if ($type === 'ROMAN') {
0295             $input = strtoupper($input);
0296             $input = preg_replace(array_keys(self::$_romanconvert), array_values(self::$_romanconvert), $input);
0297 
0298             $split = preg_split('//', strrev($input), -1, PREG_SPLIT_NO_EMPTY);
0299 
0300             for ($x =0; $x < sizeof($split); $x++) {
0301                 if ($split[$x] == '/') {
0302                     continue;
0303                 }
0304 
0305                 $num = self::$_roman[$split[$x]];
0306                 if (($x > 0 and ($split[$x-1] != '/') and ($num < self::$_roman[$split[$x-1]]))) {
0307                     $num -= $num;
0308                 }
0309 
0310                 $value += $num;
0311             }
0312 
0313             str_replace('/', '', $value);
0314         }
0315 
0316         return $value;
0317     }
0318 
0319     /**
0320      * Convert input to type value string
0321      *
0322      * @param  integer $value Input string
0323      * @param  string  $type  Type to convert to
0324      * @return string
0325      * @throws Zend_Measure_Exception When more than 200 digits are calculated
0326      */
0327     private function _fromDecimal($value, $type)
0328     {
0329         $tempvalue = $value;
0330         if ($this->_units[$type][0] <= 16) {
0331             $newvalue = '';
0332             $count    = 200;
0333             $base     = $this->_units[$type][0];
0334 
0335             while (call_user_func(Zend_Locale_Math::$comp, $value, 0, 25) <> 0) {
0336                 $target = call_user_func(Zend_Locale_Math::$mod, $value, $base);
0337 
0338                 $newvalue = strtoupper(dechex($target)) . $newvalue;
0339 
0340                 $value = call_user_func(Zend_Locale_Math::$sub, $value, $target, 0);
0341                 $value = call_user_func(Zend_Locale_Math::$div, $value, $base, 0);
0342 
0343                 --$count;
0344                 if ($count === 0) {
0345                     // require_once 'Zend/Measure/Exception.php';
0346                     throw new Zend_Measure_Exception("Your value '$tempvalue' cannot be processed because it extends 200 digits");
0347                 }
0348             }
0349 
0350             if ($newvalue === '') {
0351                 $newvalue = '0';
0352             }
0353         }
0354 
0355         if ($type === 'ROMAN') {
0356             $i        = 0;
0357             $newvalue = '';
0358             $romanval = array_values(array_reverse(self::$_roman));
0359             $romankey = array_keys(array_reverse(self::$_roman));
0360             $count    = 200;
0361             while (call_user_func(Zend_Locale_Math::$comp, $value, 0, 25) <> 0) {
0362                 while ($value >= $romanval[$i]) {
0363                     $value    -= $romanval[$i];
0364                     $newvalue .= $romankey[$i];
0365 
0366                     if ($value < 1) {
0367                         break;
0368                     }
0369 
0370                     --$count;
0371                     if ($count === 0) {
0372                         // require_once 'Zend/Measure/Exception.php';
0373                         throw new Zend_Measure_Exception("Your value '$tempvalue' cannot be processed because it extends 200 digits");
0374                     }
0375                 }
0376 
0377                 $i++;
0378             }
0379 
0380             $newvalue = str_replace('/', '', preg_replace(array_values(self::$_romanconvert), array_keys(self::$_romanconvert), $newvalue));
0381         }
0382 
0383         return $newvalue;
0384     }
0385 
0386     /**
0387      * Set a new type, and convert the value
0388      *
0389      * @param  string $type New type to set
0390      * @throws Zend_Measure_Exception When a unknown type is given
0391      * @return void
0392      */
0393     public function setType($type)
0394     {
0395         if (empty($this->_units[$type]) === true) {
0396             // require_once 'Zend/Measure/Exception.php';
0397             throw new Zend_Measure_Exception('Unknown type of number:' . $type);
0398         }
0399 
0400         $value = $this->_toDecimal($this->getValue(-1), $this->getType(-1));
0401         $value = $this->_fromDecimal($value, $type);
0402 
0403         $this->_value = $value;
0404         $this->_type  = $type;
0405     }
0406 
0407     /**
0408      * Alias function for setType returning the converted unit
0409      * Default is 0 as this class only handles numbers without precision
0410      *
0411      * @param  string  $type  Type to convert to
0412      * @param  integer $round (Optional) Precision to add, will always be 0
0413      * @return string
0414      */
0415     public function convertTo($type, $round = 0, $locale = null)
0416     {
0417         $this->setType($type);
0418 
0419         // Roman numerals do not need a formatting
0420         if ($this->getType() === self::ROMAN) {
0421             return $this->_value;
0422         }
0423 
0424         return $this->toString($round, $locale);
0425     }
0426 }