Chapitre 1010. Représentation des nombres. 1 Représentation des

publicité
PTSI1 – 2016/2017 – INFO
Lycée La Martinière-Monplaisir – Lyon
Chapitre 1010. Représentation des nombres.
Nous avons vu au chapitre 1, qu’en informatique, les données sont stockées sous
forme de bits (binary digit en anglais).
Nous allons maintenant détailler la façon
dont on traduit les nombres sous cette
forme.
1
Représentation des nombres entiers
1.1
Écriture d’un nombre entier naturel en base k
H L’écriture 1984 exprime un entier naturel formé de 4 unités, 8 dizaines, 9 centaines et 1 millier :
1984 =
Le choix de faire des paquets de 10 est cependant arbitraire : on peut tout aussi bien décider de
faire des paquets de 2, 5, 8, 16, 20 (chez les Mayas), 60 (chez les Babyloniens)... Si on choisit de
faire des paquets de k, on dit qu’on décompose un nombre en base k.
H Décomposons par exemple 45 en base 10, en base 5 et en base 2 :
45 =
=
=
Remarque : Vu l’importance de la base 2, il est bon de connaître les premières puissances de 2 :
20 21 22 23
24
25
26
27
28
29
210
On adopte parfois les notations suivantes : on écrit la suite des chiffres qui permet d’exprimer le
nombre dans la base k et on met k en indice :
4510 =
=
On distingue aussi la représentation binaire (en base 2) en la soulignant : 45 =
.
Remarque : Tout comme on peut additionner deux nombres écrits en base 10 en utilisant l’algorithme appris à l’école primaire, on peut effectuer l’addition entre deux nombres écrits avec la
représentation binaire.
Par exemple, 310 + 610 = 910 en décimal.
En binaire,
1
H Lorsqu’on veut écrire un nombre dans une base k avec k > 10, il nous "manque" en quelque sorte
des unités. Il faut les inventer ; par exemple, pour la base 16 (ou base hexadécimale, très utilisée
en informatique : elle apparaît dans les clés wifi), les chiffres "hexadécimaux" sont :
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A (dix), B (onze), C (douze), D (treize), E (quatorze), F (quinze)
On a 45 =
1.2
Algorithme de décomposition
Sur ces exemples assez simples, on peut s’en sortir comme cela, en tâtonnant.
La méthode générale pour décomposer un entier n en base k est la suivante :
– On effectue des divisions euclidiennes successives par k jusqu’à avoir un quotient nul ;
– On récupère les restes en remontant les calculs.
Illustrons par un exemple avec x = 58 et k = 5.
58 =
=
=
En combinant toutes les lignes :
58 =
=
Exercice : Avec cette méthode, déterminer l’écriture binaire de 22.
2
Il s’agit là d’un algorithme, qu’on peut décrire de la façon suivante :
Entrées: x, k entiers
Sorties: décomposition de l’entier x dans la base k
n←x
E ← mot vide
tantque n > 0 faire
q ← quotient dans la division euclidienne de n par k
r ← reste dans la division euclidienne de n par k
Ajouter r devant le mot E
n←q
fin tantque
Renvoyer le mot E
1.3
La représentation des entiers naturels
Rappelons que la mémoire des ordinateurs est constituée de circuits ne pouvant être que dans deux
états ; l’un est identifié à 0 et l’autre à 1, les deux chiffres en base deux, ce qui justifie l’appellation bit
(binary digit c’est-à-dire chiffre binaire en anglais).
Il est donc naturel de représenter les entiers naturels dans la mémoire des ordinateurs par des bits
contenant leur décomposition en base 2. Par exemple,
13 =
donc il faut
bits pour coder 13, il s’écrira en machine :
Remarque : Avec n bits, on peut représenter tous les entiers de 0 à l’entier représenté en base 2 par
11 . . . 1 =
| {z }
n bits
1.4
Cas des entiers relatifs
H Codage naïf (non retenu) :
Pour représenter les entiers relatifs, une solution est d’ajouter un bit donnant le signe : par
exemple 0 pour les positifs ou nul et 1 pour les négatifs.
Défauts :
– 0 a deux représentations :
– L’addition binaire n’est plus respectée.
3
H Codage utilisé : le complément à deux
Pour n = 8 bits, cette méthode va permettre de coder les entiers entre −27 = −128 et 27 −1 = 127.
• Si l’entier x à coder est positif ou nul :
• Si l’entier x à coder est négatif, entre −128 = −27 et −1 :
Généralisons avec n bits. On pourra représenter les entiers relatifs x entre −2n−1 et 2n−1 − 1.
– Si x ≥ 0 alors x est représenté par sa notation binaire.
– Si x < 0 alors x est représenté par la notation binaire de x + 2n , qui est un entier entre
2n−1 et 2n − 1.
+2n
2n codes
0
−2n−1
...
−1
2n−1 entiers
2n−1 − 1
1
...
2n−1
2n − 1
2n−1 entiers
2n−1 entiers
2n entiers à représenter
Exemple : donner la représentation de 67, de −31 et de 0 avec cette convention (sur 8 bits).
4
Remarque : Le langage Python ne connaît pas le type entier naturel. Ainsi, un entier naturel est
considéré comme un entier relatif.
1.5
Dépassement de capacité et entiers longs
Les langages de programmation se fixent en général un nombre d’octets sur lesquels ils vont coder les
entiers. Par exemple, par défaut, en Python, c’est 4 octets, c’est-à-dire 32 bits, d’où une plage d’entiers
de −2 147 483 648 à 2 147 483 647. Si l’on dépasse ces limites, on passe automatiquement, en Python,
à un autre type que le type entier : le type long ("entiers longs").
La représentation des entiers n’est donc en pratique limitée que par la taille de la mémoire disponible
en machine.
2
Représentation des nombres réels
2.1
Approximation binaire d’un réel de l’intervalle [0, 1[
H Pour écrire un nombre réel x de façon approchée, nous avons l’habitude d’utiliser l’écriture décimale de x et de la tronquer (ou de faire un arrondi) ; par exemple, une valeur approchée de π
est 3, 14 (à 10−2 près).
Ainsi le nombre π − 3 a pour valeur approchée 0, 14 =
4
1
+ 2 (à 10−2 près), et c’est un réel
10 10
de [0, 1[.
H Généralisons : tout réel x ∈ [0, 1[ aura une valeur approchée (à 10−N près) de la forme :
b1
b2
bN
+
+ ··· + N
101 102
10
où b1 , b2 , ... , bN sont des entiers entre 0 et 9 : x ≈
On peut faire exactement la même chose en remplaçant les "10" par des "2". Tout réel x ∈ [0, 1[
aura une valeur approchée (à 2−N près) de la forme :
où b1 , b2 , ... , bN sont des entiers
On écrira : x ≈ 0, b1 b2 . . . bN (on souligne pour préciser qu’on est en base 2). On parle de développement binaire / en base 2.
H Exemple : Calculer le nombre x qui a pour développement binaire : 0, 11011.
5
2.2
Codage d’un réel quelconque
Vous connaissez sans doute la notation scientifique d’un nombre décimal x ; par exemple :
; −423 =
12789 =
; 0, 0064001 =
De manière générale, la notation scientifique de x est de la forme x = ε × m × 10e , où :
• ε=
• m
s’appelle le signe (il donne le signe du réel x) ;
s’appelle la mantisse ;
• e
s’appelle l’exposant.
Une telle écriture existe toujours et est unique pour x non nul.
De la même manière, en informatique, on utilise le fait qu’un réel x non nul s’écrit toujours de
manière unique sous la forme :
x = ε × m × 2e ,
où :
• ε=
• m
• e
s’appelle le signe (il donne le signe du réel x) ;
s’appelle la mantisse ;
s’appelle l’exposant.
Remarque : L’écriture précédente s’appelle écriture en virgule flottante normalisée. Le terme « flottant »
vient du fait que la place de la virgule dépend de l’exposant choisi. Le terme « normalisé » vient du
choix d’une mantisse appartenant à [1, 2[.
Coder un réel non nul dans un ordinateur revient donc à coder son signe, sa mantisse et son exposant.
Avec la norme IEEE 754, on se donne 64 bits pour stocker ces trois données, dans l’ordre :
• le codage du signe ne nécessite qu’un seul bit (en général, 0 code le signe positif et 1 le signe
négatif) ;
• le codage de l’exposant se fait sur 11 bits ;
• le codage de la mantisse se fait sur 52 bits.
H L’exposant : Il y a 11 bits pour représenter l’exposant. Si x est non nul et a pour exposant e
compris entre −1022 et 1023 alors on prend comme code l’écriture binaire de e + 1023 qui est un
entier entre 1 et 2046.
H La mantisse : La mantisse m est nécessairement un nombre de l’intervalle [1, 2[. Sachant cela, il
revient au même de coder m − 1 qui, lui, est dans [0, 1[. On utilise donc la méthode présentée
plus haut pour coder m − 1. Avec 52 bits, cela permet donc d’avoir une précision de 2−52 .
Remarque : Dans la plage de 11 bits dévolue à l’exposant, les entiers 0 . . . 0 = 0 et 1 . . . 1 = 2047 ne
sont pas utilisés dans l’explication précédente.
6
L’exposant e égal à −1023 donc codé 0 . . . 0 = 0 avec une mantisse dont tous les bits sont nuls représente
le nombre 0. Il y a deux façons de représenter 0 puisque le bit donnant le signe peut prendre les deux
valeurs 0 ou 1 (les 63 autres bits sont à zéro).
L’exposant décalé égal à 2047 est utilisé pour les situations particulières (+∞, −∞, d’autres choses
plus compliquées : Nan - Not a number...). En mémoire, tous les bits dévolus à l’exposant sont égaux
à 1.
Exemple : Trouver le nombre x qui est représenté en flottant par :
1 1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 ...que des 0 pour avoir au final 64 bits
Rappelons que ceci n’est que la façon dont les choses sont codées ; lorsque le résultat d’un calcul en
Python est un flottant, l’interpréteur ne nous renvoie pas ce code ni même la représentation ε.m.2e ; il
écrit le nombre en écriture décimale ou bien avec la notation scientifique exponentielle :
Python shell
>>> -41 / 2
-20.5
>>> 1.5 * 2**167
2.8060831436753336e+50
7
3
Limites de la représentation des réels
3.1
Overflow et underflow
Le problème le plus évident est celui du dépassement arithmétique, qui survient lorsque les 64 bits ne
suffisent plus pour coder le réel x.
Il y a dépassement ou overflow lorsque la mantisse et/ou l’exposant est "trop grand" 1 .
Dans ce cas, l’ordinateur répondra +∞ ou −∞ selon le signe, ou renverra un message d’erreur :
Python shell
>>> 2.0**1023
8.98846567431158e+307
>>> 2.0**1024
Traceback (most recent call last):
File "<pyshell#27>", line 1, in <module>
2.0**1024
OverflowError: (34, ’Result too large’)
>>> sum(1/(2**k) for k in range(54))*2**1023
inf
De même, si l’exposant ou la mantisse est "trop petite", il y a dépassement par valeurs inférieures, ou
underflow.
Python shell
>>> 2.0**(-1100)
0.0
3.2
Inexactitude de la représentation, arrondis
Un autre problème, même pour des nombres ni trop grands ni trop proches de 0, est que la représentation choisie est souvent non exacte.
Pour -20,5, la représentation en mémoire est exacte, car le développement en base 2 de la mantisse
1
1
est finie. Mais pour certains nombres, le développement est infini :
m=1+ +
4 32
1
1
1
1
1
1
1
1
1
+
+
+
+
+
+
+
+
+ ...
2 24 25 28 29 212 213 216 217
i.e. : 1, 6 = 1, 10011001100110011... (ici le développement est périodique)
1, 6 = 1 +
1
1
= 0, 33333..., mais 0, 33333 n’est qu’une approximation de .
3
3
Lorsqu’il stocke le nombre 1, 6, l’ordinateur doit "tronquer" ou arrondir le développement infini, et on
C’était déjà le cas en base 10 : on a
n’a qu’une valeur approchée de 1,6. On constate que 1,6 est arrondi à :
1, 100110011001100110011001100110011001100110011001101 (la différence est à la toute fin !)
1. Plus précisément, quand l’exposant est strictement supérieur à 1023 (qui est le plus grand représentable sur 11
bits), ou s’il vaut 1023 mais que la mantisse est supérieure à la plus grande représentable sur 52 bits (que des 1).
8
En conséquence de ces arrondis, lorsque des calculs font intervenir ce nombre, on peut s’attendre à des
erreurs :
Python shell
>>> 1.6 + 1.6 + 1.6
4.800000000000001
>>> 0.1 + 0.1 + 0.1
0.30000000000000004
>>> import math as m
>>> (m.sqrt(10))**2
10.000000000000002
Ce n’est pas anodin lorsqu’on fait de la programmation : si on veut tester l’égalité de deux flottants et
qu’il y a ainsi une légère différence, cela rendra un résultat erroné !
Python shell
>>> 0.1 + 0.1 + 0.1 == 0.3
False
>>> (m.sqrt(10))**2 == 10.0
False
D’autres erreurs d’arrondis peuvent survenir lors de calculs, notamment entre des nombres dont les
ordres de grandeur sont très différents. Observons :
Python shell
>>> 1 + 2**(-54) - 1
0.0
>>> 1 - 1 + 2**(-54)
5.551115123125783e-17
Explication : C’est dû au fait que l’ordinateur effectue les opérations dans l’ordre de lecture !
• Pour 1 + 2**(-54) - 1 :
• Pour 1 - 1 + 2**(-54) :
9
Python shell
>>> 2**(60) + 1 - 2**(60)
0.0
Explication :
En conclusion, il faut retenir qu’il n’est pas possible, lorsqu’on travaille sur les flottants, de savoir de
façon certaine si le résultat d’un calcul est égal à sa valeur "théorique".
3.3
Le problème de la comparaison à 0
Une des conséquences principales est qu’un test a == 0 n’a pas de sens si a est un flottant, puisse que
celui-ci a pu souffrir d’erreurs d’arrondis. Illustrons ce problème avec des équations du second degré.
Le programme suivant renvoie le nombre de solutions réelles d’une équation du second degré donnée
par ses coefficients a, b, c :
def nbsolutions(a,b,c):
Delta = b**2 - 4*a*c
if Delta == 0:
nb = 1
elif Delta > 0:
nb = 2
else:
nb = 0
return(nb)
Les trinômes x2 + 1.4x + 0.49 et x2 + 0.2x + 0.01 sont de discriminant nul, le programme devrait donc
répondre 1. Cependant, lorsqu’on teste dans l’interpréteur :
Python shell
>>> nbsolutions(1, 1.4, 0.49)
0
>>> nbsolutions(1, 0.2, 0.01)
2
Commentaires :
10
Que suggérez-vous pour éviter ce problème ?
3.4
Quelques catastrophes dues à l’arithmétique flottante
Il y a un petit nombre connu de catastrophes dans la vie réelle qui sont attribuables à une mauvaise
gestion de l’arithmétique des ordinateurs (erreurs d’arrondis, d’annulation). Dans le premier exemple
ci-dessous cela s’est payé en vies humaines.
H Missile Patriot : En février 1991, pendant la Guerre du Golfe, une batterie américaine de missiles
Patriot, à Dharan (Arabie Saoudite), a échoué dans l’interception d’un missile Scud irakien. Le
Scud a frappé un baraquement de l’armée américaine et a tué 28 soldats. La commission d’enquête
a conclu à un calcul incorrect du temps de parcours, dû à un problème d’arrondi. Les nombres
étaient représentés en virgule fixe sur 24 bits, donc 24 chiffres binaires. Le temps était compté par
l’horloge interne du système en 1/10 de seconde. Malheureusement, 1/10 n’a pas d’écriture finie
dans le système binaire : 1/10 = 0,1 (dans le système décimal) = 0,0001100110011001100110011...
(dans le système binaire). L’ordinateur de bord arrondissait 1/10 à 24 chiffres, d’où une petite
erreur dans le décompte du temps pour chaque 1/10 de seconde. Au moment de l’attaque, la
batterie de missile Patriot était allumée depuis environ 100 heures, ce qui avait entraîné une
accumulation des erreurs d’arrondi de 0,34 s. Pendant ce temps, un missile Scud parcourt environ
500 m, ce qui explique que le Patriot soit passé à côté de sa cible. Ce qu’il aurait fallu faire c’était
redémarrer régulièrement le système de guidage du missile.
H Explosion d’Ariane 5 : Le 4 juin 1996, une fusée Ariane 5, à son premier lancement, a explosé 40
secondes après l’allumage. La fusée et son chargement avaient coûté 500 millions de dollars. La
commission d’enquête a rendu son rapport au bout de deux semaines. Il s’agissait d’une erreur
de programmation dans le système inertiel de référence. À un moment donné, un nombre codé
en virgule flottante sur 64 bits (qui représentait la vitesse horizontale de la fusée par rapport
à la plate-forme de tir) était converti en un entier sur 16 bits. Malheureusement, le nombre en
question était plus grand que 32768 (overflow), le plus grand entier que l’on peut coder sur 16
bits, et la conversion a été incorrecte.
H Bourse de Vancouver : Un autre exemple où les erreurs de calcul on conduit à une erreur notable
est le cas de l’indice de la Bourse de Vancouver. En 1982, elle a créé un nouvel indice avec une
valeur nominale de 1000. Après chaque transaction boursière, cet indice était recalculé et tronqué
après le troisième chiffre décimal et, au bout de 22 mois, la valeur obtenue était 524,881, alors
que la valeur correcte était 1098.811. Cette différence s’explique par le fait que toutes les erreurs
d’arrondi étaient dans le même sens : l’opération de troncature diminuait à chaque fois la valeur
de l’indice
11
Téléchargement