Skip to content

Commit 21e7898

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

File tree

1 file changed

+47
-27
lines changed

1 file changed

+47
-27
lines changed

ext/standard/math.c

+47-27
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,58 @@ 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);
64-
6550
/* {{{ php_round_helper
6651
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);
52+
static inline double php_round_helper(double adjusted_value, double value, double exponent, int places, int mode) {
53+
/**
54+
* When extracting the integer part, the result may be incorrect as a decimal
55+
* number due to floating point errors.
56+
* e.g.
57+
* 0.285 * 10000000000 => 2849999999.9999995
58+
* floor(0.285 * 10000000000) => 2849999999
59+
*
60+
* Therefore, change the CPU rounding mode to away from 0 only from
61+
* PHP_ROUND_GET_CURRENT_REG to PHP_ROUND_RESTORE_REG.
62+
* e.g.
63+
* 0.285 * 10000000000 => 2850000000.0
64+
* floor(0.285 * 10000000000) => 2850000000
65+
*/
66+
double integral;
67+
int cpu_round_mode = fegetround();
68+
if (value >= 0.0) {
69+
fesetround(FE_UPWARD);
70+
integral = floor(places > 0 ? value * exponent : value / exponent);
71+
} else {
72+
fesetround(FE_DOWNWARD);
73+
integral = ceil(places > 0 ? value * exponent : value / exponent);
74+
}
75+
fesetround(cpu_round_mode);
76+
6977
double value_abs = fabs(value);
7078
double edge_case;
7179

7280
switch (mode) {
7381
case PHP_ROUND_HALF_UP:
74-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
82+
case PHP_ROUND_HALF_DOWN:
83+
case PHP_ROUND_HALF_EVEN:
84+
case PHP_ROUND_HALF_ODD:
85+
if (fabs(adjusted_value) >= value_abs) {
86+
edge_case = fabs((integral + copysign(0.5, integral)) / exponent);
87+
} else {
88+
edge_case = fabs((integral + copysign(0.5, integral)) * exponent);
89+
}
90+
break;
91+
default:
92+
if (fabs(adjusted_value) >= value_abs) {
93+
edge_case = fabs((integral) / exponent);
94+
} else {
95+
edge_case = fabs((integral) * exponent);
96+
}
97+
break;
98+
}
99+
100+
switch (mode) {
101+
case PHP_ROUND_HALF_UP:
75102
if (value_abs >= edge_case) {
76103
/* We must increase the magnitude of the integral part
77104
* (rounding up / towards infinity). copysign(1.0, integral)
@@ -87,23 +114,20 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
87114
return integral;
88115

89116
case PHP_ROUND_HALF_DOWN:
90-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
91117
if (value_abs > edge_case) {
92118
return integral + copysign(1.0, integral);
93119
}
94120

95121
return integral;
96122

97123
case PHP_ROUND_CEILING:
98-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
99124
if (value > 0.0 && value_abs > edge_case) {
100125
return integral + 1.0;
101126
}
102127

103128
return integral;
104129

105130
case PHP_ROUND_FLOOR:
106-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
107131
if (value < 0.0 && value_abs > edge_case) {
108132
return integral - 1.0;
109133
}
@@ -114,15 +138,13 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
114138
return integral;
115139

116140
case PHP_ROUND_AWAY_FROM_ZERO:
117-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
118141
if (value_abs > edge_case) {
119142
return integral + copysign(1.0, integral);
120143
}
121144

122145
return integral;
123146

124147
case PHP_ROUND_HALF_EVEN:
125-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
126148
if (value_abs > edge_case) {
127149
return integral + copysign(1.0, integral);
128150
} else if (UNEXPECTED(value_abs == edge_case)) {
@@ -139,7 +161,6 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
139161
return integral;
140162

141163
case PHP_ROUND_HALF_ODD:
142-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
143164
if (value_abs > edge_case) {
144165
return integral + copysign(1.0, integral);
145166
} else if (UNEXPECTED(value_abs == edge_case)) {
@@ -188,7 +209,7 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
188209
}
189210

190211
/* round the temp value */
191-
tmp_value = php_round_helper(tmp_value, value, exponent, mode);
212+
tmp_value = php_round_helper(tmp_value, value, exponent, places, mode);
192213

193214
/* see if it makes sense to use simple division to round the value */
194215
if (abs(places) < 23) {
@@ -215,7 +236,6 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
215236
tmp_value = value;
216237
}
217238
}
218-
219239
return tmp_value;
220240
}
221241
/* }}} */

0 commit comments

Comments
 (0)