summaryrefslogtreecommitdiff
path: root/ext/standard/math.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/standard/math.c')
-rw-r--r--ext/standard/math.c199
1 files changed, 24 insertions, 175 deletions
diff --git a/ext/standard/math.c b/ext/standard/math.c
index 3d76a6ee0d..ac9c206437 100644
--- a/ext/standard/math.c
+++ b/ext/standard/math.c
@@ -24,181 +24,31 @@
#include "php.h"
#include "php_math.h"
#include "zend_multiply.h"
-#include "zend_float.h"
#include <math.h>
#include <float.h>
#include <stdlib.h>
-/* {{{ php_intlog10abs
- Returns floor(log10(fabs(val))), uses fast binary search */
-static inline int php_intlog10abs(double value) {
- int result;
- value = fabs(value);
-
- if (value < 1e-8 || value > 1e23) {
- result = (int)floor(log10(value));
- } else {
- static const double values[] = {
- 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1,
- 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
- 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
- 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
- /* Do a binary search with 5 steps */
- result = 16;
- if (value < values[result]) {
- result -= 8;
- } else {
- result += 8;
- }
- if (value < values[result]) {
- result -= 4;
- } else {
- result += 4;
- }
- if (value < values[result]) {
- result -= 2;
- } else {
- result += 2;
- }
- if (value < values[result]) {
- result -= 1;
- } else {
- result += 1;
- }
- if (value < values[result]) {
- result -= 1;
- }
- result -= 8;
- }
- return result;
-}
-/* }}} */
-
-/* {{{ php_intpow10
- Returns pow(10.0, (double)power), uses fast lookup table for exact powers */
-static inline double php_intpow10(int power) {
- static const double powers[] = {
- 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
- 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
- 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
-
- /* Not in lookup table */
- if (power < 0 || power > 22) {
- return pow(10.0, (double)power);
- }
- return powers[power];
-}
-/* }}} */
-
-/* {{{ php_round_helper
- Actually performs the rounding of a value to integer in a certain mode */
-static inline double php_round_helper(double value, int mode) {
- ZEND_FLOAT_DECLARE
- double tmp_value;
-
- ZEND_FLOAT_ENSURE();
- if (value >= 0.0) {
- tmp_value = floor(value + 0.5);
- if ((mode == PHP_ROUND_HALF_DOWN && value == (-0.5 + tmp_value)) ||
- (mode == PHP_ROUND_HALF_EVEN && value == (0.5 + 2 * floor(tmp_value/2.0))) ||
- (mode == PHP_ROUND_HALF_ODD && value == (0.5 + 2 * floor(tmp_value/2.0) - 1.0)))
- {
- tmp_value = tmp_value - 1.0;
- }
- } else {
- tmp_value = ceil(value - 0.5);
- if ((mode == PHP_ROUND_HALF_DOWN && value == (0.5 + tmp_value)) ||
- (mode == PHP_ROUND_HALF_EVEN && value == (-0.5 + 2 * ceil(tmp_value/2.0))) ||
- (mode == PHP_ROUND_HALF_ODD && value == (-0.5 + 2 * ceil(tmp_value/2.0) + 1.0)))
- {
- tmp_value = tmp_value + 1.0;
- }
- }
-
- ZEND_FLOAT_RETURN(tmp_value);
-}
-/* }}} */
-
-/* {{{ _php_math_round */
-/*
- * Rounds a number to a certain number of decimal places in a certain rounding
- * mode. For the specifics of the algorithm, see http://wiki.php.net/rfc/rounding
- */
-PHPAPI double _php_math_round(double value, int places, int mode) {
- ZEND_FLOAT_DECLARE
- double f1, f2;
- double tmp_value;
- int precision_places;
-
- ZEND_FLOAT_ENSURE();
-
- precision_places = 14 - php_intlog10abs(value);
-
- f1 = php_intpow10(abs(places));
-
- /* If the decimal precision guaranteed by FP arithmetic is higher than
- the requested places BUT is small enough to make sure a non-zero value
- is returned, pre-round the result to the precision */
- if (precision_places > places && precision_places - places < 15) {
- f2 = php_intpow10(abs(precision_places));
- if (precision_places >= 0) {
- tmp_value = value * f2;
- } else {
- tmp_value = value / f2;
- }
- /* preround the result (tmp_value will always be something * 1e14,
- thus never larger than 1e15 here) */
- tmp_value = php_round_helper(tmp_value, mode);
- /* now correctly move the decimal point */
- f2 = php_intpow10(abs(places - precision_places));
- /* because places < precision_places */
- tmp_value = tmp_value / f2;
- } else {
- /* adjust the value */
- if (places >= 0) {
- tmp_value = value * f1;
- } else {
- tmp_value = value / f1;
- }
- /* This value is beyond our precision, so rounding it is pointless */
- if (fabs(tmp_value) >= 1e15) {
- ZEND_FLOAT_RETURN(value);
- }
- }
+#ifndef PHP_ROUND_FUZZ
+# ifndef PHP_WIN32
+# define PHP_ROUND_FUZZ 0.50000000001
+# else
+# define PHP_ROUND_FUZZ 0.5
+# endif
+#endif
- /* round the temp value */
- tmp_value = php_round_helper(tmp_value, mode);
-
- /* see if it makes sense to use simple division to round the value */
- if (abs(places) < 23) {
- if (places > 0) {
- tmp_value = tmp_value / f1;
- } else {
- tmp_value = tmp_value * f1;
- }
- } else {
- /* Simple division can't be used since that will cause wrong results.
- Instead, the number is converted to a string and back again using
- strtod(). strtod() will return the nearest possible FP value for
- that string. */
-
- /* 40 Bytes should be more than enough for this format string. The
- float won't be larger than 1e15 anyway. But just in case, use
- snprintf() and make sure the buffer is zero-terminated */
- char buf[40];
- snprintf(buf, 39, "%15fe%d", tmp_value, -places);
- buf[39] = '\0';
- tmp_value = zend_strtod(buf, NULL);
- /* couldn't convert to string and back */
- if (!zend_finite(tmp_value) || zend_isnan(tmp_value)) {
- tmp_value = value;
- }
- }
+#define PHP_ROUND_WITH_FUZZ(val, places) { \
+ double tmp_val=val, f = pow(10.0, (double) places); \
+ tmp_val *= f; \
+ if (tmp_val >= 0.0) { \
+ tmp_val = floor(tmp_val + PHP_ROUND_FUZZ); \
+ } else { \
+ tmp_val = ceil(tmp_val - PHP_ROUND_FUZZ); \
+ } \
+ tmp_val /= f; \
+ val = !zend_isnan(tmp_val) ? tmp_val : val; \
+} \
- ZEND_FLOAT_RETURN(tmp_value);
-}
-/* }}} */
/* {{{ php_asinh
*/
@@ -326,21 +176,20 @@ PHP_FUNCTION(floor)
}
/* }}} */
-/* {{{ proto float round(float number [, int precision [, int mode]])
+/* {{{ proto float round(float number [, int precision])
Returns the number rounded to specified precision */
PHP_FUNCTION(round)
{
zval **value;
int places = 0;
long precision = 0;
- long mode = PHP_ROUND_HALF_UP;
double return_val;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|ll", &value, &precision, &mode) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|l", &value, &precision) == FAILURE) {
return;
}
- if (ZEND_NUM_ARGS() >= 2) {
+ if (ZEND_NUM_ARGS() == 2) {
places = (int) precision;
}
convert_scalar_to_number_ex(value);
@@ -355,7 +204,7 @@ PHP_FUNCTION(round)
case IS_DOUBLE:
return_val = (Z_TYPE_PP(value) == IS_LONG) ? (double)Z_LVAL_PP(value) : Z_DVAL_PP(value);
- return_val = _php_math_round(return_val, places, mode);
+ PHP_ROUND_WITH_FUZZ(return_val, places);
RETURN_DOUBLE(return_val);
break;
@@ -1092,7 +941,7 @@ PHPAPI char *_php_math_number_format(double d, int dec, char dec_point, char tho
}
dec = MAX(0, dec);
- d = _php_math_round(d, dec, PHP_ROUND_HALF_UP);
+ PHP_ROUND_WITH_FUZZ(d, dec);
tmplen = spprintf(&tmpbuf, 0, "%.*F", dec, d);
@@ -1191,7 +1040,7 @@ PHPAPI char *_php_math_number_format(double d, int dec, char dec_point, char tho
PHP_FUNCTION(number_format)
{
double num;
- long dec = 0;
+ long dec;
char *thousand_sep = NULL, *dec_point = NULL;
char thousand_sep_chr = ',', dec_point_chr = '.';
int thousand_sep_len = 0, dec_point_len = 0;