Calculer juste avec Python
Auteurs : Laurent Pierron mailto:lpierron@orange.fr et Sébastien SAUVAGE
Le problème par l'exemple
Une session Python 2 pas très satisfaisante :
>>> tva = 17.5/100 >>> prix = 3.50 >>> prix*tva 0.61249999999999993 >>> round(_, 2) 0.60999999999999999
De quoi de quoi, Python serait-il victime de la bogue du Pentium, et ne saurait pas effectuer de si élémentaires opérations de comptabilité ?
Nous allons montrer ici que ce problème existe sur tous les ordinateurs et comment le contourner avec Python.
Arithmétique en virgule flottante : problèmes et limites
Le dernier tutoriel de Python explique en détail le pourquoi du comment de ces étranges calculs, quelque soit le langage de programmation utilisé (Perl, C, C++, Java, Fortran, ou TurboTrucMuche voire Excel).
Je vous conseille donc de lire et relire le chapitre :
B. Floating Point Arithmetic: Issues and Limitations http://www.python.org/doc/current/tut/node16.html
En général les utilisateurs d'une calculatrice ou d'un logiciel ne se posent pas de questions car ils ne voient pas le problème, le logiciel arrondissant l'affichage pour eux (c'est ce que faisait Python jusqu'à la version 1.5, c'est ce que fait Excel entre autres).
Depuis la version 1.6, Python arrondit la représentation des nombres à virgule flottante à 17 décimales ce qui fait qu'un utilisateur sous l'interpréteur voit les pouillièmes s'afficher alors que l'utilisation de print (par exemple) les fait disparaître car il y a moins de décimales utilisées pour l'affichage (méthode str()).
Exemple :
>>> 0.2 0.20000000000000001 >>> print 0.2 0.2
Cas concret:
a = 1.7 b = 0.9 + 0.8 print a print b if a == b: print "a et b sont egaux." else: print "a et b sont differents !"
Le programme ci dessus va afficher:
1.7 1.7 a et b sont differents !
Pourtant il affiche bien 1.7 et 1.7. Comment a et b peuvent-ils être différents ? N'oubliez pas que 1.7 n'est qu'une représentation textuelle arrondie du nombre. En binaire (en interne dans Python), il est impossible de représenter des nombres tels que 0.9 ou 1.7. D'où l'approximation. (Ce problème n'est pas spécifique à Python, mais à tous les langages de programmation et tous les processeurs.)
Pour comparer ces 2 nombres, il faut procéder autrement. Par exemple en passant par une approximation:
if abs(a-b) < 0.00001: print "a et b sont egaux." else: print "a et b sont differents !"
ou même par les chaînes de caractères:
if str(a) == str(b): print "a et b sont egaux." else: print "a et b sont differents !"
Modification du comportement de Python
On peut modifier le comportement de l'interpréteur en changeant la fonction d'affichage de l'interpréteur interactif de la sorte :
import __builtin__ def mondisplay(o): if o is None: return __builtin__._ = None print o __builtin__._ = o import sys sys.displayhook = mondisplay
Ajoutez ce morceau de code dans votre fichier 'startup', chez moi c'est .pythonrc.py qui est défini par la variable d'environnement PYTHONSTARTUP sous Linux (voir les docs correspondantes sous Mac et sous Windows)
Après cet ajout dans le 'startup' le petit exemple ci-dessus donne :
% python2 Python 2.2.1 (#1, Apr 9 2002, 13:10:27) [GCC 2.96 20000731 (Red Hat Linux 7.1 2.96-98)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> tva = 17.5/100 >>> prix=3.50 >>> prix*tva 0.6125 >>> round(_,2) 0.61
Et voilà, on a excellisé le Python, il sait maintenant nous faire des divisions correctes non mais !!!
Calculs avec des entiers longs
Auteur : jean-michel
On peut aussi utiliser des entiers longs pour faire ses calculs. Dans ce cas, les valeurs sont systématiquement mémorisées en entier. Les calculs, étant faits sur des entiers, restent exacts, et il n'y a plus qu'à se préoccuper de retrouver la partie décimale à l'affichage. Exemple:
>>> tva=1750L >>> prix=350L >>> m=(prix*tva)/10000 >>> str(m)[:-2]+'.'+str(m)[-2:] '.61'
C'est moins brillant que l'exemple précédent, mais tout aussi efficace !
Le type decimal
Auteur : jean-michel
Le module decimal règle le problème. Voir le chapitre 8 de la documentation 'quoi de neuf en python 2.4' qui est très explicite à ce sujet. Succintement :
>>> 12.12-12 0.11999999999999922 >>> import decimal >>> decimal.Decimal('12.12')-decimal.Decimal('.12') Decimal("12.00") >>> print decimal.Decimal('12.12')-decimal.Decimal('.12') 12.00