web-dev-qa-db-ja.com

doubleを32ビット整数に丸める高速な方法の説明

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は、intunsigned 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.51.1として表すことができます。 32ビット整数がこのマジックナンバーに追加されると、まあ、私はここから失われます。このトリックはどのように機能しますか?

追伸:これはLuaソースコードにあります Llimits.h

[〜#〜] update [〜#〜]

  1. @Mysticialが指摘しているように、このメソッドはそれ自体を32ビットintに制限せず、数値が範囲内にある限り64ビットintに拡張することもできます。 2 ^ 52の。 (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

169
Yu Hao

doubleは次のように表されます。

double representation

また、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)が得られます。

160
Matteo Italia