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.1dans le binary64format standard, la représentation peut s'écrire exactement comme


  • 0.1000000000000000055511151231257827021181583404541015625en décimal, ou

  • 0x1.999999999999ap-4en notation hexfloat C99.


En revanche, le nombre rationnel 0.1, qui est 1/10, peut être écrit exactement comme


  • 0.1en décimal, ou

  • 0x1.99999999999999...p-4dans un analogue de la notation hexfloat C99, où le ...représente une séquence sans fin de 9.


Les constantes 0.2et 0.3dans votre programme seront également des approximations de leurs vraies valeurs. Il arrive que le plus proche doublede 0.2soit plus grand que le nombre rationnel 0.2mais que le plus proche doublede 0.3soit plus petit que le nombre rationnel 0.3. La somme de 0.1et 0.2finit par être supérieure au nombre rationnel 0.3et 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) {... }.

absest la valeur absolue. myToleranceValuedoit ê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

Posts les plus consultés de ce blog

Erreur Symfony : "Une exception a été levée lors du rendu d'un modèle"

Détecter les appuis sur les touches fléchées en JavaScript

Une chaîne vide donne "Des erreurs ont été détectées dans les arguments de la ligne de commande, veuillez vous assurer que tous les arguments sont correctement définis"