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

This code:
for(my $i = 5.5; $i < 7; $i += .1) { if($i > 6.8) { printf "%f\n", $i; print "$i\n"; } }
Produces:
6.900000 6.9 7.000000 6.99999999999999
But if I change the %f in the printf to %.14f, I get 6.99999999999999 instead of 7.0000. What gives? Seems like I shouldn't ever get 6.9999 when adding .1 to 6.9...

Replies are listed 'Best First'.
Re: weird print behavior
by kesterkester (Hermit) on Dec 22, 2003 at 22:09 UTC
    "perldoc -q .999999" will explain this better than I could. You're experiencing a non-Perl-specific problem with finite precision for floating point numbers.

    Using printf "%.2f\n", $i will give you what you expect.

      Well, that does make a certain amount of sense. However, why does this only show up sometimes? In the loop I get the problem, but when I do
      printf("%.14f\n", (6.8 + .1));
      I get 6.90000000000000.
Re: weird print behavior
by Joost (Canon) on Dec 22, 2003 at 22:55 UTC
    Ok, let's see if I can get this right :-)

    Computer systems store information in a binary format. CPUs (and some languages) use different methods of encoding floating point numbers.

    Usually a floating point number has a much larger range than an integer. For instance, my perl binary stores a floating point number as a C double, which is of a sytem dependend size, but on my machine (Linux / AMD Atlon XP), that means 8 bytes.

    Given that a floating point number on my machine has a range of about (some handwaving here...) -1.8e+308 to 1.8e+308, and that it is impossible to even store all the integers in that range in 8 bytes, some trickery is used when storing those numbers.

    Most implementations store the number using an algorithm like this: first, the number is split up in 3 parts:

    • The sign (postive or negative number): 1 bit
    • The mantissa (some number n where 0 <= n < 1): say 23 bits
    • The exponent: say 7 bits
    The actual value can be calculated using:
    my $value = ($sign ? 1 : -1) * $mantissa * ( 2 ** $exponent);
    This design has some complications, though: most obviously, you only have as much precision as the number of bits in the mantissa. This also means that you can (and usually will) lose precision by doing aritmatic on floating point numbers.

    There's more complications here, but I can't find the docs on them now, and anyway I'm getting a headache trying to think about binary numbers between 0 and 1, so let's leave it at this, and just state the (hopefully) by now obvious:

    Use rounding when printing floating points, so you won't get bit (as often) by all of this

    Whether you want to use the rounded numbers in calculations too is more or less dependent on what sort of calculations you are doing (and the type of rounding).

    Hope this helps,
    Joost.

    Update:kesterkester++ for pointing to the docs. perlnumber has all the gory details.

Re: weird print behavior
by Zed_Lopez (Chaplain) on Dec 22, 2003 at 23:17 UTC
Re: weird print behavior
by SavannahLion (Pilgrim) on Dec 23, 2003 at 01:02 UTC
    I'm probably going to get bit for this since it doesn't directly answer your question, however I think it needs to be noted.
    You should avoid using a float (decimal) to control a loop. Basically, it boils down to what you're seeing there when the control for the loop does something unexpected. It's just good practice to avoid it since many languages suffer from this sort of thing.

    ----
    Is it fair to stick a link to my site here?

    Thanks for you patience.

        I'm probably going to get bit for this since it doesn't directly answer your question, however I think it needs to be noted.

      I wouldn't worry about questioning the question that much -- I think it's valuable feedback to the OP.

        You should avoid using a float (decimal) to control a loop. Basically, it boils down to what you're seeing there when the control for the loop does something unexpected. It's just good practice to avoid it since many languages suffer from this sort of thing.

      Your meaning is a little murky here, but if I can clarify, adding an increment that contains 'a little error' means you can end up with 'a significant error' after a 'sufficiently large' number of times through the loop.

      Another way to put it comes from my Dad, who did IBM 370 assembler and COBOL from back in the 60s -- If you test for equality with '6.0' but your loop counter actually contains '5.99999999', your loop is either going to continue forever or go one loop too far.

        Is it fair to stick a link to my site here?

      Sure. Just try to make your .sig 'smallish' when displayed on the site. A one or two line post followed by an eight line .sig looks kinda silly. :)

        Thanks for you patience.

      Sure thing. We're all here to learn and help each other; I'm just trying to return the favour and dodge the occasional flaming arrow.

      --t. alex
      Life is short: get busy!
Re: weird print behavior
by Ninthwave (Chaplain) on Dec 23, 2003 at 09:50 UTC

    I have some references from when I researched a similiar problem in 310575 . I think the main realisation is any non integer maths in computers, not just Perl is represented as binary fractions. This will cause some imprecision to even basic decimals. I would suggest if this is actual code and not a test case scenario to use 55 for 5.5 and 1 for your increament and when it comes to print it out convert it to a floating point.

    This text below is paraphrased from 310615 :
    The Perl Man Page says:
    Scalars aren't necessarily one thing or another. There's no place to declare a scalar variable to be of type "string", type "number", type "reference", or anything else. Because of the automatic conversion of scalars, operations that return scalars don't need to care (and in fact, cannot care) whether their caller is looking for a string, a number, or a reference.
    The int function.
    The question is are all numbers floating points and int just changes what is supplied as output? It seems that the number is a floating point as it is passed through out memory into different memory space, it characteristics come with it.
    Floating point math
    Native Numbers in Perl
    Man on Modlib
    These are good documents. From Native Numbers in Perl: . Forcing a numeric value to a particular format does not change the number stored in the value. So if any of the decimal values can not be stored as a binary fraction nothing using int will strip that value away and you get the repeating .999999 which when moving between int and float gives you the error.

    And there are modules to help, so the game of using maths to do this doesn't work without external help. So there is no bug outside of the limitation of computers as is, which I think we felt when doing this, but it is funny how this conversion under the hood can introduce errors in simple programming.

    "No matter where you go, there you are." BB