web-dev-qa-db-fra.com

Une méthode rapide pour arrondir un double à un entier 32 bits expliquée

En lisant Lua's le code source, j'ai remarqué que Lua utilise un macro pour arrondir un double à un 32 bits int. J'ai extrait le macro, et cela ressemble à ceci:

union i_cast {double d; int i[2]};
#define double2int(i, d, t)  \
    {volatile union i_cast u; u.d = (d) + 6755399441055744.0; \
    (i) = (t)u.i[ENDIANLOC];}

Ici ENDIANLOC est défini comme endianness , 0 pour petit endian, 1 pour big endian. Lua gère soigneusement l'endianisme. t représente le type entier, comme int ou unsigned int.

J'ai fait quelques recherches et il existe un format plus simple de macro qui utilise la même pensée:

#define double2int(i, d) \
    {double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}

Ou dans un style C++:

inline int double2int(double d)
{
    d += 6755399441055744.0;
    return reinterpret_cast<int&>(d);
}

Cette astuce peut fonctionner sur n'importe quelle machine en utilisant IEEE 754 (ce qui signifie à peu près toutes les machines aujourd'hui). Cela fonctionne pour les nombres positifs et négatifs, et l'arrondi suit Banker's Rule . (Ce n'est pas surprenant, car il suit IEEE 754.)

J'ai écrit un petit programme pour le tester:

int main()
{
    double d = -12345678.9;
    int i;
    double2int(i, d)
    printf("%d\n", i);
    return 0;
}

Et il génère -12345679, comme prévu.

Je voudrais entrer dans les détails de la façon dont fonctionne ce délicat macro. Le nombre magique 6755399441055744.0 est en fait 2^51 + 2^52, ou 1.5 * 2^52, et 1.5 en binaire peut être représenté par 1.1. Quand un entier 32 bits est ajouté à ce nombre magique, eh bien, je suis perdu d'ici. Comment fonctionne cette astuce?

P.S: C'est dans le code source de Lua, Llimits.h .

[~ # ~] mise à jour [~ # ~] :

  1. Comme le souligne @Mysticial, cette méthode ne se limite pas à un int 32 bits, elle peut également être étendue à un int 64 bits tant que le nombre est dans la plage de 2 ^ 52. (Le macro a besoin d'une modification.)
  2. Certains matériaux disent que cette méthode ne peut pas être utilisée dans Direct3D .
  3. Lorsque vous travaillez avec l'assembleur Microsoft pour x86, il existe un macro encore plus rapide écrit en Assembly (il est également extrait de la source Lua):

    #define double2int(i,n)  __asm {__asm fld n   __asm fistp i}
    
  4. Il existe un nombre magique similaire pour les nombres à simple précision: 1.5 * 2 ^23

169
Yu Hao

Un double est représenté comme ceci:

double representation

et il peut être vu comme deux entiers de 32 bits; maintenant, le int pris dans toutes les versions de votre code (en supposant qu'il s'agit d'un int 32 bits) est celui de droite sur la figure, donc ce que vous faites à la fin est en prenant simplement les 32 bits de mantisse les plus bas.


Maintenant, au nombre magique; comme vous l'avez correctement dit, 6755399441055744 est 2 ^ 51 + 2 ^ 52; l'ajout d'un tel nombre force le double à entrer dans la "gamme douce" entre 2 ^ 52 et 2 ^ 53, qui, comme l'explique Wikipedia ici , a une propriété intéressante:

Entre 252= 4 503 599 627 370 476 et 253= 9,007,199,254,740,992 les nombres représentables sont exactement les entiers

Cela découle du fait que la mantisse a une largeur de 52 bits.

L'autre fait intéressant sur l'ajout de 251+252 est qu'il n'affecte la mantisse que dans les deux bits les plus élevés - qui sont de toute façon rejetés, car nous ne prenons que ses 32 bits les plus bas.


Dernier point mais non le moindre: le signe.

La virgule flottante IEEE 754 utilise une représentation de magnitude et de signe, tandis que les nombres entiers sur les machines "normales" utilisent l'arithmétique du complément à 2; comment est-ce géré ici?

Nous n'avons parlé que d'entiers positifs; supposons maintenant que nous avons affaire à un nombre négatif dans la plage représentable par un int de 32 bits, donc moins (en valeur absolue) que (-2 ^ 31 + 1); appeler -a. Un tel nombre est évidemment rendu positif en ajoutant le nombre magique, et la valeur résultante est 252+251+ (- a).

Maintenant, qu'obtenons-nous si nous interprétons la mantisse dans la représentation du complément à 2? Il doit être le résultat de la somme complémentaire de 2 de (252+251) et (-a). Encore une fois, le premier terme affecte uniquement les deux bits supérieurs, ce qui reste dans les bits 0 ~ 50 est la représentation du complément à 2 de (-a) (encore une fois, moins les deux bits supérieurs).

Étant donné que la réduction du nombre de complément à 2 à une plus petite largeur se fait simplement en coupant les bits supplémentaires sur la gauche, prendre les 32 bits inférieurs nous donne correctement (-a) en 32 bits, l'arithmétique du complément à 2.

160
Matteo Italia