web-dev-qa-db-fra.com

Clarification sur le type décimal dans Python

Tout le monde sait, ou du moins, tous les programmeurs devraient savoir , que l'utilisation du type float pourrait conduire à des erreurs de précision. Cependant, dans certains cas, une solution exacte serait excellente et il existe des cas où la comparaison à l'aide d'une valeur epsilon n'est pas suffisante. Quoi qu'il en soit, ce n'est pas vraiment le point.

Je connaissais le type Decimal dans Python mais je n'ai jamais essayé de l'utiliser. Il déclare que "Les nombres décimaux peuvent être représentés exactement" et j'ai pensé que cela signifiait une implémentation intelligente qui permet de représenter n'importe quel nombre réel. Mon premier essai a été:

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

Assez déçu, je suis retourné à la documentation et j'ai continué à lire:

Le contexte de l'arithmétique est un environnement spécifiant la précision [...]

Ok, donc il y a en fait une précision. Et les numéros classiques peuvent être reproduits:

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

Donc, ma question est: existe-t-il un moyen d'avoir un type décimal avec une précision infinie? Sinon, quelle est la façon la plus élégante de comparer 2 nombres décimaux (par exemple, d3 <1 devrait retourner Faux si le delta est inférieur à la précision).

Actuellement, lorsque je ne fais que des divisions et des multiplications, j'utilise le type Fraction:

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

Est-ce la meilleure approche? Quelles pourraient être les autres options?

35
Maxime Chéramy

La classe décimale est la meilleure pour l'addition de type financier, la multiplication de soustraction, les problèmes de type division:

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

Le module Fraction fonctionne bien avec le domaine de problème de nombre rationnel que vous décrivez:

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

Pour la virgule flottante pure multi précision pour les travaux scientifiques, considérons mpmath .

Si votre problème peut être lié au domaine symbolique, considérez sympy . Voici comment gérer le problème 1/3:

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

Sympy utilise mpmath pour la virgule flottante de précision arbitraire, inclut la possibilité de gérer symboliquement les nombres rationnels et irrationnels.

Considérons la représentation en virgule flottante pure de la valeur irrationnelle de √2:

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

Comparez avec sympy:

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

Vous pouvez également réduire les valeurs:

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

Cependant, vous pouvez voir des problèmes avec Sympy similaires à la virgule flottante droite si vous ne faites pas attention:

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

C'est mieux fait avec Decimal:

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

Ou en utilisant Fractions ou Sympy et en conservant des valeurs telles que 1.1 en tant que ratios:

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

Ou utilisez Rational dans sympy:

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

Vous pouvez jouer avec sympy live .

47
dawg

Donc, ma question est: existe-t-il un moyen d'avoir un type décimal avec une précision infinie?

Non, car le stockage d'un nombre irrationnel nécessiterait une mémoire infinie.

Là où Decimal est utile, cela représente des choses comme des montants monétaires, où les valeurs doivent être exactes et la précision est connue a priori.

D'après la question, il n'est pas tout à fait clair que Decimal est plus approprié pour votre cas d'utilisation que float.

4
NPE

existe-t-il un moyen d'avoir un type décimal avec une précision infinie?

Non; pour tout intervalle non vide sur la ligne réelle, vous ne pouvez pas représenter tous les nombres de l'ensemble avec une précision infinie en utilisant un nombre fini de bits. C'est pourquoi Fraction est utile, car il stocke le numérateur et le dénominateur sous forme d'entiers, qui peut être représentés avec précision:

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

Si vous êtes nouveau dans Decimal, ce message est pertinent: précision arbitraire en virgule flottante Python disponible?

L'idée essentielle des réponses et des commentaires est que pour les problèmes de calcul difficiles où la précision est nécessaire, vous devez utiliser le module mpmathhttps://code.google.com/p/mpmath/ =. Une observation importante est que,

Le problème avec l'utilisation des nombres décimaux est que vous ne pouvez pas faire grand-chose en termes de fonctions mathématiques sur les objets décimaux

1
William Denman