Aclaración sobre el tipo decimal en Python

Todo el mundo sabe, o al menos, todos los progtwigdores deben saber , que el uso del tipo de coma float podría llevar a errores de precisión. Sin embargo, en algunos casos, una solución exacta sería excelente y hay casos en los que la comparación con un valor épsilon no es suficiente. De todos modos, ese no es realmente el punto.

Conocía el tipo Decimal en Python, pero nunca intenté usarlo. Afirma que “los números decimales se pueden representar exactamente” y pensé que significaba una implementación inteligente que permite representar cualquier número real. Mi primer bash fue:

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

Muy decepcionado, volví a la documentación y seguí leyendo:

El contexto para la aritmética es un entorno que especifica la precisión […]

Ok, así que en realidad hay una precisión. Y los temas clásicos se pueden reproducir:

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

Entonces, mi pregunta es: ¿hay una manera de tener un tipo decimal con una precisión infinita? De lo contrario, ¿cuál es la forma más elegante de comparar 2 números decimales (por ejemplo, d3 <1 debería devolver False si el delta es menor que la precisión).

Actualmente, cuando solo hago divisiones y multiplicaciones, uso el tipo de Fraction :

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

¿Es el mejor enfoque? ¿Cuáles podrían ser las otras opciones?

La clase decimal es la mejor para la sum de tipos financieros, la multiplicación de resta y los problemas de tipo de división:

 >>> (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') 

El módulo de Fracción funciona bien con el dominio de problema de número racional que describe:

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

Para el punto flotante de precisión múltiple pura para el trabajo científico, considere mpmath .

Si su problema se puede mantener en el reino simbólico, considere Sympy . Aquí es cómo manejaría el problema de 1/3:

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

Sympy usa mpmath para el punto flotante de precisión arbitraria, incluye la capacidad de manejar números racionales y números irracionales simbólicamente.

Considere la representación de punto flotante puro del valor irracional de √2:

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

Comparar con Sympy:

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

También puedes reducir los valores:

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

Sin embargo, puede ver problemas con Sympy similares a un punto flotante recto si no tiene cuidado:

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

Esto se hace mejor con Decimal:

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

O usando Fractions o Sympy y manteniendo valores como 1.1 como relaciones:

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

O usa Rational en Sympy:

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

Puedes jugar con sympy live .

Entonces, mi pregunta es: ¿hay una manera de tener un tipo decimal con una precisión infinita?

No, ya que almacenar un número irracional requeriría una memoria infinita.

Donde Decimal es útil es representar cosas como cantidades monetarias, donde los valores deben ser exactos y la precisión se conoce a priori.

A partir de la pregunta, no queda del todo claro que el Decimal sea ​​más apropiado para su caso de uso que para float .

¿Hay una manera de tener un tipo decimal con una precisión infinita?

No; para cualquier intervalo no vacío en la línea real, no puede representar todos los números en el conjunto con una precisión infinita utilizando un número finito de bits. Por esta razón, Fraction es útil, ya que almacena el numerador y el denominador como enteros, que se pueden representar con precisión:

 >>> Fraction("1.25") Fraction(5, 4) 

Si eres nuevo en Decimal, esta publicación es relevante: ¿Hay una precisión arbitraria de punto flotante de Python disponible?

La idea esencial de las respuestas y los comentarios es que para problemas difíciles de cálculo en los que se necesita precisión, debe usar el módulo mpmath https://code.google.com/p/mpmath/ . Una observación importante es que,

El problema con el uso de números decimales es que no se puede hacer mucho en cuanto a las funciones matemáticas en los objetos decimales