cours1 min de lecture

Nombres flottants et virgule flottante

Comment la machine approxime les nombres réels et pourquoi 0.1 + 0.2 ne vaut pas exactement 0.3.
programme

Introduction

Les entiers, c'est bien — mais comment représente-t-on 3,143{,}14 ou 13\frac{1}{3} ? Les nombres réels posent un problème fondamental : il en existe une infinité entre deux entiers consécutifs, alors qu'une machine ne dispose que d'un nombre fini de bits. Il faudra donc approximer. C'est ce que fait la représentation en virgule flottante, normalisée par le standard IEEE 754 et utilisée par tous les processeurs modernes.

Cette approximation a un effet déroutant : en Python, 0.1 + 0.2 ne vaut pas exactement 0.3. Comprendre pourquoi vous évitera bien des bugs.

L'analogie avec la notation scientifique

En mathématiques, on note souvent un grand ou un petit nombre en notation scientifique :

6,02210231,610196{,}022 \cdot 10^{23} \qquad 1{,}6 \cdot 10^{-19}

Trois éléments structurent cette écriture :

  • une mantisse : ici 6,0226{,}022 ou 1,61{,}6,
  • une base : ici 10,
  • un exposant : ici 2323 ou 19-19.

La virgule flottante reprend exactement cette idée, en base 2. Un flottant s'écrit alors comme :

x=(1)sm2ex = (-1)^s \cdot m \cdot 2^e

ss est le bit de signe, mm la mantisse (en binaire, entre 1 et 2 environ), et ee l'exposant (un entier signé).

La norme IEEE 754, vue simplifiée

La norme IEEE 754 fixe la répartition des bits. En simple précision (32 bits) :

ChampBitsRôle
Signe ss10 = positif, 1 = négatif
Exposant ee8Codé avec un biais de 127
Mantisse mm23Partie fractionnaire après le « 1, » implicite

En double précision (64 bits, le float Python par défaut) :

ChampBitsRôle
Signe ss10 = positif, 1 = négatif
Exposant ee11Biais de 1023
Mantisse mm52Partie fractionnaire
Le programme officiel ne vous demande aucune connaissance précise des bits de la norme IEEE 754. Ce qui compte, c'est de retenir le principe : signe, mantisse, exposant — et les conséquences sur la précision.

Pourquoi 0,10{,}1 pose problème

En base 10, 110\frac{1}{10} s'écrit simplement 0,1. Mais en base 2, 110\frac{1}{10} a un développement infini périodique :

110=(0,0001100110011)2\frac{1}{10} = (0{,}0001100110011\ldots)_2

Comme la mantisse a une taille bornée (52 bits en double précision), la machine tronque ce développement infini. Le résultat est une approximation très proche de 0,10{,}1, mais pas exactement 0,10{,}1.

La même chose se produit en base 10 avec 13\frac{1}{3} : son écriture décimale 0,33330{,}3333\ldots ne s'arrête jamais. Si on devait l'écrire avec un nombre fini de chiffres, on obtiendrait une approximation, jamais la valeur exacte. La base 2 a le même genre de souci, mais pour d'autres fractions — notamment celles dont le dénominateur n'est pas une puissance de 2.

La preuve par Python

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

L'écart est minuscule — de l'ordre de 101710^{-17} — mais il est non nul, et il suffit à faire échouer un test d'égalité strict. Ce comportement n'est pas un bug de Python : c'est la conséquence directe de la représentation binaire des flottants.

⏵ Ctrl+↵ pour exécuter
Aucune exécution pour l'instant.

Sortie attendue (extrait) :

0.1000000000000000055511151231257827021181583404541015625
0.200000000000000011102230246251565404236316680908203125
0.3000000000000000166533453694

Les deux premières lignes affichent la valeur binaire exacte des floats 0.1 et 0.2 (le constructeur Decimal(float) n'arrondit pas). La 3ᵉ ligne correspond à l'addition : Python repasse alors par le contexte Decimal de précision par défaut, qui arrondit à 28 chiffres significatifs. La valeur reste différente de 0.3 exact — ce qui confirme l'erreur de représentation binaire.

Que renvoie en Python l'expression 0.1+0.2==0.30.1 + 0.2 == 0.30.1+0.2==0.3 ?

Comment comparer deux flottants

La règle d'or est simple : on ne teste jamais l'égalité stricte de deux flottants. À la place, on vérifie qu'ils sont suffisamment proches, à une tolérance ε\varepsilon près.

def proche(a, b, eps=1e-9):
    return abs(a - b) < eps

print(proche(0.1 + 0.2, 0.3))  # True

Python fournit aussi math.isclose pour cette comparaison, plus robuste :

import math
print(math.isclose(0.1 + 0.2, 0.3))  # True
Cette précaution est systématique. Que vous compariez le résultat d'un calcul de physique, une distance, une fraction… utilisez toujours isclose ou une tolérance explicite quand des flottants sont en jeu.

Valeurs spéciales — inf, -inf, nan

IEEE 754 prévoit quelques valeurs particulières :

  • inf (infini positif) : résultat d'un dépassement ou d'une division par zéro positive.
  • -inf (infini négatif).
  • nan (Not a Number) : résultat d'une opération invalide, comme 0/00/0 ou 1\sqrt{-1}.
import math
print(math.inf)              # inf — constante du module math
print(1 / 0.0)               # ZeroDivisionError en Python pur (et non inf)
print(float('nan') == float('nan'))  # False — un nan n'est jamais égal à lui-même !
En Python pur, 1 / 0.0lève une ZeroDivisionError — l'infini ne sort pas naturellement de la division. Pour obtenir inf, on passe par la constante math.inf (ou par des bibliothèques comme NumPy, où 1 / 0.0 renvoie effectivement inf).

Combien de bits utilise un float en double précision ?

Pièges courants

  • Ne jamais tester a == b pour des flottants. Utiliser math.isclose.
  • Additionner beaucoup de petits flottants peut perdre en précision (les petits termes deviennent négligeables face au cumul).
  • Convertir une str en float (float("0.1")) crée la même approximation : il n'y a pas de chemin pour obtenir un 0,10{,}1 exact en flottant binaire.

Pour aller plus loin

Pour des calculs financiers ou scientifiques exigeants, Python fournit deux alternatives :

  • Le module decimal (calcul en base 10, à précision arbitraire) — bien adapté aux montants monétaires.
  • Le module fractions (rationnels exacts pq\frac{p}{q}) — utile pour les calculs symboliques où les approximations sont interdites.
from fractions import Fraction
print(Fraction(1, 10) + Fraction(2, 10))  # 3/10

Ces alternatives sont plus lentes, mais elles fournissent le résultat exact.