Un argument souvent utilisé pour se moquer du JavaScript est de dire qu'il ne sait pas faire une addition car lorsque l'on fait 0.1
+ 0.2
, le résultat affiché est 0.30000000000000004
.
Mais ce problème n'est pas spécifique au JavaScript et se retrouve dans la plupart des langages de programmation, et si vous faites 0.1 + 0.2 == 0.3
, vous vous rendrez vite compte que le résultat est false
.
Résumé du problème
Le problème est qu'en informatique les nombres réels sont représentés sur la base de puissances de 2 et certains nombres ne sont pas représentables de cette manière et on se retrouve avec des décimales infinies pour lesquelles on est obligé de faire une approximation. C'est d'ailleurs un problème que l'on retrouve aussi dans notre système décimal lorsque le nombre ne peut être représenté sous forme d'une puissance de 10. Par exemple, si on essaie l'opération suivante :
1/3 + 1/3 + 1/3 == 1
Le nombre 1/3
ne peut être représenté sous forme décimale, si on essayait
, on serait obligé de tronquer le chiffre de manière arbitraire rendant dans le même temps la comparaison invalide.
0.3333333333 + 0.3333333333 + 0.3333333333 == 1
0.9999999999 == 1 // false
C'est exactement le même problème que l'on aura avec les décimales en binaire. 0.1
, 0.2
et 0.3
ne peuvent pas être représentés en binaire et on se retrouvera avec des approximations.
0.1
sera stocké comme0.100000001490116119384765625
0.2
sera stocké comme0.20000000298023223876953125
0.3
sera stocké comme0.300000011920928955078125
Du coup 0.1 + 0.2 == 0.3
sera en fait traduit par
0.100000001490116119384765625 + 0.20000000298023223876953125 == 0.300000011920928955078125
Ce qui est effectivement incorrect.
La norme IEEE 754
La norme IEEE 754 est la norme la plus employée actuellement pour le calcul des nombres à virgule flottante et la représentation de ces nombres en binaire.
Pour un système 32 bits, un nombre flottant est représenté de la manière suivante :
- 1 bit pour le signe (0 pour un positif, 1 pour un négatif)
- 8 bits pour l'exposant (la puissance de 2 compris entre -127 et 128)
- 23 bits pour représenter la mantisse (ou la significande)
Ce système permet des calculs rapides en se reposant sur le système des puissances de 2, comme on le ferait avec des nombres réels et la notation scientifique. L'inconvénient est que certains nombres représentables en décimal ne peuvent être traduits sous forme de puissances de 2 (vous pouvez d'ailleurs tester cette conversion via cet outil en ligne).
Dans le cas du nombre 0.2
, on doit d'abord le représenter en binaire, pour cela, on peut diviser 2
(10
en binaire) par 10
(1010
en binaire). Comme 1/3
, 10/1010
donne des décimales à l'infinie 0.00110011001100110011001100...
.
0.00110011001100110011001100...
= 1.10011001100110011001100... x 2^-3
On a donc maintenant la mantisse -3
et la mantisse 10011001100...
. Vu que l'exposant commence à -127
, il faudra représenter 124
pour obtenir la valeur -3
.
- Signe (positif) : 0
- Exposant (124) : 01111100
- Mantisse (on arrondit): 10011001100110011001101
Et voilà ! On a représenté 0.1
en binaire 00111110010011001100110011001101
!
Si on essaie de refaire la conversion en décimale, on obtiendra 0.20000000298023223877
. Cette représentation est donc approximative avec tout de même une précision à 9 décimale sur un système 32 bits, ce qui est amplement suffisant pour la plupart des besoins.
Comment mitiger le problème ?
Maintenant que l'on comprend comment sont représentés les nombres dans notre système, on peut aussi imaginer comment mitiger le problème si la situation l'impose. La première solution est de contourner le problème en travaillant sur des entiers plutôt que des floats (par exemple sur un système bancaire on peut travailler en centimes d'euros plutôt qu'en euros). L'autre solution est d'utiliser le système d'(Arithmétique multiprécision)[https://fr.wikipedia.org/wiki/Arithmétique_multiprécision] qui sera plus précis (mais aussi plus couteux) à l'aide de librairies pour votre langage de programmation.