Skip to content

Commit feeb2fb

Browse files
committed
Temporarily changed floating point rounding mode.
1 parent 1b41af8 commit feeb2fb

File tree

2 files changed

+128
-27
lines changed

2 files changed

+128
-27
lines changed

ext/standard/math.c

+46-27
Original file line numberDiff line numberDiff line change
@@ -46,32 +46,58 @@ static inline double php_intpow10(int power) {
4646
}
4747
/* }}} */
4848

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-
6549
/* {{{ php_round_helper
6650
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+
6976
double value_abs = fabs(value);
7077
double edge_case;
7178

7279
switch (mode) {
7380
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:
75101
if (value_abs >= edge_case) {
76102
/* We must increase the magnitude of the integral part
77103
* (rounding up / towards infinity). copysign(1.0, integral)
@@ -87,23 +113,20 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
87113
return integral;
88114

89115
case PHP_ROUND_HALF_DOWN:
90-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
91116
if (value_abs > edge_case) {
92117
return integral + copysign(1.0, integral);
93118
}
94119

95120
return integral;
96121

97122
case PHP_ROUND_CEILING:
98-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
99123
if (value > 0.0 && value_abs > edge_case) {
100124
return integral + 1.0;
101125
}
102126

103127
return integral;
104128

105129
case PHP_ROUND_FLOOR:
106-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
107130
if (value < 0.0 && value_abs > edge_case) {
108131
return integral - 1.0;
109132
}
@@ -114,15 +137,13 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
114137
return integral;
115138

116139
case PHP_ROUND_AWAY_FROM_ZERO:
117-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
118140
if (value_abs > edge_case) {
119141
return integral + copysign(1.0, integral);
120142
}
121143

122144
return integral;
123145

124146
case PHP_ROUND_HALF_EVEN:
125-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
126147
if (value_abs > edge_case) {
127148
return integral + copysign(1.0, integral);
128149
} else if (UNEXPECTED(value_abs == edge_case)) {
@@ -139,7 +160,6 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
139160
return integral;
140161

141162
case PHP_ROUND_HALF_ODD:
142-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
143163
if (value_abs > edge_case) {
144164
return integral + copysign(1.0, integral);
145165
} else if (UNEXPECTED(value_abs == edge_case)) {
@@ -188,7 +208,7 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
188208
}
189209

190210
/* 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);
192212

193213
/* see if it makes sense to use simple division to round the value */
194214
if (abs(places) < 23) {
@@ -215,7 +235,6 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
215235
tmp_value = value;
216236
}
217237
}
218-
219238
return tmp_value;
220239
}
221240
/* }}} */

ext/standard/php_math.h

+82
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,86 @@ PHPAPI zend_string * _php_math_zvaltobase(zval *arg, int base);
130130
#define PHP_ROUND_AWAY_FROM_ZERO 0x08
131131
#endif
132132

133+
/**
134+
* Macro to change CPU rounding mode
135+
* arm uses inline asm because it may or may not have a build function
136+
* depending on the compiler.
137+
*/
138+
#if defined(__x86_64__) || defined(_WIN64)
139+
#define PHP_ROUND_GET_CURRENT_REG() unsigned int reg = _mm_getcsr()
140+
#define PHP_ROUND_RESTORE_REG() _mm_setcsr(reg)
141+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() _mm_setcsr((reg & ~0x6000) | 0x4000)
142+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() _mm_setcsr((reg & ~0x6000) | 0x2000)
143+
144+
#elif defined(__arm__) || defined(__aarch64__)
145+
#define PHP_ROUND_GET_CURRENT_REG() \
146+
uint64_t reg, new_reg;\
147+
__asm__ __volatile__ ("mrs %0, fpcr" : "=r"(reg))
148+
#define PHP_ROUND_RESTORE_REG() __asm__ __volatile__ ("msr fpcr, %0" : : "r" (reg))
149+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() do {\
150+
new_reg = (reg & ~0xc00000) | 0x400000;\
151+
__asm__ __volatile__ ("msr fpcr, %0" : : "r" (new_reg));\
152+
} while (0)
153+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() do {\
154+
new_reg = (reg & ~0xc00000) | 0x800000;\
155+
__asm__ __volatile__ ("msr fpcr, %0" : : "r" (new_reg));\
156+
} while (0)
157+
158+
#elif defined(HAVE__CONTROLFP_S)
159+
#define PHP_ROUND_GET_CURRENT_REG() \
160+
unsigned int reg;\
161+
unsigned int* _reg;\
162+
_controlfp_s(&reg, 0, 0)
163+
#define PHP_ROUND_RESTORE_REG() _controlfp_s(_reg, reg, _MCW_RC)
164+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() _controlfp_s(_reg, _RC_UP, _MCW_RC)
165+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() _controlfp_s(_reg, _RC_DOWN, _MCW_RC)
166+
167+
#elif defined(HAVE__CONTROLFP)
168+
#define PHP_ROUND_GET_CURRENT_REG() unsigned int reg = _controlfp(0, 0)
169+
#define PHP_ROUND_RESTORE_REG() _controlfp(reg, _MCW_RC)
170+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() _controlfp(_RC_UP, _MCW_RC)
171+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() _controlfp(_RC_DOWN, _MCW_RC)
172+
173+
#elif defined(HAVE__FPU_SETCW) /* glibc systems */
174+
#define PHP_ROUND_GET_CURRENT_REG() \
175+
fpu_control_t reg, new_reg;\
176+
_FPU_GETCW(reg)
177+
#define PHP_ROUND_RESTORE_REG() _FPU_SETCW(reg)
178+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() do {\
179+
new_reg = (reg & ~_FPU_RC_ZERO) | _FPU_RC_UP;\
180+
_FPU_SETCW(new_reg);\
181+
} while (0)
182+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() do {\
183+
new_reg = (reg & ~_FPU_RC_ZERO) | _FPU_RC_DOWN;\
184+
_FPU_SETCW(new_reg);\
185+
} while (0)
186+
187+
#elif defined(HAVE_FPSETPREC) /* FreeBSD */
188+
#define PHP_ROUND_GET_CURRENT_REG() fp_rnd_t reg = fpgetround()
189+
#define PHP_ROUND_RESTORE_REG() fpsetround(reg)
190+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() fpsetround(FP_RP)
191+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() fpsetround(FP_RM)
192+
193+
#elif defined(HAVE_FPU_INLINE_ASM_X86)
194+
#define PHP_ROUND_GET_CURRENT_REG() \
195+
unsigned int reg, new_reg;\
196+
__asm__ __volatile__ ("fnstcw %0" : "=m" (reg))
197+
#define PHP_ROUND_RESTORE_REG() __asm__ __volatile__ ("fldcw %0" : : "m" (reg))
198+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() do {\
199+
new_reg = (reg & ~0xc00) | 0x800;\
200+
__asm__ __volatile__ ("fldcw %0" : : "m" (new_reg));\
201+
} while (0)
202+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() do {\
203+
new_reg = (reg & ~0xc00) | 0x400;\
204+
__asm__ __volatile__ ("fldcw %0" : : "m" (new_reg));\
205+
} while (0)
206+
207+
#else
208+
#define PHP_ROUND_GET_CURRENT_REG() /* NOP */
209+
#define PHP_ROUND_RESTORE_REG() /* NOP */
210+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() /* NOP */
211+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() /* NOP */
212+
213+
#endif
214+
133215
#endif /* PHP_MATH_H */

0 commit comments

Comments
 (0)