Les mathématiques en virgule flottante sont-elles cassées ?
Considérez le code suivant :
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Pourquoi ces inexactitudes se produisent-elles?
Solution du problème
Les mathématiques binaires à virgule flottante sont comme ça. Dans la plupart des langages de programmation, il est basé sur la norme IEEE 754. Le nœud du problème est que les nombres sont représentés dans ce format comme un nombre entier multiplié par une puissance de deux; les nombres rationnels (tels que 0.1
, qui est 1/10
) dont le dénominateur n'est pas une puissance de deux ne peuvent pas être représentés exactement.
Car 0.1
dans le binary64
format standard, la représentation peut s'écrire exactement comme
0.1000000000000000055511151231257827021181583404541015625
en décimal, ou0x1.999999999999ap-4
en notation hexfloat C99.
En revanche, le nombre rationnel 0.1
, qui est 1/10
, peut être écrit exactement comme
0.1
en décimal, ou0x1.99999999999999...p-4
dans un analogue de la notation hexfloat C99, où le...
représente une séquence sans fin de 9.
Les constantes 0.2
et 0.3
dans votre programme seront également des approximations de leurs vraies valeurs. Il arrive que le plus proche double
de 0.2
soit plus grand que le nombre rationnel 0.2
mais que le plus proche double
de 0.3
soit plus petit que le nombre rationnel 0.3
. La somme de 0.1
et 0.2
finit par être supérieure au nombre rationnel 0.3
et donc en désaccord avec la constante de votre code.
Un traitement assez complet des problèmes d'arithmétique en virgule flottante est What Every Computer Scientist Should Know About Floating-Point Arithmetic. Pour une explication plus facile à digérer, voir floating-point-gui.de.
Remarque complémentaire : tous les systèmes de numération positionnels (base N) partagent ce problème avec précision
Les vieux nombres décimaux simples (base 10) ont les mêmes problèmes, c'est pourquoi des nombres comme 1/3 finissent par 0,333333333...
Vous venez de tomber sur un nombre (3/10) qui se trouve être facile à représenter avec le système décimal, mais qui ne correspond pas au système binaire. Cela va aussi dans les deux sens (dans une certaine mesure): 1/16 est un nombre laid en décimal (0,0625), mais en binaire, il a l'air aussi net qu'un 10 000e en décimal (0,0001) ** - si nous étions dans l'habitude d'utiliser un système de numération en base 2 dans nos vies quotidiennes, vous regarderiez même ce nombre et comprendriez instinctivement que vous pourriez y arriver en divisant par deux quelque chose, en le divisant encore par deux, et encore et encore.
** Bien sûr, ce n'est pas exactement la façon dont les nombres à virgule flottante sont stockés en mémoire (ils utilisent une forme de notation scientifique). Cependant, cela illustre le fait que les erreurs de précision binaires en virgule flottante ont tendance à apparaître parce que les nombres du "monde réel" avec lesquels nous sommes généralement intéressés à travailler sont si souvent des puissances de dix - mais uniquement parce que nous utilisons un système de nombres décimaux jour- aujourd'hui. C'est aussi pourquoi nous dirons des choses comme 71 % au lieu de "5 sur 7" (71 % est une approximation, car 5/7 ne peut pas être représenté exactement avec un nombre décimal).
Donc non : les nombres binaires à virgule flottante ne sont pas cassés, ils sont juste aussi imparfaits que tous les autres systèmes de numération en base N :)
Note latérale : Travailler avec des flottants dans la programmation
En pratique, ce problème de précision signifie que vous devez utiliser des fonctions d'arrondi pour arrondir vos nombres à virgule flottante au nombre de décimales qui vous intéresse avant de les afficher.
Vous devez également remplacer les tests d'égalité par des comparaisons qui autorisent une certaine tolérance, ce qui signifie :
Ne fais pasif (x == y) {... }
Faites plutôt if (abs(x - y) < myToleranceValue) {... }
.
où abs
est la valeur absolue. myToleranceValue
doit être choisi pour votre application particulière - et cela aura beaucoup à voir avec la "marge de manœuvre" que vous êtes prêt à autoriser, et quel peut être le plus grand nombre que vous allez comparer (en raison de problèmes de perte de précision ). Méfiez-vous des constantes de style "epsilon" dans la langue de votre choix. Celles-ci ne doivent pas être utilisées comme valeurs de tolérance.
Commentaires
Enregistrer un commentaire