http://www.perlmonks.org?node_id=737737

satish.rpr has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I have an issue with PERL 5.8.x and later with respect to floating point numbers.The issue is as such. The below printf statement produces the output correctly in 5.6.1 whereas the output is different in 5.8.6.Is this is bug with the new version of PERL. Any workaround for this to get the output in 5.8.1 similar to that of 5.6.1.

printf("%.32g\n",0.99999999976716936);

Perl 5.6.1 output:
0.99999999976716936 --> GOOD

Perl 5.8.6 output:
0.99999999976716925 --> ERROR
  • Comment on Perl 5.8.x floating point representation error

Replies are listed 'Best First'.
Re: Perl 5.8.x floating point representation error
by davido (Cardinal) on Jan 21, 2009 at 04:45 UTC

    An issue discussed frequently enough to warrant a spot in perlfaq4.

    Put in simple terms, you cannot rely on floating point to be free of rounding errors in some of the last few digits of precision. You're out at the limits there. Different versions of Perl, different architectures, and different alignment of planetary orbits will introduce minor rounding errors as the internal binary representation of floating point numbers manifests its limitations.

    I cannot explain why one version may differ from another, but can comfortably assert that this is documented and expected behavior.

    Math::BigFloat is one solution. Stringification may be another solution, depending on what you're doing with the FP numbers.

    Searching through an old node of mine on a similar topic I found a great link that explains the concept better than I possibly could: What Every Computer Scientist Should Know About Floating Point Arithmetic.

    Enjoy!


    Dave

      thank you Dave,

      But using Math::BigFloat either did not solve my issue. BigFloat truncates after 16 digits in the exponential part and then adds zeros. Any workaround for this ?

      use Math::BigFloat;

      Math::BigFloat->accuracy(32);
      Math::BigFloat->precision(-32);
      $test = Math::BigFloat->new(0.99999999976716932);

      print $test;

      output:
      0.99999999976716900000000000000000

      but i need 0.99999999976716932

        Try it this way (notice the quotes):

        use Math::BigFloat; Math::BigFloat->accuracy(32); Math::BigFloat->precision(-32); $test = Math::BigFloat->new('0.99999999976716932'); print $test; 0.99999999976716932000000000000000

        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        my $string = "0.99999999976716932"; my $number = 0.99999999976716932; print " string = $string number = $number "; __END__ string = 0.99999999976716932 number = 0.999999999767169
Re: Perl 5.8.x floating point representation error
by gone2015 (Deacon) on Jan 21, 2009 at 21:20 UTC

    Well... I'm inclined to agree with you that your perl 5.8.6 result is incorrect. The decimal to binary conversion is incorrectly rounded -- so worse than the usual representation error.

    On my perl 5.10.0, using IEEE-754 floating point doubles, your 0.99999999976716936 value is converted to the binary floating point value whose bit pattern is 0x3FEF_FFFF_FFDF_FFFF (or +0x1.F_FFFF_FFDF_FFFFp-1, if you prefer). This appears to be the same as your 5.8.6 result.

    Looking at that and the next two larger values I see:

    0x3FEF_FFFF_FFDF_FFFF == 0.999999999767169245323827908578 (approx) 0x3FEF_FFFF_FFE0_0000 == 0.999999999767169356346130371093 (approx) original value == 0.99999999976716936 0x3FEF_FFFF_FFE0_0001 == 0.999999999767169467368432833609 (approx)
    What's odd about this is that the decimal to binary conversion is not 'correctly rounded' (I'm assuming round-to-nearest as the only sensible rounding for the conversion) -- 0x3FEF_FFFF_FFE0_0000 is clearly a lot closer to the original 0.99999999976716936 than the values on either side of it !

    I'm damned if I can see a good reason for getting this wrong. I note only that if you truncate the original value to 16 significant digits (ie to 0.9999999997671693) then 0x3FEF_FFFF_FFDF_FFFF is the correctly converted result.

    I get the same results on 64-bit Linux machine and 32-bit Winders. So, either two different libraries have the same decimal to binary fault, or something in perl is wrong.

    This is bad. IEEE-754 requires the result of a conversion from binary to decimal and back to binary to be the original binary value, provided that at least 17 significant decimal digits are used (for doubles). I find that:

    0x3FEF_FFFF_FFE0_0000 -> 0.99999999976716936 -> 0x3FEF_FFFF_FFDF_FFF +F

    However, wrong as this is, the general advice -- not to depend on floating point results to be exactly equal to something -- always applies.

Re: Perl 5.8.x floating point representation error
by gone2015 (Deacon) on Jan 25, 2009 at 20:32 UTC

    FWIW: I've looked at the Perl source (in particular numeric.c) and have submitted a bug report.

    It turns out that Perl has a home-grown Perl_my_atof(), which makes a reasonable fist of converting decimal to binary, given that it is trying to do so in a system-independent manner. To produce the required "correctly rounded" result it is necessary to use some extended precision arithmetic, which is hard to do in a system-independent fashion. The code in Perl_my_atof() handles the digits before and after the decimal point as separate floating point values, which is a form of extended precision -- unfortunately, the way it does this can cause multiple roundings to occur...

    It goes wrong in in this case because your number 0.99999999976716936 has 17 digits after the decimal point, which is rounded to 99999999976716928 and rounded again when it is divided by 10**17 to give 0.999999999767169245323827908578 (approx).

    IMHO Perl_my_atof() should be rewritten to use the atof() C library function to do the actual conversion -- having done all the Perl specific parsing and locale and such, at the text level. That way the local library can use the required local means to get the required precision.