Résumé
Les nombres à décimales d’un programme ne sont pas les nombres réels des mathématiques : ils forment un sous-ensemble fini de rationnels dont le dénominateur est une puissance de deux, et toute valeur qui n’appartient pas à ce sous-ensemble est remplacée par la plus proche qui lui appartient. Cette leçon étudie comment un nombre à virgule flottante est stocké sur 64 bits (signe, exposant et mantisse), pourquoi n’a pas de représentation exacte et quelle valeur prend sa place, combien de chiffres sont sûrs en float et en double, comment s’accumulent les erreurs d’arrondi, pourquoi deux réels ne se comparent pas avec == et comment on les compare avec une tolérance justifiée, et ce que sont l’infini et la valeur non numérique que le format réserve aux résultats qui ne tiennent pas ou n’existent pas.
Objectifs d’apprentissage
- Expliquer pourquoi n’a pas de représentation binaire exacte et quelle valeur est stockée à sa place.
- Prévoir l’effet de l’arrondi dans des sommes répétées et dans la somme de nombres d’ordres de grandeur très différents.
- Comparer des nombres réels avec une tolérance adéquate au lieu de
==, en justifiant le choix de la tolérance. - Choisir entre
floatetdoubleselon la précision requise, et reconnaître l’infini et la valeur non numérique.
L’écriture scientifique en base 2
La leçon Les entiers et leur représentation a montré qu’un entier de bits représente exactement valeurs consécutives. Or les nombres à décimales posent une difficulté d’un autre ordre : entre deux réels quelconques il y en a une infinité d’autres, et aucun nombre fini de bits ne peut tous les distinguer. La solution qu’adoptent tous les ordinateurs actuels est celle-là même dont la science se sert pour écrire des grandeurs très grandes ou très petites : l’écriture scientifique. En base 10, la distance moyenne de la Terre au Soleil s’écrit mètres ; le nombre a un signe, quelques chiffres significatifs (la mantisse, ) et une puissance de la base (l’exposant, ), qui dit où va la virgule. S’il en est ainsi, un nombre fixe de chiffres couvre des grandeurs d’ordres très différents, au prix de ne garder que les premiers chiffres de chacune.
L’ordinateur fait de même en base 2. Tout nombre non nul s’écrit d’une seule manière sous la forme
où est le signe (0 pour un nombre positif, 1 pour un négatif), est la mantisse écrite en binaire, dont le premier chiffre est toujours 1, et est l’exposant. Que le premier chiffre soit toujours 1 a une conséquence pratique : il n’est pas nécessaire de le stocker, et le bit ainsi économisé est gagné pour la précision. Le nom de virgule flottante décrit précisément cette forme : la virgule n’occupe pas une place fixe parmi les chiffres, mais « flotte » selon l’exposant.
Le type double de C++ utilise, dans tous les compilateurs actuels, le format binaire double précision normalisé au niveau international, sur 64 bits : 1 bit pour le signe, 11 bits pour l’exposant et 52 bits pour la mantisse, sans le chiffre initial. L’exposant est stocké avec un biais : le champ de 11 bits contient , de sorte qu’il stocke des valeurs non signées de 1 à 2046 pour des exposants de à (les valeurs 0 et 2047 du champ sont réservées au zéro, aux nombres très petits, à l’infini et à la valeur non numérique, que l’on verra à la fin de la leçon). Le type float utilise le format simple précision, sur 32 bits : 1 de signe, 8 d’exposant et 23 de mantisse.
Les bits d’un double peuvent se voir. Le programme suivant utilise std::bit_cast, de l’en-tête <bit>, qui réinterprète les 64 bits du nombre comme un entier non signé de 64 bits, sans en changer aucun ; on s’en sert ici sans plus d’explication, comme d’un instrument pour regarder la mémoire. La spécification {:064b} écrit cet entier en binaire avec 64 chiffres, comme dans la leçon précédente, et {:016X} en hexadécimal avec 16.
#include <bit>#include <cstdint>#include <print>int main(){ const double tenth{0.1}; // Les 64 bits du nombre, lus comme un entier non signé. const std::uint64_t bits{std::bit_cast<std::uint64_t>(tenth)}; std::println("{:064b}", bits); std::println("{:016X}", bits);}00111111101110011001100110011001100110011001100110011001100110103FB999999999999ALa figure regroupe les 64 bits qu’a écrits le programme et les lit champ par champ.
Démonstration
Les 64 bits, en hexadécimal
3FB999999999999A. On les lit de gauche à droite : 1 bit de signe, 11 d’exposant et 52 de mantisse.Le signe est 0 : le nombre est positif.
Le champ de l’exposant, en binaire, vaut 1019 ; on lui retranche le biais, 1023, et l’exposant est .
La mantisse, avec le 1 initial qui n’est pas stocké, est en binaire : le groupe se répète et le dernier groupe, , est arrondi vers le haut. Sa valeur est d’environ .
Le nombre stocké est , c’est-à-dire : très près de , mais pas .
Chargement du contenu…