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

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

Hi Monks,

I have a ascii file with numbers with two deciamal places (plus and minus). I want simply read the numbers and add them.

I see following problem: (I reduced the code to a minimum. the @nr holds the content of the file. )

my @nr=('32431.19', '20', '-10', '-31800'); my $x=0; foreach (@nr) { $x+=$_; print "$_ => $x\n"; }
which results in :
32431.19 => 32431.19 20 => 32451.19 -10 => 32441.19 -31800 => 641.189999999999

for the last sum I expect 641.19 but I get 641.189999999999 !!

What can I do to prevent this kind of rounding error ?

Many Thanks for your comments

Replies are listed 'Best First'.
Re: Problems with number resolution
by haukex (Archbishop) on Jan 01, 2018 at 18:46 UTC

    davies has already linked you to What Every Programmer Should Know About Floating-Point Arithmetic, which is definitely worth reading, and poj has shown you one possible solution. Here are two more that give the desired output:

    If you don't care about the inaccurate internal representation, use printf for output, as in: printf "%.2f => %.2f\n", $_, $x;

    If you don't care so much about the performance of your program, stick a use bignum; at the top of your program (or at least in the lexical scope of the code you showed).

      Thanks haukex for your comment.

      Using "use bignum" is the solution what I was looking for.
Re: Problems with number resolution
by davies (Prior) on Jan 01, 2018 at 17:12 UTC
Re: Problems with number resolution
by syphilis (Archbishop) on Jan 01, 2018 at 23:19 UTC
    What can I do to prevent this kind of rounding error ?

    Update: This is for the main part a rehash of what haukex had posted 5 hours earlier ... something I had failed to observe first time round.

    You could change the last line of your code from:

    print "$_ => $x\n";
    to
    printf "$_ => %.2f\n", $x;
    Any module that does decimal (as opposed to binary) arithmetic would also provide correct results.
    These modules include Math::BigFloat, Math::Decimal and Math::Decimal64.

    Cheers,
    Rob

      Thanks syphilis for your comment !

      I have looked into all suggestions and learned a lot ! Thanks to all !

      One question to Math::BigFloat, Math::Decimal and Math::Decimal64 :

      * Is this correct: As soon as at least one variable in an expression is a Math object (e.g. MEtoD64 after use Math::Decimal64 qw/:all/;) the calculation is done with the Math-functions, not with the standard functions. If there is no Math object in the expression, the calculation is still done with the fast standard functions. So I can use both (accurate and fast arithmetic) on the contrary when I use "use bignum", where I always use the accurate Math functions for all operations.

      * How can I check if a variable is a standard scalar or a Math object (like "ref" for hash/array)?

      * Is there a good overview document, pointing out the differences between Math::BigFloat, Math::Decimal, Math::Decimal64, Math::BigInt and Math::BigRat. (When to use which library best?)

        As to checking what kind of data is stored in a reference, use the ref function.

        Cheers, Sören

        Créateur des bugs mobiles - let loose once, run everywhere.
        (hooked on the Perl Programming language)

Re: Problems with number resolution
by poj (Abbot) on Jan 01, 2018 at 17:20 UTC

    Hold the sum as an integer.

    my @nr = (32431.19, 20, -10, -31800); my $x = 0; for (@nr) { $x += $_*100; print "$_ => ".($x/100)."\n"; }
    poj

      Thanks Poj,

      To have the sum as integer, the (number*100) has to be converted to an integer using round.
      use Math::Round qw/round/; my @nr=('32431.19', '20', '-10', '-31800'); my $x=0; foreach (@nr) { $x+=round($_*100); print "$_ => ".$x/100 ."\n"; }
        Why do you use round here? I don't think you need it. Or can your input numbers have more than two decimals? I think you said that there could be only two decimal places.

        Another point is that I don't see any reason to print incremental results each time through the look (except possibly for debugging purposes). Put the print statement after the end of the foreach loop.

        And you might want to use printf instead of print in this specific case. In fact, using printf might be the only change that you need in your original code, as shown with this example under the Perl debugger:

        DB<1> @nr=('32431.19', '20', '-10', '-31800'); DB<2> $x=0; DB<3> $x += $_ for @nr; DB<4> print $x 641.189999999999 DB<5> printf '%.2f', $x; 641.19
Re: Problems with number resolution
by BillKSmith (Monsignor) on Jan 01, 2018 at 22:18 UTC
    Check the FAQ.
    perldoc -q "Why am I getting long decimals"
    Bill
Re: Problems with number resolution
by karlgoethebier (Abbot) on Jan 02, 2018 at 17:30 UTC

    The math and me! The more illuminated brothers may forgive me. But using Math::BigFloat i get the results you expect:

    #!/usr/bin/env perl use strict; use warnings; use Math::BigFloat; use feature qw(say); my $x = Math::BigFloat->new(0); my @nr = ( '32431.19', '20', '-10', '-31800' ); foreach (@nr) { $x += $_; say qq($_\t=>\t$x); } __END__ karls-mac-mini:playground karl$ ./math.pl 32431.19 => 32431.19 20 => 32451.19 -10 => 32441.19 -31800 => 641.19

    Best regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

      Yes, Math::BigFloat probably does it right. But it may be quite slow.

      When dealing with monetary value having typically 2 decimal places, many (most ?) business databases actually store monetary values as integers with a scale factor of two (in other words, they store internally monetary amounts in cents instead of dollars or euros and perform the necessary conversions when needed). I guess this is deemed to be more accurate and probably faster. For tariffs related with usually small amounts (such as rating telephone calls), I have seen price rates actually stored internally as integers in 1/10,000 of a euro. But anytime the database engine reads from the database or writes to it, the scaling factor is applied. Most people using the database or even developing for it don't ever get to see this, as this is buried deep in the very low levels of the database software.

Re: Problems with number resolution
by Anonymous Monk on Jan 01, 2018 at 18:01 UTC
    Switch to Perl 6! Then your numbers will be stored with Perfect Accuracy, provided that you don't need more than about 19 digits after the decimal point.