2

I was relying on sprintf('%0.1f', 2.25) === '2.3' but it turns out it comes in at 2.2!

In fact it seems random:

php > for ($j=0;$j<=10;$j++) { printf( "%s -> %0.1f\n",$j+ 0.05, $j+0.05); } 
0.05 -> 0.1 // Up, as expected
1.05 -> 1.1 // Up, as expected
2.05 -> 2.0 // Down!
3.05 -> 3.0 // Down!
4.05 -> 4.0 // Down!
5.05 -> 5.0 // Down!
6.05 -> 6.0 // Down!
7.05 -> 7.0 // Down!
8.05 -> 8.1 // Up, as expected
9.05 -> 9.1 // Up, as expected

Have I completely missed the point? I feel like a rug is being pulled away from under me and that all I learnt at school is wrong...! Surely a function to round numbers should do it consistently? (I note that round($n, 1) works as expected.)

artfulrobot
  • 20,637
  • 11
  • 55
  • 81
  • 11
    because 2.05 internally is really 2.0499999123423423423 or whatever. you're dealing with floats. accuracy is not their forte. – Marc B Nov 24 '14 at 14:36
  • 2
    `Surely a function to round numbers should do it consistently?`. If it was in fact a function for rounding numbers. But it isn't - `sprintf — Return a formatted string`, as per [php.net](http://php.net/manual/en/function.sprintf.php). – Alternatex Nov 24 '14 at 14:36
  • 1
    Obligatory https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – Tim Seguine Nov 24 '14 at 14:39
  • @MarcB Get it. How would you know? I mean obvs you could figure out the binary representation of the floating point number manually, but how can you get PHP to output 2.049999123 (or whatever it is)? - just for interest! – artfulrobot Nov 24 '14 at 15:01
  • @artfulrobot I think the takeaway is that you should use a function that is designed specifically for rounding. And that even then, you still might be surprised about the result. But: http://ideone.com/1wwao8 – Tim Seguine Nov 24 '14 at 15:20
  • 2
    http://en.wikipedia.org/wiki/Floating_point and http://en.wikipedia.org/wiki/Double_precision are good reading too. I think floating point is super hard though... don't feel bad if you don't really get it after reading all this stuff. But the short of it is that some of these numbers in binary is a repeating number and some aren't. Kinda like how you can't exactly write the fraction 1/3 as a decimal without infinite digits, but 1/4 easily can be. The cut-off of these repeating digits, if they happen right around where you want to round, can lead to surprising results. – Adam D. Ruppe Nov 24 '14 at 15:31

3 Answers3

7

You should be using round

http://php.net/manual/en/function.round.php

round(2.25, 2) === floatval('2.3')
cmorrissey
  • 8,493
  • 2
  • 23
  • 27
5

To summarise what has been said in comments in an answer:

Because printf is not a rounding function, but a low-level function to give a string representation of a number. In this case the number internally is not the same as the number set, e.g. it might be 2.249999991231231123 because of the limitations of the floating point internal representation.

So printf is rounding a different number to that which you entered/calculated, which is correctly 2.2 in this example.

Therefore as the other answer (and my original question) points out, a better solution is to use round() (and possibly sprintf upon the result).

artfulrobot
  • 20,637
  • 11
  • 55
  • 81
3

As an explanation of why round can offer better results than a function not specifically designed for rounding, we need to consider the limits of the double precision floating point representation. Doubles can represent between 15 and 17 decimal significand digits. From the Wikipedia article on double precision:

If a decimal string with at most 15 significant digits is converted to IEEE 754 double precision representation and then converted back to a string with the same number of significant digits, then the final string should match the original. If an IEEE 754 double precision is converted to a decimal string with at least 17 significant digits and then converted back to double, then the final number must match the original

The implementation of round can and should make use of this to "do the right thing" in most cases.

Example 1:

<?=number_format(2.05,14); //give me 15 significant digits. Guaranteed to produce the orginal number

outputs:

2.05000000000000

Example 2:

<?=number_format(2.05,16); //give me 17 significant digits. Not guaranteed to produce the orginal number

outputs:

2.0499999999999998

This is just a demonstration the IEEE 754 behavior.

I am going to guess (because I haven't read its implementation) that sprintf doesn't really try to do anything particularly intelligent with regards to rounding, whereas round probably tries to round "correctly" (per IEEE 754) with respect to the number of significant digits you asked for.

Tim Seguine
  • 2,887
  • 25
  • 38
  • Great explanation, thanks. Do you think if I'd phrased the question as "why is php's printf not particularly intelligent" I might not have got slammed down with a -2? ;-) – artfulrobot Nov 24 '14 at 15:59
  • @artfulrobot I am not sure, since I didn't downvote. Some people seem to react negatively to "naive" solutions especially when similar things come up often on SO. There are a lot of questions about floating point on SO and I would assume there is some degree of "fatigue" among the long time readers. – Tim Seguine Nov 24 '14 at 16:02