Seconde · A1
2nde
Aller plus loin · Scripts

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 à 13\dfrac{1}{3} 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 00 et des 11 : la machine compte en base deux. Un nombre décimal, dans le cours, est un nombre de la forme a10n\dfrac{a}{10^{n}}. Un nombre que la machine sait écrire exactement, c’est de la même façon un nombre de la forme

a2n\dfrac{a}{2^{n}}

aa est un entier et nn un entier naturel.

Certains passent sans dommage. Le nombre 0,50{,}5 vaut 12\dfrac{1}{2}, le nombre 0,250{,}25 vaut 14\dfrac{1}{4}, le nombre 0,750{,}75 vaut 34\dfrac{3}{4} : tous trois ont un dénominateur qui est une puissance de 22. Et de fait :

>>> 0.5 + 0.25 == 0.75
True

Reste la question qui décide de tout : 0,10{,}1 est-il de cette forme ?

La démonstration du cours, une base plus bas

Le cours a démontré que 13\dfrac{1}{3} n’est pas décimal ainsi : si l’on avait 13=a10n\dfrac{1}{3} = \dfrac{a}{10^{n}}, alors 10n10^{n} serait un multiple de 33 ; or la somme des chiffres de 10n10^{n} vaut 11, qui n’est pas un multiple de 33. Contradiction.

Refaisons-la pour la machine. Supposons que 110\dfrac{1}{10} soit de la forme a2n\dfrac{a}{2^{n}}. En multipliant les deux membres par 10×2n10 \times 2^{n}, on obtient

2n=10a=2×5×a,2^{n} = 10a = 2 \times 5 \times a,

autrement dit 2n2^{n} serait un multiple de 55. Or le dernier chiffre des puissances de 22 tourne en rond : 11, puis 22, 44, 88, 66, puis de nouveau 22, 44, 88, 66, sans fin. C’est inévitable, puisque doubler un nombre terminé par 22, 44, 88 ou 66 redonne toujours l’une de ces quatre terminaisons. Jamais 00, jamais 55 : aucune puissance de 22 n’est un multiple de 55, quel que soit l’exposant. La supposition est fausse.

Le nombre 0,10{,}1 n’a pas d’écriture finie en base deux. Son écriture y est 0,00011001100110{,}0001100110011\ldots, où le bloc 00110011 se répète sans fin, comme les 33 de 0,3330{,}333\ldots 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, 33 ne divise aucune puissance de 1010 ; en base deux, le 55 caché dans 10=2×510 = 2 \times 5 ne divise aucune puissance de 22.

Ce que la machine range à la place

Un nombre occupe 6464 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 à 11, 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 0,10{,}1. Le voisin de 0,20{,}2 est lui aussi au-dessus, tandis que celui de 0,30{,}3 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 0,10{,}1 n’atteignent pas 11 :

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 0,10{,}1 exactement ; écrit sans guillemets, Decimal(0.1) ne fait que recopier le flottant défectueux. Mais Decimal n’écrit pas mieux 13\dfrac{1}{3} 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 2\sqrt{2} 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 13\dfrac{1}{3}, irrationalité de 2\sqrt{2}).
  • 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.
← Retour au chapitre A1