Skip to content

Commit a8b422c

Browse files
committed
optimized round() processing
1 parent 34e2dc5 commit a8b422c

File tree

3 files changed

+160
-107
lines changed

3 files changed

+160
-107
lines changed

ext/standard/math.c

Lines changed: 25 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -30,50 +30,6 @@
3030

3131
#include "basic_functions.h"
3232

33-
/* {{{ php_intlog10abs
34-
Returns floor(log10(fabs(val))), uses fast binary search */
35-
static inline int php_intlog10abs(double value) {
36-
value = fabs(value);
37-
38-
if (value < 1e-8 || value > 1e22) {
39-
return (int)floor(log10(value));
40-
} else {
41-
/* Do a binary search with 5 steps */
42-
int result = 15;
43-
static const double values[] = {
44-
1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2,
45-
1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13,
46-
1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
47-
48-
if (value < values[result]) {
49-
result -= 8;
50-
} else {
51-
result += 8;
52-
}
53-
if (value < values[result]) {
54-
result -= 4;
55-
} else {
56-
result += 4;
57-
}
58-
if (value < values[result]) {
59-
result -= 2;
60-
} else {
61-
result += 2;
62-
}
63-
if (value < values[result]) {
64-
result -= 1;
65-
} else {
66-
result += 1;
67-
}
68-
if (value < values[result]) {
69-
result -= 1;
70-
}
71-
result -= 8;
72-
return result;
73-
}
74-
}
75-
/* }}} */
76-
7733
/* {{{ php_intpow10
7834
Returns pow(10.0, (double)power), uses fast lookup table for exact powers */
7935
static inline double php_intpow10(int power) {
@@ -92,20 +48,26 @@ static inline double php_intpow10(int power) {
9248

9349
/* {{{ php_round_helper
9450
Actually performs the rounding of a value to integer in a certain mode */
95-
static inline double php_round_helper(double value, int mode) {
96-
double integral, fractional;
51+
static inline double php_round_helper(double adjusted_value, double value, double coefficient, int mode) {
52+
double integral, fractional, edge_case;
53+
double value_abs = fabs(value);
9754

9855
/* Split the input value into the integral and fractional part.
9956
*
10057
* Both parts will have the same sign as the input value. We take
10158
* the absolute value of the fractional part (which will not result
10259
* in branches in the assembly) to make the following cases simpler.
10360
*/
104-
fractional = fabs(modf(value, &integral));
61+
fractional = fabs(modf(adjusted_value, &integral));
62+
if (fabs(adjusted_value) >= value_abs) {
63+
edge_case = fabs((integral + copysign(0.5, integral)) / coefficient);
64+
} else {
65+
edge_case = fabs((integral + copysign(0.5, integral)) * coefficient);
66+
}
10567

10668
switch (mode) {
10769
case PHP_ROUND_HALF_UP:
108-
if (fractional >= 0.5) {
70+
if (value_abs >= edge_case) {
10971
/* We must increase the magnitude of the integral part
11072
* (rounding up / towards infinity). copysign(1.0, integral)
11173
* will either result in 1.0 or -1.0 depending on the sign
@@ -120,7 +82,7 @@ static inline double php_round_helper(double value, int mode) {
12082
return integral;
12183

12284
case PHP_ROUND_HALF_DOWN:
123-
if (fractional > 0.5) {
85+
if (value_abs > edge_case) {
12486
return integral + copysign(1.0, integral);
12587
}
12688

@@ -151,11 +113,9 @@ static inline double php_round_helper(double value, int mode) {
151113
return integral;
152114

153115
case PHP_ROUND_HALF_EVEN:
154-
if (fractional > 0.5) {
116+
if (value_abs > edge_case) {
155117
return integral + copysign(1.0, integral);
156-
}
157-
158-
if (UNEXPECTED(fractional == 0.5)) {
118+
} else if (value_abs == edge_case) {
159119
bool even = !fmod(integral, 2.0);
160120

161121
/* If the integral part is not even we can make it even
@@ -169,11 +129,9 @@ static inline double php_round_helper(double value, int mode) {
169129
return integral;
170130

171131
case PHP_ROUND_HALF_ODD:
172-
if (fractional > 0.5) {
132+
if (value_abs > edge_case) {
173133
return integral + copysign(1.0, integral);
174-
}
175-
176-
if (UNEXPECTED(fractional == 0.5)) {
134+
} else if (value_abs == edge_case) {
177135
bool even = !fmod(integral, 2.0);
178136

179137
if (even) {
@@ -196,56 +154,30 @@ static inline double php_round_helper(double value, int mode) {
196154
* mode. For the specifics of the algorithm, see http://wiki.php.net/rfc/rounding
197155
*/
198156
PHPAPI double _php_math_round(double value, int places, int mode) {
199-
double f1, f2;
157+
double f1;
200158
double tmp_value;
201-
int precision_places;
202159

203160
if (!zend_finite(value) || value == 0.0) {
204161
return value;
205162
}
206163

207164
places = places < INT_MIN+1 ? INT_MIN+1 : places;
208-
precision_places = 14 - php_intlog10abs(value);
209165

210166
f1 = php_intpow10(abs(places));
211167

212-
/* If the decimal precision guaranteed by FP arithmetic is higher than
213-
the requested places BUT is small enough to make sure a non-zero value
214-
is returned, pre-round the result to the precision */
215-
if (precision_places > places && precision_places - 15 < places) {
216-
int64_t use_precision = precision_places < INT_MIN+1 ? INT_MIN+1 : precision_places;
217-
218-
f2 = php_intpow10(abs((int)use_precision));
219-
if (use_precision >= 0) {
220-
tmp_value = value * f2;
221-
} else {
222-
tmp_value = value / f2;
223-
}
224-
/* preround the result (tmp_value will always be something * 1e14,
225-
thus never larger than 1e15 here) */
226-
tmp_value = php_round_helper(tmp_value, mode);
227-
228-
use_precision = places - precision_places;
229-
use_precision = use_precision < INT_MIN+1 ? INT_MIN+1 : use_precision;
230-
/* now correctly move the decimal point */
231-
f2 = php_intpow10(abs((int)use_precision));
232-
/* because places < precision_places */
233-
tmp_value = tmp_value / f2;
168+
/* adjust the value */
169+
if (places >= 0) {
170+
tmp_value = value * f1;
234171
} else {
235-
/* adjust the value */
236-
if (places >= 0) {
237-
tmp_value = value * f1;
238-
} else {
239-
tmp_value = value / f1;
240-
}
241-
/* This value is beyond our precision, so rounding it is pointless */
242-
if (fabs(tmp_value) >= 1e15) {
243-
return value;
244-
}
172+
tmp_value = value / f1;
173+
}
174+
/* This value is beyond our precision, so rounding it is pointless */
175+
if (fabs(tmp_value) >= 1e15) {
176+
return value;
245177
}
246178

247179
/* round the temp value */
248-
tmp_value = php_round_helper(tmp_value, mode);
180+
tmp_value = php_round_helper(tmp_value, value, f1, mode);
249181

250182
/* see if it makes sense to use simple division to round the value */
251183
if (abs(places) < 23) {

ext/standard/tests/math/bug24142.phpt

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,65 @@
22
Bug #24142 (round() problems)
33
--FILE--
44
<?php
5-
$v = 0.005;
6-
for ($i = 1; $i < 10; $i++) {
7-
echo "round({$v}, 2) -> ".round($v, 2)."\n";
8-
$v += 0.01;
9-
}
5+
echo "round(0.005, 2)\n";
6+
var_dump(round(0.005, 2));
7+
echo "\n";
8+
9+
echo "round(0.015, 2)\n";
10+
var_dump(round(0.015, 2));
11+
echo "\n";
12+
13+
echo "round(0.025, 2)\n";
14+
var_dump(round(0.025, 2));
15+
echo "\n";
16+
17+
echo "round(0.035, 2)\n";
18+
var_dump(round(0.035, 2));
19+
echo "\n";
20+
21+
echo "round(0.045, 2)\n";
22+
var_dump(round(0.045, 2));
23+
echo "\n";
24+
25+
echo "round(0.055, 2)\n";
26+
var_dump(round(0.055, 2));
27+
echo "\n";
28+
29+
echo "round(0.065, 2)\n";
30+
var_dump(round(0.065, 2));
31+
echo "\n";
32+
33+
echo "round(0.075, 2)\n";
34+
var_dump(round(0.075, 2));
35+
echo "\n";
36+
37+
echo "round(0.085, 2)\n";
38+
var_dump(round(0.085, 2));
1039
?>
1140
--EXPECT--
12-
round(0.005, 2) -> 0.01
13-
round(0.015, 2) -> 0.02
14-
round(0.025, 2) -> 0.03
15-
round(0.035, 2) -> 0.04
16-
round(0.045, 2) -> 0.05
17-
round(0.055, 2) -> 0.06
18-
round(0.065, 2) -> 0.07
19-
round(0.075, 2) -> 0.08
20-
round(0.085, 2) -> 0.09
41+
round(0.005, 2)
42+
float(0.01)
43+
44+
round(0.015, 2)
45+
float(0.02)
46+
47+
round(0.025, 2)
48+
float(0.03)
49+
50+
round(0.035, 2)
51+
float(0.04)
52+
53+
round(0.045, 2)
54+
float(0.05)
55+
56+
round(0.055, 2)
57+
float(0.06)
58+
59+
round(0.065, 2)
60+
float(0.07)
61+
62+
round(0.075, 2)
63+
float(0.08)
64+
65+
round(0.085, 2)
66+
float(0.09)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
--TEST--
2+
Fix GH-12143: Remove pre round
3+
--FILE--
4+
<?php
5+
echo "HALF_UP\n";
6+
var_dump(round(1.700000000000145, 13, PHP_ROUND_HALF_UP));
7+
var_dump(round(-1.700000000000145, 13, PHP_ROUND_HALF_UP));
8+
var_dump(round(123456789012344.5, -1, PHP_ROUND_HALF_UP));
9+
var_dump(round(-123456789012344.5, -1, PHP_ROUND_HALF_UP));
10+
echo "\n";
11+
12+
echo "HALF_DOWN\n";
13+
var_dump(round(1.70000000000015, 13, PHP_ROUND_HALF_DOWN));
14+
var_dump(round(-1.70000000000015, 13, PHP_ROUND_HALF_DOWN));
15+
var_dump(round(123456789012345.0, -1, PHP_ROUND_HALF_DOWN));
16+
var_dump(round(-123456789012345.0, -1, PHP_ROUND_HALF_DOWN));
17+
var_dump(round(1.500000000000001, 0, PHP_ROUND_HALF_DOWN));
18+
var_dump(round(-1.500000000000001, 0, PHP_ROUND_HALF_DOWN));
19+
echo "\n";
20+
21+
echo "HALF_EVEN\n";
22+
var_dump(round(1.70000000000025, 13, PHP_ROUND_HALF_EVEN));
23+
var_dump(round(-1.70000000000025, 13, PHP_ROUND_HALF_EVEN));
24+
var_dump(round(1.70000000000075, 13, PHP_ROUND_HALF_EVEN));
25+
var_dump(round(-1.70000000000075, 13, PHP_ROUND_HALF_EVEN));
26+
var_dump(round(12345678901234.5, 0, PHP_ROUND_HALF_EVEN));
27+
var_dump(round(-12345678901234.5, 0, PHP_ROUND_HALF_EVEN));
28+
var_dump(round(1.500000000000001, 0, PHP_ROUND_HALF_EVEN));
29+
var_dump(round(-1.500000000000001, 0, PHP_ROUND_HALF_EVEN));
30+
echo "\n";
31+
32+
echo "HALF_ODD\n";
33+
var_dump(round(1.70000000000025, 13, PHP_ROUND_HALF_ODD));
34+
var_dump(round(-1.70000000000025, 13, PHP_ROUND_HALF_ODD));
35+
var_dump(round(1.70000000000075, 13, PHP_ROUND_HALF_ODD));
36+
var_dump(round(-1.70000000000075, 13, PHP_ROUND_HALF_ODD));
37+
var_dump(round(12345678901233.5, 0, PHP_ROUND_HALF_ODD));
38+
var_dump(round(-12345678901233.5, 0, PHP_ROUND_HALF_ODD));
39+
var_dump(round(1.500000000000001, 0, PHP_ROUND_HALF_ODD));
40+
var_dump(round(-1.500000000000001, 0, PHP_ROUND_HALF_ODD));
41+
?>
42+
--EXPECT--
43+
HALF_UP
44+
float(1.7000000000001)
45+
float(-1.7000000000001)
46+
float(123456789012340)
47+
float(-123456789012340)
48+
49+
HALF_DOWN
50+
float(1.7000000000001)
51+
float(-1.7000000000001)
52+
float(123456789012340)
53+
float(-123456789012340)
54+
float(2)
55+
float(-2)
56+
57+
HALF_EVEN
58+
float(1.7000000000002)
59+
float(-1.7000000000002)
60+
float(1.7000000000008)
61+
float(-1.7000000000008)
62+
float(12345678901234)
63+
float(-12345678901234)
64+
float(2)
65+
float(-2)
66+
67+
HALF_ODD
68+
float(1.7000000000003)
69+
float(-1.7000000000003)
70+
float(1.7000000000007)
71+
float(-1.7000000000007)
72+
float(12345678901233)
73+
float(-12345678901233)
74+
float(2)
75+
float(-2)

0 commit comments

Comments
 (0)