Pourquoi la machine ne sait pas écrire 0,1
Sur toutes les machines du monde, 0,1 plus 0,2 ne donne pas 0,3. Ce n'est ni un bug ni un défaut de Python : c'est le théorème du chapitre, celui qui interdit à un tiers de s'écrire en base dix, transporté une base plus bas.
Ouvrez une console Python. Tapez une addition que n’importe quel élève de sixième pose de tête.
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
Ce n’est pas une faute de frappe, et ce n’est pas propre à Python : le même calcul donne le même résultat en JavaScript, en C, en Java, dans un tableur. Ce n’est pas non plus un bug. C’est exactement le théorème du chapitre, celui qui interdit à de s’écrire en base dix, transporté une base plus bas.
Ce qu’une machine peut écrire exactement
Une mémoire ne contient que des et des : la machine compte en base deux. Un nombre décimal, dans le cours, est un nombre de la forme . Un nombre que la machine sait écrire exactement, c’est de la même façon un nombre de la forme
où est un entier et un entier naturel.
Certains passent sans dommage. Le nombre vaut , le nombre vaut , le nombre vaut : tous trois ont un dénominateur qui est une puissance de . Et de fait :
>>> 0.5 + 0.25 == 0.75
True
Reste la question qui décide de tout : est-il de cette forme ?
La démonstration du cours, une base plus bas
Le cours a démontré que n’est pas décimal ainsi : si l’on avait , alors serait un multiple de ; or la somme des chiffres de vaut , qui n’est pas un multiple de . Contradiction.
Refaisons-la pour la machine. Supposons que soit de la forme . En multipliant les deux membres par , on obtient
autrement dit serait un multiple de . Or le dernier chiffre des puissances de tourne en rond : , puis , , , , puis de nouveau , , , , sans fin. C’est inévitable, puisque doubler un nombre terminé par , , ou redonne toujours l’une de ces quatre terminaisons. Jamais , jamais : aucune puissance de n’est un multiple de , quel que soit l’exposant. La supposition est fausse.
Le nombre n’a pas d’écriture finie en base deux. Son écriture y est , où le bloc se répète sans fin, comme les de se répètent en base dix. Le critère du chapitre se transpose mot pour mot : une fraction irréductible s’écrit exactement dans une base lorsque son dénominateur ne contient que des facteurs premiers de cette base. En base dix, ne divise aucune puissance de ; en base deux, le caché dans ne divise aucune puissance de .
Ce que la machine range à la place
Un nombre occupe bits : un pour le signe, onze pour l’ordre de grandeur, cinquante-deux pour les chiffres du nombre lui-même. S’y ajoute un premier chiffre toujours égal à , qu’il est donc inutile de stocker : cela fait cinquante-trois chiffres binaires significatifs. Le nombre de motifs possibles est fini, et l’ensemble des nombres que la machine sait représenter l’est aussi. Quand vous écrivez 0.1, elle range le plus proche voisin disponible. On peut le lui faire avouer :
>>> from decimal import Decimal
>>> Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
Voilà le nombre réellement stocké : un cheveu au-dessus de . Le voisin de est lui aussi au-dessus, tandis que celui de tombe légèrement en dessous. La somme des deux premiers dépasse donc le troisième, et le test d’égalité répond False. Aucune erreur n’a été commise : deux arrondis se sont additionnés.
Les dégâts, et le réflexe
L’addition de la machine perd une propriété qu’on croyait acquise : le résultat dépend de l’ordre dans lequel on regroupe les termes.
>>> 0.1 + 0.2 + 0.3
0.6000000000000001
>>> 0.3 + 0.2 + 0.1
0.6
Et dix additions de n’atteignent pas :
s = 0.0
for _ in range(10):
s += 0.1
print(s) # 0.9999999999999999
D’où une règle qui vaut pour tous vos programmes : ne jamais tester l’égalité de deux résultats décimaux. On compare l’écart à une tolérance, abs(a - b) < 1e-9, ou l’on appelle math.isclose(a, b). Une boucle while x != 1.0 peut tourner indéfiniment.
Quand l’exactitude est vraiment requise, deux outils. Fraction(1, 10) calcule sur des entiers, numérateur et dénominateur, et ne perd rien. Decimal("0.1"), avec les guillemets, travaille en base dix et stocke exactement ; écrit sans guillemets, Decimal(0.1) ne fait que recopier le flottant défectueux. Mais Decimal n’écrit pas mieux que votre feuille : aucune base ne sauve tout.
Une norme et une date
Avant 1985, chaque constructeur avait son format de nombres à virgule flottante, et le même programme rendait des résultats différents d’une machine à l’autre. La norme IEEE 754, publiée en 1985 et portée par William Kahan (Berkeley, prix Turing 1989), a fixé les formats et les règles d’arrondi. Elle n’a pas supprimé l’erreur : elle l’a rendue prévisible, ce qui est tout ce qu’on demande à un instrument de mesure.
Le scribe de la tablette de Yale approchait en base soixante, Théon l’encadrait avec une échelle d’entiers, Ahmès partageait ses pains en fractions unitaires. Votre machine fait la même chose, avec cinquante-trois chiffres binaires et la même impossibilité de fond. L’extra L’échelle de Théon et les pains d’Ahmès raconte les deux premiers épisodes de cette histoire.
Sources
- Démonstrations D3 et D4 du cours A1 (non-décimalité de , irrationalité de ).
- Norme IEEE 754 pour l’arithmétique binaire à virgule flottante, 1985, révisée en 2008 et 2019 ; William Kahan, prix Turing 1989.
- Toutes les sorties de cette page ont été exécutées, non recopiées.