1

When a float becomes long enough PHP seems to have trouble giving back the correct rounded number. Is there an alternate way of rounding to give back the expected number?

echo round(104045.7549999995, 2) // yields: 104045.76 wrong
echo round(104045.754999995, 2) // yields: 104045.75

Comparing that to JS, we get the expected correct values every time:

Math.round(104045.7549999995*100)/100; // yields: 104045.75
Math.round(104045.754999995*100)/100; // yields: 104045.75

It looks like PHP only looks ahead X number of decimals places.

Edit: I have now tested in Postgres as well:

select round(104045.7549999995, 2); // yields: 104045.75
select round(104045.754999995, 2); // yields: 104045.75
Royal Wares
  • 1,192
  • 10
  • 23
  • 2
    I actually believe PHP is correct here. PHP seems to be going to the far right and rounding from there, which at 3DP would yield `.755` - leading to `.76`. – Scoots Sep 14 '18 at 11:24
  • 2
    My assertion is correct it would seem: https://ideone.com/Scug1g – Scoots Sep 14 '18 at 11:27
  • 1
    Actually, mathematically the OP is correct, in this example JS has it right and PHP is wrong. I don't know enough about the underlying implementations to say why, or how to get the desired behaviour in PHP - other than I guess this is to do with the perennial issue of floats never being handled with 100% accuracy (this applies to any language, it's a fundamental fact about computers). – Robin Zigmond Sep 14 '18 at 11:28
  • Check - http://php.net/manual/en/language.types.float.php – Sougata Bose Sep 14 '18 at 11:28
  • @SougataBose it's okay that PHP has that precision issue as Robin said it's fundamental in computers, however I'm still looking to work around it to yield the results I need. – Royal Wares Sep 14 '18 at 11:32
  • Suggests - So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available. - http://php.net/manual/en/ref.bc.php & http://php.net/manual/en/ref.gmp.php – Sougata Bose Sep 14 '18 at 11:33
  • 3
    The first number is stored internally as 104045.755. By the time you're invoking the round function it's already too late. – James Sep 14 '18 at 11:35
  • @James you're right, $f = 104045.754999995; echo $f; yields: 104045.75499999 (omitting the last digit) I guess the only way around this would be to work with strings and create a function to iterate over the characters which will be painful. – Royal Wares Sep 14 '18 at 11:40
  • This might help you https://stackoverflow.com/questions/48368421/function-round-php-not-work-correctly, have a look – Shameem Ahmed Mulla Sep 14 '18 at 12:05

1 Answers1

0

If you need infinite precision this won't help, but if you "just need a couple more decimal places" then read on.

Import your numbers as strings. We already know that PHP can't handle the precision you need in its float type. The part before the decimal can be stored in an INT and the part after the decimal can go into a FLOAT, but because the number is much smaller you get more precision in your decimal places.

$x = "104045.754999999999995";  // this is approx the precision limit, any more 9s and we get .76

$parts = explode(".", $x);
$i = intval($parts[0]);
$f = floatval("0." . $parts[1]);

$broken = $i + $f; // $broken will have the original problem, it needs to round off $f a bit to store it

$rounded = $i + round($f, 2);  // after we round off $f there is enough precision in $rounded to easily store the entire number

echo $rounded;
James
  • 20,957
  • 5
  • 26
  • 41