Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Math 101 anyone?

by smcone (Acolyte)
on Oct 12, 2004 at 07:38 UTC ( #398401=perlquestion: print w/ replies, xml ) Need Help??
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.10-100);"

Can someone with supreme wisdom please explain to me why perl comes to this result from simple math with a non-whole 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.

Comment on Math 101 anyone?
Re: Math 101 anyone?
by davido (Archbishop) on Oct 12, 2004 at 07:44 UTC
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...

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.

    -Mark

      So to get exact results, do all your calculations in integers, and divide by 100 for display purposes.

      Or, more transparently: use bignum - for example:

      perl -Mbignum -e "print (100.10-100);"

      gives the correct answer 0.1


      s^^unp(;75N=&9I<V@`ack(u,^;s|\(.+\`|"$`$'\"$&\"\)"|ee;/m.+h/&&print$&
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
      Even using whole numbers, you still run into problems when you need to compute exchange rates or percent-off 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.

        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
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.

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 non-integer 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 0-256 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.

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.10-100));"

    Thanks,
    Greg W.
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?

      Try this:

      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

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://398401]
Approved by claree0
Front-paged by Trimbach
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (10)
As of 2014-08-30 19:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The best computer themed movie is:











    Results (293 votes), past polls