Beim Lesen von Luas Quellcode habe ich festgestellt, dass Lua a verwendet macro
, um a double
auf 32 Bit zu runden int
. Ich habe das extrahiert macro
und es sieht so aus:
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];}
Hier ENDIANLOC
wird Endianness definiert , 0
für Little Endian, 1
für Big Endian. Lua geht vorsichtig mit Endianness um. t
steht für den Integer-Typ, wie int
oder unsigned int
.
Ich habe ein wenig recherchiert und es gibt ein einfacheres Format macro
, das denselben Gedanken verwendet:
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
Oder im C ++ - Stil:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
Dieser Trick kann auf jeder Maschine mit IEEE 754 funktionieren (was heutzutage so ziemlich jede Maschine bedeutet). Es funktioniert sowohl für positive als auch für negative Zahlen, und die Rundung folgt der Banker-Regel . (Dies ist nicht überraschend, da es IEEE 754 folgt.)
Ich habe ein kleines Programm geschrieben, um es zu testen:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
Und es gibt erwartungsgemäß -12345679 aus.
Ich möchte ins Detail gehen, wie dies schwierig macro
funktioniert. Die magische Zahl 6755399441055744.0
ist tatsächlich 2^51 + 2^52
oder 1.5 * 2^52
und und 1.5
kann binär dargestellt werden als 1.1
. Wenn eine 32-Bit-Ganzzahl zu dieser magischen Zahl hinzugefügt wird, bin ich von hier aus verloren. Wie funktioniert dieser Trick?
PS: Dies ist im Lua-Quellcode Llimits.h .
UPDATE :
- Wie @Mysticial hervorhebt, beschränkt sich diese Methode nicht auf 32-Bit
int
, sondern kann auch auf 64-Bit erweitert werden,int
solange die Zahl im Bereich von 2 ^ 52 liegt. (Dasmacro
muss geändert werden.) - Einige Materialien sagen, dass diese Methode in Direct3D nicht verwendet werden kann .
Wenn Sie mit Microsoft Assembler für x86 arbeiten, wird noch schneller
macro
geschriebenassembly
(dies wird auch aus der Lua-Quelle extrahiert):#define double2int(i,n) __asm {__asm fld n __asm fistp i}
Es gibt eine ähnliche magische Zahl für Zahlen mit einfacher Genauigkeit:
1.5 * 2 ^23
ftoi
. Aber wenn Sie über SSE sprechen, warum nicht einfach die einzelne Anweisung verwenden CVTTSD2SI
?
double -> int64
liegen tatsächlich im 2^52
Bereich. Diese treten besonders häufig auf, wenn ganzzahlige Faltungen mit Gleitkomma-FFTs durchgeführt werden.