double を 32 ビット int に丸める高速な方法を説明します 質問する

double を 32 ビット int に丸める高速な方法を説明します 質問する

読むときルアdoubleのソースコードを見ると、Luaは値を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 はエンディアン性を慎重に処理します。引数はまたは のt ような整数型に置き換えられます。intunsigned 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);
}

このトリックは、どのマシンでも機能しますIEEE754 規格(これは今日のほぼすべてのマシンに当てはまります)。これは正の数と負の数の両方に機能し、丸めは次のように行われます。銀行家のルール(これは 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 ビット整数を加えると、

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

アップデート

  1. @Mysticial が指摘しているように、この方法は 32 ビットに限定されず、数値が 2 52の範囲内であればint64 ビットに拡張することもできます。(ただし、マクロには多少の修正が必要です。)int

  2. いくつかの資料では、この方法は使用できないと記載されていますダイレクト3D

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

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

ベストアンサー1

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

二重表現

これは 2 つの 32 ビット整数として見ることができます。ここで、intコードのすべてのバージョンで取得される (32 ビット であると仮定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 つの興味深い事実は、仮数部の上位 2 ビットのみに影響することです。下位 32 ビットのみを取得するため、これらのビットはいずれにしても破棄されます。


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

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

これまでは正の整数についてのみ説明してきましたが、今度は 32 ビットで表現できる範囲の負の数、つまり (-2 31int + 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) が得られます。

おすすめ記事