Les nombres en Python


Aller, un petit article pour débutant pour changer: la représentation des nombres en Python.

Les types de base

Il y a plus de types et notations numériques de base que vous ne le pensez:

>>> type(1)
 
>>> type(1.0)
 
>>> type(11111111111111111111)
 
>>> type(1j)
 
>>> type(1e10)

1 est celui que tout le monde connaît le mieux. Il représente le type entier, c’est à dire sans virgule. C’est aussi celui qu’on utilise le plus puisqu’il sert aux indices, aux slicings, aux boucles, au compteurs, etc.

Il peut être négatif:

>>> -2 + 1
-1

Et permet la plupart des opérations mathématiques ordinaires:

>>> 1 + 1 # addition
2
>>> 1 - 1 # soustraction
0
>>> 4 / 2 # division
2
>>> 2 * 3 # multiplication
6
>>> 2 ** 3 # puissance
8

Ça va sans dire, mais ça va mieux en le disant, Python n’applique pas de transtypage automatique en dehors des types numériques. Comme disait ma maîtresse, on ne peut pas additionner des choux et des carottes, ni des strings et des bas nylons int:

>>> 1 + 1
2
>>> 1 + "1"
Traceback (most recent call last):
  File "", line 1, in 
    1 + "1"
TypeError: unsupported operand type(s) for +: 'int' and 'str'
 
>>> 1 + int("1")
2
>>> str(1) + "1"
'11'

Quand un entier est trop gros, il est transformé automatiquement en type long. Il n’y a rien à faire, Python gère ça tout seul. Un long, c’est juste un gros entier, un peu plus lent.

>>> import sys
>>> sys.maxint # la limite de transformation d'un int en long
9223372036854775807
>>> type(9223372036854775807)
 
>>> type(9223372036854775807 + 1)

Mais on peut en faire un soi-même si on se sent l’âme d’un créateur de long, même un tout petit. Il suffit de rajouter un L:

>>> 1L
1L
>>> type(1L)

Ce type n’existe plus en Python 3.

Les int et long sont limités à ce qui est entier. Pour tout ce qui a une virgule, on utilise les flottants, qui acceptent les mêmes opérations:

>>> 0.1 - 2.3 * 4.5 ** 6.7 / 8.9
-6149.7096223012195
>>> type(-6149.7096223012195)

Tout nombre qui contient un . est automatiquement un float.

D’ailleurs, si on fait une opération entre un int et un float, on se retrouve avec un résultat en flottant:

>>> 1 + 1. # même pas besoin de chiffre après la virgule
2.0

En fait, la plupart des opérations mathématiques un peu avancées retournent un float:

>>> import math
>>> math.cos(math.pi)
1.0

Les floats ont aussi une autre particularité: on peut les représenter en notation scientifique:

>>> 6.02214078e23 # j'aimerais avoir une mole d'euros pour Noel
6.02214078e+23

Je ne l’ai jamais utilisé, et je n’ai aucune idée de si c’est utile pour nos amis scientifiques (y en a des biens). En tout cas, c’est là.

D’ailleurs, puisqu’on parle science, on est pas limité aux réels avec Python. On peut taper dans les nombres imaginaires (la notation utilise “j” et non pas “i”) :

>>> 1j + 1
(1+1j)
1
(1+1j)

Et il y a même un module math dédié:

>>> math.sqrt(-1) # racine carrée FAIL
Traceback (most recent call last):
  File "", line 1, in 
    math.sqrt(-1)
ValueError: math domain error
>>> import cmath # WIN
>>> cmath.sqrt(-1)
1j

Et tout ce petit monde est compatible ensemble:

>>> 0 + 1.0 + 9999999999999999999999999999 + 1j
(1e+28+1j)

La seule chasse gardée des entiers, c’est l’indexing et le slicing:

>>> l = range(10)
>>> l[1]
1
>>> l[1.0]
Traceback (most recent call last):
  File "", line 1, in 
    l[1.0]
TypeError: list indices must be integers, not float
 
>>> l[1j]
Traceback (most recent call last):
  File "", line 1, in 
    l[1j]
TypeError: list indices must be integers, not complex

L’éternel problème de la virgule qui part en couille

Généralement, un débutant apprenant la programmation va commencer comme ça:

>>> 4 / 2 # COOOL !
2
>>> 3 / 2 # WTF ?!!
1

Donc là il va sur les forums, ne fait aucune recherche, et pose la même question que tous les débutants ont posé. Et une bonne âme répond patiemment que si il avait pris le temps de taper 3 secondes sur Google, espèce de connard d’étudiant fumeur de joint qui joue à League of Legend dans l’amphi, il aurait su que l’opération de division en Python 2.7 est une opération de division entière. Elle vire donc tout ce qu’il y a après la virgule.

Pour y remédier, il y a plusieurs possibilités.

1 – Utiliser des floats :

>>> 3. / 2.
1.5

2 – Utiliser truediv :

>>> from operator import truediv
>>> truediv(3, 2)
1.5

3 – utiliser le comportement de Python 3 :

>>> from __future__ import division # c'est quand même la classe
>>> 3 / 2 # comportement naturel
1.5
>>> 3 // 2 # division entière
1

Utilisez la solution 1 dans le shell pour faire vite fait, la 2 si vous modifiez un programme existant, et la 3 quand vous créer un nouveau programme.

Bien, revenons à notre connard de joint qui fume de l’amphi en cours de League of Legend. Il va maintenant vouloir se faire la main, et se lancer dans la création d’applications qui manquent comme des annuaires, des todo lists et des gestionnaires de budget. Sur ces derniers, il va bien entendu manipuler des sous, et les sous, il faut que ça soit précis. Alors quand il tombe là dessus:

>>> 0.1 + 0.1 + 0.1 - 0.3 # ZERO !!!
5.551115123125783e-17
>>> print "O_o dah FUCK ?"
O_o dah FUCK ?

Il retourne sur le forum où on l’avait si gentiment aidé, pour refaire exactement ce qu’on lui avait dit de pas faire. Et le bon con de service (le geek de niveau intermédiaire qui est pas encore lassé de répondre aux débutants car il l’était il y a encore pas si longtemps alors il comprend, lui, pas comme ces snobs élitistes agressifs qu’il jure ne jamais devenir ce qu’il deviendra bien entendu après 6 mois de hotline gratuite sur le forum de commentcamarche.com) va lui répondre que si il avait cherché sur Google…

Il aurait donc vu que les ordinateurs représentent tout en base 2, et que certains nombres à virgules ne peuvent être qu’approximés.

Stupeur, effroi, affolement. Comment calculer le budget litière de sa coloc dans son super programme ?

Dans un autre langage, on lui recommanderait de tout multiplier par 100000 puis de tout diviser par 100000, pour être bien sûr.

En Python, il y a un module pour ça™

Décimaux et fractions

>>> from decimal import Decimal
>>> Decimal("0.1") + Decimal("0.1") + Decimal("0.1") - Decimal("0.3")
Decimal('0.0')

Joie. Bonheur. Victoire de l’homme sur la machine et le boudin blanc.

On peut même définir jusqu’à quel point on veut être précis (en gros à partir de quand il arrondit) :

>>> from decimal import getcontext
>>> getcontext().prec = 6 # largement suffisant si vous n'êtes pas Paypal ou le Fisc

Pour les nostalgiques de Javascript ou de Buzz l’éclair, on peut même créer Not A Number et l’infini:

>>> Decimal('NaN')
Decimal('NaN')
>>> Decimal('-Infinity')
Decimal('-Infinity')

(float() le permet aussi, mais je vous le déconseille : le résultat est dépendant de l’implémentation)

Je vous invite à lire la doc, car il y a pas mal de choses à savoir si on veut éviter certains pièges.

Bref, le module Decimal permet de calculer avec le minimum d’approximation, évitant les erreurs d’arrondie et vous infusant de ce sentiment (fugace) que vous maîtrisez ce que vous faites. Vous pouvez manipuler des sous. Enfin presque. Car il y a évidement ensuite la douloureuse question des devises, mais la réponse à ce problème est un à pip install de là et dépasse les considérations de cet article.

Donc, pour tout ce qui a beson d’être précis au poil de cul, vous allez utiliser Decimal. Ou pas.

Car Decimal est lent. 100 fois plus lent qu’un float environ. Alors, pour la compta, ça va. Pour le séquençage ADN d’une patate, qui je le rappelle, a plus de chromosomes que nous, la biatch, c’est moyen.

Et là, pour pallier ce genre de truc, il faut taper dans l’extension en C, donc installer un module comme cdecimal (qui a la même API, il suffit ensuite de remplacer les imports) ou encore plus rapide, gmpy (API différente).

Bon, j’en ai jamais eu besoin dans toute ma vie, Decimal me suffit largement. Il calcule toujours plus vite que moi sur Excel OpenOffice LibreOffice. Mais je suis sûr qu’il y a des biologistes qui ne peuvent pas faire sans, hein. Vu la communauté qu’il y a sur scipy, la lib de calculs scientifiques haute performance, je ne doute pas de la question.

Pour finir sur une note plus légère, sachez que Python permet aussi de manipuler des fractions.

>>> from fractions import Fraction
>>> Fraction(3, 2)
Fraction(3, 2)
>>> int(Fraction(3, 2) + Fraction(1, 2))
2

Si vous êtes dans un cas où vous manipulez beaucoup de “parts de”, c’est fort pratique.

11 thoughts on “Les nombres en Python

  • Sam Post author

    Trop lennnnnnnnnnnnnnnt Xavier. Je l’avais corrigé. Mouahahaha !

  • roro

    Le problème des nombres, c’est qu’il y en a beaucoup.
    Mais le coup à double détente de l’amphi, est heureusement là pour leur rabattre le caquet.
    Je me demande si le rire n’influe pas sur la circulation sanguine intra-cérébrale.
    Trés bon article. Et toujours cette clarté qui caractérise les grands esprits. (je me place pour noel prochain.)

  • swordofpain

    Pour le problème de précision limitée, c’est pas plus rapide (bon, plus moche d’accord), de faire une comparaison à seuil plutôt que de déballer Decimal ? Dans le genre :

    from math import fabs
    THRESHOLD = 1e-6
    class TFloat(float):
        def __eq__(self, other):
            return fabs(self - other) < THRESHOLD
     
    .3 - .1 - .1 - .1 == TFloat(0.) // True
  • Sam Post author

    Je ne connaissais pas cette technique, c’est intéressant.

  • Megahertz

    Depuis quelques temps je lis avec plaisir ce blog et ses merveilleux articles mais là, que vois-je ?

    Il va maintenant vouloir se faire la main, et se lancer dans la création d’applications qui manquent comme des annuaires, des todo lists et des gestionnaires de budget.

    Diantre, je suis grillé ! C’est exactement ce que je comptais faire : un gestionnaire de todo-list pour m’entraîner en Python.
    Après avoir parcouru une partie du web et n’ayant pas trouvé le Saint Graal, je me suis décidé à en coder un exactement adapté à mes besoins.
    Serais-je un connard de joint qui fume de l’amphi ? ='(

    M’enfin, ce n’est pas grave, je vous souhaite de très bonnes faites quand même, ainsi qu’aux lecteurs !

  • Sam Post author

    Moi dans l’ordre j’ai fais: le gestionnaire de contact, la todo list puis le gestionnaire de budget. Après quand on passe en programmation Web, on code son blog. Et puis parfois, on se tate à coder “son propre RPG”. Bref, les étudiants en infos se ressemblent tous :-)

  • Sebastien

    Rhôô, mais il y a déjà un super gestionnaire de todo list (en python, oeuf corse), c’est yokadi !

    http://yokadi.github.com/

    Packagé sur Debian, mais installable avec une petite pip ou directement en téléchargeant la tarball depuis le site.

  • Xavier Combelle

    Je viens de relire l’article et je m’aperçois que j’avais zappé fractions, je savais même pas que ce module existait. Merci Sam.

  • bigduke

    J’suis à la bourre mais j’aime relire les bases !

    Pour la partie sur les notations scientifiques : oui super pratique !! Tu utilises la constante d’Avogadro dans ton exemple et sans cette notation, bah on resterait sur le papier pour faire un calcul… sans parler des raccourcis : int(1e6) pour de grosses itérations.

    Un truc en plus pour les valeurs complexes : c’est natif à python donc complex(1) renvoie 1+0j sans import de librairies tierces.

    On utilise le “j” pour la partie imaginaire et éviter de confondre avec le “i” des entiers. Ça se retrouve en math également. ;)

    Et encore un truc : complex(1).real renvoie la partie réelle, soit “1.0” et complex(1).imag renvoie la partie imaginaire soit “0.0”.

    Ça passe directement en type float malgré l’utilisation de “1” en argument car l’ensemble des réels est un sous-ensemble des complexes, donc l’argument est automatiquement pris comme un réel (float).

    Du reste, merci !

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.