web-dev-qa-db-fra.com

Quelle est la meilleure façon de comparer les flottants pour une quasi-égalité en Python?

Il est bien connu que comparer les flottants pour l’égalité est un peu délicat à cause des problèmes d’arrondi et de précision.

Par exemple: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

Quelle est la façon recommandée de gérer cela en Python?

Il existe sûrement une fonction de bibliothèque standard pour cela quelque part?

278
Gordon Wrigley

Python 3.5 ajoute les math.isclose et cmath.isclose fonctions comme décrit dans PEP 485 .

Si vous utilisez une version antérieure de Python, la fonction équivalente est donnée dans documentation .

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

rel_tol est une tolérance relative, elle est multipliée par la plus grande des magnitudes des deux arguments; Plus les valeurs sont grandes, plus la différence autorisée entre elles est grande, tout en les considérant égales.

abs_tol est une tolérance absolue qui est appliquée telle quelle dans tous les cas. Si la différence est inférieure à l'une de ces tolérances, les valeurs sont considérées égales.

261
Mark Ransom

Est-ce que quelque chose d'aussi simple que le suivant ne suffit pas?

return abs(f1 - f2) <= allowed_error
63
Andrew White

Je conviens que la réponse de Gareth est probablement la plus appropriée en tant que fonction/solution légère.

Mais j’ai pensé qu’il serait utile de noter que si vous utilisez NumPy ou si vous envisagez de le faire, il existe une fonction prédéfinie pour cela.

numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)

Un petit avertissement cependant: installer NumPy peut être une expérience non triviale selon votre plate-forme.

36
J.Makela

Utilisez le module decimal de Python, qui fournit la classe Decimal.

D'après les commentaires:

Il convient de noter que si vous faites un travail très mathématique et que vous n'avez pas absolument besoin de la précision décimale, cela peut vraiment gâcher le processus. Les flotteurs sont beaucoup, beaucoup plus rapides à traiter, mais imprécis. Les décimales sont extrêmement précises mais lentes.

13
jathanism

Je ne suis au courant d'aucun élément de la bibliothèque standard Python (ou ailleurs) qui implémente la fonction AlmostEqual2sComplement de Dawson. Si c'est le genre de comportement que vous souhaitez, vous devrez le mettre en œuvre vous-même. (Dans ce cas, plutôt que d'utiliser les astuces astucieuses de Dawson au niveau des bits, vous feriez probablement mieux d'utiliser des tests plus conventionnels de la forme if abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2 ou similaire. Pour obtenir un comportement de type Dawson, vous pourriez dire quelque chose comme if abs(a-b) <= eps*max(EPS,abs(a),abs(b)) pour un petit EPS fixe, ce n'est pas exactement comme Dawson, mais son esprit est similaire.

11
Gareth McCaughan

La sagesse commune selon laquelle les nombres en virgule flottante ne peuvent pas être comparés pour l'égalité est inexacte. Les nombres en virgule flottante ne sont pas différents des nombres entiers: si vous évaluez "a == b", vous obtiendrez true s'ils sont identiques et faux sinon (avec la compréhension que deux NaN ne sont bien sûr pas des nombres identiques).

Le problème actuel est le suivant: si j’ai fait des calculs et que je ne suis pas sûr que les deux chiffres que je dois comparer sont tout à fait corrects, alors quoi? Ce problème est le même pour la virgule flottante que pour les entiers. Si vous évaluez l'expression entière "7/3 * 3", la comparaison ne sera pas égale à "7 * 3/3".

Supposons donc que nous demandions "Comment comparer des nombres entiers pour l’égalité?" Dans une telle situation. Il n'y a pas de réponse unique; Ce que vous devez faire dépend de la situation, notamment du type d’erreurs que vous avez et de ce que vous voulez réaliser.

Voici quelques choix possibles.

Si vous voulez obtenir un résultat "vrai" si les nombres mathématiquement exacts sont égaux, vous pouvez essayer d'utiliser les propriétés des calculs que vous effectuez pour prouver que vous obtenez les mêmes erreurs dans les deux nombres. Si cela est réalisable, et que vous comparez deux nombres résultant d'expressions qui donneraient des nombres égaux s'ils étaient calculés exactement, alors vous obtiendrez "vrai" de la comparaison. Une autre approche consiste à analyser les propriétés des calculs et à prouver que l'erreur ne dépasse jamais un certain montant, par exemple un montant absolu ou un montant relatif à l'un des intrants ou à l'un des extrants. Dans ce cas, vous pouvez demander si les deux nombres calculés diffèrent d'au plus ce montant et renvoyer "true" s'ils se situent dans l'intervalle. Si vous ne pouvez pas prouver que l'erreur est liée, vous pouvez deviner et espérer que tout se passera bien. Une façon de deviner consiste à évaluer de nombreux échantillons aléatoires et à voir quelle sorte de distribution vous obtenez dans les résultats.

Bien sûr, puisque nous ne fixons l'exigence que vous obtenez "vrai" si les résultats mathématiquement exacts sont égaux, nous laissons ouverte la possibilité que vous obteniez "vrai" même s'ils sont inégaux. (En fait, nous pouvons satisfaire à l'exigence en renvoyant toujours "true". Cela simplifie le calcul mais est généralement indésirable, je vais donc discuter de l'amélioration de la situation ci-dessous.)

Si vous voulez obtenir un "faux" résultat si les nombres mathématiquement exacts sont inégaux, vous devez prouver que votre évaluation des nombres donne des nombres différents si les nombres mathématiquement exacts sont inégaux. Cela peut être impossible à des fins pratiques dans de nombreuses situations courantes. Alors considérons une alternative.

Une exigence utile pourrait être que nous obtenions un résultat "faux" si les nombres mathématiquement exacts diffèrent de plus d'un certain montant. Par exemple, nous allons peut-être calculer où une balle lancée dans un jeu informatique a voyagé, et nous voulons savoir si elle frappe une chauve-souris. Dans ce cas, nous voulons certainement être "vrais" si la balle frappe le bâton, et nous voulons être "faux" si le ballon est loin du bâton, et nous pouvons accepter une réponse "vraie" incorrecte si le ballon entre en jeu. une simulation mathématiquement exacte a raté la chauve-souris mais se situe à un millimètre de la frappe. Dans ce cas, nous devons prouver (ou deviner/estimer) que notre calcul de la position de la balle et de la position de la batte a une erreur combinée d'au plus un millimètre (pour toutes les positions d'intérêt). Cela nous permettrait de toujours renvoyer "faux" si la balle et le bâton sont distants de plus d’un millimètre, de "vrais" s’ils se touchent et de "vrais" s’ils sont suffisamment proches pour être acceptables.

Ainsi, la manière dont vous décidez quoi retourner lorsque vous comparez des nombres à virgule flottante dépend beaucoup de votre situation spécifique.

En ce qui concerne la manière de prouver les limites d’erreur pour les calculs, cela peut être un sujet compliqué. Toute implémentation à virgule flottante utilisant le standard IEEE 754 en mode arrondi au plus proche renvoie le nombre à virgule flottante le plus proche du résultat exact pour toute opération de base (notamment multiplication, division, addition, soustraction, racine carrée). (En cas d'égalité, arrondissez donc le bit le plus faible est pair.) (Faites particulièrement attention à la racine carrée et à la division; votre implémentation linguistique pourrait utiliser des méthodes non conformes à la norme IEEE 754.) En raison de cette exigence, nous connaissons la l'erreur dans un résultat unique est au plus égale à la moitié de la valeur du bit le moins significatif. (Si c’était plus, l’arrondi serait passé à un nombre différent qui est dans la moitié de la valeur.)

Partir de là devient beaucoup plus compliqué; L'étape suivante consiste à effectuer une opération dans laquelle l'une des entrées présente déjà une erreur. Pour les expressions simples, ces erreurs peuvent être suivies dans les calculs pour atteindre une limite sur l'erreur finale. En pratique, cela n’est fait que dans quelques situations, comme travailler sur une bibliothèque de mathématiques de haute qualité. Et, bien sûr, vous avez besoin d'un contrôle précis pour savoir exactement quelles opérations sont effectuées. Les langages de haut niveau donnent souvent beaucoup de mou au compilateur, vous pouvez donc ne pas savoir dans quel ordre les opérations sont effectuées.

Il y a beaucoup plus qui pourrait être (et est) écrit sur ce sujet, mais je dois m'arrêter là. En résumé, la réponse est la suivante: il n’existe pas de routine de bibliothèque pour cette comparaison car il n’existe pas de solution unique répondant à la plupart des besoins qui vaille la peine d’être insérée dans une routine de bibliothèque. (Si la comparaison avec un intervalle d'erreur relatif ou absolu vous suffit, vous pouvez le faire simplement sans routine de bibliothèque.)

11
Eric Postpischil

Si vous voulez l'utiliser dans le contexte testing/TDD, je dirais que c'est une méthode standard:

from nose.tools import assert_almost_equals

assert_almost_equals(x, y, places=7) #default is 7
4
volodymyr

math.isclose () a été ajouté à Python 3.5 pour cela ( code source ). En voici un exemple: Python 2. La différence par rapport à one-liner de Mark Ransom est qu’il peut gérer correctement "inf" et "-inf".

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    '''
    Python 2 implementation of Python 3.5 math.isclose()
    https://hg.python.org/cpython/file/tip/Modules/mathmodule.c#l1993
    '''
    # sanity check on the inputs
    if rel_tol < 0 or abs_tol < 0:
        raise ValueError("tolerances must be non-negative")

    # short circuit exact equality -- needed to catch two infinities of
    # the same sign. And perhaps speeds things up a bit sometimes.
    if a == b:
        return True

    # This catches the case of two infinities of opposite sign, or
    # one infinity and one finite number. Two infinities of opposite
    # sign would otherwise have an infinite relative tolerance.
    # Two infinities of the same sign are caught by the equality check
    # above.
    if math.isinf(a) or math.isinf(b):
        return False

    # now do the regular computation
    # this is essentially the "weak" test from the Boost library
    diff = math.fabs(b - a)
    result = (((diff <= math.fabs(rel_tol * b)) or
               (diff <= math.fabs(rel_tol * a))) or
              (diff <= abs_tol))
    return result
4
user2745509

J'ai trouvé la comparaison suivante utile:

str(f1) == str(f2)
3
Kresimir

Dans certains cas où vous pouvez affecter la représentation du nombre source, vous pouvez les représenter sous forme de fractions au lieu de flottants, en utilisant un numérateur entier et un dénominateur. De cette façon, vous pouvez avoir des comparaisons exactes.

Voir Fraction du module fractions pour plus de détails.

1
eis

Utile dans le cas où vous voulez vous assurer que 2 chiffres sont identiques, sans nécessité de spécifier la tolérance:

  • Trouver la précision minimale des 2 chiffres

  • Arrondissez les deux à la précision minimale et comparez

def isclose(a,b):                                       
    astr=str(a)                                         
    aprec=len(astr.split('.')[1]) if '.' in astr else 0 
    bstr=str(b)                                         
    bprec=len(bstr.split('.')[1]) if '.' in bstr else 0 
    prec=min(aprec,bprec)                                      
    return round(a,prec)==round(b,prec)                               

Comme écrit, ne fonctionne que pour les nombres sans le "e" dans leur représentation sous forme de chaîne (ce qui signifie 0,9999999999995e-4 <nombre <= 0,9999999999995e11)

Exemple:

>>> isclose(10.0,10.049)
True
>>> isclose(10.0,10.05)
False
1
CptHwK

J'ai bien aimé la suggestion de @Sesquipedal mais avec modification (un cas d'utilisation spécial lorsque les deux valeurs sont 0 renvoie False). Dans mon cas, j'étais sur Python 2.7 et je venais d'utiliser une fonction simple:

if f1 ==0 and f2 == 0:
    return True
else:
    return abs(f1-f2) < tol*max(abs(f1),abs(f2))
1
IronYeti

Pour comparer jusqu’à un nombre décimal donné sans atol/rtol:

def almost_equal(a, b, decimal=6):
    return '{0:.{1}f}'.format(a, decimal) == '{0:.{1}f}'.format(b, decimal)

print(almost_equal(0.0, 0.0001, decimal=5)) # False
print(almost_equal(0.0, 0.0001, decimal=4)) # True 
0
Vlad

C'est peut-être un peu moche, mais cela fonctionne plutôt bien si vous n'avez pas besoin de plus que la précision flottante par défaut (environ 11 décimales). Fonctionne bien sur python 2.7.

La fonction round_to utilise la méthode de formatage de la chaîne intégrée. class pour arrondir le flottant à une chaîne qui représente le flottant avec le nombre de décimales nécessaires, puis applique la fonction intégrée eval à la chaîne arrondie de flottant pour revenir au type numérique flottant.

La fonction is_close applique simplement une condition simple au flottant arrondi.

def round_to(float_num, decimal_precision):
    return eval("'{:." + str(int(decimal_precision)) + "f}'.format(" + str(float_num) + ")")

def is_close(float_a, float_b, decimal_precision):
    if round_to(float_a, decimal_precision) == round_to(float_b, decimal_precision):
        return True
    return False

a = 10.0 / 3
# Result: 3.3333333333333335
b = 10.0001 / 3
# Result: 3.3333666666666666

print is_close(a, b, decimal_precision=4)
# Result: False

print is_close(a, b, decimal_precision=3)
# Result: True
0
Albert Alomar