Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?

perl subtraction

by jagdish.eashwar (Novice)
on Jan 10, 2010 at 03:43 UTC ( #816567=perlquestion: print w/replies, xml ) Need Help??
jagdish.eashwar has asked for the wisdom of the Perl Monks concerning the following question:

I am unable to understand why
perl -e "print 17554.61 + 2194.33 - 19748.94"
does not give a zero,but
perl -e "print 19748.94 - 19748.94"

Replies are listed 'Best First'.
Re: perl subtraction
by syphilis (Chancellor) on Jan 10, 2010 at 03:53 UTC
    It's because the internal base 2 representation of the result of adding 17554.61 and 2194.33 is not exactly the same as the internal base 2 representation of 19748.94.


      I have written a script in which I enter the invoice amount, the tax amount, and the invoice total. Then I make the script check that the invoice amount + the tax amount equal the invoice total. It works well most of the time, but yesterday one invoice had these amounts (invoice amount = 17554.61, tax = 2194.33, and total = 19748.94) and the script informed me that the two figures were not equal. As you have explained, it does so because the internal base 2 representations of the two numbers are not the same. Is there any way of getting around this hurdle? How can perl be made to see that the two figures are the same in the decimal format?

        One general approach is to maintain and operate on amounts internally as fixed-point integer quantities; e.g., as the number of cents, or possibly mills (tenths of a cent). While the numeric representation used (at least by Perl) is still double-precision floating point, the goal is to never produce a non-zero fractional result. Addition, subtraction and multiplication on integers always yield integer results. Operations that can produce fractional results (e.g., division) must sooner or later (and sooner is usually better) be reconciled into the fixed point representation that has been selected.

        Or maybe check out something like integer or Math::FixedPrecision.

Re: perl subtraction
by Anonymous Monk on Jan 10, 2010 at 03:57 UTC
Re: perl subtraction
by kiparsky (Novice) on Jan 10, 2010 at 06:18 UTC
    You could also use printf to format the value reported. Try something like this:

    printf ("\$%2.2f\n", $val);

    where $val is the value that you want to print. This will give you dollar sign, and value to two decimal places. Your first example comes back as $0.00, which is probably what you want. This might be a little easier to deal with than changing over to representing your figures in cents, if you've already written some code.

      Thanks. What I understand now is that the internal base 2 representation is not likely to cause any problems if I am dealing with only integers. In which case, I can convert the amounts to cents in the section of the code where I check whether the two amounts are equal. Thanks again.

        What I understand now is that the internal base 2 representation is not likely to cause any problems if I am dealing with only integers.

        Yes, as long as they're not too large. If you keep the numbers small than 9,007,199,254,740,992, you'll be ok.

        >perl -e"printf qq{%.0f\n}, 9007199254740992 + 3" 9007199254740996 even? how odd!

        Have fun calculating tax without using decimals, though. What you want to do then is perform rounding:

        # If working with cents $tax_amount = sprintf('%.0f', $amount * $tax_rate);

        I'm afraid that didn't work out as I expected. I multiplied the amounts by 100 to get the amounts in cents. But when I compare the two, my script is still telling me that they are not equal./p>

        use warnings; use strict; my($amt1,$amt2,$mytot,$perltot,$diff); print "Enter amt1 : "; chomp($amt1 = <STDIN>); print "\nEnter amt2 : "; chomp($amt2 = <STDIN>); print "\nEnter the total : "; chomp($mytot = <STDIN>); $mytot = $mytot * 100; # convert to cents $perltot = ($amt1 * 100) + ($amt2 * 100); #convert to cents $diff = $perltot - $mytot; print "$mytot - $perltot = $diff\n
Re: perl subtraction
by educated_foo (Vicar) on Jan 10, 2010 at 03:52 UTC
    It's because you don't understand how floating point works.
Re: perl subtraction
by ikegami (Pope) on Jan 10, 2010 at 08:21 UTC
    61/100, 33/100 and 94/100 are all periodic numbers in binary (like 1/3 is in decimal). It would take infinite storage to represent them accurately as floats. You're noticing errors from the inability to store the numbers accurately.
    _____ 61/100 = 1.3851EB * 2**(-1) _____ 33/100 = 1.51EB8 * 2**(-2) _____ 94/100 = 1.E147A * 2**(-1)
    (Using hex for the mantisa)

      perl -e "print 100.61 + 100.33 - 200.94"
      works correctly.
        Sometimes the errors cancel out. Floats also do a bit of rounding for you to try to avoid problems. You need to do some of your own.
Re: perl subtraction
by LanX (Bishop) on Jan 10, 2010 at 12:33 UTC

      Thanks. Removing the decimal using the substitute function helped. That tip was in the footnotes. I was earlier removing it by multiplying by 100.

Re: perl subtraction
by lyklev (Pilgrim) on Jan 10, 2010 at 22:06 UTC

    As previous posts have mentioned, floating point numbers are only stored in a limited precision. If your number can be written in a binary non-repeating decimal , your ok, but for repeating floating point numbers in binary way, some accuracy is lost. For example, 1/4th can be written as a binary non-repeating decimal, (1/4 = 0.01 binary), but 1/5th is repeating in binary form (1/5 = 0.00110011...) and will get truncated. This means that for a computer, 1/5 is not 0.2!

    The important lesson is that you should never (I mean it!) compare floating point numbers for equality. By the way, this goes for most programming languages.

    To solve your problem, write the numbers as a formatted string wide enough, and compare those. For example:

    my $val1 = 17554.61 - 2194.33; my $val2 = 19748.94; my $val1_str = sprintf("12.2f", $val1); my $val2_str = sprintf("12.2f", $val2); print ("equal\n") if ($val1_str eq $val2_str);
Re: perl subtraction
by swampyankee (Parson) on Jan 10, 2010 at 20:24 UTC

    You've just run into a problem that computer programmers have dealt with for decades: floating point numbers do not form a closed set under the operations of normal arithmetic. The reason is, of course, that floating point numbers only represent a finite subset of rational numbers.

    Information about American English usage here and here. Floating point issues? Please read this before posting. — emc

Re: perl subtraction
by BrowserUk (Pope) on Jan 10, 2010 at 07:16 UTC


Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://816567]
Approved by McDarren
and the questions are moot...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (5)
As of 2018-05-20 18:34 GMT
Find Nodes?
    Voting Booth?