En savoir plus

Les erreurs d'arrondi

Pourquoi un ordinateur affirme que 0,1 + 0,2 ne fait pas tout a fait 0,3, et comment ne jamais se faire piéger.

L'idée de base#

Un ordinateur n'a pas une infinité de chiffres pour écrire un nombre. Il travaille en binaire avec une boite de taille fixe (souvent 64 bits, le float dit a double précision). Des qu'un nombre ne rentre pas exactement dans cette boite, la machine l'arrondit au plus proche qu'elle sait représenter. Cette petite erreur, invisible au début, peut s'accumuler.

Ce n'est pas un défaut de Python ou de JavaScript : c'est la norme IEEE 754, identique dans presque tous les langages.

Le grand classique qui choque tout le monde#

print(0.1 + 0.2) # 0.30000000000000004 print(0.1 + 0.2 == 0.3) # False

Pourquoi ? Parce que 0.1 en binaire, c'est comme 1/3 en décimal : 0,3333... a l'infini. La machine coupe. Trois petites coupures additionnées, et le résultat tombe a coté.

Démo : le calculateur a double affichage#

Tape deux nombres décimaux et compare ce que voit l'humain (calcul exact) avec ce que voit la machine (calcul en float).

Addition

+
Vue machine (float)
-
Vue exacte (décimal)
-

Démo : l'accumulation qui dérape#

On additionne 0.1 un grand nombre de fois. Chaque petite erreur va dans le meme sens et finit par se voir. C'est ce genre de phénomene qui a fait rater sa cible au missile Patriot en 1991.

Somme répétée

Résultat attendu
-
Résultat obtenu (float)
-

Les deux grandes familles d'erreurs#

TypeCe qui se passeExemple parlant
Arrondi de représentationLe nombre n'existe pas exactement en binaire0.1 n'est pas vraiment 0,1
Erreur d'annulationOn soustrait deux nombres tres proches, les décimales utiles disparaissent1.0000001 - 1.0000000

Comment s'en protéger (réflexes de pro)#

1. Ne jamais tester l'égalité de deux flottants. On compare a une tolérance :

if abs(a - b) < 1e-9: # assez proche ...

2. Pour l'argent : pas de float. On utilise decimal.Decimal ou on travaille en cents (entiers).

from decimal import Decimal print(Decimal("0.1") + Decimal("0.2")) # 0.3 exact

3. Sommer du plus petit au plus grand, ou utiliser math.fsum() qui compense les erreurs :

import math print(math.fsum([0.1] * 1_000_000)) # 100000.0 pile