Luaのソース コードを読んでいるときに、Lua がマクロを使用してdouble
値を 32 ビット値に丸めていることに気付きましたint
。マクロはLlimits.h
ヘッダー ファイルで定義され、次のように読み取られます。
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 はエンディアンを慎重に処理します。引数はorの ような整数型に置き換えられます。t
int
unsigned int
少し調査したところ、同じ手法を使用するマクロのより単純な形式があることがわかりました。
#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
、期待どおりに出力されます。
このトリッキーなマクロがどのように機能するかを詳しく理解したいと思います。マジック ナンバー6755399441055744.0
は実際には 2 51 + 2 52、つまり 1.5 × 2 52であり、2 進数の 1.5 は 1.1 と表すことができます。このマジック ナンバーに任意の 32 ビット整数を追加すると—</p>
さて、ここから道に迷った。このトリックはどのように機能しますか?
アップデート
@Mysticial が指摘しているように、このメソッドは 32 ビットに限定されず、数値が 2 52の範囲内にある限り
int
、64 ビットに拡張することもできます。(ただし、マクロにはいくつかの変更が必要です。)int
この方法はDirect3Dでは使用できないという資料もあります。
x86 用の Microsoft アセンブラーを使用する場合、アセンブリ コードで記述されたさらに高速なマクロがあります (以下も Lua ソースから抽出されます)。
#define double2int(i,n) __asm {__asm fld n __asm fistp i}
単精度数には同様のマジック ナンバーがあります: 1.5 × 2 23です。