smcone has asked for the
wisdom of the Perl Monks concerning the following question:
Hello Perl Monks 
I am a new user to this site  but a professional programmer for many years  hope I can be a vital contributor to your esteemed collection of knowledge.
I have a simple math question.
I recently stumbled upon a very obscure problem in one of my apps.
According to Perl,
100.10  100.00 = 0.0999999999999943
and not ".10" as I would have imagined from my early school days.
Try it yourself in your favorite shell (this happens in v5.83):
perl e "print (100.10100);"
Can someone with supreme wisdom please explain to me why perl comes to this result from simple math with a nonwhole number. This is causing thousands of calculations in my app to be off by .01  .02  but when money is involved it needs to be 100% correct.
Is this a perl bug? Or am I missing something completely obvious?
Look forward to your replies.
Re: Math 101 anyone? by davido (Archbishop) on Oct 12, 2004 at 07:44 UTC 
 [reply] 
Re: Math 101 anyone? by Crian (Chaplain) on Oct 12, 2004 at 07:46 UTC 
The Problem lies in the fact, that numbers on computers are in binary format. 1/10 is a binary number that can not be displayed (or stored) exactly in a storage with finite memory.
If you try to add binary fractions you will never end: 1/16 + 1/32 + ...
Sorry if I miss the right english words for that, I'm not a native speaker...
 [reply] 
Re: Math 101 anyone? by kvale (Monsignor) on Oct 12, 2004 at 07:51 UTC 
By their very nature, floating point numbers, such as 100.10, can be represented only approximately by finite precision numbers. perl, for instance, uses doubles to represent floating point numbers. On the other hand, integers are represented exactly in perl and only promoted to floating point when a fractional component is encountered.
So to get exact results, do all your calculations in integers, and divide by 100 for display purposes.
 [reply] 

 [reply] [d/l] [select] 
Re: Math 101 anyone? by BrowserUk (Pope) on Oct 12, 2004 at 08:10 UTC 
...but when money is involved it needs to be 100% correct
If you store your currency as whole numbers of pennies/cents etc, then you will never get roundoffs and have total accuracy upto $/£/? 9 trillion (see machine accuracy).
For a simple explanation of why floating point doesn't give exact results, see Re: Re: Re: Bug? 1+1 != 2.
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." David Dunham
"Think for yourself!"  Abigail
"Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side."  tachyon
 [reply] 

Even using whole numbers, you still run into problems when you need to compute exchange rates or percentoff discounts. The company I work for has had to take a really close look at all of our rounding methods to make sure we don't have any pricing discrepancies.
 [reply] 

Once you move to storing your prices in pence/cents etc., and roundoff through division will be decimal pence/cents and it's usually ok to round these amounts up or down to the nearest whole pence/cents in favour of the customer without dramatically affecting your bottom line.
The caveat is that you should accumulate all your sums and perform your discounting/exchange rate calculations on the total, not on each individual value prior to totalling.
For example, if your customer is buying 10,000 widgets @ 10 pence each and is entitled to a 17.5% discount.
$unitPrice = 10.0;
$discount = 0.175;
$units = 10_000;
$totalPayable = int( $unitPrice * ( 1  $discount ) * $units ); ## Cor
+rect way
printf "Total: £%.2f\n", $totalPayable / 100;
##Total: £825.00
$totalPayable = int( $unitPrice * ( 1  $discount ) ) * $units; ## WRO
+NG!
printf "Total: £%.2f\n", $totalPayable / 100;
## Total: £800.00
I once sat through a 3 hr long lecture on (UK) accounting conventions related to a very similar problem, the upshot of which was that you should always total up before applying any conversion that required division. The rules probably vary dependant upon where you live or pay taxes.
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." David Dunham
"Think for yourself!"  Abigail
"Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side."  tachyon
 [reply] [d/l] 

Re: Math 101 anyone? by SpanishInquisition (Pilgrim) on Oct 12, 2004 at 13:43 UTC 
but when money is involved it needs to be 100% correct.
No it doesn't! Use this to your advantage and skim the remainders... Like in Office Space or Superman 3.
 [reply] 
Re: Math 101 anyone? by Preceptor (Chaplain) on Oct 12, 2004 at 19:33 UTC 
Apologies for error, it's been a while since I did this :).
I'll be using 8 bit numbers where relevant. I'm well aware that most systems use 32 and 64 bit, but this is _much_ simpler.
The problem is a simple one  representing fractions in binary.
As I'm sure most will be aware, a decimal integer, can be represented as a sum of powers of two.
Eg.
100 = 64 + 32 + 4 = 2 ^ 6 + 2 ^ 5 + 2 ^ 2
= 1100100
Positionally, the binary number is increasing powers of two. So the rightmost is 2 ^ 0, and the leftmost is 2 ^ 6.
The problem appears when we try and represent a fraction in binary.
In order to represent noninteger numbers, clearly we need a binary point (not decimal :)).
So we can have 1000.1010.
The 'rule' for numbers to the right of the point, is that the 'powers' are negative.
Eg. in decimal
10 ^ 1 = 0.1
10 ^ 2 = 0.01
10 ^ 3 = 0.001
If we continue to work in binary, the same logic applies. Unfortunately:
2 ^ 1 = 1/2
2 ^ 2 = 1/4
2 ^ 3 = 1/8
binary 1010.1010 = 2 ^ 3 + 2 ^ 1 + 2 ^ 1 + 2 ^ 3 = decimal 10.625
As you can see, there are quite a few decimal fractions (eg. 0.1) that become recurring fractions when represented as a binary number.
This is the source of that particular quirk you noticed.
Floating point numbers are functionally similar to fixed point in this regard.
The weakness of fixed point becomes quickly obvious  if you 'reserve' 4 bits for the integer, and the other 4 for the mantissa then you end up with an 8 bit number being from 0 to 16 rather than the 0256 range you'd have normally.
The workaround was to use floating point. Essentially, the 8 bit number gets cut up into 3 parts. The integer, the mantissa and the exponent. The exponent is a multiplier of 'powers of 2' of the binary number.
Although I'll leave it there, because there's many better explanations of floating point numbers on the net.
Essentially, the problem in your calculation comes from rounding errors when converting to/from binary fractions.
 [reply] [d/l] [select] 
Re: Math 101 anyone? by gawatkins (Monsignor) on Oct 12, 2004 at 20:10 UTC 
Hello smcone,
You can use the sprintf function to round your numbers to 2 decimal places, something like this:.
perl e "print sprintf ('%.2f', (100.10100));"
Thanks,
Greg W.
 [reply] [d/l] 
Re: Math 101 anyone? by Crian (Chaplain) on Oct 13, 2004 at 09:23 UTC 
I just tried out the behaviour of Perl in this case and found this:
perl e "print 1/10"
gives the result 0.1
Also does
perl e "print 1.0 / 10"
and
perl e "print 1.0 / 10.0".
But
perl e "print 100.1  100.0"
gives the well known 0.0999999999999943.
(All under ActiveStates Perl 5.8.0 under Windows 2000.)
Is there a special mechanism in Perl that "saves" the fraction? Or do I miss something else?
 [reply] [d/l] [select] 

printf "%.32f\n", 1/10;
0.10000000000000001000000000000000
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." David Dunham
"Think for yourself!"  Abigail
"Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side."  tachyon
 [reply] [d/l] 

