Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clarification on the Decimal type in Python

Everybody knows, or at least, every programmer should know, that using the float type could lead to precision errors. However, in some cases, an exact solution would be great and there are cases where comparing using an epsilon value is not enough. Anyway, that's not really the point.

I knew about the Decimal type in Python but never tried to use it. It states that "Decimal numbers can be represented exactly" and I thought that it meant a clever implementation that allows to represent any real number. My first try was:

>>> from decimal import Decimal >>> d = Decimal(1) / Decimal(3) >>> d3 = d * Decimal(3) >>> d3 < Decimal(1) True 

Quite disappointed, I went back to the documentation and kept reading:

The context for arithmetic is an environment specifying precision [...]

OK, so there is actually a precision. And the classic issues can be reproduced:

>>> dd = d * 10**20 >>> dd Decimal('33333333333333333333.33333333') >>> for i in range(10000): ...    dd += 1 / Decimal(10**10) >>> dd Decimal('33333333333333333333.33333333') 

So, my question is: is there a way to have a Decimal type with an infinite precision? If not, what's the more elegant way of comparing 2 decimal numbers (e.g. d3 < 1 should return False if the delta is less than the precision).

Currently, when I only do divisions and multiplications, I use the Fraction type:

>>> from fractions import Fraction >>> f = Fraction(1) / Fraction(3) >>> f Fraction(1, 3) >>> f * 3 < 1 False >>> f * 3 == 1 True 

Is it the best approach? What could be the other options?

like image 469
Maxime Chéramy Avatar asked Dec 03 '13 14:12

Maxime Chéramy


People also ask

What is the decimal type in Python?

By default, Python interprets any number that includes a decimal point as a double precision floating point number. The Decimal is a floating decimal point type which more precision and a smaller range than the float.

How do you represent decimals in Python?

If value is a tuple , it should have three components, a sign ( 0 for positive or 1 for negative), a tuple of digits, and an integer exponent. For example, Decimal((0, (1, 4, 1, 4), -3)) returns Decimal('1.414') .


2 Answers

The Decimal class is best for financial type addition, subtraction multiplication, division type problems:

>>> (1.1+2.2-3.3)*10000000000000000000 4440.892098500626                            # relevant for government invoices... >>> import decimal >>> D=decimal.Decimal >>> (D('1.1')+D('2.2')-D('3.3'))*10000000000000000000 Decimal('0.0') 

The Fraction module works well with the rational number problem domain you describe:

>>> from fractions import Fraction >>> f = Fraction(1) / Fraction(3) >>> f Fraction(1, 3) >>> f * 3 < 1 False >>> f * 3 == 1 True 

For pure multi precision floating point for scientific work, consider mpmath.

If your problem can be held to the symbolic realm, consider sympy. Here is how you would handle the 1/3 issue:

>>> sympy.sympify('1/3')*3 1 >>> (sympy.sympify('1/3')*3) == 1 True 

Sympy uses mpmath for arbitrary precision floating point, includes the ability to handle rational numbers and irrational numbers symbolically.

Consider the pure floating point representation of the irrational value of √2:

>>> math.sqrt(2) 1.4142135623730951 >>> math.sqrt(2)*math.sqrt(2) 2.0000000000000004 >>> math.sqrt(2)*math.sqrt(2)==2 False 

Compare to sympy:

>>> sympy.sqrt(2) sqrt(2)                              # treated symbolically >>> sympy.sqrt(2)*sympy.sqrt(2)==2 True 

You can also reduce values:

>>> import sympy >>> sympy.sqrt(8) 2*sqrt(2)                            # √8 == √(4 x 2) == 2*√2... 

However, you can see issues with Sympy similar to straight floating point if not careful:

>>> 1.1+2.2-3.3 4.440892098500626e-16 >>> sympy.sympify('1.1+2.2-3.3') 4.44089209850063e-16                   # :-( 

This is better done with Decimal:

>>> D('1.1')+D('2.2')-D('3.3') Decimal('0.0') 

Or using Fractions or Sympy and keeping values such as 1.1 as ratios:

>>> sympy.sympify('11/10+22/10-33/10')==0 True >>> Fraction('1.1')+Fraction('2.2')-Fraction('3.3')==0 True 

Or use Rational in sympy:

>>> frac=sympy.Rational >>> frac('1.1')+frac('2.2')-frac('3.3')==0 True >>> frac('1/3')*3 1 

You can play with sympy live.

like image 67
dawg Avatar answered Sep 21 '22 05:09

dawg


So, my question is: is there a way to have a Decimal type with an infinite precision?

No, since storing an irrational number would require infinite memory.

Where Decimal is useful is representing things like monetary amounts, where the values need to be exact and the precision is known a priori.

From the question, it is not entirely clear that Decimal is more appropriate for your use case than float.

like image 29
NPE Avatar answered Sep 23 '22 05:09

NPE