Données Float

Identification

Infoforall

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 ?

Perceval et les floats
Perceval gère les floats de tête lui !

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 - Ensemble des réels

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 une quotient de deux entiers. On peut y placer Pi, 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 donc 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 à plusieus 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

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

Alors, quelqu'un a une idée pour expliquer cela ?

En tous cas, vous devriez comprendre pourquoi on les nomme les flottants et pas les réels : on ne peut pas nécessairement encoder la valeur exacte d'un réel dans un ordinateur. Le type du nombre qu'on a stocké en mémoire se nomme alors un flottant, ou float en anglais.

2 - Représentation d'un réel en base 10

Avant d'expliquer réellement comment on peut encoder efficacement les nombres réels, regardons pourquoi certains réels ne sont pas décimaux (c'est à dire représentables par un nombre fini de chiffres après la virgule).

Commençons par N = 1/10

Si on le calcule, on obtient N = 0.1

Nombre fini de chiffres car on peut l'écrire sous forme d'un somme de puissance de 10 : N = 1/10 = 1.10-1

C'est un nombre décimal (quotient avec des puissances de 10)

La somme des plusieurs nombres décimaux donne un nombre décimal.

Exemple avec N = 1/10 + 1/20

Si on le calcule, on obtient N = 0.15

Nombre fini de chiffres car on peut l'écrire sous forme d'un somme de puissance de 10 :

N = 1/10 + 1/20 = 1/10 + 5/100 ou N = 1/10 + 1/20 = 15/100

N = 1.10-1 + 5.10-2

C'est un nombre décimal (car la somme des plusieurs nombres décimaux donne un nombre décimal)

Passons à N = 1/4

Si on le calcule, on obtient N = 0.25

Nombre fini de chiffres car on peut l'écrire sous forme d'un somme de puissance de 10 :

N = 25/100 ou N = 2/10 + 5/100

N = 25.10-2

C'est un nombre décimal (quotient avec des puissances de 10)

Cherchons maintenant N = 1/3

Cette fois, on ne parvient pas à obtenir un nombre fini de chiffres après la virgule. On ne pourra obtenir qu'un résultat approximatif.

N = 0.333

ou N = 0.3333

ou N = 0.33333

et on ne pourra jamais obtenir le résultat juste et exact. Pour cela, il faudra une infinité de 3.

1/3 n'est pas un nombre décimal : on ne peut pas l'exprimer sous la forme d'un quotient d'une puissance de 10.

C'est le même principe pour la représentation des réels en utilisant le type float si ce n'est qu'on n'utilise pas les puissances de 10 mais les puissances de 2, c'est plus facile pour l'ordinateur.

3 - Représentation d'un réel en base 2

Nous allons faire la même chose mais avec les nombres pouvant s'écrire comme un quotient d'un entier et d'une puissance de 2.

Puissance négative de 2

Il est important de les connaître ou de savoir les retrouver.

Vous connaissez déjà les puissances positives : 2 (21) - 4 (22) - 8 (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

Exemples simples de représentation en base 2

Il suffit d'étendre aux puissances négatives

Nombre M = 1010, 1011
Les bits codent 8421 0.50.250.1250.0625
Les bits codent 23222120 2-12-22-32-4
On obtient donc 820.50.1250.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 = 0101, 0110
Les bits codent 8421 0.50.250.1250.0625
Les bits codent 23222120 2-12-22-32-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 = 1111, 1111
Les bits codent 8421 0.50.250.1250.0625
Les bits codent 23222120 2-12-22-32-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 = 0000, 1100
Les bits codent 8421 0.50.250.1250.0625
Les bits codent 23222120 2-12-22-32-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 ?

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
  • nombrenombre voulu
  • TANT QUE nombre > 0
    • nombrenombre * 2
    • bit suivant ← partie entière de nombre (0 ou 1)
    • SI nombre >= 1
      • nombrenombre - 1

Implanté en Python dans une fonction, on peut obtenir ceci

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
def decomposition(nombre): '''Décompose nombre, réel dans [0;1[ en suite de bits ::param nombre(float) :: nombre est un réel dans l'intervalle [0;1[ ::return (str) :: renvoie un string contenant la décomposition ''' 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 if __name__ == '__main__' : print(decomposition(0.5)) print(decomposition(0.25))

Exemple avec N = 0.25 10

  • Premier bit avant la virgule ← 0
  • nombre0.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
  • nombre0.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
  • nombre0.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

Comme vous pouvez le voir, on ne peut donc pas écrire 0.1 avec un nombre fini de bit. La valeur stockée sera nécessairement approximative.

La séquence 1100 va revenir à l'infini.

Pourtant, si vous utilisez le programme fourni, vous pourrez constater qu'il s'arrête !

Pourquoi ? Simplement car l'ordinateur travaille avec des flottants et ne fait donc pas de calculs justes. Et les petites erreurs s'accumulent et finissent par donner un résultat erroné.

Vous pourriez le constater en rajoutant un print sur la variable nombre dans la boucle while.

06° Insérer l'instruction print(nombre) sur la ligne 11. L'ancienne ligne 11 va donc se trouver en ligne 12. Trouver les 10 premiers bits de la décomposition de 0.1. Vous devriez constater que le programme s'arrête au bout d'un moment, là où l'algorithme lui fonctionne bien en boucle !

Voici un résultat éventuel :

...CORRECTION...

0.2 0.4 0.8 0.6000000000000001 0.20000000000000018 0.40000000000000036 0.8000000000000007 0.6000000000000014 0.20000000000000284 0.4000000000000057 0.8000000000000114 0.6000000000000227 0.20000000000004547 0.40000000000009095 0.8000000000001819 0.6000000000003638 0.2000000000007276 0.4000000000014552 0.8000000000029104 0.6000000000058208 0.20000000001164153 0.40000000002328306 0.8000000000465661 0.6000000000931323 0.20000000018626451 0.40000000037252903 0.8000000007450581 0.6000000014901161 0.20000000298023224 0.4000000059604645 0.800000011920929 0.6000000238418579 0.20000004768371582 0.40000009536743164 0.8000001907348633 0.6000003814697266 0.20000076293945312 0.40000152587890625 0.8000030517578125 0.600006103515625 0.20001220703125 0.4000244140625 0.800048828125 0.60009765625 0.2001953125 0.400390625 0.80078125 0.6015625 0.203125 0.40625 0.8125 0.625 0.25 0.5 0.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 !

On retiendra donc : pas de test d'égalité avec des floats et surveillance importante de l'évolution des valeurs calculées.

07° Utiliser le programme Python pour trouver la décomposition de 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 0.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.

Vous avez vu le principe fondalemental : on utilise les puissances négatives de 2 pour encoder derrière la virgule.

Oui, mais si le nombre est très grand ou très petit, on fait comment ? On stocke plein de zéro ?

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

NombreNotation SCIMantisse m en base 2Exposant 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 le Shell 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

Voilà : vous savez maintenant pourquoi certains calculs avec les flottants donnent des résultats qui semblent imprécis.

Si on résume :

  • On cherche une représentation en notation scientifique à base de puissance de 2
  • On fournit la mantisse en base 2.

Reste à voir comment stocker concrêtement cela en mémoire.

5 - Float : Simple et double precision

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...

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.

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.

Voilà. Le principe est donc simple : on enregistre les réels en utilisant une notation scientifique à base de puissance de 2. On stocke l'exposant comme un entier et la mantisse comme un nombre à virgule.

Pour les détails, vous aurez le loisir de les découvrir au fur et à mesure de vos études.

Le point important par contre : pour l'ordinateur, les nombres flottants ne sont qu'une suite de bits, qui n'ont que le sens qu'on veut bien leur donner !

Activité publiée le 25 10 2019
Dernière modification : 25 10 2019
Auteur : ows. h.