Visualiseur de motifs binaires en virgule flottante IEEE 754

Décompose un nombre décimal en son motif binaire IEEE 754 en simple précision (32 bits) ou double précision (64 bits), ou décode un motif binaire pour le reconvertir en valeur décimale. Le signe, l'exposant et la mantisse sont affichés avec un code couleur, avec prise en charge de NaN, de l'infini et des nombres dénormalisés.

L'exemple classique : pourquoi 0.1 + 0.2 ne donne pas 0.3

En JavaScript, `0.1 + 0.2` donne `0.30000000000000004`, et non `0.3`. L'examen des motifs binaires en double précision explique pourquoi.

Valeur Hexadécimal en double précision (64 bits) Binaire en double précision (64 bits)
0.1 0x3FB999999999999A 0011111110111001100110011001100110011001100110011001100110011010
0.2 0x3FC999999999999A 0011111111001001100110011001100110011001100110011001100110011010
0.3 0x3FD3333333333333 0011111111010011001100110011001100110011001100110011001100110011
0.1 + 0.2 0x3FD3333333333334 0011111111010011001100110011001100110011001100110011001100110100

0.1, 0.2 et 0.3 sont tous des fractions périodiques infinies en binaire, si bien qu'aucun d'eux ne tient exactement dans la mantisse de 52 bits et que chacun est arrondi. Comme les erreurs d'arrondi de 0.1 et de 0.2 ne s'annulent pas par hasard, leur somme finit par différer d'un seul bit du motif exact de 0.3 (0x3FD3333333333334). Essayez de saisir ces valeurs dans le convertisseur ci-dessus et comparez vous-même les derniers bits.

Astuces

  • Convertissez 0.1, 0.2 et 0.3 en bits en double précision et comparez-les au résultat de 0.1+0.2 (0.30000000000000004) — vous pouvez repérer visuellement que seul le dernier bit diffère.
  • Saisir « -0 » dans le champ décimal produit un motif binaire où seul le bit de signe est activé (différent de « +0 »). Comme nombres, 0 === -0, mais les représentations binaires sont distinctes.
  • Lorsque le champ de l'exposant est entièrement nul, une mantisse nulle signifie zéro, et une mantisse non nulle signifie un nombre dénormalisé — une représentation spéciale utilisée pour atteindre des valeurs extrêmement proches de zéro au prix de la précision.
  • Basculer entre simple précision (32 bits) et double précision (64 bits) pour une même valeur comme 0.1 montre à quel point l'erreur d'arrondi est plus importante en simple précision.
  • Pour décoder un motif binaire et le reconvertir en valeur décimale, saisissez soit une chaîne hexadécimale précédée de `0x`, soit une chaîne binaire comportant exactement autant de chiffres que la précision sélectionnée (le préfixe `0b` est facultatif).

Questions fréquentes

0.1, 0.2 et 0.3 sont tous des fractions périodiques infinies en binaire, si bien qu'un ordinateur doit arrondir chacune d'elles pour qu'elle tienne dans la mantisse finie (52 bits en double précision). Comme les erreurs d'arrondi de 0.1 et de 0.2 ne s'annulent pas par hasard, leur somme finit par différer légèrement du motif binaire exact de 0.3 (0.30000000000000004). Ce n'est pas un bug propre à un langage particulier — cela se produit dans tout langage conforme à IEEE 754.

La simple précision (float, 32 bits) utilise 1 bit de signe, 8 bits d'exposant et 23 bits de mantisse. La double précision (double, 64 bits) utilise 1 bit de signe, 11 bits d'exposant et 52 bits de mantisse. Davantage de bits de mantisse donnent plus de chiffres significatifs (précision), et davantage de bits d'exposant donnent une plage représentable plus large. La plupart des langages de programmation utilisent la double précision par défaut pour leur type à virgule flottante.

C'est un motif binaire où le champ de l'exposant est entièrement nul mais où la mantisse est non nulle. Un nombre à virgule flottante normalisé possède un 1 implicite en tête de sa mantisse, mais un nombre dénormalisé l'omet et fixe à la place l'exposant à sa valeur minimale, ce qui permet de représenter des valeurs encore plus proches de zéro que le plus petit nombre normalisé, au prix d'une certaine précision.

IEEE 754 possède un bit de signe indépendant, si bien que même lorsque l'exposant et la mantisse sont tous deux entièrement nuls, un bit de signe à 0 donne « +0 » et un bit de signe à 1 donne « -0 » — deux motifs binaires distincts. Numériquement, 0 === -0 est vrai, mais certaines opérations révèlent la différence, comme 1/0 qui produit +Infinity alors que 1/-0 produit -Infinity.

Les deux utilisent un motif binaire où le champ de l'exposant est entièrement à un. Si la mantisse est entièrement nulle, cela représente l'infini (le bit de signe distinguant +Infinity de -Infinity) ; si la mantisse est non nulle, cela représente NaN (Not a Number, pas un nombre). Ces motifs résultent d'opérations mathématiquement indéfinies telles que la division par zéro ou ∞ - ∞.
ツールくん

Anecdote — Comment IEEE 754 est devenu un langage commun

Avant 1985, chaque fabricant d'ordinateurs utilisait sa propre représentation en virgule flottante. IBM, DEC, Cray et d'autres pouvaient calculer la même valeur, disons « 0.1 », et obtenir un comportement d'arrondi et des plages représentables subtilement différents, si bien que porter un programme numérique d'une machine à une autre produisait souvent des résultats légèrement différents. Pour résoudre ce chaos, un groupe de travail dirigé par William Kahan, de l'université de Californie à Berkeley — qui avait également participé à la conception du coprocesseur numérique Intel 8087 —, a mené la création d'une norme à l'échelle de toute l'industrie, publiée en 1985 sous le nom d'IEEE 754.

L'idée centrale d'IEEE 754 consiste à répartir un nombre fini de bits (32 ou 64) en trois rôles — signe, exposant et mantisse — afin d'approximer l'ensemble infini des nombres réels. L'exposant gère l'échelle (la grandeur du nombre), tandis que la mantisse gère la précision (le nombre de chiffres significatifs), ce qui permet à un nombre fixe de bits de couvrir avec souplesse aussi bien des grandeurs très petites que très grandes. L'exposant est stocké avec un biais ajouté (127 en simple précision, 1023 en double précision) afin que la simple comparaison de deux motifs binaires en tant qu'entiers non signés suffise à déterminer quelle valeur est la plus grande, évitant ainsi la complexité supplémentaire d'une représentation signée comme le complément à deux.

Seules les fractions dont le dénominateur est une puissance de deux (1/2, 1/4, 3/8, etc.) peuvent être représentées exactement en binaire. La valeur décimale 0.1 est la fraction 1/10, et comme 10 n'est pas une puissance de deux, elle devient la fraction binaire périodique infinie 0.0001100110011... Avec seulement un nombre fini de bits de mantisse disponibles, il faut la tronquer et l'arrondir à un moment donné — c'est la raison fondamentale pour laquelle un ordinateur ne peut pas représenter 0.1 exactement. C'est aussi pourquoi les systèmes comptables ayant besoin d'une arithmétique décimale exacte évitent généralement les nombres à virgule flottante au profit de l'arithmétique entière ou d'un type décimal dédié (comme le BCD).

IEEE 754 a ensuite été révisé en 2008 et en 2019, ajoutant la demi-précision (16 bits), la précision quadruple (128 bits) et des formats en virgule flottante décimale, mais la division fondamentale en signe, exposant et mantisse, ainsi que ses modes d'arrondi, sont restés inchangés depuis l'édition originale de 1985. Comme presque tous les types float/double de tous les langages de programmation se conforment à cette norme, la question « pourquoi 0.1 + 0.2 n'est pas égal à 0.3 ? » se pose essentiellement sous la même forme, quel que soit le langage utilisé.