Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

(s)printf and rounding woes

by Not_a_Number (Prior)
on Nov 04, 2003 at 22:25 UTC ( #304559=perlquestion: print w/replies, xml ) Need Help??

Not_a_Number has asked for the wisdom of the Perl Monks concerning the following question:

Can anyone shed any light on this (I find the documentation for printf and sprintf to be particularly vague...):

for my $num ( 0 .. 101 ) { $num += 0.005; printf "%.2f\n", $num; }

Output: I get (using ASP 5.61, WinXP):

0.01 1.00 2.00 3.00 .. 8.01 ..

followed by seemingly random series of x.00 or x.01. Interestingly(?), if I change the number of decimals that I want to round to:

for my $num ( 0 .. 101 ) { $num += 0.0005; my $result = sprintf "%.3f", $num; print "$result\n"; }

I get similarly strange results, but not for the same 'numbers' (eg I get 2.001 and 3.001, as compared with 2.00 and 3.00 in the first case

Am I missing something (as usual)?

tia, dave

Replies are listed 'Best First'.
Re: (s)printf and rounding woes
by ysth (Canon) on Nov 04, 2003 at 23:18 UTC
    Any integer + .005 is not going to be exactly representable in floating point. Sometimes you are getting a repesentable value above the true one and sometimes one below. Try this:
    for my $num ( 0 .. 101 ) { $num += 0.005; printf "%.2f\t%.20g\n", $num, $num; }
    to see the actual values you are rounding and it should make more sense. (The output starts:)
    0.01 0.0050000000000000001041 1.00 1.0049999999999998934 2.00 2.0049999999999998934 3.00 3.0049999999999998934 4.00 4.0049999999999998934 5.00 5.0049999999999998934 6.00 6.0049999999999998934 7.00 7.0049999999999998934 8.01 8.0050000000000007816 9.01 9.0050000000000007816 10.01 10.005000000000000782 11.01 11.005000000000000782 12.01 12.005000000000000782 13.01 13.005000000000000782 14.01 14.005000000000000782 15.01 15.005000000000000782 16.00 16.004999999999999005
Re: (s)printf and rounding woes
by Zaxo (Archbishop) on Nov 04, 2003 at 22:34 UTC

    That is a general property of binary floating-point numbers. The difficulty will show up in any language. If you need consistency in a fixed-point decimal representation, you should scale the numbers to be represented as integers.

    After Compline,
    Zaxo

Re: (s)printf and rounding woes
by arthas (Hermit) on Nov 04, 2003 at 23:38 UTC

    Hi!

    Problem is that computers represent floating point numbers in binary; this means a decimal number can't exacly be represented in binary form, and the precision in doing this is finite. This affects all languages. As somebody already replied, if precision really matters to you, you need to work with integers, and perform the division as late as you can.

    There's a perlfaq entry about this, which might make things clearer to you.

    Hope this helps!

    Michele.

Re: (s)printf and rounding woes
by pg (Canon) on Nov 04, 2003 at 23:49 UTC

    If someone should be blamed for this, it is not Perl, but c, and Perl just inherits it. If you are using languages support fixed decimal, for example COBOL or PL/SQL, then this problem disappears.

    Here is an example what can happen to c: (It does not give 14720)

    #include <stdio.h> void main() { float x = 147.2; printf("%20.20f", x * 100); }

    If you really care the accuracy and precision, try Math::BigFloat.

    Now here is something a little bit off topic, but interesting. If you do this in c:

    float x = 147.2; printf("%d", x * 100);

    It produces -167772160, which is ridiculous, as c tries to interprete that piece of memory as integer, as you required.

    Good you are using Perl. In Perl, if you do the same thing with:

    $x = 147.2; printf("%d", $x * 100);

    It produces 14719 (not precise, but better than c), as internally Perl does:

    float x = 147.2; printf("%d", (int)(x * 100));
      Now here is something a little bit off topic, but interesting. If you do this in c:
      float x = 147.2; printf("%d", x * 100);
      It produces -167772160, which is ridiculous, as c tries to interprete that piece of memory as integer, as you required.

      Well gcc has had static format checking included for only at least 10 years. Which would give a warning on the above, and so make that mistake practically impossible. In fact there are solutions for printing arbitrary objects via. printf like mechanisms in C. Which might be nice in perl (if XS wasn't so ick I'd be happier to look at doing it).

      In fact, to be somewhat on topic, it can sometimes be pretty annoying that perl doesn't give any warnings if you do...

      my $i = 1; my $j = 2.3; printf("%f %d\n", $i, $j);

      ...although more annoying and probably easier to fix would be to have all the normal printf() POSIX extensions like "%'d" work.

      --
      James Antill
Re: (s)printf and rounding woes
by BrowserUk (Patriarch) on Nov 05, 2003 at 05:00 UTC

    You might find this Re: Re: Re: Bug? 1+1 != 2 post and to a lesser extent this Re: machine accuracy useful in understanding why doing math with floating point representations of numbers on computer systems can lead to such apparently strange effects.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!
    Wanted!

Re: (s)printf and rounding woes
by Not_a_Number (Prior) on Nov 05, 2003 at 10:07 UTC

    Thanks all. I should have realised this myself. In fact, further reading TFM shows that the problem of rounding with (s)printf is specifically addressed in perlfaq4: Does Perl have a round() function?

    dave

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://304559]
Approved by PERLscienceman
Front-paged by arthas
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (1)
As of 2022-10-02 03:16 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My preferred way to holiday/vacation is:











    Results (7 votes). Check out past polls.

    Notices?