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 [~ # ~] :
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.)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}
Il existe un nombre magique similaire pour les nombres à simple précision: 1.5 * 2 ^23
Un double
est représenté comme ceci:
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.