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

This weird thing just happened in my code. All of a sudden, a simple addition with reasonably uncomplicated values (4253.95 + 0.9) result in something that looks like a float approximation. Like this:

4253.95 + 0.9 = 4254.84999999999

If I try to reproduce it, it comes out correct with 4254.85, so that's no help, except I can see that the internals of the vars are different (see below)

I can work around/force the correct hehaviour if I stringify the numerical values before doing the addition.

I thought Devel::Peek might discover something strange about this, but I'm not sure how to interpret the output of this piece of code:

my $self = shift; my ($amount) = @_; my $amountOld = $self->amountAccount; my $amountNew = $amountOld + $amount; $self->amountAccount($amountNew); if(length($amountOld) < 8 && length($amountNew) >= 8) { use Devel::Peek; print("\n\n"), Dump($amountOld), Dump($amount), Dump($amountNe +w), print("Went bad here: ($amountOld + $amount = $amountNew)\n\n"); $amount = 0.9; $amountOld = 4253.95; $amountNew = $amountOld + $amount; print("\nNew values\n"), Dump($amountOld), Dump($amount), Dump +($amountNew), print("Correct output: ($amountOld + $amount = $amountN +ew)\n\n"); }

Output:

SV = PVNV(0x2c8ef8c) at 0x23b4958 REFCNT = 1 FLAGS = (PADBUSY,PADMY,NOK,POK,pNOK,pPOK) IV = 4256 NV = 4253.95 PV = 0x2d179a4 "4253.95"\0 CUR = 7 LEN = 35 SV = PVNV(0x2c8ef44) at 0x23b4928 REFCNT = 1 FLAGS = (PADBUSY,PADMY,NOK,pIOK,pNOK) IV = 0 NV = 0.9 PV = 0 SV = PVNV(0x2c8efa4) at 0x23b4988 REFCNT = 1 FLAGS = (PADBUSY,PADMY,NOK,POK,pNOK,pPOK) IV = 4309 NV = 4254.84999999999 PV = 0x2d17d9c "4254.84999999999"\0 CUR = 16 LEN = 35 Went bad here: (4253.95 + 0.9 = 4254.84999999999) New values SV = PVNV(0x2c8ef8c) at 0x23b4958 REFCNT = 1 FLAGS = (PADBUSY,PADMY,NOK,pNOK) IV = 4256 NV = 4253.95 PV = 0x2d179a4 "4253.95"\0 CUR = 7 LEN = 35 SV = PVNV(0x2c8ef44) at 0x23b4928 REFCNT = 1 FLAGS = (PADBUSY,PADMY,NOK,pIOK,pNOK) IV = 0 NV = 0.9 PV = 0x2dfa064 "0.9"\0 CUR = 3 LEN = 35 SV = PVNV(0x2c8efa4) at 0x23b4988 REFCNT = 1 FLAGS = (PADBUSY,PADMY,NOK,pNOK) IV = 4309 NV = 4254.85 PV = 0x2d17d9c "4254.84999999999"\0 CUR = 16 LEN = 35 Correct output: (4253.95 + 0.9 = 4254.85)

As you can see, the flags are different, but I'm not sure what that means. Is it something that is off in the first set?

Is there something else I can look at to understand what's going on here?

OS: w2k

perl -v This is perl, v5.8.3 built for MSWin32-x86-multi-thread (with 8 registered patches, see perl -V for more detail)

Any ideas?

/J

Replies are listed 'Best First'.
Re: 4253.95 + 0.9 = 4254.84999999999 (need help to interpret internals)
by dragonchild (Archbishop) on Feb 25, 2005 at 13:31 UTC
    This looks to be the standard "You can't really represent floating point numbers in binary" problem. It's not a problem with Perl - it's a problem with binary computing in general. AKA the "Rounding Error"™.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      Well, it may not be a problem with Perl, but it's a problem for me.

      So can I avoid it somehow? Stringification of the values before doing the operation solves my problem. Why is that? Is it something accumulated that gets re-set?

      (I understand that it changes Perl's internal representation of what is stored in the variable, but what I wonder is how and why that changes what it does when performing the addition).

      This is something that popped up in an old application where I do basically the same thing all over the place, but this particular thing haven't been a problem in the past.

      Right now I have "solved" id by stringifying the value before doing the calculation, but that doesn't feel very solid.

      /J

        An alternative which is used in C a lot is to make them into integers by multiplying everything by 10**N where N is the number of decimal places (or precision) you want to have in your answer. Then, when you're done, you divide the answer by 10**N and you're good.

        *ponders* This sounds like a good candidate for a CPAN module doing overloaded math ... Or, you could just use Math::BigFloat (which may be overkill)

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        When you stringify them, you're forcing perl to round them. If it makes you feel any better about it, you could explicitly round them with sprintf.

        Caution: Contents may have been coded under pressure.
        So can I avoid it somehow?

        Yes, multiply everything by 100 (i.e. deal with cents instead of dollars), and divide by 100 (i.e. convert to dollars) when you print. You won't have the problem, because it doesn't occur for numbers with no decimals, unless they get really big (> 2 billion on a 32 bit system).

        >perl -e "$a=0; $a += 0.10 for (1..60); print($a, $/);" 5.99999999999999 >perl -e "$a=0; $a += 10 for (1..60); print($a/100, $/);" 6
Re: 4253.95 + 0.9 = 4254.84999999999 (need help to interpret internals)
by Roy Johnson (Monsignor) on Feb 25, 2005 at 14:04 UTC
Re: 4253.95 + 0.9 = 4254.84999999999 (need help to interpret internals)
by BrowserUk (Patriarch) on Feb 25, 2005 at 14:43 UTC

    See Re: Re: Re: Bug? 1+1 != 2 for why this happens, and what to do about it.


    Examine what is said, not who speaks.
    Silence betokens consent.
    Love the truth but pardon error.
Re: 4253.95 + 0.9 = 4254.84999999999 (need help to interpret internals)
by virtualsue (Vicar) on Feb 25, 2005 at 15:40 UTC
    It has already been said, but it bears repeating since so many people get caught out. There is no such thing as floating point with "reasonably uncomplicated values". Just because a number looks sane in base 10 doesn't mean it's a rational number in binary.

    You have a few choices about how to deal with this issue. It does depend on how important this is to you, and what you are trying to accomplish. People writing software for financial institutions will do different things from students just playing with lab results.


    Update: Hi ikegami, I meant "rational" loosely as a synonym for sane just to avoid repeating myself. I shouldn't have used a word with a well-known mathematical meaning. :-)
      Just because a number looks sane in base 10 doesn't mean it's a rational number in binary.

      Rationality is independant of base. A rational number in base 10 will still be rational in base 2. You mean not periodic. A number which isn't periodic in base 10 may be periodic in base 2, but periodic numbers are rational.

Re: 4253.95 + 0.9 = 4254.84999999999 (need help to interpret internals)
by mdillon (Priest) on Feb 25, 2005 at 15:09 UTC
    If you care about your money, use Math::BigFloat, as dragonchild suggested.

    As others have said, the problem is that floating point binary can't represent most decimal fractions exactly. It's analogous to the way that 1/3 is a repeating decimal (0.3333...). 0.95 and 0.9 are both repeating binary numbers. As can you see, it goes both ways: even 1/3 in trinary notation does not repeat: 0.1.

    The solution is to use "fixed point" or "arbitrary precision" decimal arithmetic. This is usally implemented internally as an integer (or byte array) with a scale. I'm not sure how Math::BigFloat does its job, but it is the standard module for arbitrary precision arithmetic in Perl.

Re: 4253.95 + 0.9 = 4254.84999999999 (need help to interpret internals)
by belg4mit (Prior) on Mar 01, 2005 at 13:28 UTC
    Someone mentioned Math::BigFloat. I recently discovered a new-ish (as of 5.8.0) pragma use bignum which transparently overloads all things numeric with the Math::BigFoo equivalent. It's worth looking into but if you're going to be doing heavy lifting I recommend setting a precision level or using one of the alternative support libraries since the native Calc can cause problems ("precision" grows, memory consumption skyrockets, and execution time slows to a crawl).

    --
    I'm not belgian but I play one on TV. On dit que je parle comme un belge aussi.

Re: 4253.95 + 0.9 = 4254.84999999999 (need help to interpret internals)
by jplindstrom (Monsignor) on Feb 25, 2005 at 17:59 UTC
    Thanks everyone for your replies!

    I think that what confused me the most with this is that seemingly identical values don't behave the same way. I.e. the result of

    perl -e "print 4253.95 + 0.9"

    is 4254.85. But the result of my other values, which are 4253.95 and 0.9, is 4254.84999999999.

    /J

      Your diagnostics are still not telling you the whole of the story, because Devel::Peek is performing the same sort of rounding on the NV value it shows you that perl normally does.

      If you want to look at these values in more detail, best to ask for it explicitly with sprintf:

      my $a = 4253.95; my $b = 0.9; my $c = $a + $b; print "$a + $b = $c\n"; printf "%.30f + %.30f = %.30f\n", $a, $b, $c;

      If you use this with your real code, it is more likely to show you the actual difference between the numbers you get when you enter them explicitly as above and those derived in your code. My guess would be accumulated rounding errors; another possibility would be something modifying $# (see perlvar), but trying to create a testcase to illustrate that I can't get it to do anything at all.

      Hugo