Math.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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_Locale
  17. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * Utility class for proxying math function to bcmath functions, if present,
  23. * otherwise to PHP builtin math operators, with limited detection of overflow conditions.
  24. * Sampling of PHP environments and platforms suggests that at least 80% to 90% support bcmath.
  25. * Thus, this file should be as light as possible.
  26. *
  27. * @category Zend
  28. * @package Zend_Locale
  29. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  30. * @license http://framework.zend.com/license/new-bsd New BSD License
  31. */
  32. class Zend_Locale_Math
  33. {
  34. // support unit testing without using bcmath functions
  35. public static $_bcmathDisabled = false;
  36. public static $add = array('Zend_Locale_Math', 'Add');
  37. public static $sub = array('Zend_Locale_Math', 'Sub');
  38. public static $pow = array('Zend_Locale_Math', 'Pow');
  39. public static $mul = array('Zend_Locale_Math', 'Mul');
  40. public static $div = array('Zend_Locale_Math', 'Div');
  41. public static $comp = array('Zend_Locale_Math', 'Comp');
  42. public static $sqrt = array('Zend_Locale_Math', 'Sqrt');
  43. public static $mod = array('Zend_Locale_Math', 'Mod');
  44. public static $scale = 'bcscale';
  45. public static function isBcmathDisabled()
  46. {
  47. return self::$_bcmathDisabled;
  48. }
  49. /**
  50. * Surprisingly, the results of this implementation of round()
  51. * prove better than the native PHP round(). For example, try:
  52. * round(639.795, 2);
  53. * round(267.835, 2);
  54. * round(0.302515, 5);
  55. * round(0.36665, 4);
  56. * then try:
  57. * Zend_Locale_Math::round('639.795', 2);
  58. */
  59. public static function round($op1, $precision = 0)
  60. {
  61. if (self::$_bcmathDisabled) {
  62. $return = round($op1, $precision);
  63. if (strpos((string) $return, 'E') === false) {
  64. return self::normalize(round($op1, $precision));
  65. }
  66. }
  67. if (strpos($op1, 'E') !== false) {
  68. $op1 = self::floatalize($op1);
  69. }
  70. $op1 = trim(self::normalize($op1));
  71. $length = strlen($op1);
  72. if (($decPos = strpos($op1, '.')) === false) {
  73. $op1 .= '.0';
  74. $decPos = $length;
  75. $length += 2;
  76. }
  77. if ($precision < 0 && abs($precision) > $decPos) {
  78. return '0';
  79. }
  80. $digitsBeforeDot = $length - ($decPos + 1);
  81. if ($precision >= ($length - ($decPos + 1))) {
  82. return $op1;
  83. }
  84. if ($precision === 0) {
  85. $triggerPos = 1;
  86. $roundPos = -1;
  87. } elseif ($precision > 0) {
  88. $triggerPos = $precision + 1;
  89. $roundPos = $precision;
  90. } else {
  91. $triggerPos = $precision;
  92. $roundPos = $precision -1;
  93. }
  94. $triggerDigit = $op1[$triggerPos + $decPos];
  95. if ($precision < 0) {
  96. // zero fill digits to the left of the decimal place
  97. $op1 = substr($op1, 0, $decPos + $precision) . str_pad('', abs($precision), '0');
  98. }
  99. if ($triggerDigit >= '5') {
  100. if ($roundPos + $decPos == -1) {
  101. return str_pad('1', $decPos + 1, '0');
  102. }
  103. $roundUp = str_pad('', $length, '0');
  104. $roundUp[$decPos] = '.';
  105. $roundUp[$roundPos + $decPos] = '1';
  106. if ($op1 > 0) {
  107. return self::Add($op1, $roundUp, $precision);
  108. } else {
  109. return self::Sub($op1, $roundUp, $precision);
  110. }
  111. } elseif ($precision >= 0) {
  112. return substr($op1, 0, $decPos + ($precision ? $precision + 1: 0));
  113. }
  114. return (string) $op1;
  115. }
  116. /**
  117. * Convert a scientific notation to float
  118. * Additionally fixed a problem with PHP <= 5.2.x with big integers
  119. *
  120. * @param string $value
  121. */
  122. public static function floatalize($value)
  123. {
  124. $value = strtoupper($value);
  125. if (strpos($value, 'E') === false) {
  126. return $value;
  127. }
  128. $number = substr($value, 0, strpos($value, 'E'));
  129. if (strpos($number, '.') !== false) {
  130. $post = strlen(substr($number, strpos($number, '.') + 1));
  131. $mantis = substr($value, strpos($value, 'E') + 1);
  132. if ($mantis < 0) {
  133. $post += abs((int) $mantis);
  134. }
  135. $value = number_format($value, $post, '.', '');
  136. } else {
  137. $value = number_format($value, 0, '.', '');
  138. }
  139. return $value;
  140. }
  141. /**
  142. * Normalizes an input to standard english notation
  143. * Fixes a problem of BCMath with setLocale which is PHP related
  144. *
  145. * @param integer $value Value to normalize
  146. * @return string Normalized string without BCMath problems
  147. */
  148. public static function normalize($value)
  149. {
  150. $convert = localeconv();
  151. $value = str_replace($convert['thousands_sep'], "",(string) $value);
  152. $value = str_replace($convert['positive_sign'], "", $value);
  153. $value = str_replace($convert['decimal_point'], ".",$value);
  154. if (!empty($convert['negative_sign']) and (strpos($value, $convert['negative_sign']))) {
  155. $value = str_replace($convert['negative_sign'], "", $value);
  156. $value = "-" . $value;
  157. }
  158. return $value;
  159. }
  160. /**
  161. * Localizes an input from standard english notation
  162. * Fixes a problem of BCMath with setLocale which is PHP related
  163. *
  164. * @param integer $value Value to normalize
  165. * @return string Normalized string without BCMath problems
  166. */
  167. public static function localize($value)
  168. {
  169. $convert = localeconv();
  170. $value = str_replace(".", $convert['decimal_point'], (string) $value);
  171. if (!empty($convert['negative_sign']) and (strpos($value, "-"))) {
  172. $value = str_replace("-", $convert['negative_sign'], $value);
  173. }
  174. return $value;
  175. }
  176. /**
  177. * Changes exponential numbers to plain string numbers
  178. * Fixes a problem of BCMath with numbers containing exponents
  179. *
  180. * @param integer $value Value to erase the exponent
  181. * @param integer $scale (Optional) Scale to use
  182. * @return string
  183. */
  184. public static function exponent($value, $scale = null)
  185. {
  186. if (!extension_loaded('bcmath')) {
  187. return $value;
  188. }
  189. $split = explode('e', $value);
  190. if (count($split) == 1) {
  191. $split = explode('E', $value);
  192. }
  193. if (count($split) > 1) {
  194. $value = bcmul($split[0], bcpow(10, $split[1], $scale), $scale);
  195. }
  196. return $value;
  197. }
  198. /**
  199. * BCAdd - fixes a problem of BCMath and exponential numbers
  200. *
  201. * @param string $op1
  202. * @param string $op2
  203. * @param integer $scale
  204. * @return string
  205. */
  206. public static function Add($op1, $op2, $scale = null)
  207. {
  208. $op1 = self::exponent($op1, $scale);
  209. $op2 = self::exponent($op2, $scale);
  210. return bcadd($op1, $op2, $scale);
  211. }
  212. /**
  213. * BCSub - fixes a problem of BCMath and exponential numbers
  214. *
  215. * @param string $op1
  216. * @param string $op2
  217. * @param integer $scale
  218. * @return string
  219. */
  220. public static function Sub($op1, $op2, $scale = null)
  221. {
  222. $op1 = self::exponent($op1, $scale);
  223. $op2 = self::exponent($op2, $scale);
  224. return bcsub($op1, $op2, $scale);
  225. }
  226. /**
  227. * BCPow - fixes a problem of BCMath and exponential numbers
  228. *
  229. * @param string $op1
  230. * @param string $op2
  231. * @param integer $scale
  232. * @return string
  233. */
  234. public static function Pow($op1, $op2, $scale = null)
  235. {
  236. $op1 = self::exponent($op1, $scale);
  237. $op2 = self::exponent($op2, $scale);
  238. return bcpow($op1, $op2, $scale);
  239. }
  240. /**
  241. * BCMul - fixes a problem of BCMath and exponential numbers
  242. *
  243. * @param string $op1
  244. * @param string $op2
  245. * @param integer $scale
  246. * @return string
  247. */
  248. public static function Mul($op1, $op2, $scale = null)
  249. {
  250. $op1 = self::exponent($op1, $scale);
  251. $op2 = self::exponent($op2, $scale);
  252. return bcmul($op1, $op2, $scale);
  253. }
  254. /**
  255. * BCDiv - fixes a problem of BCMath and exponential numbers
  256. *
  257. * @param string $op1
  258. * @param string $op2
  259. * @param integer $scale
  260. * @return string
  261. */
  262. public static function Div($op1, $op2, $scale = null)
  263. {
  264. $op1 = self::exponent($op1, $scale);
  265. $op2 = self::exponent($op2, $scale);
  266. return bcdiv($op1, $op2, $scale);
  267. }
  268. /**
  269. * BCSqrt - fixes a problem of BCMath and exponential numbers
  270. *
  271. * @param string $op1
  272. * @param integer $scale
  273. * @return string
  274. */
  275. public static function Sqrt($op1, $scale = null)
  276. {
  277. $op1 = self::exponent($op1, $scale);
  278. return bcsqrt($op1, $scale);
  279. }
  280. /**
  281. * BCMod - fixes a problem of BCMath and exponential numbers
  282. *
  283. * @param string $op1
  284. * @param string $op2
  285. * @return string
  286. */
  287. public static function Mod($op1, $op2)
  288. {
  289. $op1 = self::exponent($op1);
  290. $op2 = self::exponent($op2);
  291. return bcmod($op1, $op2);
  292. }
  293. /**
  294. * BCComp - fixes a problem of BCMath and exponential numbers
  295. *
  296. * @param string $op1
  297. * @param string $op2
  298. * @param integer $scale
  299. * @return string
  300. */
  301. public static function Comp($op1, $op2, $scale = null)
  302. {
  303. $op1 = self::exponent($op1, $scale);
  304. $op2 = self::exponent($op2, $scale);
  305. return bccomp($op1, $op2, $scale);
  306. }
  307. }
  308. if ((defined('TESTS_ZEND_LOCALE_BCMATH_ENABLED') && !TESTS_ZEND_LOCALE_BCMATH_ENABLED)
  309. || !extension_loaded('bcmath')) {
  310. require_once 'Zend/Locale/Math/PhpMath.php';
  311. Zend_Locale_Math_PhpMath::disable();
  312. }