En lisant le code source de Lua , j'ai remarqué que Lua utilise a macro
pour arrondir a double
à 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
se définit comme endianness , 0
pour little endian, 1
pour big endian. Lua gère soigneusement l'endianness. t
représente le type entier, comme int
ou unsigned int
.
J'ai fait un peu de recherche et il existe un format plus simple 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 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 la règle du banquier . (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 produit -12345679, comme prévu.
Je voudrais entrer dans les détails sur le fonctionnement de cette astuce 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
. Lorsqu'un entier 32 bits est ajouté à ce nombre magique, eh bien, je suis perdu d'ici. Comment fonctionne cette astuce?
PS: C'est dans le code source de Lua, Llimits.h .
MISE À JOUR :
- Comme le souligne @Mysticial, cette méthode ne se limite pas à 32 bits
int
, elle peut également être étendue à 64 bitsint
tant que le nombre est compris entre 2 ^ 52. (Lemacro
besoin de quelques modifications.) - Certains matériaux indiquent que cette méthode ne peut pas être utilisée dans Direct3D .
Lorsque vous travaillez avec l'assembleur Microsoft pour x86, il existe une
macro
écriture encore plus rapideassembly
(elle est également extraite de la source Lua):#define double2int(i,n) __asm {__asm fld n __asm fistp i}
Il existe un nombre magique similaire pour un nombre à précision unique:
1.5 * 2 ^23
la source
ftoi
. Mais si vous parlez SSE, pourquoi ne pas simplement utiliser l'instruction uniqueCVTTSD2SI
?double -> int64
sont en effet dans la2^52
fourchette. Celles-ci sont particulièrement courantes lors de l'exécution de convolutions entières à l'aide de FFT à virgule flottante.Réponses:
A
double
est représenté comme ceci:et il peut être vu comme deux entiers de 32 bits; maintenant, la
int
prise dans toutes les versions de votre code (en supposant que ce soit un 32 bitsint
) est celle de droite sur la figure, donc ce que vous faites à la fin est juste de prendre les 32 bits les plus bas de la mantisse.Maintenant, au nombre magique; comme vous l'avez correctement dit, 6755399441055744 est 2 ^ 51 + 2 ^ 52; l'ajout d'un tel nombre oblige le
double
à aller dans la "gamme douce" entre 2 ^ 52 et 2 ^ 53, ce qui, comme l'explique Wikipedia ici , a une propriété intéressante:Cela découle du fait que la mantisse a une largeur de 52 bits.
L'autre fait intéressant à propos de l'ajout de 2 51 +2 52 est qu'il n'affecte la mantisse que dans les deux bits les plus élevés - qui sont de toute façon ignorés, puisque 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 grandeur et de signe, tandis que les 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 ayons affaire à un nombre négatif dans la plage représentable par un 32 bits
int
, donc inférieur (en valeur absolue) à (-2 ^ 31 + 1); appelle-le-a
. Un tel nombre est évidemment rendu positif en ajoutant le nombre magique, et la valeur résultante est 2 52 +2 51 + (- a).Maintenant, qu'obtient-on si nous interprétons la mantisse dans la représentation complémentaire de 2? Il doit être le résultat de la somme du complément à 2 de (2 52 +2 51 ) 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 complémentaire de 2 de (-a) (encore une fois, moins les deux bits supérieurs).
Puisque la réduction d'un nombre de complément à 2 à une largeur plus petite 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.
la source
int64_t
vous pouvez le faire en décalant la mantisse vers la gauche puis vers la droite de 13 bits. Cela effacera l'exposant et les deux bits du nombre «magique», mais gardera et propagera le signe à l'entier signé 64 bits entier.union { double d; int64_t l; } magic; magic.d = input + 6755399441055744.0; magic.l <<= 13; magic.l >>= 13;