Chapitre 3 sur 8
Signes, virgule et précision
Nous sommes chaque jour confrontés à des nombres négatifs et à des nombres à virgules. En binaire, il n’y a ni signe moins, ni virgule décimale. Dans ce cas, comment la machine, qui ne travaille qu’en binaire, gère-t-elle cela ?
Nous allons également aborder la notion de précision d’un nombre. C’est à dire, la taille allouée pour son stockage : combien de chiffres significatifs peut-on stocker.
Gestion des négatifs
Dès que l’on parle de soustraction, on réalise qu’un nombre peut être négatif. Dès lors, on se pose la question du stockage du nombre en binaire. Évidemment, il faut traduire le signe en représentation binaire. Pour se faire, les nombre binaires sont toujours de taille fixe.
Ainsi, pour un nombre exprimé sur un octet, le bit de poids le plus fort sert à exprimer le signe. 0 vaut + et 1 vaut -. On réalise donc qu’une variable codée sur un octet peut contenir de -128 à +127 s’il autorise les négatifs. Si on précise qu’il n’est pas signé, les valeurs vont de 0 à 255.
Pour trouver le négatif d’un nombre binaire, il suffit d’inverser tous les bits de ce nombre et d’ajouter un. On ajoute un car il n’y a qu’un seul zéro. Cela évite ainsi d’avoir un zéro positif et un zéro négatif, ce qui mènerait au cas où +0 ≠ -0, c’est pour cette raison que l’on peut aller jusqu’à -128 et non -127.
Ainsi 0111 0100 est un nombre positif (116dec). La représentation binaire de son négatif est 1000 1100, soit tous les bits inversés, plus un : 1000 1011 + 0000 0001. Le premier bit, marqueur du signe, se trouve automatiquement ajusté.
Cette opération s’appelle le complément à deux. Si vous êtes curieux, la fiche Wikipedia vous en apprendra d’avantage sur les raison de ce marquage.
Maintenant que vous savez cela, et sachant qu’en arithmétique, A - B = A + (-B), vous pouvez vous faciliter la vie pour faire des soustractions en binaire.
Gestion de la virgule
Pour calculer les nombres après la virgule en binaire, comme souvent, la logique est la même qu’en décimal. En décimal, 0,1 est égal à 1/10, soit 10-1 ; 0,01 à 10-2 etc.
En toute logique, en binaire, 0,1bin vaut 2-1 etc. On divise par deux à chaque fois. Ainsi, pour les quatre premiers rangs après la virgule, on a :
- 0,5
- 0,25
- 0,125
- 0,0625
Il ne reste plus qu’à comprendre comment représenter la virgule. Le secret réside en effet dans l’écriture scientifique.
Virgule fixe
Il suffit de définir la position de la virgule et on sait instantanément la valeur du nombre. Par exemple, pour un nombre stocké sur un octet, soit huit bits, si on définit arbitrairement la position de la virgule juste avant après le quatrième bit, alors on sait que 0110 1001 = 0110,1001.
C’est extrêmement simple. Cette manière de faire s’appelle virgule fixe, car la position de la virgule est connue d’avance. L’inconvénient de cette méthode est que, pour un nombre avec peu de chiffres après la virgule, on perd un espace de stockage significatif. Si le nombre en question est 0110 1000, on perd trois bits “inutilement”.
La virgule fixe est tout de même utilisée. Sur les processeurs à faible coût – les microcontrôleurs – n’ayant que la capacité de traiter les entiers et virgules fixes, c’est l’unique solution. Par ailleurs, dans certaines situations, la virgule fixe permet d’augmenter la vitesse d’exécution ou d’améliorer l’exactitude des calculs.
Virgule flottante
Pour palier à ce problème de perte d’espace induit par la virgule fixe, on utilise la méthode d’écriture à virgule flottante. Cette écriture représente les nombres de la manière suivante : signe × mantisse × baseexposant. La position de la virgule est fixée dans la mantisse. Par la suite, on la fait flotter en faisant varier l’exposant.
C’est exactement comme dans les notations scientifique et ingénieur. Ainsi, pour une même taille de stockage de quatre chiffres par exemple, on peut stocker aussi bien 1234 que 1,234 ou encore 0,1234, il suffit d’indiquer grâce à l’exposant où se place la virgule.
IEE754
Il subsiste tout de même plusieurs problèmes. D’une part, comment se mettre d’accord sur la position de la virgule dans la mantisse ? D’autre part, l’exposant pouvant être un entier relatif (entier signé), il faut également gérer le signe à ce niveau.
Sans standard, chaque constructeur abouti à implémentation maison. Par conséquent, porter les logiciels d’une machine à une autre se révèle complexe en raison des différences de représentations internes et de comportement des nombres flottants.
C’est là qu’intervient l’IEEE avec sa norme 754. Cette dernière permet de fixer les formats de représentation des nombres à virgule flottante, des modes d’arrondis, des valeurs spéciales ainsi qu’un ensemble d’opérations de base.
Les arrondis
Le standard définit quatre types d’arrondis :
- vers l’infini négatif,
- vers l’infini positif,
- vers zéro,
- au plus proche.
Les trois premiers arrondis permettent de représenter les nombres qui dépassent la limite de stockage ou de précision du format de représentation du nombre. Ainsi, si l’on peut stocker jusqu’à un milliard et que le résultat d’une opération dépasse ce nombre, alors le résultat annoncé sera vers l’infini positif.
Au plus proche est le mode d’arrondi naturel, c’est ce que nous entendons quand nous pensons à arrondir un nombre. La norme précise pour ce mode que si le nombre est entre deux, il est arrondi à la valeur la plus proche avec un bit de poids faible à zéro (mode d’arrondi par défaut).
La précision
La précision est déterminée par la taille de la mantisse. Il ne faut pas confondre précision et amplitude. La précision est déterminée par la taille de la mantisse tandis que l’amplitude est définie par la taille de l’exposant de la base.
Si l’on prend en exemple l’écriture scientifique en base 10, pour une écriture avec trois chiffres après la virgule et un exposant entier relatif à un chiffre, la précision est de quatre chiffres, mais l’on peut représenter des ordres de grandeur allant de +1,999M à -1,999M.
On se rend cependant bien compte que l’on ne sera pas en mesure d’avoir une précision allant jusqu’à l’unité pour le milliard. Le maximum que l’on puisse stocker est 1,999e9 et même sur une valeur plus petite, par exemple 1,123e9, la précision n’est pas disponible au deçà des millions : 1,123e9 = 1 123 000 000.
En réduisant l’ordre de grandeur, on dispose bien entendu d’une précision plus fine. Ainsi, 1,123e2 = 112,3 et 1,123e-9 est égal à 0,000000001123.
Ainsi, la norme définit quatre formats pour représenter les nombres à virgule flottante en base 2 :
- simple précision (aussi appelé binary32),
- simple précision étendue,
- double précision (aussi appelé binary64),
- double précision étendue.
Nous ne verrons que les simple et double précisions, qui sont les plus utilisés (simple précision étendue est aujourd’hui obsolète). Nous rappelons que la valeur s’obtient par l’opération signe x mantisse x baseexposant. La base est ici toujours 2.
La norme s’approche de la notation scientifique décimale, ainsi le premier bit de la mantisse est toujours 1 et la virgule se place après ce 1.
Cette convention permet de s’affranchir de son écriture effective, le premier bit de la mantisse est implicite et permet alors de gagner un bit de précision. Ainsi, pour une mantisse de 23 bits, la précision est de 24 bits.
Précision | Encodage | Signe | Exposant | Mantisse | Précision | Chiffres significatifs |
---|---|---|---|---|---|---|
Simple précision | 32 bits | 1 bit | 8 bits | 23 bits | 24 bits | ±7 |
Double précision | 64 bits | 1 bit | 11 bits | 53 bits | 54 bits | ±16 |
Nous constatons que pour le format double précision, la mantisse est très élargie alors que l’exposant l’est dans une moindre mesure. Cela est justifié par la volonté d’augmenter la précision plus que l’amplitude des ordres de grandeur représentés.
Décalage de l’exposant
Nous l’avons vu, l’exposant est un entier relatif, il peut donc être positif ou négatif. Cependant, on n’utilise ni la règle du complément à deux ni celle du bit de signe pour déterminer le signe de l’exposant. Cela rendrait la comparaison de nombres plus difficiles, et donc moins performante.
On utilise alors un décalage de l’exposant en lui ajoutant 2(nombre de bits de l’exposant - 1) - 1. Ce décalage permet de stocker l’exposant comme non signé.
Ce décalage peut sembler obscur de prime abord. Prenons toutefois un exemple : dans le cas du binary32, l’exposant est codé sur 8 bits. La formule devient donc 28 - 1 - 1 = 128 - 1 = 127. Ainsi, -127, décallé vers la valeur zéro, sert pour le zéro, les valeurs -126 à -1 sont codées par l’intervalle 1 à 126 (126 - 127 = -1) et les valeurs supérieures allant de 127 à 255 codent les nombres positifs allant de 1 à 128.
On sait que sur 8 bits, 256 valeurs sont possibles, notre règle permet d’avoir la même amplitude que si l’on utilisait le bit de poids plus fort pour le signe, soit 27 valeurs = 128. 128 signés représente 128 x 2 = 256 valeurs. Avec notre décalage, nous avons 127 négatifs plus 128 positifs plus le zéro, soit 256 valeurs !
Nombres dénormalisés
On a vu que notre notation fait fi du premier bit de la mantisse, il est dit implicite car il est toujours à un. En réalité, ce bit de poids fort est déterminé par la valeur de l’exposant décalé. Si la mantisse est non nulle et que l’exposant décalé est égal à zéro, alors il vaut nul et le nombre est dit dénormalisé.
Cette notation des nombres permet de gagner en précision pour représenter de très petits nombres s’approchant du zéro. Les nombres dénormalisés assurent une continuité avec les nombres normalisés.
Voici un petit tableau des ordres de grandeur, copié de la page Wikipedia sur les nombres dénormalisés pour la simple précision.
Type | Exposant | Mantisse | Valeur approchée | Écart précédent |
---|---|---|---|---|
Zéro | 0000 0000 | 000 0000 0000 0000 0000 0000 | 0,0 | – |
Plus petit nombre dénormalisé | 0000 0000 | 000 0000 0000 0000 0000 0001 | 1,4 × 10−45 | 1,4 × 10−45 |
Nombre dénormalisé suivant | 0000 0000 | 000 0000 0000 0000 0000 0010 | 2,8 × 10−45 | 1,4 × 10−45 |
Nombre dénormalisé suivant | 0000 0000 | 000 0000 0000 0000 0000 0011 | 4,2 × 10−45 | 1,4 × 10−45 |
… | … | … | … | … |
Plus grand nombre dénormalisé | 0000 0000 | 111 1111 1111 1111 1111 1111 | 1,175 494 21 × 10−38 | 1,4 × 10−45 |
Plus petit nombre normalisé | 0000 0001 | 000 0000 0000 0000 0000 0000 | 1,175 494 21 × 10−38 | 1,4 × 10−45 |
Nombre normalisé suivant | 0000 0001 | 000 0000 0000 0000 0000 0001 | 1,175 494 21 × 10−38 | 1,4 × 10−45 |
Cas particuliers
Nous venons de le voir, si l’exposant décalé vaut zéro, le nombre est dénormalisé. D’autres cas particuliers permettent de représenter des nombres spéciaux.
Si l’exposant décalé est à zéro et que la mantisse est également à zéro, il s’agit d’une valeur nulle qui peut être le +0 ou -1 selon le bit de signe.
Par ailleurs, pour e représentant la taille de codage de l’exposant, si l’exposant vaut 2e - 1 – soit sa valeur la plus haute, 28 - 1, donc 255 dans le cas du simple précision – et que la mantisse est également égale à zéro, il s’agit de l’infini ; + ou - selon le bit de signe.
Enfin, si l’exposant vaut 2e - 1 et que la mantisse est non nulle, alors il s’agit de NaN, résultat d’une opération invalide (comme une division par zéro).
Petit tableau récapitulatif.
Type | Exposant décalé | Mantisse |
---|---|---|
Zéros | 0 | 0 |
Nombres dénormalisés | 0 | différente de 0 |
Nombres normalisés | 1 à 2e - 2 | quelconque |
Infinis | 2e - 1 | 0 |
NaNs | 2e - 1 | différente de 0 |
Arrondis
Nous avons expliqué comment sont calculés les décimales en binaire. Il s’agit à chaque fois d’un rang en puissance négatif de la base : 2-1 etc. Tout nombre rationnel peut être exprimé sous forme fractionnelle.
Cependant, pour que la représentation soit finie, il faut que les nombres premiers constitutifs du dénominateur se retrouvent dans la factorisation de la base. Cette explication étant, je le concède, assez complexe, prenons un exemple concret.
Ainsi, 1/2, 3/5 ou 4/10 ont des représentations finies, car 2 et 5 sont les facteurs premiers de 10. Cependant, 1/3, 2/7 ou 5/9 n’ont pas de représentation finie car 3 et 7 sont des nombres premiers non puissance de 10. La factorisation en nombres premiers de 9 étant 3 x 3, 3 n’est pas un nombre premier de la base.
En base 2, cela revient à dire que seuls sont représentables les nombres dont le dénominateur est puissance de deux. En effet, deux étant un nombre premier, il n’est divisible que par un et lui-même.
Ainsi, 1/10 qui est représenté par 0,1 en décimal, n’a pas de représentation exacte en binaire, 1/3 non plus. Les représentations exactes en binaires sont de fait plus rares qu’en décimal puisqu’en décimal, il faut que la factorisation en nombres premiers contienne 2 et/ou 5 tandis qu’en binaire on ne peut admettre que 2.
Ce faisant, en décimal, on représente de manière finie 1/2, 1/4, 1/5, 1/8 et 1/10 etc, en binaire, on pourra représenter de manière finie 1/2, 1/4, 1/8, 1/16, 1/32, 1/64 etc.
Ceci aboutit nécessairement au recours à l’arrondi dans les représentation des nombres. Dès lors que l’on effectue des opérations sur ces nombres, les erreurs d’arrondis s’accumulent et peuvent mener à des résultats faussés. C’est pourquoi en JavaScript par exemple, 0,1 + 0,2 = 0,30000000000000004.
D’autres langages, comme le PHP, affichent le résultat de manière convenable, mais on s’aperçoit qu’il ne s’agit que d’un arrondis lors de l’affichage de la variable, car si l’on fait var_dump((0.1 + 0.2) === 0.3);"
, on obtient bool(false)
.
Vitesse de calcul
Les nombres à virgule flottante ont une telle importance dans la mise en œuvre de l’informatique, qu’on les utilise pour mesurer la puissance des ordinateurs. La vitesse de calcul des ordinateurs, notamment des supercalculateurs, se mesure en FLOPS, le nombre d’opérations a virgule flottante que la machine est capable d’exécuter par seconde.
On en a moins l’habitude pour évaluer la puissance des ordinateurs personnels, pourtant c’est une mesure plus expressive que la vitesse d’horloge. Si l’on fait un parallèle avec les moteurs à explosion, c’est comme annoncer la cylindrée d’un moteur plutôt que sa puissance, même s’il existe une relation, la puissance est un meilleur indicateur.
Si vous souhaitez évaluer le nombre de FLOPS de votre machine, jetez un petit coup d’œil à cette discussion [en].
Dans le prochain chapitre, on lâche un peu les nombres pour parler des lettres et de leur encodage avec Unicode !
Commentaires
Rejoignez la discussion !