Skip to content

Commit 8718755

Browse files
committed
Merge branch 'dval_to_lval' into PHP-5.5
* dval_to_lval: Fix rounding of zend_dval_to_lval Fix zend_dval_to_lval outside 64bit integers range
2 parents 64a2a8a + a86fcfb commit 8718755

File tree

4 files changed

+87
-6
lines changed

4 files changed

+87
-6
lines changed

Zend/tests/bug39018.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ print "\nDone\n";
6464
--EXPECTF--
6565
Notice: String offset cast occurred in %s on line %d
6666

67+
Notice: Uninitialized string offset: %s in %s on line 6
68+
6769
Notice: Uninitialized string offset: 0 in %s on line %d
6870

6971
Notice: Uninitialized string offset: 0 in %s on line %d

Zend/tests/dval_to_lval_32.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
zend_dval_to_lval preserves low bits (32 bit long)
3+
--SKIPIF--
4+
<?php
5+
if (PHP_INT_SIZE != 4)
6+
die("skip for machines with 32-bit longs");
7+
?>
8+
--FILE--
9+
<?php
10+
/* test doubles around -4e21 */
11+
$values = [
12+
-4000000000000001048576.,
13+
-4000000000000000524288.,
14+
-4000000000000000000000.,
15+
-3999999999999999475712.,
16+
-3999999999999998951424.,
17+
];
18+
/* see if we're rounding negative numbers right */
19+
$values[] = -2147483649.8;
20+
21+
foreach ($values as $v) {
22+
var_dump((int)$v);
23+
}
24+
25+
?>
26+
--EXPECT--
27+
int(-2056257536)
28+
int(-2055733248)
29+
int(-2055208960)
30+
int(-2054684672)
31+
int(-2054160384)
32+
int(2147483647)

Zend/tests/dval_to_lval_64.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
zend_dval_to_lval preserves low bits (64 bit long)
3+
--SKIPIF--
4+
<?php
5+
if (PHP_INT_SIZE != 8)
6+
die("skip for machines with 64-bit longs");
7+
?>
8+
--FILE--
9+
<?php
10+
/* test doubles around -4e21 */
11+
$values = [
12+
-4000000000000001048576.,
13+
-4000000000000000524288.,
14+
-4000000000000000000000.,
15+
-3999999999999999475712.,
16+
-3999999999999998951424.,
17+
];
18+
19+
foreach ($values as $v) {
20+
var_dump((int)$v);
21+
}
22+
23+
?>
24+
--EXPECT--
25+
int(2943463994971652096)
26+
int(2943463994972176384)
27+
int(2943463994972700672)
28+
int(2943463994973224960)
29+
int(2943463994973749248)

Zend/zend_operators.h

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,40 @@ END_EXTERN_C()
6868

6969
#if ZEND_DVAL_TO_LVAL_CAST_OK
7070
# define zend_dval_to_lval(d) ((long) (d))
71-
#elif SIZEOF_LONG == 4 && defined(HAVE_ZEND_LONG64)
71+
#elif SIZEOF_LONG == 4
7272
static zend_always_inline long zend_dval_to_lval(double d)
7373
{
7474
if (d > LONG_MAX || d < LONG_MIN) {
75-
return (long)(unsigned long)(zend_long64) d;
75+
double two_pow_32 = pow(2., 32.),
76+
dmod;
77+
78+
dmod = fmod(d, two_pow_32);
79+
if (dmod < 0) {
80+
/* we're going to make this number positive; call ceil()
81+
* to simulate rounding towards 0 of the negative number */
82+
dmod = ceil(dmod) + two_pow_32;
83+
}
84+
return (long)(unsigned long)dmod;
7685
}
77-
return (long) d;
86+
return (long)d;
7887
}
7988
#else
8089
static zend_always_inline long zend_dval_to_lval(double d)
8190
{
8291
/* >= as (double)LONG_MAX is outside signed range */
83-
if (d >= LONG_MAX) {
84-
return (long)(unsigned long) d;
92+
if (d >= LONG_MAX || d < LONG_MIN) {
93+
double two_pow_64 = pow(2., 64.),
94+
dmod;
95+
96+
dmod = fmod(d, two_pow_64);
97+
if (dmod < 0) {
98+
/* no need to call ceil; original double must have had no
99+
* fractional part, hence dmod does not have one either */
100+
dmod += two_pow_64;
101+
}
102+
return (long)(unsigned long)dmod;
85103
}
86-
return (long) d;
104+
return (long)d;
87105
}
88106
#endif
89107
/* }}} */

0 commit comments

Comments
 (0)