Nombres à virgule flottante, en python float

Création

On a vu jusqu’ici comment utiliser les nombres entiers via le type int. Cependant de nombreuses situations font appels à des nombres à virgules. Le type python qui permet de les manipuler est float qui vient de nombres à virgules flottantes.

On peut créer de tels nombres de deux façons distinctes.

REMARQUE lorsqu’on a introduit les int, on avait parlé de la division euclidienne //. Lorsqu’on utilise une seule barre / la division de deux int renvoie un float.

>>> resultat = 1 / 3
>>> type(resultat)
<class 'float'>
>>> resultat
0.3333333333333333

ATTENTION c’est là encore un endroit où l’on observe une grosse différence entre python2 et python3!

Mentionnons pour conclure que les float offrent aussi des représentations des infinis et d’une donnée manquante:

>>> float("inf")
inf
>>> float("-inf")
-inf
>>> float("NaN")
nan

Ici inf vient de infinite et nan de not a number.

Arrondis

De nombreux nombres réels ont une écriture décimale infinie. On a vu 1 / 3 ci-dessus, mais les racines carrés, les logarithmes, les exponentielles et la trigonométrie fournissent facilement d’autres exemples. Cependant les float ne peuvent stocker qu’un nombre finie de décimales. On a donc des erreurs d’arrondis fréquentes qui peuvent avoir des implications surprenantes.

On a ainsi attendu avant d’aborder les float car c’est un type étonnamment compliqué. Pour s’en convaincre, on pourra aller regarder la norme IEEE 754 qui la précise.

On va se contenter pour cette partie de quelques exemples de comportements inattendus en guise d’avertissements.

>>> for n in range(50):
...     if 0.1 * n != n / 10:
...             print(n, end=" ")
...
3 6 7 12 14 17 19 23 24 28 29 33 34 38 39 41 46 48

On voit ici que pour de nombreux entiers entre 0 et 50, diviser par 10 et multiplier par 0.1 ne donne pas le même résultat. Si on prend le premier nombre problématique:

>>> 0.1 * 3
0.30000000000000004
>>> 3 / 10
0.3

Un autre résultat inattendu

>>> for n in range(100):
...     if 10 * (n / 10) != n:
...             print(n, end=" ")
...
>>> for n in range(100):
...     if 100 * (n / 100) != n:
...             print(n, end=" ")
...
7 14 28 29 55 56 57 58

On pourrait ici se demander d’où proviennent les erreurs d’arrondis. Mais il faut se souvenir que les ordinateurs travaillent en binaire. On a donc des conversions base10 -> base2, puis les opérations arithmétiques, puis des conversions base2 -> base10.

A chaque étape, on peut avoir des erreurs d’arrondis. Par exemple, alors que 1/5 a une expansion décimale finie 0.2, ce n’est pas le cas en binaire où l’expansion est 0.00110011001100110011.... Celle-ci est donc tronquée pour être stockée en mémoire.

Un autre exemple de ce qui peut mal se passer est le suivant

>>> 0.7 + 0.1 + 0.2 == 0.7 + 0.2 + 0.1
False
>>> 0.1 + (0.2 + 0.3) == (0.1 + 0.2) + 0.3
False

avec des float l’addition n’est ni associative ni commutative!

On prendra donc des précautions lorsqu’on manipulera des float, plus précisément:

Conversions

On a jusqu’ici travailler séparément avec les différents types. Il s’avère qu’on a des passerelles systématiques pour passer des uns aux autres. On utilise pour cela les fonctions portant le nom des types.

Cas numérique

On peut faire ces conversions pour les nombres:

>>> x = 1
>>> x
1
>>> type(x)
<class 'int'>
>>> y = float(x)
>>> y
1.0
>>> type(y)
<class 'float'>
>>> z = int(y)
>>> z
1
>>> type(z)
<class 'int'>

ATTENTION si on on applique int à un flottant on tronque après la virgule:

>>> x = 1.5
>>> x
1.5
>>> type(x)
<class 'float'>
>>> y = int(x)
>>> y
1
>>> type(y)
<class 'int'>

REMARQUE si on veut plutôt un arrondi on utilise la fonction round:

>>> round(1.7)
2
>>> int(1.7)
1

La conversion marche aussi depuis les str pour les int

>>> nombre = "1234"
>>> type(nombre)
<class 'str'>
>>> nombre
'1234'
>>> n = int(nombre)
>>> n
1234
>>> type(n)
<class 'int'>

et pour les float

>>> flottant = "1.123"
>>> type(flottant)
<class 'str'>
>>> flottant
'1.123'
>>> x = float(flottant)
>>> x
1.123
>>> type(x)
<class 'float'>

ATTENTION il faut que les str représente des nombres valides sous peine de plantage.

>>> int("qslkjf")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'qslkjf'
>>> int("1.123")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '1.123'
>>> float("1,1233")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: '1,1233'

Cas des conteneurs

On a là aussi des passerelles.

Exercices

  1. On suppose donnée une variable n: int, donnez un code permettant de calculer la quantité 112+122++1n2\frac{1}{1^2} + \frac{1}{2^2} + \ldots + \frac{1}{n^2}.
  2. Donner un code permettant de répondre à la question suivante. En python pour quelles valeurs de n a-t-on 10n+10n10n1\frac{10^n + 10^{-n}}{10^n} \neq 1?
  3. On suppose qu’on a une variable message: str, donner un code générant une variable compteur: list[tuple[str, int]] calculant le nombre de fois que chaque caractère apparaît dans le message. Par exemple on aurait

    >>> message = "OUPSS!"
    >>> compteur
    [('O', 1), ('U', 1), ('P', 1), ('S', 2), ('!', 1)]

Pour aller plus loin

On pourra obtenir la description précise de la syntaxe des flottants ici

>>> help("FLOAT")
>>> help(float)

Pour les arrondis on pourra jeter un coup d’oeil ici.

On pourra regarder les documentations des différents types pour les conversions.

>>> help(int)
>>> help(float)
>>> help(list)
>>> help(tuple)
>>> help(str)

Corrections

  1. On peut faire une simple boucle

    >>> n = 1000
    >>> somme = 0
    >>> for k in range(1, 1 + n):
    ...     somme = somme + 1 / k ** 2
    ...
    >>> somme
    1.6439345666815615
    >>> from math import pi
    >>> pi ** 2 / 6
    1.6449340668482264

    Les deux dernières instructions permettent de visualiser la limite lorsque n tend vers l’infini: π26\frac{\pi^2}{6}.

  2. On constate que dès n=9 les erreurs d’arrondis posent problème:

    >>> for n in range(15):
    ...     calcul = (10 ** n + 10 ** (-n)) / 10 ** n
    ...     if calcul != 1:
    ...             print(f"OK pour n={n}")
    ...
    OK pour n=0
    OK pour n=1
    OK pour n=2
    OK pour n=3
    OK pour n=4
    OK pour n=5
    OK pour n=6
    OK pour n=7
    OK pour n=8
  3. >>> message = "the quick brown fox jumps over the lazy dog"
    >>> compteur = list()
    >>> for lettre in message:
    ...     lettre_trouvee = False
    ...     for indice, (caractere, compte) in enumerate(compteur):
    ...             if lettre == caractere:
    ...                     compteur[indice] = (lettre, compte + 1)
    ...                     lettre_trouvee = True
    ...     if not lettre_trouvee:
    ...             compteur.append((lettre, 1))
    ...
    >>> compteur
    [('t', 2), ('h', 2), ('e', 3), (' ', 8), ('q', 1), ('u', 2), ('i', 1), ('c', 1), ('k', 1), ('b', 1), ('r', 2), ('o', 4), ('w', 1), ('n', 1), ('f', 1), ('x', 1), ('j', 1), ('m', 1), ('p', 1), ('s', 1), ('v', 1), ('l', 1), ('a', 1), ('z', 1), ('y', 1), ('d', 1), ('g', 1)]
    ATTENTION