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

Why does this not sum to zero ?

#!/usr/bin/perl -w use strict; my @amounts = qw(-161.05 177.28 -16.23); my $sum = 0.0; map {$sum += $_} @amounts; print "sum = $sum\n";


sum = -1.06581410364015e-14

This is data taken from an accounts processing system - works fine across 3000 customers with upto 10000 transactions each. But there was one guy with this data that the non-zero happened to.

Is it in the conversion from string to float? I get the same behaviour on 5.005_03 and 5.6.0. Both of these running on Intel Pentium III and Linux RH 7.2

I also have an example of C code not converting from string to float ok, specifically for "48.15" (converts to 48.14999...). This converts incorrectly on Intel platforms, but ok on Solaris - is this a manifestation of the Intel Pentium rounding issue?

Edit kudra, 2002-09-26 Removed br tags in code

Replies are listed 'Best First'.
Re: Sum to Zero?
by Abigail-II (Bishop) on Sep 26, 2002 at 07:06 UTC
    It has to do with binary computers not able to represent all decimal numbers correctly. That explains the behaviour of both the Perl and the C program (it's also explained in the FAQ).

    As for Solaris, one should realize that Sun hardware has been 64 bits for years, while typical Intel platforms is 32 bits. That doesn't mean you don't have rounding issues on Solaris, you do, but the results may differ.

    Abigail

Re: Sum to Zero?
by helgi (Hermit) on Sep 26, 2002 at 09:22 UTC
    This is a FAQ and is dealt with in perlfaq4 - Data Manipulation, specifically

    perldoc -q decimals

    A.K.A. "Why am I getting long decimals (eg, 19.9499999999999) instead of the numbers I should be getting (eg, 19.95)?"

    The problem has nothing to do with the Intel Pentium issue.

    -- Regards,
    Helgi Briem
    helgi AT decode DOT is

Re: Sum to Zero?
by thinker (Parson) on Sep 26, 2002 at 08:23 UTC
    Hi leriksen,

    When faced with a similair problem in the past, what I did was multiplied the sums by 100 before summing, then divided by 100 to get the result. That precision worked for my currency, yours may differ.

    Hope this helps

    thinker
Re: Sum to Zero?
by RMGir (Prior) on Sep 26, 2002 at 12:00 UTC
    A few people mentioned using sprintf or multiplication and division to force the rounding, both of which approximate the easiest answer.

    Store your currency values in cents (or pence, or pfennig, or whatver your smallest currency unit is) and only convert them to dollars (or euros, or dm (oops, no more dm), or whatever) when its time to display them.

    That will avoid most rounding issues.

    You should still use floating point for storage, though, since you need to decide what you're going to do about fractions of cents resulting from interest, taxes, and such, but now you have to make a concious decision about that, which is good; before, your were ignoring the problem with rounding...

    If you just keep the fractions of cents and then round to the nearest cent using some bankers rounding option or other when it's time to pay out or display money, you'll get results compatible with what you got when you were computing in dollars.
    --
    Mike

Re: Sum to Zero?
by physi (Friar) on Sep 26, 2002 at 07:52 UTC
    You can avoid this, by using the printf function like this:
    printf "sum=%.2f\n", $sum;

      That's not avoiding the problem. That's hiding the problem :)

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you do not talk about Perl club."
      -- Chip Salzenberg

        :-)
        YEAH that's the right word :-)
        But that's how life work's, hide it if you can't change it :-)
Re: Sum to Zero?
by Anonymous Monk on Sep 26, 2002 at 23:26 UTC
    Thanks everyone for the responses - I like the solution of storing as cents and just converting to dollars and cents as required best. I believe this is called "normalising". I was hoping for a solution with as little overhead as possible, I suppose 1 multiplication by 100 for every amount on input and one division by 100 for every output is about as good as I can hope for.

    But I am still unclear as to why a conversion from a string has inaccuracies - why does (for examples sake) "0.15" convert to 0.1499999999999263575 or such like.

    Reading the above mentioned FAQ, I dont understand why low precision floats can't be represented correctly. In the FAQ they state

    However, 19.95 can't be precisely represented as a binary floating point number,...,

    Why not? Surely there is a bit pattern in the IEEE float format that precisely represents 19.95 (1.995E1). If a float is stored in 32 bits on a particulat platfrom, some bits are for the mantissa and some are for the exponent and sign. If 24 bits store the mantissa, isn't there a bit pattern that precisely represents 1.995 ?

    If that is so, why can't the conversion library get that bit exact. The machine can represent 0.15 to however decimal places it stores, why can't the conversion library create the correct representation? I understand that _expressions_ will potentially have inaccuracies, like exp(ln(x)) resulting in something very close but not quite x. But a string literal should (in my utopian view) convert precisely. The only exception to this would be a string literal expressing a value beyound the precision of the platform, OS or perl (most restrictive win's) - e.g. "0.1E-2048" will _probably_ end up being represented as 0.0E0

    Anyone have a view why string literal conversion of seemingly inoccuous floats has inaccuracies?

    Oh and thanks kudra for removing the br tags - my first post and all <blush>