14. Arithmétique en nombres à virgule flottante : problèmes et limites
**********************************************************************

Les nombres à virgule flottante sont représentés, au niveau matériel,
en fractions de nombres binaires (base 2). Par exemple, la fraction
décimale :

   0.125

a la valeur 1/10 + 2/100 + 5/1000 et, de la même manière, la fraction
binaire :

   0.001

a la valeur 0/2 + 0/4 + 1/8. Ces deux fractions ont une valeur
identique, la seule différence est que la première est une fraction
décimale, la seconde est une fraction binaire.

Malheureusement, la plupart des fractions décimales ne peuvent pas
avoir de représentation exacte en fractions binaires. Par conséquent,
en général, les nombres à virgule flottante que vous donnez sont
seulement approximés en fractions binaires pour être stockés dans la
machine.

Le problème est plus simple à aborder en base 10. Prenons par exemple,
la fraction 1/3. Vous pouvez l’approximer en une fraction décimale :

   0.3

ou, mieux,

   0.33

ou, mieux,

   0.333

etc. Peu importe le nombre de décimales que vous écrivez, le résultat
ne vaut jamais exactement 1/3, mais c’est une estimation s’en
approchant toujours mieux.

De la même manière, peu importe combien de décimales en base 2 vous
utilisez, la valeur décimale 0.1 ne peut pas être représentée
exactement en fraction binaire. En base 2, 1/10 est le nombre
périodique suivant :

   0.0001100110011001100110011001100110011001100110011...

Arrêtez à n’importe quelle quantité finie de bits, et vous obtiendez
une approximation.

Pour Python, sur une machine typique, 53 bits sont utilisés pour la
précision d’un flottant, donc la valeur stockée lorsque vous entrez le
nombre décimal "0.1" est la fraction binaire

   0.00011001100110011001100110011001100110011001100110011010

qui est proche, mais pas exactement égale, à 1/10.

Il est facile d’oublier que la valeur stockée est une approximation de
la fraction décimale d’origine, du fait de la manière dont les
flottants sont affichés dans l’interpréteur. Python n’affiche qu’une
approximation décimale de la valeur stockée en binaire. Si Python
devait afficher la vraie valeur décimale de l’approximation binaire
stockée pour 0.1, il afficherait :

   >>> 0.1
   0.1000000000000000055511151231257827021181583404541015625

C’est bien plus de décimales que ce qu’attendent la plupart des
utilisateurs, donc Python affiche une valeur arrondie afin d’améliorer
la lisibilité :

   >>> 0.1
   0.1

Il est important de comprendre qu’en réalité, c’est une illusion : la
valeur stockée n’est pas exactement 1/10, c’est simplement à
*l’affichage* que la valeur stockée est arrondie. Ceci devient évident
dès que vous effectuez des opérations arithmétiques avec ces valeurs :

   >>> 0.1 + 0.2
   0.30000000000000004

Ce comportement est inhérent à la nature même de la représentation des
nombres à virgule flottante dans la machine : ce n’est pas un bogue
dans Python et ce n’est pas non plus un bogue dans votre code. Vous
pouvez observer le même type de comportement dans tous les autres
langages utilisant le support matériel pour le calcul des nombres à
virgule flottante (bien que certains langages ne rendent pas visible
la différence par défaut, ou pas dans tous les modes d’affichage).

Une autre surprise est inhérente à celle-ci. Par exemple, si vous
tentez d’arrondir la valeur 2.675 à deux décimales, vous obtiendrez

   >>> round(2.675, 2)
   2.67

La documentation de la primitive "round()" indique qu’elle arrondit à
la valeur la plus proche en s’éloignant de zéro. Puisque la fraction
décimale est exactement à mi-chemin entre 2.67 et 2.68, vous devriez
vous attendre à obtenir (une approximation binaire de) 2.68. Ce n’est
pourtant pas le cas, car lorsque la fraction décimale "2.675" est
convertie en flottant, elle est stockée par une approximation dont la
valeur exacte est

   2.67499999999999982236431605997495353221893310546875

Puisque l’approximation est légèrement plus proche de 2.67 que 2.68,
l’arrondi se fait vers le bas.

Si vous êtes dans une situation où les arrondis de nombres décimaux à
mi-chemin ont de l’importance, vous devriez utiliser le module
"decimal". Soit dit en passant, le module "decimal" propose aussi un
moyen pratique de « voir » la valeur exacte stockée pour n’importe
quel flottant.

   >>> from decimal import Decimal
   >>> Decimal(2.675)
   Decimal('2.67499999999999982236431605997495353221893310546875')

Une autre conséquence du fait que 0.1 n’est pas exactement stocké 1/10
est que la somme de dix valeurs de 0.1 ne donne pas 1.0 non plus :

   >>> sum = 0.0
   >>> for i in range(10):
   ...     sum += 0.1
   ...
   >>> sum
   0.9999999999999999

L’arithmétique des nombres binaires à virgule flottante réserve
beaucoup de surprises de ce genre. Le problème avec « 0.1 » est
expliqué en détails ci-dessous, dans la section « Erreurs de
représentation ». Voir The Perils of Floating Point pour une liste
plus complète de ce genre de surprises.

Il est vrai qu’il n’existe pas de réponse simple, cependant ne vous
méfiez pas trop des nombres à virtule flottante ! Les erreurs, en
Python, dans les opérations de nombres à virgule flottante sont dues
au matériel sous-jacent, et sur la plupart des machines ne sont pas
plus importantes que 1 sur 2**53 par opération. C’est plus que
nécessaire pour la plupart des tâches, mais vous devez garder à
l’esprit que ce ne sont pas des opérations décimales, et que chaque
opération sur des nombres à virgule flottante peut souffrir d’une
nouvelle erreur.

Bien que des cas pathologiques existent, pour la plupart des cas
d’utilisations courants vous obtiendrez le résultat attendu à la fin
en arrondissant simplement au nombre de décimales désirées à
l’affichage. Pour un contrôle fin sur la manière dont les flottants
sont affichés, consultez dans Syntaxe de formatage de chaîne les
spécifications de formattage de la méthode "str.format()"


14.1. Erreurs de représentation
===============================

Cette section explique en détail l’exemple du « 0.1 » et montre
comment vous pouvez effectuer une analyse exacte de ce type de cas par
vous-même. Nous supposons que la représentation binaire des nombres
flottants vous est familière.

Le terme *Representation error* signifie que la plupart des fractions
décimales ne peuvent être représentées exactement en binaire. C’est la
principale raison pour laquelle Python (ou Perl, C, C++, Java,
Fortran, et beuacoup d’autres) n’affiche habituellement pas le
résultat exact en décimal:

   >>> 0.1 + 0.2
   0.30000000000000004

Pourquoi ? 1/10 et 2/10 ne sont pas représentable de manière exacte en
fractions binaires. Cependant, toutes les machines d’aujourd’hui
(Juillet 2010) suivent la norme IEEE-754 en ce qui concerne
l’arithmétique des nombres à virgule flottante. et la plupart des
plateformes utilisent un « IEEE-754 double precision » pour
représenter les floats de Python. Les « IEEE-754 double precision »
utilisent 53 bits de précision, donc a la lecture l’ordinateur essaie
de convertir 0.1 dans la fraction la plus proche possible de la forme
*J*/2***N* avec *J* un nombre entier d’exactement 53 bits. Réecrire :

   1 / 10 ~= J / (2**N)

en :

   J ~= 2**N / 10

en se rappelant que *J* fait exactement 53 bits (donc ">= 2**52" mais
"< 2**53"), la meilleure valeur possible pour *N* est 56 :

   >>> 2**52
   4503599627370496
   >>> 2**53
   9007199254740992
   >>> 2**56/10
   7205759403792793

Donc 56 est la seule valeur possible pour *N* qui laisse exactement 53
bits pour *J*. La meilleure valeur possible pour *J* est donc ce
quotient, arrondi:

   >>> q, r = divmod(2**56, 10)
   >>> r
   6

Puisque la retenue est plus grande que la moitié de 10, la meilleure
approximation est obtenue en arrondissant par le haut :

   >>> q+1
   7205759403792794

Par conséquent la meilleure approximation possible pour 1/10 en «
IEEE-754 double precision » est cette au desus de 2**56, soit :

   7205759403792794 / 72057594037927936

Notez que puisque l’arrondi a été fait vers le haut, le résultat est
en réalité légèrement plus grand que 1/10 ; si nous n’avions pas
arrondi par le haut, le quotient aurait été légèrement plus petit que
1/10. Mais dans aucun cas il ne vaut *exactement* 1/10 !

Donc l’ordinateur ne « voit » jamais 1/10 : ce qu’il voit est la
fraction exacte donnée ci-dessus, la meilleure approximation utilisant
les nombres à virgule flottante double précision de l“« IEEE-754 » :

   >>> .1 * 2**56
   7205759403792794.0

Si on multiplie cette fraction par 10**30, on peut observer les
valeurs de ses 30 décimales de poid fort.

   >>> 7205759403792794 * 10**30 // 2**56
   100000000000000005551115123125L

signifiant que la valeur exacte stockée dans l’ordinateur est
approximativement évale à la valeur décimale
0.100000000000000005551115123125. Dans les versions antérieurs à
Python 2.7 et Python 3.1, Python arrondissait ces valeurs à 17
décimales significatives, affichant “0.10000000000000001”. Dans les
versions actuelles de Python, la valeur affichée est la valeur dont la
fraction est la plus courte possible tout en redonnant exactement la
même représentation une fois reconverti en binaire, affichant
simplement “0.1”.
