Skip to content

Commit edce2fd

Browse files
committed
Changes the CPU rounding mode only during a specific process.
1 parent 1b41af8 commit edce2fd

File tree

1 file changed

+46
-30
lines changed

1 file changed

+46
-30
lines changed

ext/standard/math.c

+46-30
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <float.h>
2828
#include <math.h>
2929
#include <stdlib.h>
30+
#include <fenv.h>
3031

3132
#include "basic_functions.h"
3233

@@ -46,32 +47,30 @@ static inline double php_intpow10(int power) {
4647
}
4748
/* }}} */
4849

49-
#define PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent) do {\
50-
if (fabs(adjusted_value) >= value_abs) {\
51-
edge_case = fabs((integral + copysign(0.5, integral)) / exponent);\
52-
} else {\
53-
edge_case = fabs((integral + copysign(0.5, integral)) * exponent);\
54-
}\
55-
} while(0);
56-
57-
#define PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent) do {\
58-
if (fabs(adjusted_value) >= value_abs) {\
59-
edge_case = fabs((integral) / exponent);\
60-
} else {\
61-
edge_case = fabs((integral) * exponent);\
62-
}\
63-
} while(0);
50+
#define PHP_ROUND_BASIC_EDGE_CASE() do {\
51+
if (places > 0) {\
52+
edge_case = fabs((integral + copysign(0.5, integral)) / exponent);\
53+
} else {\
54+
edge_case = fabs((integral + copysign(0.5, integral)) * exponent);\
55+
}\
56+
} while (0)
57+
#define PHP_ROUND_ZERO_EDGE_CASE() do {\
58+
if (places > 0) {\
59+
edge_case = fabs((integral) / exponent);\
60+
} else {\
61+
edge_case = fabs((integral) * exponent);\
62+
}\
63+
} while (0)
6464

6565
/* {{{ php_round_helper
6666
Actually performs the rounding of a value to integer in a certain mode */
67-
static inline double php_round_helper(double adjusted_value, double value, double exponent, int mode) {
68-
double integral = adjusted_value >= 0.0 ? floor(adjusted_value) : ceil(adjusted_value);
67+
static inline double php_round_helper(double integral, double value, double exponent, int places, int mode) {
6968
double value_abs = fabs(value);
7069
double edge_case;
7170

7271
switch (mode) {
7372
case PHP_ROUND_HALF_UP:
74-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
73+
PHP_ROUND_BASIC_EDGE_CASE();
7574
if (value_abs >= edge_case) {
7675
/* We must increase the magnitude of the integral part
7776
* (rounding up / towards infinity). copysign(1.0, integral)
@@ -87,23 +86,23 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
8786
return integral;
8887

8988
case PHP_ROUND_HALF_DOWN:
90-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
89+
PHP_ROUND_BASIC_EDGE_CASE();
9190
if (value_abs > edge_case) {
9291
return integral + copysign(1.0, integral);
9392
}
9493

9594
return integral;
9695

9796
case PHP_ROUND_CEILING:
98-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
97+
PHP_ROUND_ZERO_EDGE_CASE();
9998
if (value > 0.0 && value_abs > edge_case) {
10099
return integral + 1.0;
101100
}
102101

103102
return integral;
104103

105104
case PHP_ROUND_FLOOR:
106-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
105+
PHP_ROUND_ZERO_EDGE_CASE();
107106
if (value < 0.0 && value_abs > edge_case) {
108107
return integral - 1.0;
109108
}
@@ -114,15 +113,15 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
114113
return integral;
115114

116115
case PHP_ROUND_AWAY_FROM_ZERO:
117-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
116+
PHP_ROUND_ZERO_EDGE_CASE();
118117
if (value_abs > edge_case) {
119118
return integral + copysign(1.0, integral);
120119
}
121120

122121
return integral;
123122

124123
case PHP_ROUND_HALF_EVEN:
125-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
124+
PHP_ROUND_BASIC_EDGE_CASE();
126125
if (value_abs > edge_case) {
127126
return integral + copysign(1.0, integral);
128127
} else if (UNEXPECTED(value_abs == edge_case)) {
@@ -139,7 +138,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
139138
return integral;
140139

141140
case PHP_ROUND_HALF_ODD:
142-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
141+
PHP_ROUND_BASIC_EDGE_CASE();
143142
if (value_abs > edge_case) {
144143
return integral + copysign(1.0, integral);
145144
} else if (UNEXPECTED(value_abs == edge_case)) {
@@ -167,6 +166,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
167166
PHPAPI double _php_math_round(double value, int places, int mode) {
168167
double exponent;
169168
double tmp_value;
169+
int cpu_round_mode;
170170

171171
if (!zend_finite(value) || value == 0.0) {
172172
return value;
@@ -176,19 +176,36 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
176176

177177
exponent = php_intpow10(abs(places));
178178

179-
/* adjust the value */
180-
if (places >= 0) {
181-
tmp_value = value * exponent;
179+
/**
180+
* When extracting the integer part, the result may be incorrect as a decimal
181+
* number due to floating point errors.
182+
* e.g.
183+
* 0.285 * 10000000000 => 2849999999.9999995
184+
* floor(0.285 * 10000000000) => 2849999999
185+
*
186+
* Therefore, change the CPU rounding mode to away from 0 only from
187+
* fegetround to fesetround.
188+
* e.g.
189+
* 0.285 * 10000000000 => 2850000000.0
190+
* floor(0.285 * 10000000000) => 2850000000
191+
*/
192+
cpu_round_mode = fegetround();
193+
if (value >= 0.0) {
194+
fesetround(FE_UPWARD);
195+
tmp_value = floor(places > 0 ? value * exponent : value / exponent);
182196
} else {
183-
tmp_value = value / exponent;
197+
fesetround(FE_DOWNWARD);
198+
tmp_value = ceil(places > 0 ? value * exponent : value / exponent);
184199
}
200+
fesetround(cpu_round_mode);
201+
185202
/* This value is beyond our precision, so rounding it is pointless */
186203
if (fabs(tmp_value) >= 1e15) {
187204
return value;
188205
}
189206

190207
/* round the temp value */
191-
tmp_value = php_round_helper(tmp_value, value, exponent, mode);
208+
tmp_value = php_round_helper(tmp_value, value, exponent, places, mode);
192209

193210
/* see if it makes sense to use simple division to round the value */
194211
if (abs(places) < 23) {
@@ -215,7 +232,6 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
215232
tmp_value = value;
216233
}
217234
}
218-
219235
return tmp_value;
220236
}
221237
/* }}} */

0 commit comments

Comments
 (0)