Codage des nombres

par Tom
le 30/11/2021

Si vous déprimez à cause de l'IT, vous inquiétez pas, ce sera pire après

Je suppose ici que vous connaissez le binaire pour les entiers, si ce n'est pas le cas je vous invite à jeter un coup d'œil à mon cours sur le sujet.

Les entiers c'est bien, mais ça fait pas tout, on voudrait aussi pouvoir stocker/encoder des nombres "à virgule", autrement dit des nombres ayant une partie fractionnaire.


Terminologie

Le nombre -3,1415 possède :

  • un signe (ici -)
  • une partie entière (3)
  • une partie fractionnaire/décimale (,1415)

On nomme epsilon le plus petit nombre supérieur à zéro dans un contexte donné.

L'epsilon des nombres entiers est 1, celui des nombres réels (au sens mathématique)... n'existe pas.


Codage à virgule fixe

Le codage à virgule fixe consiste à stocker les nombres avec une "échelle", c'est-à-dire qu'on va les stocker et les manipuler grosso modo comme des entiers, après les avoir multipliés par un facteur.

Exemple : euros

On souhaite stocker des valeurs en euros dans un système ne supportant que les entiers.

Une quantité en euros peut ne pas être entière, mais on connaît la "granularité" qu'elle peut avoir : le centime. On considère que 3,141519 n'est pas une valeur en euros qu'on devra traiter, car il n'existe pas de "sous-centime" (dans notre cas d'usage).

On pourrait donc se dire que comme nos valeurs ne vont jamais "plus loin" que le centime, on peut donc simplement les multiplier par 100 et les stocker comme des entiers !

Exemple : je veux stocker 149,50€ → j'écris 14950

On remarquera que les opérations simples sur les entiers fonctionnent plutôt bien, c'est-à-dire que si j'additionne deux entiers obtenus via le ×100, et que je les divise par 100, je tombe sur le nombre en euros que j'aurais obtenu en additionnant les nombres originaux.

Mathématiquement, c'est logique : $\frac{a \times 100 + b \times 100}{100} = \frac{100(a + b)}{100} = a + b$

Vu autrement, c'est simplement une conversion d'unités (au lieu de compter en euros, on compte en centimes).

Note: si je veux multiplier deux nombres en virgule fixe, il faut bien que je prenne en compte le facteur d'échelle.

En effet, si je fais $(a \times 100) \times (b \times 100)$ j'obtiens $ab \times 10000$ (et pas $ab \times 100$) !

La multiplication de deux nombres $a\times 100$ et $b\times 100$ est donc $\frac{(a \times 100) \times (b \times 100)}{100}$

En vrai

Ici, j'ai parlé en base 10. Évidemment, on s'intéresse plutôt à la manière binaire de coder les nombres.

Dans l'exemple précédent, j'ai utilisé 100 comme facteur d'échelle. Pour les véritables nombres à virgule fixe, on utilise (comme pour les entiers) des puissances de 2.

Ce qui est logique ! En base 10, pour déplacer la virgule, on multiplie/divise par 10, donc logiquement en base 2, on multiplie/divise par 2.

$101,001_2 \times 100_2 = 101001_2$

En fait, c'est plus ou moins tout, pour les réels positifs en tout cas.

Bon à savoir : l'intervalle des nombres représentables pour des nombres en virgule fixe m.n est $\left[0;2^{m}-2^{-n} \right]$

→ codage en 4.4 → $\left[0;15,9375\right]$

Et les nombres négatifs ?

Comme pour les entiers, les nombres à virgule fixe peuvent être codés en complément à deux pour stocker des valeurs négatives.

Il faut toutefois retenir quelque chose d'assez important : même pour un nombre négatif, la partie fractionnaire est positive.

Un exemple pour voir : je prends le nombre $1,25$, je l'encode en virgule fixe 4.4 : 0001,0100.

Pour trouver son opposé, comme pour les entiers signés la formule est (~x) + 1, donc :

  • nombre : 0001,0100
  • complément : 1110,1011
  • +1 : 1110,1100

Je pourrais être tenté de lire les deux parties séparément, donc à gauche j'ai 1110 soit -2, et à droite j'ai 1100 soit 0,75. Attendez, mais ça fait -2,75, c'est pas le bon résultat ?

Justement : la partie fractionnaire est positive, ça veut dire que le nombre n'est pas $-(2 + 0,75)$ mais $-2 + 0,75$ ce qui fait bien $-1,25$ qui est l'opposé de $1,25$.

Bon à savoir : l'intervalle des nombres représentables pour des nombres en virgule fixe m.n en complément à deux est $\left[-2^{m-1}-1;2^{m-1}-2^{-n} \right]$

→ codage en 4.4 → $\left[-8;7,9375\right]$

Pour lire rapidement un nombre

Gardez en tête que les nombres en complément à deux sont ordonnés seulement entre nombres de même signe.

Exemple : -8 < -7 < -1 → 1000 < 1001 < 1111

Exemple : 0 < 1 < 6 < 7 → 0000 < 0001 < 0110 < 0111

C'est utile pour lire les nombres à virgule fixe, voici quelques exemples (en écriture 4.4, mais généralisable) :

  • 0 000,0000 : le nombre zéro, facile
  • 0 000,0001 : +, partie entière = zéro, partie fractionnaire = epsilon, donc c'est le plus petit nombre réel représentable strictement supérieur à zéro
  • 0 111,0000 : +, partie entière = max, partie fractionnaire = zéro, donc c'est le plus grand entier représentable
  • 1 000,0000 : -, partie entière = zéro, partie fractionnaire = zéro, donc c'est le plus petit nombre représentable
  • 1 111,1111 : -, partie entière = max, partie fractionnaire = max, donc c'est le plus grand nombre réel strictement inférieur à zéro

Codage à virgule flottante

Celui-là est un peu plus compliqué, mais en fait pas tant que ça.

Vous vous souvenez sûrement qu'au lycée, en sciences, on vous demandait souvent d'écrire les résultats non pas sous forme numérique "complète" ($300 000 000$) mais plutôt dans ce qu'ils appelaient l'écriture scientifique ($3\cdot 10^8$).

On va ici voir ça de manière un peu plus formelle.

L'écriture scientifique

Prenons une base au hasard, 10 par exemple. Je souhaite écrire un nombre réel.

Je vais le noter dans une écriture de la forme : $\pm A \cdot 10^B$.

Terminologie :

  • $\pm$ est le signe c'est-à-dire $+$ ou $-$
  • A est la mantisse telle que... ?
  • B est l'exposant tel que $B \in \mathbb{Z}$
  • Note : quand S vaut 1, on ne l'écrit généralement pas.

La contrainte pour S est assez intuitive, et celle pour B est simplement donnée par définition de l'écriture scientifique.

Mais quid de A ? A est logiquement réel, ou plutôt décimal (réel possédant une écriture numérique finie). Mais peut-il prendre n'importe quelle valeur dans $\mathbb{D}$$ ?

Si j'écris $34\cdot10^5$, on voit qu'en fait on peut réécrire sous la forme $3,4\cdot10^6$.

Idem, si j'écris $0,78\cdot10^-7$, on peut réécrire comme $7,8\cdot10^-8$.

A est donc compris dans l'intervalle $\left[1; 10\right[$.

Autrement dit, il n'a qu'un seul chiffre à gauche de sa virgule. J'insiste bien sur le fait qu'il faut que vous compreniez ça et pas seulement que vous l'appreniez bêtement, parce que ça sera très utile pour la suite.

Nous avons donc défini l'écriture scientifique, avec l'exemple en base 10.

L'écriture scientifique en base 2

C'est pas bien différent.

Les écritures seront de la forme $\pm A \cdot 2^B$.

  • $\pm$ est le signe c'est-à-dire $+$ ou $-$
  • A est la mantisse telle que... ?
  • B est l'exposant tel que $B \in \mathbb{Z}$

Ici, si vous avez bien compris, vous devriez pouvoir deviner que A est compris dans l'intervalle $\left[1;2\right[$.

Et c'est là qu'on a quelque chose d'intéressant : A est toujours de la forme "1,quelquechose" ! En informatique, on essaie de ne stocker que le strict nécessaire, pour ne pas s'encombrer de données redondantes.

Ici, on voit que la partie entière de A est toujours 1, il n'y a que la partie fractionnaire qui change. Du coup, pas besoin de stocker ce premier 1 (vu qu'il ne change jamais), on n'a besoin de stocker que la partie fractionnaire (en se rappelant qu'il y a un 1 devant).

Nouveau terme : la partie fractionnaire de la mantisse est appelée la pseudo-mantisse.

On peut donc redéfinir l'écriture scientifique en base 2 sous la forme $\pm (1 + P) \cdot 2^B$, avec $P \in \left[0; 1\right[$.

Avec cette écriture, on peut donner à peu près n'importe quel nombre (ou plutôt, comme la mantisse est décimale, et que $\mathbb{D}$ est dense dans $\mathbb{R}$, presque tout nombre réel).

Stockage de l'écriture scientifique en base 2

On a défini un concept mathématique abstrait, c'est bien. Pouvoir le stocker, c'est mieux.

On a donc trois choses à stocker :

  • le signe, $+$ ou $-$
  • la pseudomantisse, nombre décimal sur $\left[0; 1\right[$
  • l'exposant, nombre entier sur $\mathbb{Z}$

Le signe, c'est facile, on lui donne un bit, 0 pour positif, 1 pour négatif.

Ensuite, la pseudomantisse. Et bien en fait, on va juste l'écrire comme nombre à virgule fixe. En l'occurrence, la pseudomantisse n'a pas de partie entière, donc ça sera un nombre à virgule fixe ayant une partie entière sur... 0 bits.

Par exemple pour une pseudomantisse de $0,25$, si on la stocke sur 4 bits, ça donne juste 0100.

Pour finir, l'exposant. Là, on doit stocker un entier signé. Il existe plusieurs méthodes :

  • signe et valeur : simple à lire, compliqué à manipuler, il y a deux zéros (+0 et -0)
  • complément à un : un peu moins simple à lire, compliqué à manipuler, il y a aussi deux zéros
  • complément à deux : compliqué à lire, facile à manipuler, un seul zéro → technique la plus utilisée

Le problème, c'est que le complément à deux, malgré tous ses avantages, a aussi quelques inconvénients. Notamment, on ne peut pas comparer simplement deux nombres de signe opposé, il faut vérifier au préalable. Or, comparer des nombres, c'est quand même bien pratique.

Ici, on a donc trouvé une technique particulière : le biais. Disons par exemple que l'exposant est sur 8 bits.

Il y a 256 ($2^8$) valeurs possibles, en non signé ça donne $\left[0;255\right]$.

Ici, ce qu'on va faire, c'est ajouter 127 ($2^{8 - 1} - 1$) à l'exposant qu'on veut stocker. L'intervalle stockable (l'intervalle de valeurs possibles pour l'exposant) devient donc $\left[-127;128\right]$.

Forcément, en appliquant ce biais, les nombres stockés ne sont plus trop utilisables pour des multiplications, des divisions, etc... mais en soi, ça n'importe pas, en effet ici on veut juste stocker un nombre et pouvoir faire des comparaisons facilement, et ajouter un terme constant ne change rien au résultat d'une comparaison.

L'exposant après cette addition est simplement appelé exposant biaisé. Et cet exposant biaisé n'est jamais négatif ! Justement, on a ajouté à l'exposant l'opposé de la valeur minimale possible, ce qui fait que même si l'exposant est négatif, après le biais, il se retrouve toujours positif.

Note : cette technique de biais n'est utilisée que pour le stockage, c'est un "détail d'implémentation". Il faut bien différencier le théorique du pratique.

À retenir :

  • Une écriture scientifique en base 2 est caractérisée par son signe, sa mantisse et son exposant.
  • Un nombre en virgule flottante est caractérisé par son bit de signe, sa pseudomantisse et son exposant biaisé.

Mais du coup, en résumé, ça donne quoi ?

C'est pas très compliqué, voici comment c'est défini.

Pour un format de virgule flottante à E bits d'exposant et M bits de mantisse :

  • D'abord, on stocke le bit de signe, sur 1 bit (logique)
  • Ensuite, on stocke l'exposant biaisé, qui pour rappel est toujours ≥ 0, simplement en binaire sur E bits
  • Pour finir, on stocke la pseudomantisse, en virgule fixe, sur M - 1 bits (oui, car M est le nombre de bits de la mantisse, donc pour la pseudomantisse ça fait un bit de moins)

Nombres spéciaux

Il existe plusieurs cas particuliers utilisés pour encoder des nombres spéciaux.

Infinis

Quand l'exposant biaisé est maximal (par exemple, sur 8 bits, quand il vaut 255), et que la mantisse vaut zéro, on considère que le nombre encodé est $\pm\infty$.

NaN

Quand l'exposant biaisé est maximal, et que la mantisse est non nulle (peu importe sa valeur), on considère que le nombre encodé est une valeur spéciale appelée "NaN".

Ce sont les initiales de "Not a Number", qui signifie littéralement "Pas un nombre". C'est une valeur qui est utilisée pour signaler qu'une opération effectuée n'a pas de résultat, comme $\frac{0}{0}$ ou $\infty - \infty$.

Cette valeur a plusieurs propriétés intéressantes, notamment le fait qu'elle n'est égale à aucune valeur, pas même elle-même !. Anecdotiquement, c'est pour ça qu'historiquement, une méthode assez populaire pour vérifier si un nombre est NaN était de vérifier si x != x (car NaN est le seul nombre différent de lui-même).

Il s'agit également d'un élément dit absorbant, car toute opération impliquant NaN donne NaN ($NaN + x$, $1 / NaN$, etc).

Nombres dénormalisés

Non