Autoblog de sebsauvage.net

Ce site n'est pas le site officiel de sebsauvage.net
C'est un blog automatisé qui réplique les articles de sebsauvage.net

1,7 est différent de 1,7 ?

vendredi 27 janvier 2012 à 13:25

Je reprend ici un petit article technique que j'avais écrit en anglais pour ma page de snippets Python.

Il sert juste à illustrer le fait que certaines notions en informatique ne sont pas du tout intuitives, et qu'il est utile d'apprendre tout cela. Le domaine du développement logiciel est plein de petits chausses-trappes de ce genre, et voir des développeurs se prendre les pieds dans le tapis me fait sourire (pas méchamment, je vous l'assure).


Je vais ici parler d'opérations élémentaires: Additionner et comparer des nombres. Élémentaires, mais pas évidentes pour autant. Cela a l'air très simple, mais rien qu'à ce niveau, il y a de quoi rendre perplexe le développeur débutant. J'ai choisi ici d'illustrer avec des exemples en langage Python, mais ce problème se reproduit dans la plupart les langages.


Commençons par créer deux variables (en Python):

a = 1.7
b = 0.9 + 0.8

En principe, a et b contiennent la même valeur, on est bien d'accord ? Affichons cela pour vérifier:

print a
print b
Ce qui affiche:
1.7
1.7

Bien. Maintenant comparons a et b:

if a == b:
    print "a et b sont égaux."
else:
    print "a et b sont différents !"

En principe, il doit afficher "a et b sont égaux", n'est-ce pas ? Faux !

a et b sont différents !

Comment est-ce possible ? Comment 1,7 peut-il être différent de 1,7 ?


Il y a deux choses qui sont à l'origine de cette incompréhension et qu'il fondamentale de connaître:

A l'instant même où vous faites a=1.7, la variable a ne contient pas 1,7, mais une approximation binaire de la valeur décimale 1,7, c'est à dire quelque chose du genre: 1.10110011001100110011001100110011... (Avec les bits précédents, il s'agit de la valeur 1.699999999953434)

En binaire, on peut facilement stocker les fractions dont le diviseur est une puissance de deux. Quelques exemples:
Mais dans tous les autres cas, l'ordinateur est bien en mal de stocker la valeur. Vous pouvez tester en ligne avec ce petit convertisseur. L'ordinateur fait donc deux approximations:

De la même manière, quand vous affichez une date:

print datetime.datetime.now()
2012-01-27 21:25:20.904000

Il faut bien garder à l'esprit que "2012-01-27 21:25:20.904000" n'est pas la date: C'est une représentation textuelle d'une donnée codée en binaire dans la mémoire de l'ordinateur. Ce que vous voyez à l'écran n'est pas forcément exactement ce qu'il y a dans la mémoire de l'ordinateur.

Bien sûr, il existe des bibliothèques mathématiques et des codages binaires qui permettent de stocker correctement des nombres décimaux, mais cela fait du travail en plus pour le processeur.

Alors comment comparer nos variables a et b précédentes sans recourir à ces codages spéciaux ? Soit en considérant que les valeurs sont égales si elles sont très proches:

if abs(a-b) < 0.00001:
    print "a et b sont égaux."
else:
    print "a et b sont différents !"

ou même en les convertissant en chaînes de caractères:

if str(a) == str(b):
    print "a et b sont égaux."
else:
    print "a et b sont différents !"


Et oui, l'ordinateur a ses limites, et même quand on développe dans un langage "haut niveau", il est bon de les connaître. ;-)


EDIT: Quelques précisions (signalées par Benjamin et Kévin):

EDIT: Jean-Francois me fait remarquer que je me suis un peu raté sur l'IEEE-754: C'est également un standard à virgule flottante, donc soumis aux mêmes problèmes d'imprécision. La différence est que l'IEEE-754 définit clairement des règles (comme les méthodes d'arrondi à respecter), ce qui permet d'avoir un standard stable et d'avoir les mêmes comportements partout. Pour éviter ces problèmes d'imprécision, il faut recourir à d'autres encodages de nombres, généralement sous forme de bibliothèque (comme GMP ou CLN).

Source : http://sebsauvage.net/rhaa/index.php?2012/01/27/12/25/35-1-7-est-different-de-1-7-