Re: perl subtraction
by syphilis (Archbishop) 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.
Cheers, Rob | [reply] |
|
|
Hi,
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?
| [reply] |
|
|
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.
| [reply] |
Re: perl subtraction
by Anonymous Monk on Jan 10, 2010 at 03:57 UTC
|
| [reply] |
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. | [reply] [d/l] |
|
|
| [reply] |
|
|
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);
| [reply] [d/l] [select] |
|
|
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
| [reply] [d/l] |
|
|
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. | [reply] |
Re: perl subtraction
by ikegami (Patriarch) 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) | [reply] [d/l] |
|
|
perl -e "print 100.61 + 100.33 - 200.94"
works correctly. | [reply] [d/l] |
|
|
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.
| [reply] |
Re: perl subtraction
by LanX (Saint) on Jan 10, 2010 at 12:33 UTC
|
| [reply] |
|
|
| [reply] |
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);
| [reply] [d/l] |
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
| [reply] |
Re: perl subtraction
by BrowserUk (Patriarch) on Jan 10, 2010 at 07:16 UTC
|
| [reply] |