<?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-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: Number.php 24594 2012-01-05 21:27:01Z matthew $
*/
/**
* 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 precision
*
* @category Zend
* @package Zend_Measure
* @subpackage Zend_Measure_Number
* @copyright Copyright (c) 2005-2012 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(), $e->getCode(), $e);
}
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);
}
}