Compelete emit_base
[hiphop-php.git] / hphp / zend / zend-math.cpp
blob8e6cffb465dec84de520250d09579a4b17fb8166
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1998-2010 Zend Technologies Ltd. (http://www.zend.com) |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 2.00 of the Zend license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.zend.com/license/2_00.txt. |
12 | If you did not receive a copy of the Zend license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@zend.com so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/zend/zend-math.h"
20 #include <cstdio>
21 #include <cstdlib>
22 #include <cstring>
24 namespace HPHP {
25 ///////////////////////////////////////////////////////////////////////////////
27 static inline int php_intlog10abs(double value) {
28 int result;
29 value = fabs(value);
31 if (value < 1e-8 || value > 1e22) {
32 result = (int)floor(log10(value));
33 } else {
34 static const double values[] = {
35 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1,
36 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
37 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
38 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
39 /* Do a binary search with 5 steps */
40 result = 15;
41 if (value < values[result]) {
42 result -= 8;
43 } else {
44 result += 8;
46 if (value < values[result]) {
47 result -= 4;
48 } else {
49 result += 4;
51 if (value < values[result]) {
52 result -= 2;
53 } else {
54 result += 2;
56 if (value < values[result]) {
57 result -= 1;
58 } else {
59 result += 1;
61 if (value < values[result]) {
62 result -= 1;
64 result -= 8;
66 return result;
68 /* }}} */
70 static inline double php_intpow10(int power) {
71 static const double powers[] = {
72 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
73 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
74 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
76 /* Not in lookup table */
77 if (power < 0 || power > 22) {
78 return pow(10.0, (double)power);
80 return powers[power];
83 static inline double php_round_helper(double value, int mode) {
84 double tmp_value;
86 if (value >= 0.0) {
87 tmp_value = floor(value + 0.5);
88 if ((mode == PHP_ROUND_HALF_DOWN && value == (-0.5 + tmp_value)) ||
89 (mode == PHP_ROUND_HALF_EVEN && value == (0.5 + 2 * floor(tmp_value/2.0))) ||
90 (mode == PHP_ROUND_HALF_ODD && value == (0.5 + 2 * floor(tmp_value/2.0) - 1.0)))
92 tmp_value = tmp_value - 1.0;
94 } else {
95 tmp_value = ceil(value - 0.5);
96 if ((mode == PHP_ROUND_HALF_DOWN && value == (0.5 + tmp_value)) ||
97 (mode == PHP_ROUND_HALF_EVEN && value == (-0.5 + 2 * ceil(tmp_value/2.0))) ||
98 (mode == PHP_ROUND_HALF_ODD && value == (-0.5 + 2 * ceil(tmp_value/2.0) + 1.0)))
100 tmp_value = tmp_value + 1.0;
104 return tmp_value;
107 double php_math_round(double value, int places,
108 int mode /* = PHP_ROUND_HALF_UP */) {
109 double tmp_value;
111 // We need to avoid calculating log(0.0) later, but log(epsilon) is fine.
112 if (std::isinf(value) || value == 0.0) {
113 return value;
116 int precision_places = 14 - php_intlog10abs(value);
117 double f1 = php_intpow10(abs(places));
119 /* If the decimal precision guaranteed by FP arithmetic is higher than
120 * the requested places BUT is small enough to make sure a non-zero value
121 * is returned, pre-round the result to the precision */
122 if (precision_places > places && precision_places - places < 15) {
123 double f2 = php_intpow10(abs(precision_places));
124 if (precision_places >= 0) {
125 tmp_value = value * f2;
126 } else {
127 tmp_value = value / f2;
129 /* preround the result (tmp_value will always be something * 1e14,
130 * thus never larger than 1e15 here) */
131 tmp_value = php_round_helper(tmp_value, mode);
132 /* now correctly move the decimal point */
133 f2 = php_intpow10(abs(places - precision_places));
134 /* because places < precision_places */
135 tmp_value = tmp_value / f2;
136 } else {
137 /* adjust the value */
138 if (places >= 0) {
139 tmp_value = value * f1;
140 } else {
141 tmp_value = value / f1;
143 /* This value is beyond our precision, so rounding it is pointless */
144 if (fabs(tmp_value) >= 1e15) {
145 return value;
149 /* round the temp value */
150 tmp_value = php_round_helper(tmp_value, mode);
152 /* see if it makes sense to use simple division to round the value */
153 if (abs(places) < 23) {
154 if (places > 0) {
155 tmp_value /= f1;
156 } else {
157 tmp_value *= f1;
159 } else {
160 /* Simple division can't be used since that will cause wrong results.
161 * Instead, the number is converted to a string and back again using
162 * strtod(). strtod() will return the nearest possible FP value for
163 * that string. */
165 /* 40 Bytes should be more than enough for this format string. The
166 * float won't be larger than 1e15 anyway. But just in case, use
167 * snprintf() and make sure the buffer is zero-terminated */
168 char buf[40];
169 snprintf(buf, 39, "%15fe%d", tmp_value, -places);
170 buf[39] = '\0';
171 tmp_value = strtod(buf, nullptr);
173 /* couldn't convert to string and back */
174 if (std::isinf(tmp_value)) {
175 tmp_value = value;
179 return tmp_value;
183 ///////////////////////////////////////////////////////////////////////////////