Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / zend-math.cpp
blob548ffa550e3cd0b383338f142ffc515c56f42f3d
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/runtime/base/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 if (std::isinf(value)) {
112 return value;
115 int precision_places = 14 - php_intlog10abs(value);
116 double f1 = php_intpow10(abs(places));
118 /* If the decimal precision guaranteed by FP arithmetic is higher than
119 * the requested places BUT is small enough to make sure a non-zero value
120 * is returned, pre-round the result to the precision */
121 if (precision_places > places && precision_places - places < 15) {
122 double f2 = php_intpow10(abs(precision_places));
123 if (precision_places >= 0) {
124 tmp_value = value * f2;
125 } else {
126 tmp_value = value / f2;
128 /* preround the result (tmp_value will always be something * 1e14,
129 * thus never larger than 1e15 here) */
130 tmp_value = php_round_helper(tmp_value, mode);
131 /* now correctly move the decimal point */
132 f2 = php_intpow10(abs(places - precision_places));
133 /* because places < precision_places */
134 tmp_value = tmp_value / f2;
135 } else {
136 /* adjust the value */
137 if (places >= 0) {
138 tmp_value = value * f1;
139 } else {
140 tmp_value = value / f1;
142 /* This value is beyond our precision, so rounding it is pointless */
143 if (fabs(tmp_value) >= 1e15) {
144 return value;
148 /* round the temp value */
149 tmp_value = php_round_helper(tmp_value, mode);
151 /* see if it makes sense to use simple division to round the value */
152 if (abs(places) < 23) {
153 if (places > 0) {
154 tmp_value /= f1;
155 } else {
156 tmp_value *= f1;
158 } else {
159 /* Simple division can't be used since that will cause wrong results.
160 * Instead, the number is converted to a string and back again using
161 * strtod(). strtod() will return the nearest possible FP value for
162 * that string. */
164 /* 40 Bytes should be more than enough for this format string. The
165 * float won't be larger than 1e15 anyway. But just in case, use
166 * snprintf() and make sure the buffer is zero-terminated */
167 char buf[40];
168 snprintf(buf, 39, "%15fe%d", tmp_value, -places);
169 buf[39] = '\0';
170 tmp_value = strtod(buf, nullptr);
172 /* couldn't convert to string and back */
173 if (std::isinf(tmp_value)) {
174 tmp_value = value;
178 return tmp_value;
182 ///////////////////////////////////////////////////////////////////////////////