it-swarm.com.de

Eine schnelle Methode, um ein Double auf ein 32-Bit-Int zu runden

Beim Lesen von Lua's Quellcode ist mir aufgefallen, dass Lua ein macro verwendet, um ein double auf ein 32-Bit-int zu runden. Ich habe das macro extrahiert 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 ist ENDIANLOC definiert als Endianness , 0 für Little Endian, 1 für Big Endian. Lua geht sorgsam 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 von 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 (was heute so ziemlich jede Maschine bedeutet) funktionieren. Es funktioniert sowohl für positive als auch für negative Zahlen, und die Rundung folgt 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 detailliert beschreiben, wie dieses knifflige macro funktioniert. Die magische Zahl 6755399441055744.0 ist eigentlich 2^51 + 2^52, oder 1.5 * 2^52, und 1.5 in binär kann dargestellt werden als 1.1. Wenn zu dieser magischen Zahl eine 32-Bit-Ganzzahl hinzugefügt wird, verliere ich mich von hier aus. Wie funktioniert dieser Trick?

P.S: Dies ist im Lua-Quellcode Llimits.h .

[~ # ~] Update [~ # ~] :

  1. Wie @Mysticial feststellt, beschränkt sich diese Methode nicht auf ein 32-Bit-int, sondern kann auch auf ein 64-Bit-int erweitert werden, solange sich die Zahl im Bereich befindet von 2 ^ 52. (Das macro muss geändert werden.)
  2. Einige Materialien sagen, dass diese Methode in Direct3D nicht verwendet werden kann.
  3. Wenn Sie mit Microsoft Assembler für x86 arbeiten, gibt es ein noch schnelleres macro, das in Assembly geschrieben ist (dies ist auch aus dem Lua-Quellcode extrahiert):

    #define double2int(i,n)  __asm {__asm fld n   __asm fistp i}
    
  4. Es gibt eine ähnliche magische Zahl für die Zahl mit einfacher Genauigkeit: 1.5 * 2 ^23

169
Yu Hao

Ein double wird folgendermaßen dargestellt:

double representation

und es kann als zwei 32-Bit-Ganzzahlen gesehen werden; Nun, das int, das in allen Versionen Ihres Codes verwendet wird (vorausgesetzt, es ist ein 32-Bit-int), ist das, was Sie am Ende tun nur die niedrigsten 32 Bits der Mantisse nehmen.


Nun zur magischen Zahl; Wie Sie richtig angegeben haben, ist 6755399441055744 2 ^ 51 + 2 ^ 52; Das Hinzufügen einer solchen Zahl zwingt den double, in den "süßen Bereich" zwischen 2 ^ 52 und 2 ^ 53 zu gehen, der, wie von Wikipedia erklärt hier eine interessante Eigenschaft hat:

Zwischen 252= 4,503,599,627,370,496 und 253= 9.007.199.254.740.992 Die darstellbaren Zahlen sind genau die ganzen Zahlen

Dies folgt aus der Tatsache, dass die Mantisse 52 Bits breit ist.

Die andere interessante Tatsache über das Hinzufügen von 251+252 ist, dass es die Mantisse nur in den zwei höchsten Bits betrifft - die sowieso verworfen werden, da wir nur die niedrigsten 32 Bits nehmen.


Last but not least: das Schild.

IEEE 754-Gleitkomma verwendet eine Größen- und Vorzeichendarstellung, während Ganzzahlen auf "normalen" Maschinen die 2-Komplement-Arithmetik verwenden; Wie wird das hier gehandhabt?

Wir haben nur über positive ganze Zahlen gesprochen; Nehmen wir nun an, wir haben es mit einer negativen Zahl in dem Bereich zu tun, der durch ein 32-Bit-int dargestellt werden kann, also kleiner (in absoluten Werten) als (-2 ^ 31 + 1). nennen -a. Eine solche Zahl wird offensichtlich durch Addition der magischen Zahl positiv gemacht, und der resultierende Wert ist 252+251+ (- a).

Was bekommen wir nun, wenn wir die Mantisse in der Komplementdarstellung von 2 interpretieren? Es muss das Ergebnis der Zweierkomplementsumme von (252+251) und ein). Auch hier betrifft der erste Term nur die oberen zwei Bits. In den Bits 0 bis 50 verbleibt die Zweierkomplement-Darstellung von (-a) (wiederum minus der oberen zwei Bits).

Da die Reduzierung der Zweierkomplementzahl auf eine kleinere Breite nur durch Wegschneiden der zusätzlichen Bits auf der linken Seite erfolgt, ergibt die Verwendung der unteren 32 Bits in 32-Bit-Zweierkomplementarithmetik die korrekte Zahl (-a).

160
Matteo Italia