-1

I have the age-old floating point rounding issue as many people before me have, but I can't seem to find a good solution for my situation. I'm doing some math operations in MySQL and returning the result to PHP as a float, then using round() to round the result. The problem I'm running into is with numbers like 10.50499999999977 which seem to trip up round(), rounding the number to 10.5 instead of the expected 10.51.

Is there a consistent way to have PHP round a number like this correctly? I found a quick solution, but I'm afraid this wouldn't hold up in the long run:

echo round(10.50499999999977, 2, PHP_ROUND_HALF_UP); // 10.5
echo round(round(10.50499999999977, 5, PHP_ROUND_HALF_UP), 2); // 10.51

I'm especially worried that if I use this solution and try to round a number like 10.50444449 that this would end up rounding wrong (10.5 vs 10.51).

So is there a solution that I can use to get a consistently correct rounded number with PHP?

Samsquanch
  • 8,866
  • 12
  • 50
  • 89
  • tempted to close dupe: http://stackoverflow.com/questions/588004/is-floating-point-math-broken –  Jun 18 '15 at 21:55
  • Other things that come into play would be OS and 32-bit vs. 64-bit. ^ assume must be existing authoritative answer for this. – ficuscr Jun 18 '15 at 21:57
  • @Dagon it's a similar question, but more concerning doing math on floats. Here I'm just taking an existing float and rounding it (arguably still math, but different). Even then, no solution was offered in that question. – Samsquanch Jun 18 '15 at 21:58
  • Similar problem: http://stackoverflow.com/questions/4921466/php-rounding-error – ptkoz Jun 18 '15 at 22:03

2 Answers2

3

You're seeing expected behavior:

php>  echo round(10.50499999999977);
11
php > echo round(10.50499999999977, 1);
10.5
php > echo round(10.50499999999977, 2);
10.5
php > echo round(10.50499999999977, 3);
10.505
php > echo round(10.50499999999977, 4);
10.505
php > echo round(10.50499999999977, 6);
10.505
php > echo round(10.50499999999977, 7);
10.505

As per the docs:

PHP_ROUND_HALF_UP Round val up to precision decimal places away from zero, when it is half way there. Making 1.5 into 2 and -1.5 into -2.

...049999 isn't "half-way there". it's below 0.5, so php rounds down.

Sammitch
  • 30,782
  • 7
  • 50
  • 77
Marc B
  • 356,200
  • 43
  • 426
  • 500
  • I understand that, but I was assuming it would round it like you or I would, starting at the far right, and carrying it over all the way to the left up until it gets to 2 decimal places. If this isn't the case, how can I make it round more in the way that I would expect it to round? – Samsquanch Jun 18 '15 at 22:01
  • ignore the actual decimal place. it's "really" where you told `round()` to work. since you're rounding to 2 places, the digits AFTER the `2` position are what counts. and 49... is smaller than 50...., so it rounds down. – Marc B Jun 18 '15 at 22:02
  • @Samsquanch rounding simply doesn't work that way. Ever. Maybe that's how *you* round numbers, but it is categorically incorrect to do it that way. I hope you're not dealing with money here. – Sammitch Jun 18 '15 at 22:02
  • @Sammitch I am unfortunately dealing with money that deals with prices in the fractions of a cent (one-tenth to one-third of a cent) which is what's producing these numbers. Essentially anything more than 0% of a penny should be rounded up to the nearest penny, mimicking the old system that the data was previously in. – Samsquanch Jun 18 '15 at 22:06
  • @Samsquanch well you should also not be using floats for storing values of money. There are [plenty of libraries](https://packagist.org/search/?q=money) that handle this, essentially converting to the base currency unit [eg: pennies] stored as an *integer* and performing various functions in sane and well-defined manners. – Sammitch Jun 18 '15 at 22:10
2

round() is doing exactly what you're asking:

echo round(10.50499999999977, 2, PHP_ROUND_HALF_UP); // 10.5

You're asking for rounding to two decimal places, rounding 'half' and above up.

Here the third decimal place and beyond is less than 'half' (0.00499999999777) so it's rounded down, giving 10.500. The trailing zeroes are dropped as they have no significance.