Skip to content

Commit 24e7083

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

File tree

2 files changed

+106
-27
lines changed

2 files changed

+106
-27
lines changed

ext/standard/math.c

+33-27
Original file line numberDiff line numberDiff line change
@@ -46,32 +46,45 @@ 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+
double integral;
53+
PHP_ROUND_GET_CURRENT_REG();
54+
if (value >= 0.0) {
55+
PHP_ROUND_ROUND_MODE_SWITCH_UP();
56+
integral = floor(places > 0 ? value * exponent : value / exponent);
57+
} else {
58+
PHP_ROUND_ROUND_MODE_SWITCH_DOWN();
59+
integral = ceil(places > 0 ? value * exponent : value / exponent);
60+
}
61+
PHP_ROUND_RESTORE_REG();
62+
6963
double value_abs = fabs(value);
7064
double edge_case;
7165

7266
switch (mode) {
7367
case PHP_ROUND_HALF_UP:
74-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
68+
case PHP_ROUND_HALF_DOWN:
69+
case PHP_ROUND_HALF_EVEN:
70+
case PHP_ROUND_HALF_ODD:
71+
if (fabs(adjusted_value) >= value_abs) {
72+
edge_case = fabs((integral + copysign(0.5, integral)) / exponent);
73+
} else {
74+
edge_case = fabs((integral + copysign(0.5, integral)) * exponent);
75+
}
76+
break;
77+
default:
78+
if (fabs(adjusted_value) >= value_abs) {
79+
edge_case = fabs((integral) / exponent);
80+
} else {
81+
edge_case = fabs((integral) * exponent);
82+
}
83+
break;
84+
}
85+
86+
switch (mode) {
87+
case PHP_ROUND_HALF_UP:
7588
if (value_abs >= edge_case) {
7689
/* We must increase the magnitude of the integral part
7790
* (rounding up / towards infinity). copysign(1.0, integral)
@@ -87,23 +100,20 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
87100
return integral;
88101

89102
case PHP_ROUND_HALF_DOWN:
90-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
91103
if (value_abs > edge_case) {
92104
return integral + copysign(1.0, integral);
93105
}
94106

95107
return integral;
96108

97109
case PHP_ROUND_CEILING:
98-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
99110
if (value > 0.0 && value_abs > edge_case) {
100111
return integral + 1.0;
101112
}
102113

103114
return integral;
104115

105116
case PHP_ROUND_FLOOR:
106-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
107117
if (value < 0.0 && value_abs > edge_case) {
108118
return integral - 1.0;
109119
}
@@ -114,15 +124,13 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
114124
return integral;
115125

116126
case PHP_ROUND_AWAY_FROM_ZERO:
117-
PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
118127
if (value_abs > edge_case) {
119128
return integral + copysign(1.0, integral);
120129
}
121130

122131
return integral;
123132

124133
case PHP_ROUND_HALF_EVEN:
125-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
126134
if (value_abs > edge_case) {
127135
return integral + copysign(1.0, integral);
128136
} else if (UNEXPECTED(value_abs == edge_case)) {
@@ -139,7 +147,6 @@ static inline double php_round_helper(double adjusted_value, double value, doubl
139147
return integral;
140148

141149
case PHP_ROUND_HALF_ODD:
142-
PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent);
143150
if (value_abs > edge_case) {
144151
return integral + copysign(1.0, integral);
145152
} else if (UNEXPECTED(value_abs == edge_case)) {
@@ -188,7 +195,7 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
188195
}
189196

190197
/* round the temp value */
191-
tmp_value = php_round_helper(tmp_value, value, exponent, mode);
198+
tmp_value = php_round_helper(tmp_value, value, exponent, places, mode);
192199

193200
/* see if it makes sense to use simple division to round the value */
194201
if (abs(places) < 23) {
@@ -215,7 +222,6 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
215222
tmp_value = value;
216223
}
217224
}
218-
219225
return tmp_value;
220226
}
221227
/* }}} */

ext/standard/php_math.h

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

133+
/* for round */
134+
135+
#if defined(__x86_64__) || defined(_WIN64)
136+
#define PHP_ROUND_GET_CURRENT_REG() unsigned int reg = _mm_getcsr()
137+
#define PHP_ROUND_RESTORE_REG() _mm_setcsr(reg)
138+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() _mm_setcsr((reg & 0xFFFF9FFF) | 0x00004000)
139+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() _mm_setcsr((reg & 0xFFFF9FFF) | 0x00002000)
140+
141+
#elif defined(__arm__) || defined(__aarch64__)
142+
#define PHP_ROUND_GET_CURRENT_REG() \
143+
uint64_t reg;\
144+
__asm__ __volatile__ ("mrs %0, fpcr" : "=r"(reg))
145+
#define PHP_ROUND_RESTORE_REG() __asm__ __volatile__ ("msr fpcr, %0" : : "r" (reg))
146+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() do { \
147+
uint64_t new_reg = (reg & ~0xc0000) | 0x400000;\
148+
__asm__ __volatile__ ("msr fpcr, %0" : : "r" (new_reg));\
149+
} while (0)
150+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() do { \
151+
uint64_t new_reg = (reg & ~0xc0000) | 0x800000;\
152+
__asm__ __volatile__ ("msr fpcr, %0" : : "r" (new_reg));\
153+
} while (0)
154+
155+
#elif defined(HAVE__CONTROLFP_S)
156+
#define PHP_ROUND_GET_CURRENT_REG() \
157+
unsigned int reg;\
158+
unsigned int* _reg;\
159+
_controlfp_s(&reg, 0, 0)
160+
#define PHP_ROUND_RESTORE_REG() _controlfp_s(_reg, reg, _MCW_RC)
161+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() _controlfp_s(_reg, _RC_UP, _MCW_RC)
162+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() _controlfp_s(_reg, _RC_DOWN, _MCW_RC)
163+
164+
#elif defined(HAVE__CONTROLFP)
165+
#define PHP_ROUND_GET_CURRENT_REG() unsigned int reg = _controlfp(0, 0)
166+
#define PHP_ROUND_RESTORE_REG() _controlfp(reg, _MCW_RC)
167+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() _controlfp(_RC_UP, _MCW_RC)
168+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() _controlfp(_RC_DOWN, _MCW_RC)
169+
170+
#elif defined(HAVE__FPU_SETCW) /* glibc systems */
171+
#define PHP_ROUND_GET_CURRENT_REG() \
172+
fpu_control_t reg;\
173+
_FPU_GETCW(reg)
174+
#define PHP_ROUND_RESTORE_REG() _FPU_SETCW(reg)
175+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() _FPU_SETCW((reg & ~_FPU_RC_ZERO) | _FPU_RC_UP)
176+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() _FPU_SETCW((reg & ~_FPU_RC_ZERO) | _FPU_RC_DOWN)
177+
178+
#elif defined(HAVE_FPSETPREC) /* FreeBSD */
179+
#define PHP_ROUND_GET_CURRENT_REG() fp_rnd_t reg = fpgetround()
180+
#define PHP_ROUND_RESTORE_REG() fpsetround(reg)
181+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() fpsetround(FP_RP)
182+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() fpsetround(FP_RM)
183+
184+
#elif defined(HAVE_FPU_INLINE_ASM_X86)
185+
#define PHP_ROUND_GET_CURRENT_REG() \
186+
unsigned int reg;\
187+
__asm__ __volatile__ ("fnstcw %0" : "=m" (reg))
188+
#define PHP_ROUND_RESTORE_REG() __asm__ __volatile__ ("fldcw %0" : : "m" (reg))
189+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() do { \
190+
unsigned int new_reg = (reg & ~0xc00) | 0x800;\
191+
__asm__ __volatile__ ("fldcw %0" : : "m" (new_reg));\
192+
} while (0)
193+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() do { \
194+
unsigned int new_reg = (reg & ~0xc00) | 0x400;\
195+
__asm__ __volatile__ ("fldcw %0" : : "m" (new_reg));\
196+
} while (0)
197+
198+
#else
199+
#define PHP_ROUND_GET_CURRENT_REG() /* NOP */
200+
#define PHP_ROUND_RESTORE_REG() /* NOP */
201+
#define PHP_ROUND_ROUND_MODE_SWITCH_UP() /* NOP */
202+
#define PHP_ROUND_ROUND_MODE_SWITCH_DOWN() /* NOP */
203+
204+
#endif
205+
133206
#endif /* PHP_MATH_H */

0 commit comments

Comments
 (0)