php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #61787 money_format round error
Submitted: 2012-04-20 13:38 UTC Modified: 2012-04-26 13:39 UTC
From: ubuntu at spacetrace dot org Assigned:
Status: Not a bug Package: Math related
PHP Version: 5.3.10 OS: Linux ubuntu 11.04
Private report: No CVE-ID: None
 [2012-04-20 13:38 UTC] ubuntu at spacetrace dot org
Description:
------------
if you round with money_format, some small numbers with 0.005 are rounded down instead of up.

Numbers bigger than 63.5 are all rounded falsly down like with floor()



Test script:
---------------
for($i=0;$i<70;$i++) {
echo($i.'.005 =>'.money_format('%.2i',$i+0.005).'<br>');
if($i<10) echo($i.'.00500000000000001=>'.money_format('%.2i',$i+0.00500000000000001).'<br>');
} 

Expected result:
----------------
0.005 =>0.01
0.00500000000000001=>0.01
1.005 =>1.01
1.00500000000000001=>1.01
2.005 =>2.01
2.00500000000000001=>2.01
3.005 =>3.01
3.00500000000000001=>3.01
4.005 =>4.01
4.00500000000000001=>4.01
5.005 =>5.01
5.00500000000000001=>5.01
6.005 =>6.01
6.00500000000000001=>6.01
7.005 =>7.01
7.00500000000000001=>7.01
8.005 =>8.01
8.00500000000000001=>8.01
9.005 =>9.01
9.00500000000000001=>9.01
10.005 =>10.01
11.005 =>11.01
12.005 =>12.01
13.005 =>13.01
14.005 =>14.01
15.005 =>15.01
16.005 =>16.01
...

Actual result:
--------------
0.00500000000000001=>0.01
1.005 =>1.00
1.00500000000000001=>1.01
2.005 =>2.00
2.00500000000000001=>2.00
3.005 =>3.00
3.00500000000000001=>3.00
4.005 =>4.00
4.00500000000000001=>4.00
5.005 =>5.00
5.00500000000000001=>5.00
6.005 =>6.00
6.00500000000000001=>6.00
7.005 =>7.00
7.00500000000000001=>7.00
8.005 =>8.01
8.00500000000000001=>8.01
9.005 =>9.01
9.00500000000000001=>9.01
10.005 =>10.01
11.005 =>11.01
12.005 =>12.01
13.005 =>13.01
14.005 =>14.01
15.005 =>15.01
16.005 =>16.00
17.005 =>17.00
18.005 =>18.00
19.005 =>19.00
20.005 =>20.00
21.005 =>21.00
22.005 =>22.00
23.005 =>23.00
24.005 =>24.00
25.005 =>25.00
26.005 =>26.00
27.005 =>27.00
28.005 =>28.00
29.005 =>29.00
30.005 =>30.00
31.005 =>31.00
32.005 =>32.01
33.005 =>33.01
34.005 =>34.01
35.005 =>35.01
36.005 =>36.01
37.005 =>37.01
38.005 =>38.01
39.005 =>39.01
40.005 =>40.01
41.005 =>41.01
42.005 =>42.01
43.005 =>43.01
44.005 =>44.01
45.005 =>45.01
46.005 =>46.01
47.005 =>47.01
48.005 =>48.01
49.005 =>49.01
50.005 =>50.01
51.005 =>51.01
52.005 =>52.01
53.005 =>53.01
54.005 =>54.01
55.005 =>55.01
56.005 =>56.01
57.005 =>57.01
58.005 =>58.01
59.005 =>59.01
60.005 =>60.01
61.005 =>61.01
62.005 =>62.01
63.005 =>63.01
64.005 =>64.00
65.005 =>65.00
66.005 =>66.00
67.005 =>67.00
68.005 =>68.00
69.005 =>69.00
from 64 on all numbers are rounded down like floor()

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2012-04-20 16:04 UTC] johannes@php.net
-Status: Open +Status: Not a bug
 [2012-04-20 16:04 UTC] johannes@php.net
Floating point values have a limited precision. Hence a value might 
not have the same string representation after any processing. That also
includes writing a floating point value in your script and directly 
printing it without any mathematical operations.

If you would like to know more about "floats" and what IEEE
754 is, read this:
http://www.floating-point-gui.de/

Thank you for your interest in PHP.

.
 [2012-04-21 10:11 UTC] ubuntu at spacetrace dot org
so why does php round 7.005 down to 7.00 while it rounds 8.005 correctly up to 8.01 ? 

and why from 16 on again wrong and from 32 on again correct, from 64 on again wrong but now 
not a change from 128 on?
 [2012-04-21 14:59 UTC] rasmus@php.net
Did you visit the site we provided that explains this? What you think is 7.005 is 
actually represented as 7.004999999999999999 whereas 8.005 is 
8.005000000000000000001
That's what limited precision means. Go read http://www.floating-point-gui.de/ 
for a complete explanation.
 [2012-04-21 17:28 UTC] ubuntu at spacetrace dot org
ah ok,

that explains the behaviour.

wouldnt it be senseful, if php would always floor the float variable then?
or at least throw a notice error, if you parse a float through "%.2i"?

i understand this is not a senseful programm-style. cause you get an unpredictable behaviour.

if money_format() would round down always, then it would be predictable

are there more functions that would show the same behaviour like in this example? what about printf()
 [2012-04-21 18:17 UTC] rasmus@php.net
How does always rounding down fix anything? What if you are trying to do 3.0 but 
you actually get 2.999999999999999, if you always round down you would get 2 
instead of 3.
 [2012-04-21 21:21 UTC] ubuntu at spacetrace dot org
now that i uderstand how floating numbers work in computers, this is sure a real bug then:

they say:
"a tiny error in the 17th decimal place doesn’t matter at all "
here the bug is at the 3rd place!

and:
   " If you just don’t want to see all those extra decimal places: simply format your result rounded to a fixed number of decimal places when displaying it."
this is what i did here and that caused the bug

think about it: NO ONE wants this behaviour if he or she uses '%.2i' in a function

This function should be fixed, so it rounds mathematically correct like the round() function. 

If not, then it should throw an error like
"cannot shorten a float variable with this syntax" or so.
 [2012-04-21 21:24 UTC] rasmus@php.net
Rounding errors compound when you loop like that. There is still no bug here.
 [2012-04-21 21:54 UTC] ubuntu at spacetrace dot org
money_format('%.2i',1.005) results in 1.00
and
money_format('%.2i',7.005) results in 7.01

this is definitely a bug. you only showed me, the reason, how the bug was created. but it should still be solved!

Or, if you say, the syntax is not correct, cause you provoke an unpredictable behaviour with '%.2i' then shouldnt that throw an error notice at least?

why cannot the php-supporters use the same algorythm, like in function round(), where there IS rounded correctly
 [2012-04-21 21:55 UTC] ubuntu at spacetrace dot org
(i meant: money_format('%.2i',8.005) results in 8.01)
 [2012-04-21 22:25 UTC] rasmus@php.net
This is getting a bit tiresome. There is no bug here.

Maybe you will understand it this way. Try running this:

<?php
ini_set('precision',32);
$ns = array(1.005, 7.005, 8.005);
foreach($ns as $n) {
  echo "$n ".money_format('%.2i', $n)."\n";
}

The output on my machine is:

1.004999999999999893418589635985 1.00
7.004999999999999893418589635985 7.00
8.0050000000000007815970093361102 8.01

That is, 1.005 can't actually be represented accurately and it ends up being 
slightly below 1.005 which means when you round it you get 1.00. And 8.005 ends 
up being represented as slightly larger than 8.005 so when you round it you get 
8.01. It makes perfect sense. Simply add a fuzz factor to your floating point 
values to the appropriate precision you care about. Or, as most people know, 
when dealing with money, don't use floating point at all. Work in integers.
 [2012-04-22 20:21 UTC] ubuntu at spacetrace dot org
i am sorry, i didnt want to do any harm.
i perfectly understood it before already.

But what do you think about my suggestions? 

1. a notice error 
or
2. round it like the round() function
 [2012-04-22 20:40 UTC] rasmus@php.net
There is no efficient way to "notice the error"

If you want slow and accurate floating point manipulation it is available via the 
arbitrary precision extensions like bcmath and gmp.
 [2012-04-22 22:45 UTC] ubuntu at spacetrace dot org
maybe an efficient way would be to see if the desired amount of digits is less than the amount of digits in the float.

in my example where i tried to show 1.005 with only 2 fractional digits, the notice could be like:
"Error: input float exceeds maximum number of fractional digits"

this would save thousands of hours of debugging worldwide.

i am so keen on this issue, cause it cost me already about 100 hours of work to find out what was the cause, why in my project my bills every now and then where not correctly rounded.
it is a stupid bug really hard to track and i am sure am not the only one that had this problem. 

if php would throw a notice, that would save lots of hazzle for not-so-experiensed programmers.

how can you know, that you shouldn't use floats in money_format. this is a kind of secret knowledge right now especially cause the manual says: "string money_format ( string $format , float $number )"
 [2012-04-22 23:01 UTC] rasmus@php.net
No, that won't work. Often the value comes from expressions that always generate 
lots of digits. eg. number_format('%.2i', 1/3)
What would you expect that to do? You wold get a notice every single time even 
though there may not be any relevant loss of precision.

There are just certain things you need to eventually learn when you start 
programming.
 [2012-04-22 23:15 UTC] ubuntu at spacetrace dot org
how does the function round(7.005,2) do the trick to get 7.01? is it so much slower than how money_format does it?
 [2012-04-26 09:38 UTC] ubuntu at spacetrace dot org
could you please set the Status back to "Bug"

all people i talked to, said, that this should be solved. cause it causes follow-up bugs in your applications that are very hard to find, cause they happen so seldom
 [2012-04-26 10:07 UTC] ubuntu at spacetrace dot org
The same bug is in 
sprintf("%01.2f",7.005);
which results in 
7.00 
while 
sprintf("%01.2f",8.005);
results in 
8.01
 [2012-04-26 13:39 UTC] rasmus@php.net
-Block user comment: No +Block user comment: Yes
 [2012-04-26 13:39 UTC] rasmus@php.net
Please stop. This is not a PHP-specific issue. This is just how floating point 
works in computers. You will need to learn how to deal with it. For example, 
compile this C program:

#include <stdio.h>
int main(char *argv[], int argc) {
 	printf("%01.2f\n",7.005);
	printf("%01.2f\n",8.005);
}

cc f.c -o f

then run it:

./f

you get:

7.00
8.01

Which is exactly the same as the PHP results. PHP is a thin layer over C.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Dec 26 14:01:30 2024 UTC