5 - Gestion des flottants
Nous avons vu que l'ordinateur stocke les données sous forme d'octets.
Nous avons vu comment encoder des valeurs entières positives et négatives.
Mais commment faire pour les nombres réels ?
Attention, contrairement à la plupart des activités, la fiche de cours ci-dessous, adaptée à la 1er NSI, est terriblement édulcorée par rapport à cette page HTML.
Documents de cours : open document ou pdf
1 - Réels et flottants, c'est pareil ?
Certains nombres possèdent un nombre infini de chiffres, comme π.
L'ordinateur ne peut pas stocker l’ensemble de ces chiffres.
Alors, comment fait-il ?
Et bien, nous allons voir qu’il fait cela approximativement !
Les entiers naturels sont les entiers positifs.
Ils appartiennent à l'ensemble noté ℕ.
- 0 appartient à cet ensemble : 0 ∈ ℕ
- 5 appartient à cet ensemble : 5 ∈ ℕ
- -5 n'appartient pas à cet ensemble : -5 ∉ ℕ
- 5.2 n'appartient pas à cet ensemble : 5.2 ∉ ℕ
- ⅓ n'appartient pas à cet ensemble : ⅓ ∉ ℕ
- π n'appartient pas à cet ensemble : π ∉ ℕ
Les entiers relatifs sont les entiers positifs ou négatifs.
Ils appartiennent à l'ensemble noté ℤ.
- 0 appartient à cet ensemble : 0 ∈ ℤ
- 5 appartient à cet ensemble : 5 ∈ ℤ
- -5 appartient à cet ensemble : -5 ∈ ℤ
- 5.2 n'appartient pas à cet ensemble : 5.2 ∉ ℤ
- ⅓ n'appartient pas à cet ensemble : ⅓ ∉ ℤ
- π n'appartient pas à cet ensemble : π ∉ ℤ
Les décimaux caractérisent l'ensemble des nombres qui ne possèdent qu'un nombre limité de chiffres après la virgule. Ils peuvent tous s'exprimer comme le quotient d'un entier par une puissance de 10.
Ils appartiennent à l'ensemble noté ⅅ
- 0 appartient à cet ensemble : 0 ∈ ⅅ car on peut écrire 0 = 0 / 1
- 5 appartient à cet ensemble : 5 ∈ ⅅ car on peut écrire 5 = 5 / 1
- -5 appartient à cet ensemble : -5 ∈ ⅅ car on peut écrire -5 = -5 / 1
- 5.2 appartient à cet ensemble : 5.2 ∈ ⅅ car on peut écrire 5.2 = 52 / 10
- ⅓ n'appartient pas à cet ensemble : ⅓ ∉ ⅅ
- π n'appartient pas à cet ensemble : π ∉ ⅅ
Les rationnels caractérisent l'ensemble des nombres qu'on peut écrire comme un quotient de deux nombres entiers relatifs.
Ils appartiennent à l'ensemble noté ℚ
- 0 appartient à cet ensemble : 0 ∈ ℚ car on peut écrire 0 = 0 / 1
- 5 appartient à cet ensemble : 5 ∈ ℚ car on peut écrire 5 = 5 / 1
- -5 appartient à cet ensemble : -5 ∈ ℚ car on peut écrire -5 = -5 / 1
- 5.2 appartient à cet ensemble : 5.2 ∈ ℚ car on peut écrire 5.2 = 52 / 10
- ⅓ appartient à cet ensemble : ⅓ ∈ ℚ car on peut écrire 1/3 = 1 / 3
- π n'appartient pas à cet ensemble : π ∉ ℚ
Les irrationnels sont les nombres qui ne sont pas rationnels : on ne peut pas les exprimer comme un quotient de deux entiers. On peut y placer π, racine de 2 ou racine de 17 par exemple.
Ainsi même si on ne peut pas écrire le résultat complet de 3/7, on peut toujours noter le nombre sous la forme 3/7, ce qui est sa valeur exacte. 3/7 n'est donc pas un nombre irrationnel.
Les réels caractérisent l'ensemble des nombres : les nombres rationnels et les nombres irrationnels.
Ils appartiennent à l'ensemble noté ℝ
- 0 appartient à cet ensemble : 0 ∈ ℝ
- 5 appartient à cet ensemble : 5 ∈ ℝ
- -5 appartient à cet ensemble : -5 ∈ ℝ
- 5.2 appartient à cet ensemble : 5.2 ∈ ℝ
- ⅓ appartient à cet ensemble : ⅓ ∈ ℝ
- π appartient à cet ensemble : π ∈ ℝ
On voit donc qu'on peut donc gérer le nombre 5 de plusieurs façons puisqu'il appartient à plusieurs ensembles.
On peut le gérer comme :
- Comme un entier naturel : 5 (type int en Python) : pas de bit de signe.
- Comme un entier relatif : +5 (type int aussi en Python) : un bit de signe.
- Comme un réel : 5.0 (type float en Python)
Comment encoder ce dernier cas ?
Quelques exemples pour réactiver le problème déjà rencontré :
>>> 1/3
0.3333333333333333
>>> 1/5
0.2
>>> 1/6
0.16666666666666666
>>> import math
>>> math.sqrt(16)
4.0
>>> math.sqrt(25)
5.0
>>> math.sqrt(26)
5.0990195135927845
>>> racine_de_2 = math.sqrt(2)
>>> racine_de_2
1.4142135623730951
>>> racine_de_2 * racine_de_2
2.0000000000000004
Comme on le voit, le premier problème vient du fait qu'on n'obtient pas véritablement le réel mais une approximation du réel : on ne stocke pas toutes les décimales, surtout si elles sont en nombre infini !
Deuxième problème (lié au premier)
>>> 0.3
0.3
>>> 0.1
0.1
>>> 3*0.1
0.30000000000000004
On ne peut donc pas faire de tests d'égalité sur les floats, ils pourraient donner un faux résultat.
Enfin, et encore plus troublant :
>>> 1/8
0.125
>>> 1/16
0.0625
>>> 1/18
0.05555555555555555
2 - Décodage d'un flottant
Les flottants ne sont qu'une généralisation des integers mais en intégrant les puissances de 2 inférieures à 0 également.
2.1 - Puissance 2 avec exposant négatif
Il est important de les connaître ou de savoir les retrouver.
Vous connaissez déjà les puissances de 2 avec exposant positif :
- 2 pour 21
- 4 pour 22
- 8 pour 23
- ...
Voici quelques puissances négatives de 2
- 20 = 1
- 2-1 = 0.5
- 2-2 = 0.25
- 2-3 = 0.125
- 2-4 = 0.0625
- 2-5 = 0.03125
- 2-6 = 0.015625
- 2-7 = 0.0078125
- 2-8 = 0.00390625
- 2-9 = 0.001953125
- ...
2.2 - Exemples simples de représentation en base 2
Il suffit d'étendre aux puissances d'exposant négatif à droite de la case valant 20.
Nombre M = | 1 | 0 | 1 | 0, | 1 | 0 | 1 | 1 |
Les bits codent | 8 | 4 | 2 | 1 | 0.5 | 0.25 | 0.125 | 0.0625 |
Les bits codent | 23 | 22 | 21 | 20 | 2-1 | 2-2 | 2-3 | 2-4 |
On obtient donc | 8 | 2 | 0.5 | 0.125 | 0.0625 |
On obtient donc N = 8 + 2 + 0.5 + 0.125 + 0.0625
Au final, on peut écrire N = 1010 , 1011 2 = 10.6875 10
Comment représenter N = 6,5 en base 2 ?
Représentation de 6 : 110
Représentation de 0,5 : ,1
Représentation de 6,5 : N = 6.5 10 = 110 , 1 2
01° Calculer la valeur du réel encodé par 101 , 011
2.
...CORRECTION...
Nombre M = | 0 | 1 | 0 | 1, | 0 | 1 | 1 | 0 |
Les bits codent | 8 | 4 | 2 | 1 | 0.5 | 0.25 | 0.125 | 0.0625 |
Les bits codent | 23 | 22 | 21 | 20 | 2-1 | 2-2 | 2-3 | 2-4 |
Avant la virgule :
101
2 = (4 + 0 + 1) 10 = 5 10
Après la virgule
011
2 = (0 + 0.25 + 0.125) 10 = 0.375 10.
Au final : N = 101 , 011
2 = 5.375 10
02° Calculer la valeur du réel encodé par 1111 , 1111
2.
...CORRECTION...
Nombre M = | 1 | 1 | 1 | 1, | 1 | 1 | 1 | 1 |
Les bits codent | 8 | 4 | 2 | 1 | 0.5 | 0.25 | 0.125 | 0.0625 |
Les bits codent | 23 | 22 | 21 | 20 | 2-1 | 2-2 | 2-3 | 2-4 |
Avant la virgule :
1111
2 = (8 + 4 + 2 + 1) 10 = 15 10
Après la virgule
1111
2 = (0.5 + 0.25 + 0.125 + 0.0625) 10 = 0.9375 10.
Au final : N = 1111 , 1111
2 = 15.9375 10
03° Calculer la valeur du réel 0,750 10 exprimé en base 2. Vous utiliserez la technique de l'activation progressive des bits les plus forts.
...CORRECTION...
Nombre M = | 0 | 0 | 0 | 0, | 1 | 1 | 0 | 0 |
Les bits codent | 8 | 4 | 2 | 1 | 0.5 | 0.25 | 0.125 | 0.0625 |
Les bits codent | 23 | 22 | 21 | 20 | 2-1 | 2-2 | 2-3 | 2-4 |
On veut 0,75.
On active le bit de 0.5. Il reste 0.75 - 0.5 = 0.25.
On active le bit de 0.25. C'est fini.
Ok, nous savons maintenant passer d'une base 2 à une base 10. Nous savons faire l'inverse sur les cas simples (0.5, 0.750...) mais sur les cas plus complexes ?
3 - Encodage d'un flottant
3.1 - Décomposition binaire d'un nombre inférieur à 1
Pour trouver la représentation binaire d'un nombre voulu inférieur à 1 mais positif, vous pouvez appliquer la méthode suivante :
- Premier bit avant la virgule ← 0
- nombre ← nombre voulu
- TANT QUE nombre > 0
- nombre ← nombre * 2
- bit suivant ← partie entière de nombre (0 ou 1)
- SI nombre >= 1
- nombre ← nombre - 1
Implanté en Python dans une fonction, on peut obtenir ceci
1
2
3
4
5
6
7
8
9
10
11
12
13 | def decomposition(nombre:float) -> str:
"""Décompose nombre floattants commençant par 0 en suite de bits"""
bits = "0,"
while nombre > 0:
nombre = nombre * 2
bits = bits + str(nombre)[0] # On rajoute l'unité au string bits
if nombre >= 1 :
nombre = nombre - 1
return bits
# Exemples d'utilisation
print(decomposition(0.5))
print(decomposition(0.25))
|
3.2 - Exemple
Exemple avec N = 0.25 10
- Premier bit avant la virgule ← 0
- nombre ← 0.25
- Début TANT QUE avec nombre = 0.25 : on commence car nombre > 0
- On double nombre : nombre ← 0.5
- On récupére un nouveau 0 : 0,0
- Retour TANT QUE avec nombre = 0.5 : on continue car nombre > 0
- On double nombre : nombre ← 1.0
- On récupére un nouveau 1 : 0,01
- Test SI vérifié : nombre est >= 1.0
- On retire 1 à nombre : nombre ← 0.0
- Retour TANT QUE avec nombre = 0.0 : arrêt car nombre n'est plus > 0
04° Utiliser cet algorithme pour trouver les bits de la décomposition de 0.375.
...CORRECTION...
- Premier bit avant la virgule ← 0
- nombre ← 0.375
- Début TANT QUE avec nombre = 0.375 : on commence car nombre > 0
- On double nombre : nombre ← 0.75
- On récupére un nouveau 0 : 0,0
- Retour TANT QUE avec nombre = 0.75 : on continue car nombre > 0
- On double nombre : nombre ← 1.5
- On récupére un nouveau 1 : 0,01
- Test SI vérifié : nombre est >= 1.0
- On retire 1 à nombre : nombre ← 0.5
- Retour TANT QUE avec nombre = 0.5 : on continue car nombre > 0
- On double nombre : nombre ← 1.0
- On récupére 1 : 0,011
- Test SI vérifié : nombre est >= 1.0
- On retire 1 à nombre : nombre ← 0.0
- Fin TANT QUE avec nombre = 0.0 : arrêt car nombre n'est plus > 0
On peut donc écrire N = 0.375 10 = 0,011 2
05° Utiliser cet algorithme (pas le programme) pour trouver les 10 premiers bits de la décomposition de 0.1.
...CORRECTION...
- Premier bit avant la virgule ← 0
- nombre ← 0.1
- Début TANT QUE avec nombre = 0.1 : on commence car nombre > 0
- On double nombre : nombre ← 0.2
- On récupére un nouveau 0 : 0,0
- Retour TANT QUE avec nombre = 0.2 : on continue car nombre > 0
- On double nombre : nombre ← 0.4
- On récupére 0 : 0,00
- Retour TANT QUE avec nombre = 0.4 : on continue car nombre > 0
- On double nombre : nombre ← 0.8
- On récupére 0 : 0,000
- Retour TANT QUE avec nombre = 0.8 : on continue car nombre > 0
- On double nombre : nombre ← 1.6
- On récupére 1 : 0,000 1
- Test SI vérifié : nombre est >= 1.0
- On retire 1 à nombre : nombre ← 0.6
- Retour TANT QUE avec nombre = 0.6 : on continue car nombre > 0
- On double nombre : nombre ← 1.2
- On récupére 1 : 0,000 11
- Test SI vérifié : nombre est >= 1.0
- On retire 1 à nombre : nombre ← 0.2
- Retour TANT QUE avec nombre = 0.2 : on continue car nombre > 0
- On notera qu'on boucle à partir d'ici : le cas 0.2 est déja apparu
- On double nombre : nombre ← 0.4
- On récupére 0 : 0,000 11 0
- Retour TANT QUE avec nombre = 0.4 : on continue car nombre > 0
- On double nombre : nombre ← 0.8
- On récupére 0 : 0,000 11 00
- Retour TANT QUE avec nombre = 0.8 : continue car nombre > 0
- On double nombre : nombre ← 1.6
- On récupére 1 : 0,000 11 00 1
- Test SI vérifié : nombre est >= 1.0
- On retire 1 à nombre : nombre ← 0.6
- Retour TANT QUE avec nombre = 0.6 : continue car nombre > 0
- On double nombre : nombre ← 1.2
- On récupére 1 : 0,000 11 00 11
- Test SI vérifié : nombre est >= 1.0
- On retire 1 à nombre : nombre ← 0.2
On peut donc écrire N = 0.1 10 = 0,000 1100 1100 1100 .. 2
3.3 Bilan : le cas de 0.1
On ne peut donc pas écrire 0.1 avec un nombre fini de bit.
La valeur stockée sera nécessairement approximative.
N = 0.1 10 = 0,000 1100 1100 1100 .. 2
La séquence 1100 va revenir à l'infini.
Pourtant, la fonction decomposition() s'arrête... Oui, car les petites erreurs s'accumulent et finissent par donner un résultat erroné, approximatif.
>>> decomposition(0.1)
0,0001100110011001100110011001100110011001100110011001101
On n'a donc pas réellement stocké 0.1, mais presque 0.1.
06° Insérer l'instruction print(nombre) dans la fonction de décomposition. Cela va nous permettre de voir l'évolution de la variable nombre pendant la décomposition.
Trouver les 10 premiers bits de la décomposition de 0.1.
En visualisant la trace de la fonction, vous devriez constater que la fonction concrète s'arrête au bout d'un moment, là où l'algorithme abstrait fonctionne bien en boucle !
1
2
3
4
5
6
7
8
9
10
11
12
13 | def decomposition(nombre:float) -> str:
"""Décompose nombre floattants commençant par 0 en suite de bits"""
bits = "0,"
while nombre > 0:
nombre = nombre * 2
print(nombre)
bits = bits + str(nombre)[0] # On rajoute l'unité au string bits
if nombre >= 1 :
nombre = nombre - 1
return bits
# Exemples d'utilisation
print(decomposition(0.1))
|
Voici un résultat éventuel :
...CORRECTION...
0.2
0.4
0.8
1.6
1.2000000000000002
0.40000000000000036
0.8000000000000007
1.6000000000000014
1.2000000000000028
0.4000000000000057
0.8000000000000114
1.6000000000000227
1.2000000000000455
0.40000000000009095
0.8000000000001819
1.6000000000003638
1.2000000000007276
0.4000000000014552
0.8000000000029104
1.6000000000058208
1.2000000000116415
0.40000000002328306
0.8000000000465661
1.6000000000931323
1.2000000001862645
0.40000000037252903
0.8000000007450581
1.6000000014901161
1.2000000029802322
0.4000000059604645
0.800000011920929
1.600000023841858
1.2000000476837158
0.40000009536743164
0.8000001907348633
1.6000003814697266
1.2000007629394531
0.40000152587890625
0.8000030517578125
1.600006103515625
1.20001220703125
0.4000244140625
0.800048828125
1.60009765625
1.2001953125
0.400390625
0.80078125
1.6015625
1.203125
0.40625
0.8125
1.625
1.25
0.5
1.0
0,0001100110011001100110011001100110011001100110011001101
J'espère vous avoir montré de façon convaincante de toujours faire attention au déroulement de vos programme lorsqu'une des variables cruciales est de type float !
Donc : pas de test d'égalité avec des floats et surveillance importante de l'évolution des valeurs calculées.
07° Utiliser la fonction decomposition() pour trouver la décomposition de un tiers (1/3). A votre avis, quelle est la vraie décomposition ?
...CORRECTION...
0.6666666666666666
0.33333333333333326
0.6666666666666665
0.33333333333333304
0.6666666666666661
0.33333333333333215
0.6666666666666643
0.3333333333333286
0.6666666666666572
0.3333333333333144
0.6666666666666288
0.33333333333325754
0.6666666666665151
0.33333333333303017
0.6666666666660603
0.3333333333321207
0.6666666666642413
0.3333333333284827
0.6666666666569654
0.3333333333139308
0.6666666666278616
0.3333333332557231
0.6666666665114462
0.3333333330228925
0.666666666045785
0.3333333320915699
0.6666666641831398
0.3333333283662796
0.6666666567325592
0.3333333134651184
0.6666666269302368
0.33333325386047363
0.6666665077209473
0.33333301544189453
0.6666660308837891
0.3333320617675781
0.6666641235351562
0.3333282470703125
0.666656494140625
0.33331298828125
0.6666259765625
0.333251953125
0.66650390625
0.3330078125
0.666015625
0.33203125
0.6640625
0.328125
0.65625
0.3125
0.625
0.25
0.5
1.0
0,010101010101010101010101010101010101010101010101010101
On voit qu'il s'agit encore une fois d'une version alterée par le fait que les calculs avec des floats engendrent des erreurs d'arrondis qui s'accumulent au fil des calculs. La vraie décomposition est infinie avec une alternance de 10 10 10...
08° Vérifier la décomposition en trouvant les 6 premiers bits avec l'algorithme effectué à la main.
4 - Notation scientifique
Nous avons vu que l'encodage des nombres réels stocke souvent un résultat approximatif, proche de la valeur de réel mais pas la valeur exacte.
Le principe de l'encodage des flottants est de réecrire le nombre sous une forme proche de la notation scientifique mais avec une puissance de 2.
La notation scientifique avec une puissance de 10 permet d'écrire un nombre sous forme
- du signe
- d'un nombre décimal m (nommé la mantisse) appartenant à [1;10[ qu'on multiplie par
- une puissance de 10 où E désigne l'exposant.
On pourra donc noter : N = signe x m x 10E
L'exposant correspond à la puissance de 10 juste inférieure ou égale au nombre lui-même.
Nous allons utiliser le même principe pour gérer les flottants. La différence est que l'ordinateur gère plus rapidement les nombres lorsqu'ils sont exprimés en puissance de 2.
Notation scientifique en puissance de 2
La notation scientifique permet d'écrire un nombre sous forme
- du signe
- d'un nombre m (nommé mantisse) appartenant à [1;2[ qu'on multiplie par
- une puissance de 2 où E désigne l'exposant.
On pourra donc noter : N = signe x m x 2E
L'exposant correspond à la puissance de 2 juste inférieure ou égale au nombre lui-même.
Pour les cas un peu compliqués, on peut le trouver facilement une fois obtenue la décomposition du nombre en puissance de 2.
Exemple 1
Prenons N = 5,0
Nous savons que N = 5,0 10 = 101 , 0 2.
On constate donc que le premier 1 est décalé de 2 positions par rapport à l'unité : il encode donc 22.
On peut donc écrire
N = 5,0 10 = 1 , 01 2 x 22
Remarque : on retrouve facilement la mantisse base 10 connaissant l'exposant : 5 / 4 = 1,25.
Exemple 2
Prenons N = 1023,5
La décomposition de 1023 donne :
- Activation de 512 ou 29 : il reste 1023-512 = 511
- Activation de 256 ou 28 : il reste 511-256 = 255
- On sait que 255 correspond à un octet entièrement à 1
Comme 0.5 correspond à 2-1, on obtient donc
N = 1023,5 10 = 11 1111 1111 , 1 2.
On constate donc que le premier 1 est décalé de 9 positions par rapport à l'unité : il encode donc 29.
On écrira N = 1023,5 10 = 1 , 1111 1111 11 2 x 29
Remarque : on retrouve facilement la mantisse en base 10 connaissant l'exposant : 1023,5 / 512 = 1.9990234375.
Exemple 3
Prenons N = 0,4
Le nombre est inférieur à 1 : nous pouvons utiliser l'algorithme vu dans la partie précédente :
Comme le nombre est inférieur à 1, on part de 0 , .
0.4 * 2 donne 0.8. On rajoute 0 à la décomposition : 0 , 0 .
0.8 * 2 donne 1.6. On rajoute 1 à la décomposition : 0 , 01 .
On enlève 1 et on garde 0,6.
0.6 * 2 donne 1.2. On rajoute 1 à la décomposition : 0 , 011 .
On enlève 1 et on garde 0,2.
0.2 * 2 donne 0.4. On rajoute 0 à la décomposition : 0 , 0110 .
Nous sommes revenu au cas initial.
On peut donc écrire : N = 0 , 0110 0110 0110 0110 ...
On constate donc que le premier 1 est situé à deux positions derrière l'unité : il encode donc 2-2.
N = 1 , 10 0110 0110 0110 ... x 2-2
Remarque : on retrouve facilement la mantisse en base 10 connaissant l'exposant : 0.4 / 0.25 = 1.6.
Si on résume
Nombre | Notation SCI | Mantisse m en base 2 | Exposant E |
---|---|---|---|
N = 5 | N = + 1,25 . 22 | 1 , 01 | 2 |
N = 1023,5 | N = + 1.9990234375 . 29 | 1 , 1111 1111 11 | 9 |
N = 0.4 | N = + 1,6 . 2-2 | 1 , 10 0110 0110 0110 ... | -2 |
A chaque fois, la mantisse commence nécessairement par un 1.
09° Vérifier avec la console Python ou votre calculatrice que les valeurs des deux premières colonnes (nombre en écriture normale en base 10 et nombre en puissance de 2 en base 10) sont bien identiques.
...CORRECTION...
>>> 1.25*2**2
5.0
>>> 1.9990234375*2**9
1023.5
>>> 1.6*2**-2
0.4
10° Trouver la décomposition de 0,125 sous la forme mantisse et exposant de 2 : m.2E
. Exprimez la mantisse en base 2.
...CORRECTION...
Sans algorithme, on peut écrire : N = 0,125 10 = 0 , 001 2
On constate donc que le premier 1 est situé à trois positions derrière l'unité : il encode donc 2-3.
N = 1 , 0 x 2-3
11° Trouver la décomposition de 0,1 sous la forme mantisse et exposant de 2 : m.2E
.
Rappel : nous avons vu que 0.1 ne pouvait pas s'écrire avec un nombre fini de bits en base 2. On prendra
N = 0.1 10 = 0,000 1100 1100 1100 2
...CORRECTION...
N = 0.1 10 = 0,000 1100 1100 1100 2
On constate donc que le premier 1 est situé à quatre positions derrière l'unité : il encode donc 2-4.
N = 1 , 100 1100 1100 x 2-4
On constate en réalité qu'on s'approche peu à peu de la valeur voulue lors de l'activation des bits de poids de plus en plus faible. Nous ne pouvons pas pour l'instant réaliser de programme visualisant cela car il nous manque un outil : le tableau.
Voici donc le déroulé manuel de la création de 0.1 :
>>> 2**-4
0.0625
>>> 2**-4 + 2**-5
0.09375
>>> 2**-4 + 2**-5 + 2**-8
0.09765625
...
12° Trouver la décomposition de 1/3 sous la forme mantisse et exposant de 2 : m.2E
.
Rappel : nous avons vu que 1/3 ne pouvait pas s'écrire avec un nombre fini de bits en base 2. On prendra
N = 1/3 10 = 0,01 01 01 2
...CORRECTION...
N = 1/3 10 = 0,01 01 01 2
On constate donc que le premier 1 est situé à deux positions derrière l'unité : il encode donc 2-2.
N = 1 , 01 01 x 2-2
5 - Float : Simple et double precision
Aucune connaissance exacte de la norme n'est au programme de NSI : cette partie est donc plus une partie culture générale qu'une partie à retenir par coeur.
L'explication exacte de cet encodage est fournie par l’IEEE 754, une norme mise au point par le Institute of Electrical and Electronics Engineers. Nous allons simplement en voir le principe. Le fonctionnement réel intègre beaucoup de cas subtils et particuliers à gérer : les nombres plus petits que le premier nombre normalement encodable, les arrondis, l'encodage de l'infini...
Il existe à l'heure actuelle deux types principaux d'encodages pour les floats :
- Le type simple précision nécessite 32 bits, soit 4 octets
- le type double précision nécessite 64 bits, soit 8 octets
Encodage simple précision (32 bits / 4 octets)
On dispose de 32 bits pour encoder le flottant :
- 1 bit pour gérer le signe
- 8 bits pour gérer l'exposant
- 23 bits pour gérer la mantisse
□ □□□□□□□□ □□□□□□□□□□□□□□□□□□□□□□□
Signe s (sur 1 bit)
On utilise un bit pour gérer le signe.
0 encode le signe positif et 1 le signe négatif.
On pourra écrire signe = (-1)s. Ainsi
- si s = 0, on aura (-1)0 = 1.
- si s = 1, on aura (-1)1 = -1.
Exposant E (sur 8 bits)
On stocke un simple entier D positif ou nul sur 8 bits appartenant à l'intervalle [ 0; 28-1 ], soit [ 0 ; 255 ]
Les valeurs 0 et 255 sont reservées à un usage particulier (gestion du zéro et de l'infini, mais pas que).
Dans le cas d'un encodage normal, D est uniquement dans l'intervalle [ 1 ; 254 ]
On retrouve l'exposant E en retranchant 127 à la valeur lue D sur les 8 bits.
E = D - 127 si on veut décoder l'exposant
D = E + 127 si on veut encoder l'exposant
D veut dire exposant Décalé.
Cela veut donc dire qu'on peut encoder des nombres normalisés compris dont
- le plus petit exposant est 1 - 127, soit E = -126
- le plus grand exposant est 254 - 127, soit E = 127
Mantisse m (sur 23 bits)
Comme la mantisse m est dans [1;2[, on ne code pas le 1,
.
Nous allons pouvoir utiliser les 23 bits restants pour encoder les décimales de la mantisse.
Le bit de poids fort est à gauche et encode 2-1.
Le bit de poids faible est à droite et encode donc 2-23.
13° Retrouver le contenu des 32 bits permettant d'encoder N = + 0,125 10.
Rappel : N = + 0,125 10 = 1 , 0 x 2-3
...CORRECTION...
Le bit de signe est 0
. Le signe est donc positif car (-1)0 = 1.
L'exposant est E = - 3.
On doit donc stocker D = -3 + 127 = 124.
La décomposition donne
- Activation de 64. Reste 124 - 64 = 60
- Activation de 32. Reste 60 - 32 = 28
- Activation de 16. Reste 28 - 16 = 12
- Activation de 8 et 4. Reste 0
On stocke donc la suite de 8 bits suivante
D = 0111 1100
La mantisse vaut 1 , 0 .
Puisqu'on n'encode pas le premier 1 , les 23 bits encodants la mantisse sont à 0 :
0000 0000 0000 0000 0000 000
Lors du décodage, on lit donc 0 et on en déduit que m = 1 , 0
Pour encoder N = 0,125 10, on obtient donc l'association de bits suivante
0 0111 1100 0000 0000 0000 0000 0000 000
Vous devriez avoir trouvé la réponse suivante si on regroupe les bits par quartet.
0011 1110 0000 0000 0000 0000 0000 0000
On remarquera donc bien qu'aucun octet n'a réellement de sens : il s'agit juste d'une suite de bits ayant des rôles différents.
Voici le même contenu mémoire sans les couleurs :
0011 1110 0000 0000 0000 0000 0000 0000
En passant aux octets :
0011 1110
0000 0000
0000 0000
0000 0000
14° A l'aide de la représentation précédente, montrer que N = + 0,125 10 s'encode sur 32 bits par la suite d'octets suivante en BIG ENDIAN (on lit d'abord l'octet le plus fort):
62
0
0
0
15° Trouver le nombre relatif encodé en considérant plutôt que cette suite d'octet est la réprésentation d'un entier signé sur 4 octets.
On rappelle que dans le cas de 4 octets W
X
Y
Z
, l'entier encodé non signé vaut :
N = W
x 224 + X
x 216 + Y
x 28 + Z
...CORRECTION...
Comme on considère qu'on a stocké un entier signé sur 4 octets, le premier bit de l'octet W
est le bit de signe à 0 : 0 011 1110
Sans tenir compte de ce bit, on obtient toujours 62 pour W.
On va donc décoder N = 62 x 224 + 0 + 0 + 0.
On obtient alors N = + 1 040 187 392 en décodant les mêmes octets mais en considèrant qu'ils encodent un contenu différent d'un float.
Encodage du zéro en flottant
Oui mais avec une version dénormalisée, losque D est égal à 0
.
Avec un nombre normalisé, la mantisse commence toujours par 1 : 1,... x 20
ne peut pas valoir zéro !
La solution est donc d'encoder le zéro par un ensemble des 32 ou 64 bits à 0 : D ne contient que des bits à zéro et la mantisse ne contient que des bits à zéro.
16° Retrouver le contenu des 32 bits permettant d'encoder la valeur négative N = - 0,1 10.
Rappel : Sans le signe N = 0,1 10 = 1 , 100 1100 1100... x 2-4
...CORRECTION...
Le bit de signe est 1
. Le signe est donc négatif car (-1)1 = -1.
L'exposant est E = - 4.
On doit donc stocker D = -4 + 127 = 123.
La décomposition donne
- Activation de 64. Reste 123 - 64 = 59
- Activation de 32. Reste 59 - 32 = 27
- Activation de 16. Reste 27 - 16 = 11
- Activation de 8 et 2 et 1. Reste 0
On stocke donc la suite de 8 bits suivante
D = 0111 1011
La mantisse vaut 1 , 100 1100 1100... avec un motif qui revient à l'infini.
Puisqu'on n'encode pas le premier 1 , les 23 bits encodants la mantisse sont trouvés en complétant :
1001 1001 1001 1001 1001 100
Pour encoder N = - 0,1 10, on obtient donc l'association de bits suivante
1 0111 1011 1001 1001 1001 1001 1001 100
Hors question : regroupons par quartet
1011 1101 1100 1100 1100 1100 1100 1100
Par octet
1011 1101
1100 1100
1100 1100
1100 1100
189
204
204
204
En C et en Java, le type simple precision se nomme juste float, alors que le type double precision se nomme double.
En Python, il n'existe que le type float et derrière ce nom se cache en réalité du double precision !
Encodage double précision (64 bits / 8 octets)
On dispose de 64 bits pour encoder le flottant :
- 1 bit pour gérer le signe
- 11 bits pour gérer l'exposant
- 52 bits pour gérer la mantisse
□ □□□□□□□□□□ □ □□□□□□□□□□ □□□□□□□□□□ □□□□□□□□□□ □□□□□□□□□□ □□□□□□□□□□ □□
Signe s (sur 1 bit)
On utilise un bit pour gérer le signe.
0 encode le signe positif et 1 le signe négatif.
On pourra écrire signe = (-1)s. Ainsi
- si s = 0, on aura (-1)0 = 1.
- si s = 1, on aura (-1)1 = -1.
Exposant E (sur 11 bits)
On stocke un simple entier D positif ou nul sur 11 bits appartenant à l'intervalle [ 0; 211-1 ], soit [ 0 ; 2047 ]
Les valeurs 0 et 2047 sont reservées à un usage particulier (gestion du zéro et de l'infini, mais pas que).
Dans le cas d'un encodage normal, D est uniquement dans l'intervalle [ 1 ; 2046 ]
On retrouve l'exposant E en retranchant 1023 à la valeur lue D sur les 11 bits.
E = D - 1023 si on veut décoder l'exposant
D = E + 1023 si on veut encoder l'exposant
D veut dire exposant Décalé.
Cela veut donc dire qu'on peut encoder des nombres normalisés compris dont
- le plus petit exposant est 1 - 1023, soit E = -1022
- le plus grand exposant est 2046 - 1023, soit E = 1023
Mantisse m (sur 52 bits)
Comme la mantisse m est dans [1;2[, on ne code pas le 1,
.
Nous allons pouvoir utiliser les 52 bits restants pour encoder les décimales de la mantisse.
Le bit de poids fort est à gauche et encode 2-1.
Le bit de poids faible est à droite et encode donc 2-52.
6 - FAQ
Quel est le plus petit nombre normalisé avec 32 bits ?
Plus petit nombre normalisé avec 32 bits
Plus petit exposant : E = D - 127 = 1 - 127 = -126.
Plus petite mantisse sur 23 bits : il suffit de n'activer aucun bit, la mantisse vaut alors 1,0.
Le plus petit nombre normalisé est donc :
N = 1 x 2-126, ce qui donne
N = 1.1754943508222875 x 10-38
Quel est le plus petit nombre normalisé avec 64 bits ?
Plus petit nombre normalisé avec 64 bits
Plus petit exposant : E = D - 1023 = 1 - 1023 = -1022.
Plus petite mantisse sur 52 bits : il suffit de n'activer aucun bit, la mantisse vaut alors 1,0.
Le plus petit nombre normalisé est donc :
N = 1 x 2-1022, ce qui donne
N = 2.2250738585072014 x 10-308
Quel est le plus grand nombre normalisé avec 32 bits ?
Plus grand nombre normalisé avec 32 bits
Exposant : E = D - 127 = 254 - 127 = +127.
Mantisse sur 23 bits : il suffit d'activer les 23 bits. La mantisse vaut donc presque 2 : 2 - 2-23
Le plus grand nombre normalisé est donc :
N = (2 - 2-23) x 2+127, ce qui donne
N = 3.4028234663852886 x 1038
Quel est le plus grand nombre normalisé avec 64 bits ?
Plus grand nombre normalisé avec 64 bits
Exposant : E = D - 1023 = 2046 - 1023 = +1023.
Mantisse sur 52 bits : il suffit d'activer les 52 bits. La mantisse vaut donc presque 2 : 2 - 2-52
Le plus grand nombre normalisé est donc :
N = (2 - 2-52) x 2+1023, ce qui donne
N = 1.7976931348623157 x 10308
C'est quoi un nombre dénormalisé ? ?
Nombre flottant dénormalisé
Un nombre est dénormalisé si l'exposant D vaut 0 alors que le contenu des bits de la mantisse n'est pas composé que de 0.
Sur 32 bits, un nombre normalisé utilise la formule classique signe x 1,... x 2D-127 où la mantisse est un nombre du type 1,...
dans l'intervalle [1 ; 2[. Les 32 bits de la mantisse encodent donc les chiffres derrière le 1.
Lorsque le nombre D vaut 0, on n'utilise plus le même système. Pourquoi ? Simplement car sinon, il n'y aurait
- aucun valeur disponible entre 0 et 1,0 x 2-127 et
- un très grand nombre de valeurs entre 1,0 x 2-127 et 1,999999999999 x 2-127.
La solution choisie est de changer les règles du jeu lorsque D vaut 0
:
- La mantisse m ne commence pas par 1 mais par 0 : m =0,..... La mantisse est donc dans l'intervalle [0, 1[
- On rajoute 1 à E qui ne vaut donc pas (0 - 127) = -127 mais E est égal à -126
En réalité, le plus petit nombre stockable en utilisant un float simple precision est donc obtenu en activant uniquement le 23e bit de la mantisse:
N = (2-23) x 2-126, ce qui donne
N = 1.401298464324817 x 10-45
Cette fois, il n'y a pas d'autres valeurs en dessous, à part 0.
Par contre, il y a un grand nombre de possibité entre le plus petit nombre dénormalisé N = 1.401298464324817 x 10-45 et le plus petit nombre normalisé N = 1.1754943508222875 x 10-38.
Quel est le plus petit nombre dénormalisé avec 64 bits ?
Plus petit nombre dénormalisé avec 64 bits
Exposant : E = 0 - 1023 + 1= -1022.
Mantisse sur 52 bits : il suffit de n'activer que le 52e bit, celui qui encode 2-52. m = 2-52
Le plus petit nombre normalisé est donc :
N = 2-52 x 2-1022, ce qui donne
N = 5 x 10-324
Le suivant est donc le double N = 10 x 10-324
A quoi sert l'autre valeur dénormalisée de l'exposant D
Exposant D avec tous les bits à 1
Lorsque tous les bits de l'exposant sont à 1 (donc 255
en simple precision ou 2047
en double precision), on obtient l'autre valeur ne permettant pas d'obtenir un nombre normalisé.
Si les bits de la mantisse sont tous à zéro, cela permet d'encoder l'infini.
Si la mantisse possède des bits à 1, cela encode la réponse NaN, ce qui veut dire Not A Number.
Activité publiée le 25 10 2019
Dernière modification : 25 10 2019
Auteur : ows. h.