[PageD'Accueil] [IndexDesTitres] [IndexDesTermes

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

2016-06-05 21:42