goodvibration
goodvibration

Reputation: 6206

Python Decimal gives incorrect result

I'm having a problem with the following computation:

Decimal(3)*(Decimal(1)/Decimal(3))

Instead of returning 1.0, it returns 0.999...

This remains the case no matter how much I increase the Decimal module's precision.

Here is the full code:

from decimal import Decimal
from decimal import getcontext
getcontext().prec = 800
print Decimal(3)*(Decimal(1)/Decimal(3))

Ironically, using the "native" float solves the problem:

print float(3)*(float(1)/float(3))

Needless to say, I'm using Decimal here for a more complex computation involving exponentiation of large numbers. After bumping into this problem, I managed to minimize it to the above example.

Upvotes: 3

Views: 5377

Answers (2)

Izaak van Dongen
Izaak van Dongen

Reputation: 2545

Expanding a little on my comment:

A decimal number in Python is literally the kind of number you might write down by hand. Importantly, it doesn't understand the concept of recursion, so an infinitely recurring fraction like 1 / 3 is just represented as 0.333333333333.. up to the point of your precision. When this is multiplied by 3, you get 0.99999.. - which is sensible behaviour, because it can't actually know that that 0.33333333.. was 1 / 3 after it's been truncated. Decimals very often lose precision due to rounding when you divide (in fact when you divide by anything with a factor other than 2 or 5). If being able to do division is crucial, use a Fraction, which represents any rational number without loss of any precision by a numerator and denominator:

In [1]: from fractions import Fraction

In [2]: Fraction(3) * Fraction(1, 3)
Out[2]: Fraction(1, 1)

In [3]: print(_)
1

A Fraction will automatically simplify itself.

With your float, I assume it's just been a matter of luck that the rounding errors have cancelled themselves out. Note that float or Decimal will probably be good enough unless you need absolute precision, in which case I would recommend a Fraction (which for example means you could reliably do equality testing). You can always round an ugly number to make it at least look a little prettier:

In [4]: "{:.2f}".format(Decimal(3) * (Decimal(1) / Decimal(3)))
Out[4]: '1.00'

If you're doing something like a simulation, the difference between 0.9999999 and 1 often won't actually matter much.

Another option is to reshuffle your order of operations so that the numerator is guaranteed to be divisible by the denominator, as seen in Anilkumar's answer. This is a good solution if it's possible, but there may well be occasions where you can't do this - eg you're expecting the result to be fractional, or you get the fractional multiplicand from some kind of black box. At this point, it would be possible to track both a Decimal numerator and denominator.. and then you realise that that's what the Fraction class does, but with less hassle.

Note that you can use a Fraction for anything a Decimal can do but crucially more. Any representable decimal number is a fraction, too (the mantissa over some power of 10). eg:

In [2]: Fraction("3.141")
Out[2]: Fraction(3141, 1000)

Naturally, this will lead to some lost performance - Fractions have to keep track of more data, do more calculations and are probably somewhat more abstract.

Looking at your newly provided formula - be wary that when you raise a rational number to the power of a non-integer, the result might not be rational, so you might lose the Fraction somewhere along the way, eg:

>>> Fraction(1, 2) ** 4
Fraction(1, 16)
>>> Fraction(1, 2) ** 0.5
0.7071067811865476

Although within the context of evaluating a formula it wouldn't make much sense to try and store everything symbolically. This brings us back to the idea of a rough float often being good enough. If you really wanted some kind of surd output format, you could give sympy a shot:

In [1]: from sympy import *

In [2]: sqrt(Integer(1) / Integer(2))
Out[2]: sqrt(2)/2

This will of course slow you down even more.

Upvotes: 6

Anilkumar Battaram
Anilkumar Battaram

Reputation: 119

I think paranthesis to be changed:

from decimal import Decimal
from decimal import getcontext
getcontext().prec = 800
print (Decimal(3)*Decimal(1))/Decimal(3)

Output is 1 for me for above one

Upvotes: -1

Related Questions