Comprendre les nombres à virgule flottante (partie 0)

Bonjour, Khabrovites. Cela fait longtemps que j'aime le sujet des registres à virgule flottante. J'étais toujours inquiet de la façon dont la sortie sur l'écran, etc. Je me souviens, il y a longtemps, à l'université, j'implémentais ma classe de nombres à virgule flottante composés de 512 bits. La seule chose que je ne pouvais pas réaliser était la sortie à l'écran.

Dès que j'ai eu du temps libre, j'ai repris l'ancien. Je me suis procuré un cahier et c'est parti. Je voulais penser à tout moi-même, en ne regardant que de temps en temps la norme IEEE 754.
Et c'est ce qui en est ressorti. Pour ceux intéressés, je demande chat.

Pour maîtriser cet article, vous devez connaître les éléments suivants: qu'est-ce qu'un peu, un système binaire, arithmétique au niveau de la connaissance des degrés négatifs. L'article n'affectera pas les détails techniques de l'implémentation au niveau du processeur ainsi que les nombres normalisés et dénormalisés. L'accent est davantage mis sur la conversion d'un nombre sous forme binaire et vice versa, ainsi que sur l'explication de la manière dont les nombres à virgule flottante sont généralement stockés sous forme de bits.

Les nombres à virgule flottante sont un outil très puissant que vous devez utiliser correctement. Ils ne sont pas aussi courants que les registres entiers, mais pas aussi complexes s'ils sont pénétrés avec compétence et lentement.

Dans l'article d'aujourd'hui, j'utiliserai des registres 32 bits comme exemple. Les nombres à double précision (64 bits) fonctionnent exactement dans la même logique.

Tout d'abord, parlons de la façon dont les nombres à virgule flottante sont stockés. Les 31 bits les plus anciens sont significatifs. Un seul signifie que le nombre est négatif et zéro, respectivement, est l'opposé. Viennent ensuite 8 bits de l'exposant. Ces 8 bits sont le nombre non signé habituel. Et à la fin se trouvent 23 bits de la mantisse. Pour plus de commodité, nous désignons le signe comme S, l'exposant comme E et la mantisse, curieusement, M.

Nous obtenons la formule générale (1)s foisM fois2E127

La mantisse est considérée comme un seul bit implicite. Autrement dit, la mantisse sera de 24 bits, mais comme le 23e bit le plus élevé est toujours un, vous ne pouvez pas l'écrire. Ainsi, cette «restriction» nous donnera l'unicité de représenter n'importe quel nombre.

Mantissa est un nombre binaire ordinaire, mais contrairement aux entiers, le bit le plus significatif est 2 ^ 0 degrés puis en degrés décroissants. C'est là que l'exposant est utile. En fonction de sa valeur, la puissance du bit haut deux augmente ou diminue. C'est tout le génie de cette idée.

Essayons de montrer cela avec un bon exemple:

Imaginez le nombre 3,625 sous forme binaire. Premièrement, nous divisons ce nombre en puissances de deux. 3,625 $ = 2 + 1 + 0,5 + 0,125 = 1 \ fois 2 ^ 1 + 1 \ fois 2 ^ 0 + 1 \ fois 2 ^ {-1} + 0 \ fois 2 ^ {-2} + 1 \ fois 2 ^ { -3} $

Le degré des deux seniors est égal à un. E - 127 = 1. E = 128.

0 1 000 000 1 101 000 000 000 000 000 000 000

C'est tout notre nombre.

Essayons aussi dans la direction opposée. Supposons que nous ayons 32 bits, 32 bits arbitraires.

0 10000100 (1) 11011100101000000000000

Le même bit de poids fort implicite est indiqué entre parenthèses.

Tout d'abord, calculez l'exposant. E = 132. En conséquence, le degré des deux seniors sera égal à 5. Au total, nous avons le nombre suivant:
25+24+23+21+20+21+24+26=
=32+16+8+2+1+0,5+0,0625+0,015625=59,578125

Il est facile de deviner que nous ne pouvons stocker qu'une plage de 24 degrés deux. Par conséquent, si deux nombres diffèrent exponentiellement de plus de 24, alors une fois ajoutés, le nombre reste égal au plus grand d'entre eux.

Pour une conversion pratique, j'ai téléchargé un petit programme en C.

#include <stdio.h> union IntFloat { unsigned int integerValue; float floatValue; }; void printBits(unsigned int x) { int i; for (i = 31; i >= 0; i--) { if ((x & ((unsigned int)1 << i)) != 0) { printf("1"); } else { printf("0"); } if (i == 31) { printf(" "); } if (i == 23) { printf(" "); } } printf("\n"); } int main() { union IntFloat b0; b0.floatValue = 59.578125; printBits(b0.integerValue); b0.integerValue = 0b01000010011011100101000000000000; printf("%f\n", b0.floatValue); return 0; } 

Le pas de grille est la différence minimale entre deux nombres à virgule flottante adjacents. Si nous représentons une séquence de bits d'un tel nombre comme un entier régulier, alors le nombre à virgule flottante voisin différera en bits comme un entier par unité.

Cela peut s'exprimer autrement. Deux nombres à virgule flottante adjacents différeront de 2 ^ (E - 127 - 23). C'est-à-dire par une différence égale à la valeur du bit le moins significatif.

Pour preuve, vous pouvez changer main dans le code et recompiler.

 union IntFloat b0, b1, b2; b0.floatValue = 59.578125F; b1.integerValue = b0.integerValue + 1; b2.floatValue = b1.floatValue - b0.floatValue; printBits(b0.integerValue); printBits(b1.integerValue); printBits(b2.integerValue); printf("%f\n", b0.floatValue); printf("%f\n", b1.floatValue); printf("%f\n", b2.floatValue); short exp1 = 0b10000100; short exp2 =0b01101101; /*  ,       */ b0.integerValue = 0b01000010011111111111111111111111; b1.integerValue = b0.integerValue + 1; b2.floatValue = b1.floatValue - b0.floatValue; printBits(b0.integerValue); printBits(b1.integerValue); printBits(b2.integerValue); printf("%f\n", b0.floatValue); printf("%f\n", b1.floatValue); printf("%f\n", b2.floatValue); /*   */ printf("%d %d\n", exp1, exp2); 

Je pense que pour aujourd'hui, vous pouvez arrondir, sinon cela se révèle trop long. La prochaine fois, j'écrirai sur l'ajout de nombres à virgule flottante et la perte de précision lors de l'arrondi.

PS: Je comprends que je n'ai pas abordé le sujet des nombres dénormalisés, etc. Je ne voulais tout simplement pas beaucoup charger l'article, et cette information peut être facilement trouvée dans la norme IEEE 754 presque au tout début.

Source: https://habr.com/ru/post/fr456714/


All Articles