Vous avez vu que Python pouvait manipuler des nombres décimaux particuliers appelés nombres flottants qui correspondent aux représentations en machine de nombres réels. Dans ce chapitre, nous allons voir comment on peut écrire en binaire un nombre réel et comment sont encodés les nombres flottants.
Ce diaporama contient tous les éléments à connaître de ce chapitre.
Source : https://info-mounier.fr/premiere_nsi/types_base/data/T1_C3_Representation_reels_diapo_prof.pdf
Le cours qui suit reprend en détails tous les éléments présentés dans le diaporama (à l'écrit comme à l'oral). Des compléments sont également donnés. Sa lecture est donc essentielle pour réviser et ancrer les connaissances.
En notation décimale, les chiffres à droite de la virgule représentent les dixièmes, les centièmes, les millièmes, etc.
De la même manière, en binaire, les chiffres à droite de la virgule représentent les demis, les quarts, les huitièmes, les seizièmes, etc.
Par exemple, le nombre $(1,1101)_2$ est le nombre $1$ et $\frac{1}{2}$ et $\frac{1}{4}$ et $\frac{1}{16}$.
Les chiffres à gauche de la virgule correspondent à des puissances de 2 positives, ceux situés à droite correspondent à des puissances de 2 négatives.
Exemple : À quel nombre décimal correspond l’écriture binaire $101,1101$ ?
Puissances de 2 | $2^2$ | $2^1$ | $2^0$ | $2^{-1}$ | $2^{-2}$ | $2^{-3}$ | $2^{-4}$ | |
---|---|---|---|---|---|---|---|---|
Écriture binaire | $1$ | $0$ | $1$ | $,$ | $1$ | $1$ | $0$ | $1$ |
On a donc : $(101,1101)_2=2^2+2^0+2^{-1}+2^{-2}+2^{-4}=4+1+\dfrac{1}{2}+\dfrac{1}{4} + \dfrac{1}{16} =5,8125$.
On commence par convertir la partie entière (à gauche de la virgule). Pour la partie décimale (à droite de la virgule), on effectue des multiplications successives par 2. Après chaque multiplication, la partie entière n’est pas reportée. On poursuit le calcul jusqu’à obtenir 1.
Exemple : Quelle est l’écriture binaire du nombre décimal $5,8125$ ?
On convertit 5 en binaire. Facile : $5=({\color{red} 101})_2$.
Il reste à convertir la partie décimale $0,8125$. On fait des multiplications successives par 2 sans reporter la partie entière :
$$\begin{aligned} 0,8125 \times 2 &= {\color{blue} \mathbf{1}},625 \newline 0,625 \times 2 &= {\color{blue} \mathbf{1}},25 \newline 0,25 \times 2 &= {\color{blue} \mathbf{0}},5 \newline 0,5 \times 2 &= {\color{blue} \mathbf{1}} \end{aligned}$$
Les parties entières ($0$ ou $1$) donnent les chiffres après la virgule de l’écriture binaire de $0,8125$, à lire de haut en bas :
$$0,8125 = (0,{\color{blue} 1101})_2$$
Finalement on a : $5,8125 = ({\color{red} 101},{\color{blue} 1101})_2$.
✍️ Pour s'entraîner : faites l'exercice 1
Il existe des nombres dont l’écriture binaire sera infinie (et périodique). Par exemple, $1,2=(1,001100110011 \ldots)_2$, le cycle « $0011$ » se répétant à l’infini.
Comme on ne peut pas représenter en machine un mot infini, il ne sera pas possible de représenter de manière exacte certains nombres réels (beaucoup d’entre eux !). Nous verrons cela un peu plus tard avec tous les problèmes que cela engendre.
Dans un ordinateur, les nombres à virgule (réels) sont codés en virgule flottante. On parle de nombres flottants (le type float
de Python). La représentation binaire en machine d’un nombre flottant s’inspire de l’écriture scientifique des nombres décimaux dont voici quelques rappels.
L’écriture scientifique permet d’uniformiser la façon d’écrire des nombres décimaux. Par exemple :
Dans cette écriture, on distingue :
Ainsi, de manière générale, l’écriture scientifique d’un nombre décimal est : $$± m \times 10^n.$$
La norme IEEE 754 est la plus utilisée pour représenter les nombres flottants. Ils sont représentés sur 32 bits (format appelé « simple précision » ou binary32) ou sur 64 bits (format appelé « double précision », ou binary64) sous la forme :
$$s.m×2^n$$
où :
0
pour $+$ et 1
pour $-$) ; Ainsi, en machine, un flottant est représenté en format 32 bits (simple précision) par un mot binaire de la forme
signe | exposant | mantisse |
---|---|---|
1 bit | 8 bits | 23 bits |
et en format 64 bits (double précision) par un mot binaire de la forme
signe | exposant | mantisse |
---|---|---|
1 bit | 11 bits | 52 bits |
Exemple (début) : Représentation machine du nombre $5,8125$
On sait que $5,8125$ est positif donc le bit de signe sera 0
:
signe | exposant | mantisse |
---|---|---|
0 |
? | ? |
Rappelons que $5,8125=(101,1101)_2$. Par analogie avec l’écriture scientifique, on peut aussi écrire ce nombre binaire : $1,011101 \times 2^2$ en décalant la virgule de deux rangs vers la gauche. En faisant cela, on a fait apparaître :
Il reste maintenant à voir comment sont codées la mantisse et l'exposant !
Pour représenter les flottants, la base choisie est la base 2 (contrairement à l’écriture scientifique qui est la base 10) donc la mantisse est dans l’intervalle $[1;2[$. Il s’agit donc d’un nombre de la forme :
$$m=1,xx…xx$$
Comme cette mantisse commence toujours par le chiffre $1$, il a été choisi de ne pas coder ce « $1$ » mais uniquement les chiffres après la virgule.
Exemple (suite) : Représentation machine du nombre $5,8125$
La mantisse de ce nombre est $m=1,{\color{blue} 011101}$. Comme le « $1$ » à gauche de la virgule n’est pas codé, la mantisse sera codée par 01110100...0
en ajoutant (à droite !!) autant de zéros que nécessaire pour arriver à 23 bits (simple précision) ou 52 bits (double précision) :
signe | exposant | mantisse (23 ou 52 bits) |
---|---|---|
0 |
? | 01110100...0 |
L’exposant est codé sur 8 bits ou 11 bits selon le format utilisé. Sur 8 bits, on peut coder 256 valeurs : les entiers compris entre $-127$ et $128$. Sur 11 bits, on peut coder 2048 valeurs : les entiers compris entre $-1023$ et $1024$.
Attention, les plages de valeurs des entiers sont différentes de celles du codage en complément à deux.
En effet, en complément à deux sur 8 bits (resp. 11 bits) on coderait les entiers compris entre $-128$ et $127$ (resp. entre $-1024$ et $1023$), comme on l'a vu dans le cours sur la représentation des entiers relatifs.
L’exposant est un entier relatif mais la norme IEEE 754 n’utilise pas l’encodage par complément à 2 des entiers relatifs. Elle prévoit un décalage qui dépend de l’encodage utilisé :
L’objectif est d’obtenir un nombre positif pour coder l’exposant. En effet, dans le format simple précision, en ajoutant $127$ à tous les entiers de la plage de valeurs $[-127 .. 128]$ on obtient des valeurs positives $[0..255]$ (de même, en ajoutant $1023$ dans le format double précision on obtient les valeurs $[0..2047]$).
Exemple (suite et fin) : Représentation machine du nombre $5,8125$
Rappelons que $5,8125=(101,1101)_2=(1,011101×2^2 )_2$ donc l’exposant est $2$.
La norme IEEE 754 ne prévoit pas de coder $+2$ en complément à deux mais d’ajouter $127$ en format simple précision ($1023$ en format double précision) : on obtient alors $2+127=129$.
Il ne reste plus qu’à coder l’entier $129$ en binaire : $({\color{blue} 10000001})_2$. On ajoute éventuellement des zéros (à gauche !!) pour compléter les 8 bits réservés à l’exposant (ici c'est inutile car on obtient 8 bits directement : 10000001
)
Dans le format simple précision (sur 32 bits), on obtient finalement :
signe | exposant | mantisse (23 bits) |
---|---|---|
0 |
10000001 |
01110100...0 |
Dans le format double précision (sur 64 bits), on ajouterait $1023$ à la puissance pour obtenir $1025$ ($2+1023$) dont l’écriture binaire est $({\color{orange}10000000001})_2$ (qui est déjà sur 11 bits, donc inutile d'ajouter des zéros à gauche). On obtiendrait :
signe | exposant | mantisse (52 bits) |
---|---|---|
0 |
10000000001 |
01110100...0 |
Bilan :
En format simple précision, le nombre réel $5,8125$ est représenté sur 32 bits en machine par le mot binaire :
0 10000001 01110100000000000000000
En format double précision, il est représenté sur 64 bits en machine par le mot :
0 10000000001 0111010000000000000000000000000000000000000000000000
✍️ Pour s'entraîner : faites les exercices 2, 3, 4 et 5
Tous les nombres réels dont l’écriture en base est infinie ne peuvent être représentés de manière exacte en machine. Par exemple, on a vu que le nombre décimal $1,2$ a une écriture binaire infinie : $1,2=(1,001100110011…)_2=+1,001100110011\ldots \times 2^0$
La mantisse de ce nombre est $m=1,001100110011\ldots$ donc elle est infinie. Or, il n’y a que 23 ou 52 bits réservés pour coder la mantisse. L’ordinateur doit donc tronquer la mantisse à 23 ou 52 bits. Cela signifie qu’en format simple précision, la mantisse du nombre $1,2$ est codée par le mot binaire de 23 bits
$$\underbrace{001100110011001100110011001}_\textrm{23 bits}\xcancel{100110011 \ldots}$$
les autres bits ne pouvant pas être codés.
Seule une valeur tronquée de la mantisse peut être codée et donc la représentation en machine du nombre réel $1,2$ n’est qu’une approximation du réel $1,2$. Autrement dit, le nombre flottant 1.2
n’est qu’une valeur approchée du nombre réel $1,2$.
Cet exemple n’en est qu’un parmi tant d’autres : il y a une infinité de nombres réels qu’il est impossible de représenter de manière exacte en machine.
Dans un langage de programmation, il faut se méfier lorsque l’on manipule des nombres flottants. Par exemple, ajouter ou soustraire des flottants peut conduire à des résultats surprenants :
>>> 1.2 - 1.0
0.19999999999999996
>>> 0.5 - 0.2 - 0.2 - 0.1
-2.7755575615628914e-17
>>> 9007199254740992.0 + 1.0 + 1.0
9007199254740992.0
>>> 1 + 1 + 9007199254740992.0
9007199254740994.0
De même, il ne faut (théoriquement) jamais tester l'égalité entre deux flottants :
>>> 0.1 + 0.2 == 0.3
False
>>> from math import sqrt
>>> sqrt(2.0) ** 2 == 2.0
False
>>> 9007199254740992.0 + 1.0 == 9007199254740992.0
True
Il est important de noter que ces résultats ne sont pas des erreurs de Python, mais sont liées à la représentation approximative des nombres flottants en machine. Un ordinateur ne peut pas faire mieux !
Les calculs avec des flottants conduisent souvent à des résultats incohérents avec les résultats que l'on obtiendrait avec des nombres réels.
Voici l'écriture binaire en format double précision de deux flottants :
Nombre flottant | Représentation au format double précision (mantisse sur 52 bits) |
---|---|
1.5 |
$+1.1000000000000000000000000000000000000000000000000000 \times 2^0$ |
1.5000000000000002 |
$+1.1000000000000000000000000000000000000000000000000001 \times 2^0$ |
Comme il n'y a pas de représentation possible entre les deux proposées (la mantisse de la seconde est celle qui suit immédiatement celle de la première), il n'existe donc pas de flottant entre 1.5
et 1.5000000000000002
.
Le flottant 1.5000000000000001
n'a donc pas de représentation propre, il est représenté comme 1.5
pour un ordinateur (alors que le réel $1,5000000000000001$ est bien sûr différent du réel $1,5$) :
>>> 1.5 == 1.5000000000000001
True
La précision possible avec une mantisse sur 52 bits se situe au niveau dernier bit qui vaut $2^{-52}$ soit environ $2 \times 10^{-16}$. L’écart entre 1.5
et 1.5000000000000002
étant égal à $2 \times 10^{-16}$, on ne peut pas trouver un flottant compris entre les deux.
Références :
Germain Becker & Sébastien Point, Lycée Emmanuel Mounier, Angers
Voir en ligne : info-mounier.fr/premiere_nsi/types_base/reels