27
27
#include <float.h>
28
28
#include <math.h>
29
29
#include <stdlib.h>
30
+ #include <fenv.h>
30
31
31
32
#include "basic_functions.h"
32
33
@@ -46,32 +47,30 @@ static inline double php_intpow10(int power) {
46
47
}
47
48
/* }}} */
48
49
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)
64
64
65
65
/* {{{ php_round_helper
66
66
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 ) {
69
68
double value_abs = fabs (value );
70
69
double edge_case ;
71
70
72
71
switch (mode ) {
73
72
case PHP_ROUND_HALF_UP :
74
- PHP_ROUND_GET_EDGE_CASE ( adjusted_value , value_abs , integral , exponent );
73
+ PHP_ROUND_BASIC_EDGE_CASE ( );
75
74
if (value_abs >= edge_case ) {
76
75
/* We must increase the magnitude of the integral part
77
76
* (rounding up / towards infinity). copysign(1.0, integral)
@@ -87,23 +86,23 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
87
86
return integral ;
88
87
89
88
case PHP_ROUND_HALF_DOWN :
90
- PHP_ROUND_GET_EDGE_CASE ( adjusted_value , value_abs , integral , exponent );
89
+ PHP_ROUND_BASIC_EDGE_CASE ( );
91
90
if (value_abs > edge_case ) {
92
91
return integral + copysign (1.0 , integral );
93
92
}
94
93
95
94
return integral ;
96
95
97
96
case PHP_ROUND_CEILING :
98
- PHP_ROUND_GET_ZERO_EDGE_CASE ( adjusted_value , value_abs , integral , exponent );
97
+ PHP_ROUND_ZERO_EDGE_CASE ( );
99
98
if (value > 0.0 && value_abs > edge_case ) {
100
99
return integral + 1.0 ;
101
100
}
102
101
103
102
return integral ;
104
103
105
104
case PHP_ROUND_FLOOR :
106
- PHP_ROUND_GET_ZERO_EDGE_CASE ( adjusted_value , value_abs , integral , exponent );
105
+ PHP_ROUND_ZERO_EDGE_CASE ( );
107
106
if (value < 0.0 && value_abs > edge_case ) {
108
107
return integral - 1.0 ;
109
108
}
@@ -114,15 +113,15 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
114
113
return integral ;
115
114
116
115
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 ( );
118
117
if (value_abs > edge_case ) {
119
118
return integral + copysign (1.0 , integral );
120
119
}
121
120
122
121
return integral ;
123
122
124
123
case PHP_ROUND_HALF_EVEN :
125
- PHP_ROUND_GET_EDGE_CASE ( adjusted_value , value_abs , integral , exponent );
124
+ PHP_ROUND_BASIC_EDGE_CASE ( );
126
125
if (value_abs > edge_case ) {
127
126
return integral + copysign (1.0 , integral );
128
127
} else if (UNEXPECTED (value_abs == edge_case )) {
@@ -139,7 +138,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
139
138
return integral ;
140
139
141
140
case PHP_ROUND_HALF_ODD :
142
- PHP_ROUND_GET_EDGE_CASE ( adjusted_value , value_abs , integral , exponent );
141
+ PHP_ROUND_BASIC_EDGE_CASE ( );
143
142
if (value_abs > edge_case ) {
144
143
return integral + copysign (1.0 , integral );
145
144
} else if (UNEXPECTED (value_abs == edge_case )) {
@@ -167,6 +166,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
167
166
PHPAPI double _php_math_round (double value , int places , int mode ) {
168
167
double exponent ;
169
168
double tmp_value ;
169
+ int cpu_round_mode ;
170
170
171
171
if (!zend_finite (value ) || value == 0.0 ) {
172
172
return value ;
@@ -176,19 +176,36 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
176
176
177
177
exponent = php_intpow10 (abs (places ));
178
178
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 );
182
196
} else {
183
- tmp_value = value / exponent ;
197
+ fesetround (FE_DOWNWARD );
198
+ tmp_value = ceil (places > 0 ? value * exponent : value / exponent );
184
199
}
200
+ fesetround (cpu_round_mode );
201
+
185
202
/* This value is beyond our precision, so rounding it is pointless */
186
203
if (fabs (tmp_value ) >= 1e15 ) {
187
204
return value ;
188
205
}
189
206
190
207
/* 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 );
192
209
193
210
/* see if it makes sense to use simple division to round the value */
194
211
if (abs (places ) < 23 ) {
@@ -215,7 +232,6 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
215
232
tmp_value = value ;
216
233
}
217
234
}
218
-
219
235
return tmp_value ;
220
236
}
221
237
/* }}} */
0 commit comments