533 votes

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

Il est bien connu que la comparaison des flottants pour l'égalité est un peu compliquée en raison des problèmes d'arrondi et de précision.

Par exemple : http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

Quelle est la méthode recommandée pour traiter ce problème en Python ?

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

0 votes

@tolomea : Puisque cela dépend de votre application, de vos données et de votre domaine de problème -- et qu'il ne s'agit que d'une ligne de code -- pourquoi y aurait-il une "fonction de bibliothèque standard" ?

15 votes

@S.Lott : all , any , max , min sont toutes deux essentiellement des fonctions à une ligne, et elles ne sont pas seulement fournies dans une bibliothèque, ce sont des fonctions intégrées. Les raisons de la BDFL ne sont donc pas si importantes. La ligne de code que la plupart des gens écrivent est assez peu sophistiquée et ne fonctionne souvent pas, ce qui est une raison forte pour fournir quelque chose de mieux. Bien sûr, tout module fournissant d'autres stratégies devrait également fournir des avertissements décrivant quand elles sont appropriées, et plus important encore quand elles ne le sont pas. L'analyse numérique est difficile, et il n'est pas honteux que les concepteurs de langage ne cherchent pas à créer des outils pour l'aider.

0 votes

@Steve Jessop. Ces fonctions orientées collection n'ont pas les dépendances d'application, de données et de domaine de problème que la virgule flottante possède. Ainsi, le "one-liner" n'est clairement pas aussi important que les véritables raisons. L'analyse numérique est difficile, et ne peut pas être une partie de première classe d'une bibliothèque de langage à usage général.

112voto

Andrew White Points 23508

Est-ce que quelque chose d'aussi simple que ce qui suit n'est pas suffisant ?

return abs(f1 - f2) <= allowed_error

13 votes

Comme l'indique le lien que j'ai fourni, la soustraction ne fonctionne que si l'on connaît à l'avance l'ordre de grandeur approximatif des chiffres.

1 votes

C'est bien après coup, mais voici une expression alternative de la réponse de @AndrewWhite : return round(f1 - f2, some_precision_defaulting_to_seven_decimals) == 0.

16 votes

D'après mon expérience, la meilleure méthode pour comparer les flotteurs est la suivante : abs(f1-f2) < tol*max(abs(f1),abs(f2)) . Ce type de tolérance relative est la seule manière significative de comparer les flottants en général, car ils sont généralement affectés par des erreurs d'arrondi dans les petites décimales.

75voto

J.Makela Points 39

Je suis d'accord pour dire que la réponse de Gareth est probablement 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 l'envisagez, il existe une fonction packagée pour cela.

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

Un petit avertissement cependant : l'installation de numpy peut être une expérience non triviale en fonction de votre plateforme.

3 votes

"L'installation de numpy peut être une expérience non triviale en fonction de votre plateforme."...hum Quoi ? Sur quelles plateformes l'installation de numpy est-elle "non triviale" ? Qu'est-ce qui rendait exactement cela non trivial ?

10 votes

@John : difficile d'obtenir un binaire 64 bits pour Windows. Difficile d'obtenir numpy via pip sur Windows.

0 votes

@Ternak : Oui, mais certains de mes étudiants utilisent Windows, et je dois donc m'occuper de ce genre de choses.

19voto

jathanism Points 15208

Utilisez l'outil Python decimal qui fournit le module Decimal classe.

Dans les commentaires :

Il est intéressant de noter que si vous faites travail lourd en mathématiques et que vous n'avez pas absolument besoin de la précision de décimale, cela peut vraiment embourber les choses les choses. Les flottants sont beaucoup, beaucoup plus rapides à à traiter, mais imprécis. Les décimales sont extrêmement précises mais lentes.

15voto

Eric Postpischil Points 36641

L'idée reçue selon laquelle les nombres à virgule flottante ne peuvent pas être comparés pour vérifier leur égalité est inexacte. Les nombres à virgule flottante ne sont pas différents des nombres entiers : Si vous évaluez "a == b", vous obtiendrez vrai s'il s'agit de nombres identiques et faux sinon (étant entendu que deux NaN ne sont évidemment pas des nombres identiques).

Le problème réel est le suivant : Si j'ai effectué des calculs et que je ne suis pas sûr que les deux nombres que je dois comparer sont exactement corrects, que faire ? Ce problème est le même pour les nombres à virgule flottante que pour les nombres 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 entiers pour vérifier leur égalité" dans une telle situation. Il n'y a pas de réponse unique ; ce que vous devez faire dépend de la situation spécifique, notamment du type d'erreurs que vous avez et de ce que vous voulez obtenir.

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 possible, et que vous comparez deux nombres résultant d'expressions qui donneraient des nombres égaux si elles étaient calculées exactement, alors vous obtiendrez un résultat "vrai" à partir 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, peut-être un montant absolu ou un montant relatif à l'une des entrées ou à l'une des sorties. Dans ce cas, vous pouvez demander si les deux nombres calculés diffèrent au maximum de cette quantité, et renvoyer "vrai" s'ils sont dans l'intervalle. Si vous ne pouvez pas prouver une limite d'erreur, vous pouvez deviner et espérer le meilleur. Une façon de le faire est d'évaluer de nombreux échantillons aléatoires et de voir quel type de distribution vous obtenez dans les résultats.

Bien sûr, puisque nous n'avons fixé comme condition que vous obteniez "true" si les résultats mathématiquement exacts sont égaux, nous avons laissé ouverte la possibilité que vous obteniez "true" même s'ils sont inégaux. (En fait, nous pouvons satisfaire l'exigence en retournant toujours "vrai". Cela rend le calcul simple mais n'est généralement pas souhaitable, je discuterai donc de l'amélioration de la situation ci-dessous).

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

Une condition utile pourrait être que nous obtenions un "faux" résultat si les nombres mathématiquement exacts diffèrent de plus d'une certaine quantité. Par exemple, nous allons peut-être calculer la trajectoire d'une balle lancée dans un jeu vidéo et nous voulons savoir si elle a touché une batte. Dans ce cas, nous voulons certainement obtenir la réponse "vrai" si la balle frappe la chauve-souris, et nous voulons obtenir la réponse "faux" si la balle est loin de la chauve-souris, et nous pouvons accepter une réponse "vrai" incorrecte si la balle, dans une simulation mathématiquement exacte, a manqué la chauve-souris mais est à un millimètre de la frapper. Dans ce cas, nous devons prouver (ou deviner/estimer) que notre calcul de la position de la balle et de la position de la chauve-souris 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 la batte sont séparées de plus d'un millimètre, de renvoyer "vrai" si elles se touchent, et de renvoyer "vrai" si elles sont suffisamment proches pour être acceptables.

Ainsi, la façon dont vous décidez ce qu'il faut retourner lorsque vous comparez des nombres à virgule flottante dépend beaucoup de votre situation spécifique.

Quant à la manière de prouver les limites d'erreur pour les calculs, cela peut être un sujet compliqué. Toute implémentation à virgule flottante utilisant la norme IEEE 754 en mode round-to-nearest renvoie le nombre à virgule flottante le plus proche du résultat exact pour toute opération de base (notamment la multiplication, la division, l'addition, la soustraction, la racine carrée). (En cas d'égalité, arrondissez de manière à ce que le bit le plus bas soit pair). (Faites particulièrement attention à la racine carrée et à la division ; l'implémentation de votre langage pourrait utiliser des méthodes non conformes à la norme IEEE 754 pour celles-ci). Grâce à cette exigence, nous savons que l'erreur dans un seul résultat est au plus égale à la moitié de la valeur du bit le moins significatif. (Si c'était plus, l'arrondi serait passé à un autre nombre qui se situe dans la moitié de la valeur).

La suite devient nettement plus compliquée ; l'étape suivante consiste à effectuer une opération dont l'une des entrées comporte déjà une erreur. Pour les expressions simples, ces erreurs peuvent être suivies tout au long des calculs pour atteindre une limite sur l'erreur finale. Dans la pratique, cela ne se fait que dans quelques situations, comme le travail sur une bibliothèque de mathématiques de haute qualité. Et, bien sûr, vous avez besoin d'un contrôle précis sur les opérations qui sont effectuées. Les langages de haut niveau laissent souvent beaucoup de latitude au compilateur, de sorte que vous pouvez ne pas savoir dans quel ordre les opérations sont effectuées.

On pourrait écrire (et on écrit) beaucoup plus 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 parce qu'il n'y a pas de solution unique qui réponde à la plupart des besoins et qui vaille la peine d'être intégré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).

4 votes

D'après la discussion ci-dessus avec Gareth McCaughan, comparer correctement avec une erreur relative revient essentiellement à "abs(a-b) <= eps". max(2* -1022,abs(a),abs(b))", ce n'est pas quelque chose que je qualifierais de simple et certainement pas quelque chose que j'aurais trouvé tout seul. De plus, comme Steve Jessop l'a souligné, sa complexité est similaire à celle de max, min, any et all, qui sont tous des builtins. Ainsi, fournir une comparaison d'erreur relative dans le module mathématique standard semble être une bonne idée.

0 votes

(7/3*3 == 7*3/3) évalue Vrai en python.

2 votes

@xApple : Je viens d'exécuter Python 2.7.2 sous OS X 10.8.3 et j'ai saisi (7/3*3 == 7*3/3) . Il a imprimé False .

14voto

Gareth McCaughan Points 12261

Je ne suis pas au courant de quoi que ce soit dans la bibliothèque standard de Python (ou ailleurs) qui implémente la méthode de Dawson. AlmostEqual2sComplement fonction. Si c'est le type de comportement que vous souhaitez, vous devrez l'implémenter vous-même. (Dans ce cas, plutôt que d'utiliser les astuces de Dawson, vous feriez sans doute mieux d'utiliser des tests plus conventionnels de type if abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2 ou similaire. Pour obtenir un comportement similaire à celui de Dawson, vous pouvez dire quelque chose comme if abs(a-b) <= eps*max(EPS,abs(a),abs(b)) pour un petit nombre de valeurs fixes EPS Ce n'est pas exactement la même chose que Dawson, mais c'est similaire dans l'esprit.

0 votes

Je ne comprends pas bien ce que vous faites ici, mais c'est intéressant. Quelle est la différence entre eps, eps1, eps2 et EPS ?

0 votes

eps1 et eps2 définir une tolérance relative et une tolérance absolue : vous êtes prêt à autoriser a et b de différer d'environ eps1 fois leur taille, plus eps2 . eps est une tolérance unique ; vous êtes prêt à autoriser a et b de différer d'environ eps fois leur taille, sous réserve que tout ce qui est de taille EPS ou plus petit est supposé être de taille EPS . Si vous prenez EPS pour être la plus petite valeur non-denormale de votre type de virgule flottante, ceci est très similaire au comparateur de Dawson (sauf pour un facteur de 2^#bits car Dawson mesure la tolérance en ulps).

2 votes

Par ailleurs, je suis d'accord avec S. Lott pour dire que la bonne chose dépend toujours de votre application réelle, ce qui explique pourquoi il n'existe pas une seule fonction de bibliothèque standard pour tous vos besoins de comparaison en virgule flottante.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X