web/lib/Zend/Measure/Number.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     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 }