Utilisatrice:Eric.LEWIN/Précision et dégradation numériques
A priori pour Mathématiques_avec_Python_et_Ruby…
En informatique numérique, les calculs avec les nombres à virgule flottante se réalisent sur un ensemble (fini) de nombres décimaux, dont la structure est complexe. Toutefois il importe de connaître quelques éléments qui en mettent en lumière des particularités. Nous en montrerons ici quelques uns, dans le contexte du langage Python, bien qu'ils soient pour l'essentiel indépendants de ce langage.
import platform
print("Version de Python utilisée ici pour les tests :",platform.python_version())
# Version de Python utilisée ici pour les tests : 3.6.3
Codage des nombres non entiers, les flottants ; précision ; gamme de valeurs
modifierSuivant la norme "IEEE 754", le codage informatique des nombres non entiers gère trois aspects : le signe, positif ou négatif, une mantisse et un exposant. Du fait du codage binaire, les exposants sont des puissances du nombre 2.0, et les mantisses sont, normativement, choisies comme comprises entre 1.0 (inclus) et 2.0 (exclus)<note>Il s'agit ici de la version normalisée du codage d'un nombre. Il existe aussi une possibilité de codage, dite dénormalisée, qui déroge plus ou moins de la norme précédente.</note>. Par exemple, le nombre 4,5 se code 1,125 ⨉ 2², soit (½⁰+½³) ⨉ 2², et le nombre 0,1875 se code 1,5 ⨉ 2⁻³ = (½⁰+½¹) ⨉ 2⁻³.
4.5.hex()
0.1875.hex()
(-0.1875).hex()
Laissons de côté le signe, géré par lui-même relativement indépendamment de la valeur absolue, et ne considérons maintenant que les nombres positifs. Les exposants sont des nombres entiers, positifs ou négatifs, sont compris dans un intervalle fini, relativement symétrique par rapport à 0, à savoir [-1022;+1023]. Les mantisses ne sont pas tous les nombres compris entre 0.5 (inclus) et 1.0 (exclus), mais seulement ceux qui peuvent s'écrire comme une somme finie de puissances entiers de 1/2, de la puissance 0 (toujours présente) jusqu'à la puissance maximale 53, les puissances entre 1 et 54 étant ou pas présentes. Ce qui donne environ neuf millions de milliards nombres différents, un soupçon plus que 9,0 ⨉ 10¹⁵, noté informatiquement 9.0e+15.
Ainsi, on voit que du fait de ce codage, l'ensemble des nombres codés est fini, et tous les nombres réels ne peuvent pas être représentés. L'ensemble des nombres réels représentables ainsi n'a pas de nom réservé en français ; en anglais, on utilise le terme "float". On entendant parfois de ce fait le terme de "nombre flottant" ou "flottant", terme que nous utiliserons. L'article WIKIPÉDIA auquel cette expression renvoie est celui sur la virgule flottante.
Un point important est que, pour un flottant donné, la précision de sa représentation, au sens de la finesse, est variable. C'est-à-dire que le plus proche flottant se trouve à un écart qui est variable en valeur absolue. Toutefois, le nombre de chiffres mathématiquement significatifs, qui est une grandeur de type logarithmique, est constant, et vaut 15 chiffres. La façon usuelle de déterminer ce nombre consiste ... calculer "epsilon"
eps = 1.0
epsilon = eps
while (1.0+eps) != 1.0 :
epsilon = eps
eps = eps / 2.
epsilon
# 2.220446049250313e-16
eps
# 1.1102230246251565e-16
(1.0 + eps) - 1.0
# 0.0
(1.0 + epsilon) - 1.0
# 2.220446049250313e-16
# NB: on pourra réfléchir aux résultats des deux calculs suivants :
(2.0 + epsilon) - 2.0
(0.5 + eps) - 0.5
Python offre une liste de constantes qui renseigne précisément sur la classe des nombres flottants, appelée sans surprise "float" :
import sys
sys.float_info
print(sys.float_info.max)
print(sys.float_info.epsilon)
Deux pseudo-nombres
modifierPseudo-nombres
modifierInf = float("inf")
1./0. == Inf
-1./0. == -Inf
Inf + 42.
17. * Inf
Inf + Inf
1./Inf
-1./Inf
Inf/Inf
NaN = float("nan")
1.+NaN
0.*NaN
NaN*NaN
1./NaN
Précision numérique
modifierOn l'appelle "Epsilon", ou parfois "eps", c'est le plus petit nombre positif tel que 1.+Epsilon est différent de 1.
Epsilon = 1.
while 1.+Epsilon/2. > 1.:
Epsilon = Epsilon/2.
print(Epsilon)
Dégradation de la précision numérique
modifierUn nombre proche de 1. est donc au mieux précis à ±Epsilon, ce qu'on dit encore « un nombre standard (en Python) a au mieux 16 chiffres significatifs ». Mais cette précision peut se dégrader rapidement par calcul et propagation de l'incertitude. En voici un exemple simple :
yi = xi = 0.1
for compteur in range(1,70):
xip = 4. * xi * (1.-xi)
yip = 4. * yi - 4. * yi**2
print(compteur,xip, yip, abs(xip-yip))
xi = xip
yi = yip
On constate la perte d'un chiffre significatif, en gros toutes les 3 à 4 itérations !!!