Skip to content

number_format: float within range of int can be cast to int … #12333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ext/standard/math.c
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,16 @@ PHP_FUNCTION(number_format)
break;

case IS_DOUBLE:
// double values of >= 2^52 can not have fractional digits anymore
// Casting to long on 64bit will not loose precision on rounding
if (UNEXPECTED(
(Z_DVAL_P(num) >= 4503599627370496.0 || Z_DVAL_P(num) <= -4503599627370496.0)
&& ZEND_DOUBLE_FITS_LONG(Z_DVAL_P(num))
Comment on lines +1334 to +1335
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't ZEND_DOUBLE_FITS_LONG(Z_DVAL_P(num)) a little bit faster than the first condition? We have the difference of one execution of Z_DVAL_P.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so.

  1. This is first checking the unusual case for numbers above 2^52
  2. ZEND_DOUBLE_FITS_LONG will cast ZEND_LONG_[MIN|MAX] and compare double values as well !((d) >= (double)ZEND_LONG_MAX || (d) < (double)ZEND_LONG_MIN)
  3. Z_DVAL_P(num) results in *num.value.dval

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For number outside the order doesn't matter because two conditions must be met. But for number inside the range the second condition has only one Z_DVAL_P evaluation and first condition has two of them. The comparison of doubles occurs in both conditions.

However, I think that the difference is so small that you can ignore my comment. Probably even it may be optimized by compiler.

)) {
RETURN_STR(_php_math_number_format_long((zend_long)Z_DVAL_P(num), dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
break;
}

if (dec >= 0) {
dec_int = ZEND_LONG_INT_OVFL(dec) ? INT_MAX : (int)dec;
} else {
Expand Down
38 changes: 31 additions & 7 deletions ext/standard/tests/math/number_format_basiclong_64bit.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ define("MAX_32Bit", 2147483647);
define("MIN_64Bit", -9223372036854775807 - 1);
define("MIN_32Bit", -2147483647 - 1);

$longVals = array(
$numbers = array(
MAX_64Bit, MIN_64Bit, MAX_32Bit, MIN_32Bit, MAX_64Bit - MAX_32Bit, MIN_64Bit - MIN_32Bit,
MAX_32Bit + 1, MIN_32Bit - 1, MAX_32Bit * 2, (MAX_32Bit * 2) + 1, (MAX_32Bit * 2) - 1,
MAX_64Bit -1, MAX_64Bit + 1, MIN_64Bit + 1, MIN_64Bit - 1
MAX_64Bit - 1, MAX_64Bit + 1, MIN_64Bit + 1, MIN_64Bit - 1,
// floats rounded as int
MAX_64Bit - 1024.0, MIN_64Bit + 1024.0
);

$precisions = array(
Expand All @@ -31,12 +33,12 @@ $precisions = array(
PHP_INT_MIN,
);

foreach ($longVals as $longVal) {
foreach ($numbers as $number) {
echo "--- testing: ";
var_dump($longVal);
var_dump($number);
foreach ($precisions as $precision) {
echo "... with precision " . $precision . ": ";
var_dump(number_format($longVal, $precision));
var_dump(number_format($number, $precision));
}
}

Expand Down Expand Up @@ -199,8 +201,30 @@ foreach ($longVals as $longVal) {
--- testing: float(-9.223372036854776E+18)
... with precision 5: string(32) "-9,223,372,036,854,775,808.00000"
... with precision 0: string(26) "-9,223,372,036,854,775,808"
... with precision -1: string(26) "-9,223,372,036,854,775,808"
... with precision -5: string(26) "-9,223,372,036,854,800,384"
... with precision -1: string(26) "-9,223,372,036,854,775,810"
... with precision -5: string(26) "-9,223,372,036,854,800,000"
... with precision -10: string(26) "-9,223,372,040,000,000,000"
... with precision -11: string(26) "-9,223,372,000,000,000,000"
... with precision -17: string(26) "-9,200,000,000,000,000,000"
... with precision -19: string(27) "-10,000,000,000,000,000,000"
... with precision -20: string(1) "0"
... with precision -9223372036854775808: string(1) "0"
--- testing: float(9.223372036854775E+18)
... with precision 5: string(31) "9,223,372,036,854,774,784.00000"
... with precision 0: string(25) "9,223,372,036,854,774,784"
... with precision -1: string(25) "9,223,372,036,854,774,780"
... with precision -5: string(25) "9,223,372,036,854,800,000"
... with precision -10: string(25) "9,223,372,040,000,000,000"
... with precision -11: string(25) "9,223,372,000,000,000,000"
... with precision -17: string(25) "9,200,000,000,000,000,000"
... with precision -19: string(26) "10,000,000,000,000,000,000"
... with precision -20: string(1) "0"
... with precision -9223372036854775808: string(1) "0"
--- testing: float(-9.223372036854775E+18)
... with precision 5: string(32) "-9,223,372,036,854,774,784.00000"
... with precision 0: string(26) "-9,223,372,036,854,774,784"
... with precision -1: string(26) "-9,223,372,036,854,774,780"
... with precision -5: string(26) "-9,223,372,036,854,800,000"
... with precision -10: string(26) "-9,223,372,040,000,000,000"
... with precision -11: string(26) "-9,223,372,000,000,000,000"
... with precision -17: string(26) "-9,200,000,000,000,000,000"
Expand Down