[C++] ダブルを32ビットintに丸める高速メソッドの説明



Answers

Question

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];}

ここで、 ENDIANLOCendiannessとして定義され、 0はリトルエンディアン、 1はビッグエンディアンです。 ルアはエンディアンを注意深く処理します。 tintunsigned 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を使用するすべてのマシン(今日のほとんどすべてのマシンを意味します)で動作します。 これは正数と負数の両方で機能し、丸めは銀行のルールに従います。 (これは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.51.1と表現できます。 この32ビット整数がこの魔法の数字に追加されたとき、私はここから失われてしまいます。 このトリックはどのように機能しますか?

PS:これはLuaのソースコードLlimits.hます。

更新

  1. @Mysticialが指摘しているように、このメソッドは32ビットintに限定されず、その数が2 ^ 52の範囲内であれば64ビットintも拡張できます。 ( macroは何らかの修正が必要です。)
  2. 一部のマテリアルでは、このメソッドをDirect3Dで使用することはできません。
  3. x86用のMicrosoftアセンブラで作業する場合、アセンブラで書かれたさらに高速なmacroがありassembly (これは、Luaソースからも抽出されています)。

    #define double2int(i,n)  __asm {__asm fld n   __asm fistp i}
    
  4. 単精度数にも同様のマジックナンバーがあります: 1.5 * 2 ^23




Links