@@ -46,32 +46,58 @@ static inline double php_intpow10(int power) {
46
46
}
47
47
/* }}} */
48
48
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
49
/* {{{ php_round_helper
66
50
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 );
51
+ static inline double php_round_helper (double adjusted_value , double value , double exponent , int places , int mode ) {
52
+ /**
53
+ * When extracting the integer part, the result may be incorrect as a decimal
54
+ * number due to floating point errors.
55
+ * e.g.
56
+ * 0.285 * 10000000000 => 2849999999.9999995
57
+ * floor(0.285 * 10000000000) => 2849999999
58
+ *
59
+ * Therefore, change the CPU rounding mode to away from 0 only from
60
+ * PHP_ROUND_GET_CURRENT_REG to PHP_ROUND_RESTORE_REG.
61
+ * e.g.
62
+ * 0.285 * 10000000000 => 2850000000.0
63
+ * floor(0.285 * 10000000000) => 2850000000
64
+ */
65
+ double integral ;
66
+ PHP_ROUND_GET_CURRENT_REG ();
67
+ if (value >= 0.0 ) {
68
+ PHP_ROUND_ROUND_MODE_SWITCH_UP ();
69
+ integral = floor (places > 0 ? value * exponent : value / exponent );
70
+ } else {
71
+ PHP_ROUND_ROUND_MODE_SWITCH_DOWN ();
72
+ integral = ceil (places > 0 ? value * exponent : value / exponent );
73
+ }
74
+ PHP_ROUND_RESTORE_REG ();
75
+
69
76
double value_abs = fabs (value );
70
77
double edge_case ;
71
78
72
79
switch (mode ) {
73
80
case PHP_ROUND_HALF_UP :
74
- PHP_ROUND_GET_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
81
+ case PHP_ROUND_HALF_DOWN :
82
+ case PHP_ROUND_HALF_EVEN :
83
+ case PHP_ROUND_HALF_ODD :
84
+ if (fabs (adjusted_value ) >= value_abs ) {
85
+ edge_case = fabs ((integral + copysign (0.5 , integral )) / exponent );
86
+ } else {
87
+ edge_case = fabs ((integral + copysign (0.5 , integral )) * exponent );
88
+ }
89
+ break ;
90
+ default :
91
+ if (fabs (adjusted_value ) >= value_abs ) {
92
+ edge_case = fabs ((integral ) / exponent );
93
+ } else {
94
+ edge_case = fabs ((integral ) * exponent );
95
+ }
96
+ break ;
97
+ }
98
+
99
+ switch (mode ) {
100
+ case PHP_ROUND_HALF_UP :
75
101
if (value_abs >= edge_case ) {
76
102
/* We must increase the magnitude of the integral part
77
103
* (rounding up / towards infinity). copysign(1.0, integral)
@@ -87,23 +113,20 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
87
113
return integral ;
88
114
89
115
case PHP_ROUND_HALF_DOWN :
90
- PHP_ROUND_GET_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
91
116
if (value_abs > edge_case ) {
92
117
return integral + copysign (1.0 , integral );
93
118
}
94
119
95
120
return integral ;
96
121
97
122
case PHP_ROUND_CEILING :
98
- PHP_ROUND_GET_ZERO_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
99
123
if (value > 0.0 && value_abs > edge_case ) {
100
124
return integral + 1.0 ;
101
125
}
102
126
103
127
return integral ;
104
128
105
129
case PHP_ROUND_FLOOR :
106
- PHP_ROUND_GET_ZERO_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
107
130
if (value < 0.0 && value_abs > edge_case ) {
108
131
return integral - 1.0 ;
109
132
}
@@ -114,15 +137,13 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
114
137
return integral ;
115
138
116
139
case PHP_ROUND_AWAY_FROM_ZERO :
117
- PHP_ROUND_GET_ZERO_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
118
140
if (value_abs > edge_case ) {
119
141
return integral + copysign (1.0 , integral );
120
142
}
121
143
122
144
return integral ;
123
145
124
146
case PHP_ROUND_HALF_EVEN :
125
- PHP_ROUND_GET_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
126
147
if (value_abs > edge_case ) {
127
148
return integral + copysign (1.0 , integral );
128
149
} else if (UNEXPECTED (value_abs == edge_case )) {
@@ -139,7 +160,6 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
139
160
return integral ;
140
161
141
162
case PHP_ROUND_HALF_ODD :
142
- PHP_ROUND_GET_EDGE_CASE (adjusted_value , value_abs , integral , exponent );
143
163
if (value_abs > edge_case ) {
144
164
return integral + copysign (1.0 , integral );
145
165
} else if (UNEXPECTED (value_abs == edge_case )) {
@@ -188,7 +208,7 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
188
208
}
189
209
190
210
/* round the temp value */
191
- tmp_value = php_round_helper (tmp_value , value , exponent , mode );
211
+ tmp_value = php_round_helper (tmp_value , value , exponent , places , mode );
192
212
193
213
/* see if it makes sense to use simple division to round the value */
194
214
if (abs (places ) < 23 ) {
@@ -215,7 +235,6 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
215
235
tmp_value = value ;
216
236
}
217
237
}
218
-
219
238
return tmp_value ;
220
239
}
221
240
/* }}} */
0 commit comments