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

Can any one explain why the following is happening? Or is perl rounding incorrectly? (example):

my $y = 40.88050; $y = sprintf "%.3f", $y; my $x = 41.78050; $x = sprintf "%.3f", $x; print "$y \n $x\n";
Results are:

40.880 41.781
As far as my wife, 10-year-old, and I can figure this is wrong - they should both end in a 0 or a 1 depending on if even or odd rounding is the rule.
C is doing the exact opposite, it comes up with:

40.881 41.780

Replies are listed 'Best First'.
Re: Rounding error?
by BrowserUk (Patriarch) on Oct 19, 2004 at 01:52 UTC

    It all comes down to the inexactitude in the way computers represent floating point values. By printing the values out with as many digits of precision as you can, you'll see that one value rounds down, and the other rounds up.

    printf "%.17f\n", 40.88050; 40.88049999999999800 printf "%.17f\n", 41.78050; 41.78050000000000400

    The (probable) reason that you see a different result from C, is that you are (probably) using single precision (floats) in your C code, whilst Perl uses double precision. (Probably:)

    See Re: Re: Re: Bug? 1+1 != 2 for more information.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
      "The (probable) reason that you see a different result from C, is that you are (probably) using single precision (floats) in your C code, whilst Perl uses double precision. (Probably:)"

      This seems to be a good guess:

      #include <stdio.h> #include <stdlib.h> int main(int argc, char * * argv) { { double a = 40.88050; printf("%.3f\n",a); printf("%.17f\n",a); a = 41.78050; printf("%.3f\n",a); printf("%.17f\n",a); } { float a = 40.88050; printf("%.3f\n",a); printf("%.17f\n",a); a = 41.78050; printf("%.3f\n",a); printf("%.17f\n",a); } return 0; }

      Prints:

      40.880 40.88049999999999784 41.781 41.78050000000000352 40.881 40.88050079345703125 41.780 41.78049850463867188
Re: Rounding error?
by data64 (Chaplain) on Oct 19, 2004 at 01:48 UTC

    See Does Perl Have a round function ?. It briefly mentions the error in rounding and blames it on the IEEE representation used internally.


    Just a tongue-tied, twisted, earth-bound misfit. -- Pink Floyd

Re: Rounding error?
by pg (Canon) on Oct 19, 2004 at 01:48 UTC

    This is expected. sprintf does an unbiased rounding, so .5 not always round up.

    "C is doing the exact opposite"

    Your Perl simply calls the underlying c sprintf. Instead of saying C is doing the opposite, you should just say "my c follows slightly different rule than the c used to compile my Perl."

Re: Rounding error?
by bmann (Priest) on Oct 19, 2004 at 02:29 UTC
    my $y = 1.5; $y = sprintf "%.0f", $y; my $x = 2.5; $x = sprintf "%.0f", $x; print "$y\n$x\n";

    Still surprised? As pg says above, perl's rounding is unbiased - rather than rounding every .5 up, it always rounds to even. 1.5 and 2.5 both become 2.

    Update: This is not documented. What is documented in perlfaq4 is this:

    it probably pays not to trust whichever system rounding is being used by Perl, but to instead implement the rounding function you need yourself.

    I downloaded 5.6.1 from AS, and as ikegami says below it rounds 1.5 to 2 and 2.5 up to 3. Oh, and this all has nothing to do with the original question ;)

      This rule has its formal name, called "round to even".

      Update

      Sorry, I didn't notice that bmann already mentioned this term in his post ;-)

      Update 2

      To support what bmann said below, I tried this c code, regardless whether I define i as float or double, the round to even rule does apply. The c compiler I used is borland c 5.5, my PC is windows XP.

      #include <stdio.h> #include <stdlib.h> int main(int argc, char * * argv) { for (double i=0; i < 10; i += .5) { printf("%.1f: %.0f\n", i, i); } return 0; }

      I'm surprised by your comment, not by the output, seeing as that prints 2 and 3 for me.

      ActivePerl 5.6.1
      ActivePerl 5.8.0

      If it did give 2 for both, I'd be asking: "Can someone explain the sense of a rounding where the following prints 19, not 20:"

      $sum += sprintf("%.0f", $_/10) foreach (0..19); print($sum, $/);
        That's strange. Here's the version of perl I ran it on originally

        C:\>perl -v This is perl, v5.8.4 built for MSWin32-x86-multi-thread (with 3 registered patches, see perl -V for more detail) Copyright 1987-2004, Larry Wall Binary build 810 provided by ActiveState Corp.

        I just ran it on 5.8.4 on debian - then 5.00503, 5.8.2 and 5.8.5 on FreeBSD. All versions round both 1.5 and 2.5 to 2

        Does anyone else get something different?

        for ($i=0;$i<10;$i+=.5){ printf "%.1f: %.0f\n", $i, $i; } __END__ Output: 0.0: 0 0.5: 0 1.0: 1 1.5: 2 2.0: 2 2.5: 2 3.0: 3 3.5: 4 4.0: 4 4.5: 4 5.0: 5 5.5: 6 6.0: 6 6.5: 6 7.0: 7 7.5: 8 8.0: 8 8.5: 8 9.0: 9 9.5: 10
Re: Rounding error? (it's normal)
by grinder (Bishop) on Oct 19, 2004 at 09:13 UTC
Re: Rounding error?
by TedPride (Priest) on Oct 19, 2004 at 12:56 UTC
    The easiest thing to do is round the number yourself and then sprintf:
    printf("%.3f\n", int($_*1000+.5)/1000) for <DATA>; __DATA__ 40.88061 41.78050 42.24033 44.4