173

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の ような整数型に置き換えられます。tintunsigned 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>

さて、ここから道に迷った。このトリックはどのように機能しますか?

アップデート

  1. @Mysticial が指摘しているように、このメソッドは 32 ビットに限定されず、数値が 2 52の範囲内にある限りint、64 ビットに拡張することもできます。(ただし、マクロにはいくつかの変更が必要です。)int

  2. この方法はDirect3Dでは使用できないという資料もあります。

  3. x86 用の Microsoft アセンブラーを使用する場合、アセンブリ コードで記述されたさらに高速なマクロがあります (以下も Lua ソースから抽出されます)。

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

4

4 に答える 4

169

浮動小数点型の値は次のdoubleように表されます。

二重表現

2 つの 32 ビット整数と見なすことができます。今、intあなたのコードのすべてのバージョン (それが 32-bit であると仮定int) に取り込まれたものは、図の右側のものであるため、最終的に行っていることは、仮数の最下位 32 ビットを取っているだけです。


さて、マジックナンバーへ。あなたが正しく述べたように、 6755399441055744 は 2 51  + 2 52です。このような数値を追加すると、 が 2 52と 2 53doubleの間の「スイート レンジ」に入ります。Wikipedia で説明されているように、興味深い特性があります。

2 52  = 4,503,599,627,370,496 と 2 53  = 9,007,199,254,740,992 の間では、表現可能な数は正確に整数です。

これは、仮数が 52 ビット幅であることから生じます。

2 51  + 2 52の追加に関するもう 1 つの興味深い事実は、最下位 32 ビットのみを取得しているため、最上位 2 ビットのみが仮数に影響するということです。


最後になりましたが、標識です。

IEEE 754 浮動小数点は大きさと符号の表現を使用しますが、「通常の」マシンの整数は 2 の補数演算を使用します。これはここでどのように処理されますか?

正の整数についてのみ話しました。ここで、32 ビットで表現可能な範囲内の負の数を扱っているとします。つまり、int(絶対値で) より小さい (−2 31  + 1); −a と呼ぶ。このような数は明らかにマジック ナンバーを加算することで正になり、結果の値は 2 52  + 2 51  + (−a) になります。

仮数部を 2 の補数表現で解釈するとどうなるでしょうか。(2 52  + 2 51 ) と (−a)の 2 の補数和の結果でなければなりません。繰り返しますが、最初の項は上位 2 ビットのみに影響します。ビット 0 ~ 50 に残るのは、(-a) の 2 の補数表現です (ここでも、上位 2 ビットを引いたものです)。

2 の補数をより狭い幅に縮小するには、左側の余分なビットを切り取るだけなので、下位 32 ビットを取得すると、32 ビットの 2 の補数演算で正しく (-a) が得られます。

于 2013-06-11T02:19:15.693 に答える