Sam & Max » calculs http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Les nombres en Python 10 http://sametmax.com/les-nombres-en-python/ http://sametmax.com/les-nombres-en-python/#comments Mon, 24 Dec 2012 17:43:54 +0000 http://sametmax.com/?p=3851 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.

]]>
http://sametmax.com/les-nombres-en-python/feed/ 10