Skip to content

Commit b3f259d

Browse files
marc-mabebukka
authored andcommitted
number_format: cast large floats within range of int to int
This prevents loosing precision for numbers above 2^52. Closes GH-12333
1 parent 19eb727 commit b3f259d

File tree

3 files changed

+43
-7
lines changed

3 files changed

+43
-7
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ Standard:
104104
. Fixed bug GH-12592 (strcspn() odd behaviour with NUL bytes and empty mask).
105105
(nielsdos)
106106
. Removed the deprecated inet_ntoa call support. (David Carlier)
107+
. Cast large floats that are within int range to int in number_format so
108+
the precision is not lost. (Marc Bennewitz)
107109

108110
XML:
109111
. Added XML_OPTION_PARSE_HUGE parser option. (nielsdos)

ext/standard/math.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,6 +1330,16 @@ PHP_FUNCTION(number_format)
13301330
break;
13311331

13321332
case IS_DOUBLE:
1333+
// double values of >= 2^52 can not have fractional digits anymore
1334+
// Casting to long on 64bit will not loose precision on rounding
1335+
if (UNEXPECTED(
1336+
(Z_DVAL_P(num) >= 4503599627370496.0 || Z_DVAL_P(num) <= -4503599627370496.0)
1337+
&& ZEND_DOUBLE_FITS_LONG(Z_DVAL_P(num))
1338+
)) {
1339+
RETURN_STR(_php_math_number_format_long((zend_long)Z_DVAL_P(num), dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
1340+
break;
1341+
}
1342+
13331343
if (dec >= 0) {
13341344
dec_int = ZEND_LONG_INT_OVFL(dec) ? INT_MAX : (int)dec;
13351345
} else {

ext/standard/tests/math/number_format_basiclong_64bit.phpt

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ define("MAX_32Bit", 2147483647);
1212
define("MIN_64Bit", -9223372036854775807 - 1);
1313
define("MIN_32Bit", -2147483647 - 1);
1414

15-
$longVals = array(
15+
$numbers = array(
1616
MAX_64Bit, MIN_64Bit, MAX_32Bit, MIN_32Bit, MAX_64Bit - MAX_32Bit, MIN_64Bit - MIN_32Bit,
1717
MAX_32Bit + 1, MIN_32Bit - 1, MAX_32Bit * 2, (MAX_32Bit * 2) + 1, (MAX_32Bit * 2) - 1,
18-
MAX_64Bit -1, MAX_64Bit + 1, MIN_64Bit + 1, MIN_64Bit - 1
18+
MAX_64Bit - 1, MAX_64Bit + 1, MIN_64Bit + 1, MIN_64Bit - 1,
19+
// floats rounded as int
20+
MAX_64Bit - 1024.0, MIN_64Bit + 1024.0
1921
);
2022

2123
$precisions = array(
@@ -31,12 +33,12 @@ $precisions = array(
3133
PHP_INT_MIN,
3234
);
3335

34-
foreach ($longVals as $longVal) {
36+
foreach ($numbers as $number) {
3537
echo "--- testing: ";
36-
var_dump($longVal);
38+
var_dump($number);
3739
foreach ($precisions as $precision) {
3840
echo "... with precision " . $precision . ": ";
39-
var_dump(number_format($longVal, $precision));
41+
var_dump(number_format($number, $precision));
4042
}
4143
}
4244

@@ -199,8 +201,30 @@ foreach ($longVals as $longVal) {
199201
--- testing: float(-9.223372036854776E+18)
200202
... with precision 5: string(32) "-9,223,372,036,854,775,808.00000"
201203
... with precision 0: string(26) "-9,223,372,036,854,775,808"
202-
... with precision -1: string(26) "-9,223,372,036,854,775,808"
203-
... with precision -5: string(26) "-9,223,372,036,854,800,384"
204+
... with precision -1: string(26) "-9,223,372,036,854,775,810"
205+
... with precision -5: string(26) "-9,223,372,036,854,800,000"
206+
... with precision -10: string(26) "-9,223,372,040,000,000,000"
207+
... with precision -11: string(26) "-9,223,372,000,000,000,000"
208+
... with precision -17: string(26) "-9,200,000,000,000,000,000"
209+
... with precision -19: string(27) "-10,000,000,000,000,000,000"
210+
... with precision -20: string(1) "0"
211+
... with precision -9223372036854775808: string(1) "0"
212+
--- testing: float(9.223372036854775E+18)
213+
... with precision 5: string(31) "9,223,372,036,854,774,784.00000"
214+
... with precision 0: string(25) "9,223,372,036,854,774,784"
215+
... with precision -1: string(25) "9,223,372,036,854,774,780"
216+
... with precision -5: string(25) "9,223,372,036,854,800,000"
217+
... with precision -10: string(25) "9,223,372,040,000,000,000"
218+
... with precision -11: string(25) "9,223,372,000,000,000,000"
219+
... with precision -17: string(25) "9,200,000,000,000,000,000"
220+
... with precision -19: string(26) "10,000,000,000,000,000,000"
221+
... with precision -20: string(1) "0"
222+
... with precision -9223372036854775808: string(1) "0"
223+
--- testing: float(-9.223372036854775E+18)
224+
... with precision 5: string(32) "-9,223,372,036,854,774,784.00000"
225+
... with precision 0: string(26) "-9,223,372,036,854,774,784"
226+
... with precision -1: string(26) "-9,223,372,036,854,774,780"
227+
... with precision -5: string(26) "-9,223,372,036,854,800,000"
204228
... with precision -10: string(26) "-9,223,372,040,000,000,000"
205229
... with precision -11: string(26) "-9,223,372,000,000,000,000"
206230
... with precision -17: string(26) "-9,200,000,000,000,000,000"

0 commit comments

Comments
 (0)