421 lines
13 KiB
PHP
421 lines
13 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* Zend Framework
|
||
|
*
|
||
|
* LICENSE
|
||
|
*
|
||
|
* This source file is subject to the new BSD license that is bundled
|
||
|
* with this package in the file LICENSE.txt.
|
||
|
* It is also available through the world-wide-web at this URL:
|
||
|
* http://framework.zend.com/license/new-bsd
|
||
|
* If you did not receive a copy of the license and are unable to
|
||
|
* obtain it through the world-wide-web, please send an email
|
||
|
* to license@zend.com so we can send you a copy immediately.
|
||
|
*
|
||
|
* @category Zend
|
||
|
* @package Zend_Measure
|
||
|
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
|
||
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||
|
* @version $Id: Number.php 16220 2009-06-21 19:49:21Z thomas $
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Implement needed classes
|
||
|
*/
|
||
|
require_once 'Zend/Measure/Abstract.php';
|
||
|
require_once 'Zend/Locale.php';
|
||
|
|
||
|
/**
|
||
|
* Class for handling number conversions
|
||
|
*
|
||
|
* This class can only handle numbers without precission
|
||
|
*
|
||
|
* @category Zend
|
||
|
* @package Zend_Measure
|
||
|
* @subpackage Zend_Measure_Number
|
||
|
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
|
||
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||
|
*/
|
||
|
class Zend_Measure_Number extends Zend_Measure_Abstract
|
||
|
{
|
||
|
const STANDARD = 'DECIMAL';
|
||
|
|
||
|
const BINARY = 'BINARY';
|
||
|
const TERNARY = 'TERNARY';
|
||
|
const QUATERNARY = 'QUATERNARY';
|
||
|
const QUINARY = 'QUINARY';
|
||
|
const SENARY = 'SENARY';
|
||
|
const SEPTENARY = 'SEPTENARY';
|
||
|
const OCTAL = 'OCTAL';
|
||
|
const NONARY = 'NONARY';
|
||
|
const DECIMAL = 'DECIMAL';
|
||
|
const DUODECIMAL = 'DUODECIMAL';
|
||
|
const HEXADECIMAL = 'HEXADECIMAL';
|
||
|
const ROMAN = 'ROMAN';
|
||
|
|
||
|
/**
|
||
|
* Calculations for all number units
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $_units = array(
|
||
|
'BINARY' => array(2, '⑵'),
|
||
|
'TERNARY' => array(3, '⑶'),
|
||
|
'QUATERNARY' => array(4, '⑷'),
|
||
|
'QUINARY' => array(5, '⑸'),
|
||
|
'SENARY' => array(6, '⑹'),
|
||
|
'SEPTENARY' => array(7, '⑺'),
|
||
|
'OCTAL' => array(8, '⑻'),
|
||
|
'NONARY' => array(9, '⑼'),
|
||
|
'DECIMAL' => array(10, '⑽'),
|
||
|
'DUODECIMAL' => array(12, '⑿'),
|
||
|
'HEXADECIMAL' => array(16, '⒃'),
|
||
|
'ROMAN' => array(99, ''),
|
||
|
'STANDARD' => 'DECIMAL'
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Definition of all roman signs
|
||
|
*
|
||
|
* @var array $_roman
|
||
|
*/
|
||
|
private static $_roman = array(
|
||
|
'I' => 1,
|
||
|
'A' => 4,
|
||
|
'V' => 5,
|
||
|
'B' => 9,
|
||
|
'X' => 10,
|
||
|
'E' => 40,
|
||
|
'L' => 50,
|
||
|
'F' => 90,
|
||
|
'C' => 100,
|
||
|
'G' => 400,
|
||
|
'D' => 500,
|
||
|
'H' => 900,
|
||
|
'M' => 1000,
|
||
|
'J' => 4000,
|
||
|
'P' => 5000,
|
||
|
'K' => 9000,
|
||
|
'Q' => 10000,
|
||
|
'N' => 40000,
|
||
|
'R' => 50000,
|
||
|
'W' => 90000,
|
||
|
'S' => 100000,
|
||
|
'Y' => 400000,
|
||
|
'T' => 500000,
|
||
|
'Z' => 900000,
|
||
|
'U' => 1000000
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Convertion table for roman signs
|
||
|
*
|
||
|
* @var array $_romanconvert
|
||
|
*/
|
||
|
private static $_romanconvert = array(
|
||
|
'/_V/' => '/P/',
|
||
|
'/_X/' => '/Q/',
|
||
|
'/_L/' => '/R/',
|
||
|
'/_C/' => '/S/',
|
||
|
'/_D/' => '/T/',
|
||
|
'/_M/' => '/U/',
|
||
|
'/IV/' => '/A/',
|
||
|
'/IX/' => '/B/',
|
||
|
'/XL/' => '/E/',
|
||
|
'/XC/' => '/F/',
|
||
|
'/CD/' => '/G/',
|
||
|
'/CM/' => '/H/',
|
||
|
'/M_V/'=> '/J/',
|
||
|
'/MQ/' => '/K/',
|
||
|
'/QR/' => '/N/',
|
||
|
'/QS/' => '/W/',
|
||
|
'/ST/' => '/Y/',
|
||
|
'/SU/' => '/Z/'
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Zend_Measure_Abstract is an abstract class for the different measurement types
|
||
|
*
|
||
|
* @param integer $value Value
|
||
|
* @param string $type (Optional) A Zend_Measure_Number Type
|
||
|
* @param string|Zend_Locale $locale (Optional) A Zend_Locale
|
||
|
* @throws Zend_Measure_Exception When language is unknown
|
||
|
* @throws Zend_Measure_Exception When type is unknown
|
||
|
*/
|
||
|
public function __construct($value, $type, $locale = null)
|
||
|
{
|
||
|
if (($type !== null) and (Zend_Locale::isLocale($type, null, false))) {
|
||
|
$locale = $type;
|
||
|
$type = null;
|
||
|
}
|
||
|
|
||
|
if ($locale === null) {
|
||
|
$locale = new Zend_Locale();
|
||
|
}
|
||
|
|
||
|
if (!Zend_Locale::isLocale($locale, true, false)) {
|
||
|
if (!Zend_Locale::isLocale($locale, true, false)) {
|
||
|
require_once 'Zend/Measure/Exception.php';
|
||
|
throw new Zend_Measure_Exception("Language (" . (string) $locale . ") is unknown");
|
||
|
}
|
||
|
|
||
|
$locale = new Zend_Locale($locale);
|
||
|
}
|
||
|
|
||
|
$this->_locale = (string) $locale;
|
||
|
|
||
|
if ($type === null) {
|
||
|
$type = $this->_units['STANDARD'];
|
||
|
}
|
||
|
|
||
|
if (isset($this->_units[$type]) === false) {
|
||
|
require_once 'Zend/Measure/Exception.php';
|
||
|
throw new Zend_Measure_Exception("Type ($type) is unknown");
|
||
|
}
|
||
|
|
||
|
$this->setValue($value, $type, $this->_locale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set a new value
|
||
|
*
|
||
|
* @param integer $value Value
|
||
|
* @param string $type (Optional) A Zend_Measure_Number Type
|
||
|
* @param string|Zend_Locale $locale (Optional) A Zend_Locale Type
|
||
|
* @throws Zend_Measure_Exception
|
||
|
*/
|
||
|
public function setValue($value, $type = null, $locale = null)
|
||
|
{
|
||
|
if (empty($locale)) {
|
||
|
$locale = $this->_locale;
|
||
|
}
|
||
|
|
||
|
if (empty($this->_units[$type])) {
|
||
|
require_once 'Zend/Measure/Exception.php';
|
||
|
throw new Zend_Measure_Exception('unknown type of number:' . $type);
|
||
|
}
|
||
|
|
||
|
switch($type) {
|
||
|
case 'BINARY':
|
||
|
preg_match('/[01]+/', $value, $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'TERNARY':
|
||
|
preg_match('/[012]+/', $value, $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'QUATERNARY':
|
||
|
preg_match('/[0123]+/', $value, $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'QUINARY':
|
||
|
preg_match('/[01234]+/', $value, $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'SENARY':
|
||
|
preg_match('/[012345]+/', $value, $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'SEPTENARY':
|
||
|
preg_match('/[0123456]+/', $value, $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'OCTAL':
|
||
|
preg_match('/[01234567]+/', $value, $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'NONARY':
|
||
|
preg_match('/[012345678]+/', $value, $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'DUODECIMAL':
|
||
|
preg_match('/[0123456789AB]+/', strtoupper($value), $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'HEXADECIMAL':
|
||
|
preg_match('/[0123456789ABCDEF]+/', strtoupper($value), $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
case 'ROMAN':
|
||
|
preg_match('/[IVXLCDM_]+/', strtoupper($value), $ergebnis);
|
||
|
$value = $ergebnis[0];
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
try {
|
||
|
$value = Zend_Locale_Format::getInteger($value, array('locale' => $locale));
|
||
|
} catch (Exception $e) {
|
||
|
require_once 'Zend/Measure/Exception.php';
|
||
|
throw new Zend_Measure_Exception($e->getMessage());
|
||
|
}
|
||
|
if (call_user_func(Zend_Locale_Math::$comp, $value, 0) < 0) {
|
||
|
$value = call_user_func(Zend_Locale_Math::$sqrt, call_user_func(Zend_Locale_Math::$pow, $value, 2));
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$this->_value = $value;
|
||
|
$this->_type = $type;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert input to decimal value string
|
||
|
*
|
||
|
* @param integer $input Input string
|
||
|
* @param string $type Type from which to convert to decimal
|
||
|
* @return string
|
||
|
*/
|
||
|
private function _toDecimal($input, $type)
|
||
|
{
|
||
|
$value = '';
|
||
|
// Convert base xx values
|
||
|
if ($this->_units[$type][0] <= 16) {
|
||
|
$split = str_split($input);
|
||
|
$length = strlen($input);
|
||
|
for ($x = 0; $x < $length; ++$x) {
|
||
|
$split[$x] = hexdec($split[$x]);
|
||
|
$value = call_user_func(Zend_Locale_Math::$add, $value,
|
||
|
call_user_func(Zend_Locale_Math::$mul, $split[$x],
|
||
|
call_user_func(Zend_Locale_Math::$pow, $this->_units[$type][0], ($length - $x - 1))));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert roman numbers
|
||
|
if ($type === 'ROMAN') {
|
||
|
$input = strtoupper($input);
|
||
|
$input = preg_replace(array_keys(self::$_romanconvert), array_values(self::$_romanconvert), $input);
|
||
|
|
||
|
$split = preg_split('//', strrev($input), -1, PREG_SPLIT_NO_EMPTY);
|
||
|
|
||
|
for ($x =0; $x < sizeof($split); $x++) {
|
||
|
if ($split[$x] == '/') {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$num = self::$_roman[$split[$x]];
|
||
|
if (($x > 0 and ($split[$x-1] != '/') and ($num < self::$_roman[$split[$x-1]]))) {
|
||
|
$num -= $num;
|
||
|
}
|
||
|
|
||
|
$value += $num;
|
||
|
}
|
||
|
|
||
|
str_replace('/', '', $value);
|
||
|
}
|
||
|
|
||
|
return $value;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert input to type value string
|
||
|
*
|
||
|
* @param integer $value Input string
|
||
|
* @param string $type Type to convert to
|
||
|
* @return string
|
||
|
* @throws Zend_Measure_Exception When more than 200 digits are calculated
|
||
|
*/
|
||
|
private function _fromDecimal($value, $type)
|
||
|
{
|
||
|
$tempvalue = $value;
|
||
|
if ($this->_units[$type][0] <= 16) {
|
||
|
$newvalue = '';
|
||
|
$count = 200;
|
||
|
$base = $this->_units[$type][0];
|
||
|
|
||
|
while (call_user_func(Zend_Locale_Math::$comp, $value, 0, 25) <> 0) {
|
||
|
$target = call_user_func(Zend_Locale_Math::$mod, $value, $base);
|
||
|
|
||
|
$newvalue = strtoupper(dechex($target)) . $newvalue;
|
||
|
|
||
|
$value = call_user_func(Zend_Locale_Math::$sub, $value, $target, 0);
|
||
|
$value = call_user_func(Zend_Locale_Math::$div, $value, $base, 0);
|
||
|
|
||
|
--$count;
|
||
|
if ($count === 0) {
|
||
|
require_once 'Zend/Measure/Exception.php';
|
||
|
throw new Zend_Measure_Exception("Your value '$tempvalue' cannot be processed because it extends 200 digits");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($newvalue === '') {
|
||
|
$newvalue = '0';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($type === 'ROMAN') {
|
||
|
$i = 0;
|
||
|
$newvalue = '';
|
||
|
$romanval = array_values(array_reverse(self::$_roman));
|
||
|
$romankey = array_keys(array_reverse(self::$_roman));
|
||
|
$count = 200;
|
||
|
while (call_user_func(Zend_Locale_Math::$comp, $value, 0, 25) <> 0) {
|
||
|
while ($value >= $romanval[$i]) {
|
||
|
$value -= $romanval[$i];
|
||
|
$newvalue .= $romankey[$i];
|
||
|
|
||
|
if ($value < 1) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
--$count;
|
||
|
if ($count === 0) {
|
||
|
require_once 'Zend/Measure/Exception.php';
|
||
|
throw new Zend_Measure_Exception("Your value '$tempvalue' cannot be processed because it extends 200 digits");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$i++;
|
||
|
}
|
||
|
|
||
|
$newvalue = str_replace('/', '', preg_replace(array_values(self::$_romanconvert), array_keys(self::$_romanconvert), $newvalue));
|
||
|
}
|
||
|
|
||
|
return $newvalue;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set a new type, and convert the value
|
||
|
*
|
||
|
* @param string $type New type to set
|
||
|
* @throws Zend_Measure_Exception When a unknown type is given
|
||
|
* @return void
|
||
|
*/
|
||
|
public function setType($type)
|
||
|
{
|
||
|
if (empty($this->_units[$type]) === true) {
|
||
|
require_once 'Zend/Measure/Exception.php';
|
||
|
throw new Zend_Measure_Exception('Unknown type of number:' . $type);
|
||
|
}
|
||
|
|
||
|
$value = $this->_toDecimal($this->getValue(-1), $this->getType(-1));
|
||
|
$value = $this->_fromDecimal($value, $type);
|
||
|
|
||
|
$this->_value = $value;
|
||
|
$this->_type = $type;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Alias function for setType returning the converted unit
|
||
|
* Default is 0 as this class only handles numbers without precision
|
||
|
*
|
||
|
* @param string $type Type to convert to
|
||
|
* @param integer $round (Optional) Precision to add, will always be 0
|
||
|
* @return string
|
||
|
*/
|
||
|
public function convertTo($type, $round = 0, $locale = null)
|
||
|
{
|
||
|
$this->setType($type);
|
||
|
return $this->toString($round, $locale);
|
||
|
}
|
||
|
}
|