Lua's ソースコードを読むと、Luaはmacro
を使用してdouble
を32ビットint
に丸めていることに気付きました。 macro
を抽出すると、次のようになります。
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];}
ここで、ENDIANLOC
は エンディアンネス 、リトルエンディアンの場合は0
、ビッグエンディアンの場合は1
として定義されます。 Luaはエンディアンを慎重に処理します。 t
は、int
やunsigned int
などの整数型を表します。
私は少し調査しましたが、同じ考え方を使用するmacro
のより単純な形式があります。
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
または、C++スタイルの場合:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
このトリックは、 IEEE 754 (今日のほとんどすべてのマシンを意味します)を使用して、どのマシンでも機能します。正数と負数の両方で機能し、丸めは Banker's Rule に従います。 (IEEE 754に準拠しているため、これは驚くべきことではありません。)
私はそれをテストする小さなプログラムを書きました:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
そして、期待どおり-12345679を出力します。
このトリッキーなmacro
の仕組みを詳しく説明したいと思います。マジックナンバー6755399441055744.0
は実際には2^51 + 2^52
、または1.5 * 2^52
であり、バイナリの1.5
は1.1
として表すことができます。 32ビット整数がこのマジックナンバーに追加されると、まあ、私はここから失われます。このトリックはどのように機能しますか?
追伸:これはLuaソースコードにあります Llimits.h 。
[〜#〜] update [〜#〜]:
int
に制限せず、数値が範囲内にある限り64ビットint
に拡張することもできます。 2 ^ 52の。 (macro
には変更が必要です。)X86用Microsoftアセンブラを使用する場合、macro
で記述されたAssembly
がさらに高速になります(これもLuaソースから抽出されます)。
#define double2int(i,n) __asm {__asm fld n __asm fistp i}
単精度数には同様のマジックナンバーがあります:1.5 * 2 ^23
double
は次のように表されます。
また、2つの32ビット整数として見ることができます。これで、コードのすべてのバージョンで取得されたint
(32ビットのint
であると仮定)は、図の右側にあるため、最終的には仮数の最下位32ビットを取得します。
さて、マジックナンバーへ。正しく述べたように、6755399441055744は2 ^ 51 + 2 ^ 52です。このような数値を追加すると、double
は2 ^ 52から2 ^ 53の「甘い範囲」に入ります。これは、Wikipedia here で説明されているように、興味深い特性を持っています。
2の間52= 4,503,599,627,370,496および253= 9,007,199,254,740,992表現可能な数値は整数です
これは、仮数が52ビット幅であるという事実に基づいています。
2を追加することに関する他の興味深い事実51+252 仮数に影響を与えるのは、最上位2ビットのみです。最下位32ビットのみを使用しているため、いずれにしても破棄されます。
最後になりましたが、記号です。
IEEE 754浮動小数点は大きさと符号表現を使用しますが、「通常の」マシンの整数は2の補数演算を使用します。これはどのようにここで処理されますか?
正の整数についてのみ説明しました。ここで、32ビットint
で表現可能な範囲内の負の数を処理しているため、(絶対値で)(-2 ^ 31 + 1)より少ないと仮定します。あれを呼べ -a
。そのような数は、明らかにマジックナンバーを追加することで正になり、結果の値は2です。52+251+(-a)。
さて、仮数を2の補数表現で解釈するとどうなりますか?これは、2の補数合計(252+251)および(-a)。繰り返しますが、最初の項は上位2ビットにのみ影響します。ビット0〜50に残るのは、(-a)の2の補数表現です(再び、上位2ビットを引いたもの)。
2の補数の小さい幅への縮小は左側の余分なビットを切り取るだけで行われるため、下位32ビットを取得すると、32ビットの2の補数演算で正しく(-a)が得られます。