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

Dear Monks,

I am at my wits' end over the values "-3.5527136788005e-015" and "-1.4210854715202e-014" when 0 is expected in each case. I'm on Windows Perl 5.14.

Essentially, what I am doing is to subtract a total amount (a session value) from each item's amount. Sometimes I get the expected 0 but sometimes I get strange values even though last amount to be subtracted from the total amount is equal to that total amount:

foreach my $key (sort keys %$cart_info) { my $qty = $cart_info->{$key}->{qty}; my $amount = $cart_info->{$key}->{amount}; $session->param('CART')->{total_amount} -= $amount; $session->param('CART')->{total_qty} -= $qty;; } # First sample Before: session_total_amt: 585.86 After: key: 116, amount: 112.09, session_total_amt: 473.77 Before: session_total_amt: 473.77 After: key: 117, amount: 69.75, session_total_amt: 404.02 Before: session_total_amt: 404.02 After: key: 118, amount: 113.57, session_total_amt: 290.45 Before: session_total_amt: 290.45 After: key: 123, amount: 113.57, session_total_amt: 176.88 Before: session_total_amt: 176.88 After: key: 124, amount: 69.75, session_total_amt: 107.13 Before: session_total_amt: 107.13 After: key: 125, amount: 80.89, session_total_amt: 26.24 Before: session_total_amt: 26.24 After: key: 50, amount: 26.24, session_total_amt: -3.5527136788005e-01 +5 # Notice that both the session_total_amt and the amount to be subtract +ed are 26.24. How did I end up with -3.5527136788005e-015? # Second sample Before: session_total_amt: 319.02 After: key: 116, amount: 112.09, session_total_amt: 206.93 Before: session_total_amt: 206.93 After: key: 117, amount: 69.75, session_total_amt: 137.18 Before: session_total_amt: 137.18 After: key: 118, amount: 113.57, session_total_amt: 23.61 Before: session_total_amt: 23.61 After: key: 56, amount: 23.61, session_total_amt: -1.4210854715202e-01 +4 # Same thing here. The session_total_amt and the amount to be subtract +ed are both 23.61.

What am I missing here? (scratch head)?

Replies are listed 'Best First'.
Re: Getting stranger values in subtraction
by choroba (Cardinal) on Feb 06, 2016 at 08:14 UTC
Re: Getting stranger values in subtraction
by hdb (Monsignor) on Feb 06, 2016 at 08:13 UTC

    Computers cannot be trusted with arithmetics...

    Have a look at this thread or look for "floating point arithmetic" in Super Search.

      This might be a bit strong. Perhaps "Naive Programmers cannot be trusted to use computers to do arithmetic." :-)

      If you are aware of the limitations based on how the hardware, libraries, and language does arithmetic (iow - know your tools), computers can be used to do the job just fine. I had two courses in this in college that dealt with this topic - one in the math department and one in the CS department. It is a similar concept to students writing down a highly precise, but terribly inaccurate answer on a test because they didn't understand significant digits and error accumulation.

      --MidLifeXis

        Fully agree. In this context -3.5527136788005e-015 seems to be close enough to zero for any practical purpose.

        But this "naive" behavior can be seen so often not only with students but also in real applications for commercial purposes (which costs real money).

Re: Getting stranger values in subtraction
by Laurent_R (Canon) on Feb 06, 2016 at 10:44 UTC
    Just as a teaser, running choroba's sum under Perl 5:
    DB<1> $x = 319.02 - 112.09 - 69.75 - 113.57 - 23.61; DB<2> p $x -1.4210854715202e-14
    In both Python and Ruby, the result is -1.4210854715202004e-14, which might seem more accurate (3 more digits), but happens to be in fact a very little bit further from truth.

    The same in Perl 6:

    > my $x = 319.02 - 112.09 - 69.75 - 113.57 - 23.61; 0 > say sprintf "%.50f", $x 0.00000000000000000000000000000000000000000000000000 > say $x == 0; True
    Not only does Perl 6 give the correct result, even when printing 50 decimal digits, but even comparing the result with 0 gives the correct answer.
      Please explain why language makes a difference. All the references given so far suggest that it should not.
      Bill
        Hi Bill,

        Perl 6 is converting fractional numbers not into floating-point numbers as most languages, but into Rat (rational) type values. Internally, rational type numbers are in fact made of pairs of integer numbers, one for the numerator and one for the denominator, stored separately. So the numeric literal 319.02 is stored internally as something like (numerator = 31902, denominator = 100). When making arithmetic operations on such numbers, Perl 6 will happily add (or subtract) the integers and then print the result as a rational number. The result is accurate because there is no use of floating-point number approximation.

        Consider the following code introspecting the types of values and variables:

        > say 42.WHAT; (Int) > say 319.02.WHAT; (Rat) > say $x.WHAT; (Rat)
Re: Getting stranger values in subtraction
by Anonymous Monk on Feb 06, 2016 at 08:34 UTC
Re: Getting stranger values in subtraction
by MidLifeXis (Monsignor) on Feb 08, 2016 at 13:37 UTC

    This is covered often at PM. It has to do with how the language and machine represent floating point numbers (which is why the Perl6 comment elsewhere in this thread is quite interesting :-) ). Floating point numbers are represented as summations of various powers of two -- a binary state machine can only represent things made up of powers of two. The size of your floating point structure and how it is divided between the value and the exponent determine how many digits of precision you have.

    For example, .25 can be represented exactly given one digit for the value (and a couple of more for the exponent) because .25 == 25/100 == 1/4 == 2-2. .75 == 75/100 == 3/4 == sum(2-1, 2-2), and so on.

    You have been pointed to the CS documentation on the topic above. If at all possible, when summing financial calculations, use scaled values so that you are only dealing with integers. Rounding errors, or specifically, the skimming off of rounding errors, is the subject of a few (urban legend?) scams over the years.

    --MidLifeXis

Re: Getting stranger values in subtraction
by Anonymous Monk on Feb 06, 2016 at 08:58 UTC

    Thank you so much :)

    I modified the relevant line of code as follows based on the pointers above, and it seems to have solved the anomaly:

    #$session->param('CART')->{total_amount} -= $amount; $session->param('CART')->{total_amount} = sprintf "%.2f", $ses +sion->param('CART')->{total_amount} - $amount;

    Do we get the same problem with addition?

      Addendum:

      The issue seems to surface if the arithmetic takes place in a loop, when you do the subtraction (or addition as well?) multiple times.

      I don't seem to get this problem when the subtraction happens only once in a subroutine (not in a loop).

        Have you run the code in my reply? There's no loop involved. Also, have you clicked the link provided there and read the article? It answers your question about adition clearly.
        ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Getting stranger values in subtraction
by ExReg (Priest) on Feb 08, 2016 at 15:07 UTC

    As a sort of clue as to what is going on, notice that the discrepancy, -1.4210854715202e-014, is 2 raised to the negative 46th power. I simply means that the answer was accurate to 46 binary digits. It is sort of like how you would do a 10 divided by 3 by long division on paper and get 3.333 and then if you multiply it by three again you get 9.999. We could have carried it out to more digits, but the problem would have remained. We think in a different number base (normally) than computers.