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,58 @@ 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);
64
-
65
50
/* {{{ php_round_helper
66
51
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
+
69
77
double value_abs = fabs (value );
70
78
double edge_case ;
71
79
72
80
switch (mode ) {
73
81
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 :
75
102
if (value_abs >= edge_case ) {
76
103
/* We must increase the magnitude of the integral part
77
104
* (rounding up / towards infinity). copysign(1.0, integral)
@@ -87,23 +114,20 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
87
114
return integral ;
88
115
89
116
case PHP_ROUND_HALF_DOWN :
90
- PHP_ROUND_GET_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
91
117
if (value_abs > edge_case ) {
92
118
return integral + copysign (1.0 , integral );
93
119
}
94
120
95
121
return integral ;
96
122
97
123
case PHP_ROUND_CEILING :
98
- PHP_ROUND_GET_ZERO_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
99
124
if (value > 0.0 && value_abs > edge_case ) {
100
125
return integral + 1.0 ;
101
126
}
102
127
103
128
return integral ;
104
129
105
130
case PHP_ROUND_FLOOR :
106
- PHP_ROUND_GET_ZERO_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
107
131
if (value < 0.0 && value_abs > edge_case ) {
108
132
return integral - 1.0 ;
109
133
}
@@ -114,15 +138,13 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
114
138
return integral ;
115
139
116
140
case PHP_ROUND_AWAY_FROM_ZERO :
117
- PHP_ROUND_GET_ZERO_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
118
141
if (value_abs > edge_case ) {
119
142
return integral + copysign (1.0 , integral );
120
143
}
121
144
122
145
return integral ;
123
146
124
147
case PHP_ROUND_HALF_EVEN :
125
- PHP_ROUND_GET_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
126
148
if (value_abs > edge_case ) {
127
149
return integral + copysign (1.0 , integral );
128
150
} else if (UNEXPECTED (value_abs == edge_case )) {
@@ -139,7 +161,6 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
139
161
return integral ;
140
162
141
163
case PHP_ROUND_HALF_ODD :
142
- PHP_ROUND_GET_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
143
164
if (value_abs > edge_case ) {
144
165
return integral + copysign (1.0 , integral );
145
166
} else if (UNEXPECTED (value_abs == edge_case )) {
@@ -188,7 +209,7 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
188
209
}
189
210
190
211
/* 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 );
192
213
193
214
/* see if it makes sense to use simple division to round the value */
194
215
if (abs (places ) < 23 ) {
@@ -215,7 +236,6 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
215
236
tmp_value = value ;
216
237
}
217
238
}
218
-
219
239
return tmp_value ;
220
240
}
221
241
/* }}} */
0 commit comments