2.39. Travailler avec les flottants

Les nombres à virgule flottante ressemblent à des décimaux ordinaires, mais quelques-uns de leurs comportements surprennent les lecteurs qui ne les ont jamais rencontrés – et l’un de ces comportements est plus marqué sur MicroPython que sur le Python de bureau. Cette page couvre ce à quoi s’attendre et comment écrire du code flottant qui ne se comporte pas mal en silence.

2.39.1. Précision

Le type float de Python est un nombre à virgule flottante binaire IEEE 754. Sur la plupart des versions de MicroPython, il s’agit de la simple précision (32 bits), alors que le CPython de bureau utilise la double précision (64 bits). La simple précision offre environ sept chiffres décimaux de précision ; la double en offre environ quinze.

>>> 0.1 + 0.2
0.3000000

>>> 1.0 / 3.0
0.3333333

>>> 1e30 * 1e30
inf

La plage représentable est également plus étroite aux deux extrémités : les nombres dont la magnitude dépasse environ 3.4e38 débordent vers inf, et les nombres plus petits qu’environ 1.2e-38 sont arrondis à zéro.

2.39.2. Comparer des flottants

Le piège le plus courant est de tester l’égalité avec == :

>>> 0.1 + 0.2 == 0.3
False

Les deux expressions semblent devoir être égales, mais le résultat de 0.1 + 0.2 est la valeur représentable la plus proche, qui n’est pas exactement 0.3. Utilisez plutôt une vérification de tolérance – demandez si deux flottants sont suffisamment proches plutôt qu’identiques :

if abs(a - b) < 1e-6:
    # close enough
    ...

Le choix de la tolérance dépend de l’échelle des valeurs. Une tolérance fixe de 1e-6 fonctionne bien quand les nombres sont de l’ordre de 1 ; une tolérance relative est préférable quand les valeurs varient de plusieurs ordres de grandeur.

math.isclose() gère les deux à la fois :

from math import isclose

isclose(0.1 + 0.2, 0.3)         # True
isclose(1.0e6 + 1, 1.0e6)       # True (within default tolerance)

Les deux arguments nommés contrôlent quel type de « proximité » compte :

  • rel_tol – tolérance relative, valeur par défaut 1e-9. Deux valeurs correspondent si leur différence est inférieure à cette fraction de la plus grande des deux. Adapté aux comparaisons générales à toute échelle.

  • abs_tol – tolérance absolue, valeur par défaut 0. Deux valeurs correspondent si leur différence est inférieure à cette quantité fixe.

math.isclose() renvoie True si l’une ou l’autre tolérance est satisfaite. Les valeurs par défaut conviennent pour la plupart des paires de nombres non nuls ; le piège survient lorsque l’une des valeurs peut être exactement zéro. La vérification de tolérance relative revient à « différence ≤ rel_tol × plus grande valeur », et la plus grande valeur étant zéro, la vérification échoue toujours :

>>> isclose(0.0, 1e-12)
False

La vérification de tolérance absolue n’a pas ce problème – passez un abs_tol chaque fois que zéro est une valeur à laquelle vous pourriez comparer :

>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True

2.39.3. Dérive d’accumulation

Les longues sommes de flottants perdent en précision plus rapidement sur MicroPython que sur CPython, car chaque résultat intermédiaire est arrondi à la précision 32 bits :

total = 0.0
for _ in range(1000000):
    total += 0.1

print(total)        # noticeably off from 100000.0

Pour les additions répétées où la précision compte, deux approches aident :

  • Accumulez dans un entier chaque fois que les valeurs peuvent être mises à l’échelle en entiers – travaillez en millisecondes plutôt qu’en secondes, ou en millivolts plutôt qu’en volts, puis convertissez une seule fois à la fin.

  • Calculez par petits lots et additionnez les résultats des lots, de sorte que chaque addition se fasse entre des valeurs de magnitude similaire.

Le côté entier n’a pas cette limite – les entiers de MicroPython sont de précision arbitraire, tout comme ceux de CPython. Lorsque vous avez le choix, privilégiez l’arithmétique entière pour tout ce où la perte de précision se cumulerait.