Code-Garage #125 - Pourquoi 0.1+0.2 est différent de 0.3 en programmation ?
Durée: 7m54s
Date de sortie: 29/05/2025
Si vous avez déjà essayé d'additionner deux nombres à virgules dans votre code, vous avez sûrement déjà eu des surprises... Voici pourquoi !
Notes de l'épisode :
- IEEE 754 : https://standards.ieee.org/ieee/754/6210/
Salut et bienvenue dans ce nouvel épisode du podcast de Côte Garage, je m'appelle
Nicolas Montenard, et aujourd'hui on va parler d'un sujet qui apporte pas mal de confusions
chez les devs, on va parler des nombres à virgule.
Alors pourquoi est-ce que en jarrescript est dans plein d'autres langages si vous faites
une condition comme 0.1 plus 0.2 égal égal égal 0.3 ? Pourquoi est-ce que cette condition
vous retourne false ?
Pourtant mathématiquement on est d'accord que 0.1 plus 0.2 ça fait bien 0.3 donc ça
devrait être parfaitement égal.
Alors pourquoi est-ce que ça marche pas ici ? Est-ce que c'est un bug ? Est-ce que
c'est une erreur de calcul d'un langage spécifique comme jarrescript ? Eh bien non,
en fait c'est un comportement tout à fait normal en tout cas qui est prévu et qui
est surtout parfaitement documenté, on va voir pourquoi.
En fait c'est la faute au nombre à virgule flottante.
Faut savoir que nos ordinateurs stockent les nombres à virgule dans un format très
spécifique qui est appelé IEE 754.
C'est une norme qui est utilisée par presque tous les langages donc jarrescript, piton,
c'est java etc.
Pour représenter des nombres réels, des nombres décimaux en binaire.
En fait ça utilise un nombre limité de bits pour stocker en général 64 quand on est dans
des os 64 bits.
Mais surtout cette norme a une vraie limite puisque certains nombres décimaux, même des
nombres décimaux simples comme 0.1 ou 0.2, n'ont pas d'équivalent exact en binaire.
C'est comme quand on essaie d'écrire 1 divisé par 3 en base 10, en base décimale, ça nous
donne 0.333333 à l'infini.
C'est un petit peu pareil, en binaire, 0.1 devient une fraction infinie qui va falloir
tronquer pour faire tenir dans nos 64 bits.
Pour mieux comprendre, on va voir comment on convertit ces nombres décimaux en nombres
minaires.
Quand vous écrivez 0.2 dans votre code, c'est pas la fraction 2 sur 10 qui est stockée
parce qu'on se dit que 2 sur 10 c'est facile, c'est 2 nombres entiers donc on peut le
stocker facilement.
Mais voilà ce qui se passe vraiment.
En fait, d'abord le compilateur, il va lire le texte 0.2 comme un texte, il va savoir
que ça doit être un nombre mais pour l'instant c'est du texte, il va le convertir en binaire
justement selon la norme I3Z754 et le résultat, ça va être une approximation comme 0.0011001100111111.
C'est ce qu'on appelle un binaire périodique.
En fait, cette suite, elle est infinie mais elle va être tronquée en gros à 52 ou 53
bits à peu près pour avoir la place de stocker le reste du nombre.
Et le résultat final, si on reconvertit notre nombre binaire vers un nombre décimal classique,
ça ne nous donne pas simplement 0.2, ça va nous donner 0.2000000011002302462, etc.
Et en fait, c'est ce petit décalage qui accumulait par exemple avec celui de 0.1 qu'on a pris
comme exemple au début, c'est ce qui empêche que 0.1 plus 0.2 soit exactement égal à 0.3.
Alors, vous pouvez vous demander pourquoi est-ce qu'on ne s'arrête pas au bon moment ?
Parce qu'en fait, la conversion justement de cette partie décimale, donc quand on prend 0.2,
la partie décimale c'est 2, en binaire, elle repose sur une méthode qui est très simple.
On multiplie la partie décimale par 2 encore et encore et encore et on récupère la partie entière
à chaque étape. Là, ça peut paraître un petit peu abstrait, mais je vous fais un exemple.
On prend par exemple le chiffre 0.625, ok ? On fait 0.625 fois 2, ça équivaut à 1.25.
Ce 1.25, on va le décomposer en deux parties, on va prendre la partie entière à gauche,
donc avant la vécule, la partie décimale à droite.
Et en fait, on va retenir la partie entière, donc 1, et on va récupérer la partie décimale.
Cette partie décimale, on va la prendre et on va la remultiplier par 2.
Donc 0.25 fois 2, ça nous donne 0.5.
On récupère la partie entière 0, on récupère 0.5, on le passe dans l'étape d'après,
et donc on refait 0.5 fois 2, ça équivaut à 1, on retient 1, la partie entière,
il reste 0 et donc paf, on s'arrête.
Ce qui veut dire que le chiffre 0.625, c'est égal à 0.101,
puisqu'à chaque étape, en fait, c'était la partie entière qu'on a récupérée,
qu'était soit 1 soit 0.
Sauf que si le reste, il ne tombe jamais à 0, donc là, on avait 1.0 à la fin, 1,0,
et ben, par exemple, pour les chiffres 0,2 ou 0,1, ça s'arrête jamais.
On a une suite infinie qu'on va tronquer pour faire entrer dans les 64 bits.
Alors, il y a une méthode un petit peu plus simple pour pas forcément avoir à calculer,
faire toutes les étapes pour savoir si ça va être une suite infinie en binaires.
C'est qu'en fait, on va pouvoir regarder si le nombre, si le diviseur, est une puissance de 2 ou non.
Par exemple, si on prend l'exemple de 0.3, en base 10, c'est comme si on faisait 3 divisé par 10.
Et on va regarder le diviseur, donc là, c'est 10 et 10, ce n'est pas une puissance de 2.
Donc, on sait que 0.3 ne peut pas être représenté exactement en binaires.
Il sera approximé, on va dire, et parfois, la somme de deux approximations,
0.1 plus 0.2, par exemple, et bien, elle n'est pas exactement égale à l'approximation d'un autre de ces chiffres qui est là et 0.3.
Voilà, j'espère que cet épisode vous aura plu et surtout, vous aurez appris quelque chose.
Et surtout, ce qui a bien retenir, c'est que ce n'est pas nécessairement lié à un langage,
parce qu'on a beaucoup de gens qui parlent, par exemple, des choses qui sont très bizarres en JavaScript.
Alors, certes, il y a beaucoup de choses qui sont bizarres en JavaScript,
mais ça, vous le retrouvez aussi, comme on l'a dit, en C, en Java, en Python, en plein d'autres langages,
parce que c'est un standard, c'est une normalisation, et donc, c'est une méthode qui est utilisée un peu partout.
Alors, comment est-ce qu'on fait rapidement pour des systèmes où il ne faut absolument pas que ce genre de problème arrive,
comme par exemple dans les systèmes financiers, puisque même si c'est des petits sentiments à chaque fois,
évidemment, on n'a pas envie d'avoir de problèmes, et surtout, quand on vérifie qu'un virement attendu,
il a bien exactement la même somme qu'un virement qui va être fait.
Bon, il faut pouvoir faire des comparaisons qui sont très très exactes.
Tout simplement, ces systèmes-là vont travailler uniquement avec des entiers, quand c'est possible.
Donc, typiquement, dans des valeurs monétaires, si on prend l'euro,
au lieu de dire qu'on va transférer 2 euros, 0,00 centimes, on va prendre une valeur en centimes,
donc on va dire qu'on transfère 200 centimes.
Et donc là, on se débarrasse de tous les nombres décimaux, et donc, évidemment, ça corrige le problème.
Enfin, on n'a pas à s'occuper, en tout cas, de ces nombres décimaux.
Voilà, moi je vous retrouve la semaine prochaine pour un prochain épisode du podcast.
Évidemment, pensez à laisser 5 étoiles, et même un petit commentaire, un avis directement sur...
Alors, ça peut être Spotify, Deezer, Apple Podcast, pardon.
Ça aide au référencement, et puis moi, ça me permet d'avoir vos retours et d'améliorer encore et toujours plus le podcast.
Et sinon, je vous retrouve évidemment sur www.contierregarage.com pour retrouver tous nos cours.
Et si vous voulez, vous pouvez vous abonner à notre newsletter.
On en voit une fois par semaine toutes les ressources qu'on sort, donc les podcasts, les articles de blog, les cours, etc.
Je vous donne rendez-vous la semaine prochaine.
Prenez soin de vous. Salut !
Episode suivant:
Les infos glanées
Code-Garage
Découvrons ensemble des sujets passionnants autour du métier de dev et de la programmation en général !
Tags