1. ホーム
  2. c++

[解決済み] double 型を 32bit int 型に丸める高速な方法について説明します。

2022-04-22 22:17:51

質問

読むとき ルア のソースコードを見ていて気づいたのですが、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です。そのため 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 (つまり、今日ほとんどすべてのマシンがそうである)。正の数でも負の数でも使えますし、丸め方も次のようになります。 バンカーズルール . (これはIEEE754に準拠しているので、驚くことではありません)。

ちょっとしたプログラムを書いてテストしてみました。

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 ビットの int に拡張することができます。 int の範囲内であれば、2個までとなります。 52 . (マクロに若干の修正が必要ですが)。

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

  3. x86用のMicrosoftアセンブラで作業する場合、アセンブリコードで書かれたさらに高速なマクロがあります(以下もLuaのソースから抜粋しています)。

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

解決方法は?

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

<イグ

で、これは2つの32ビット整数と見なすことができます。 int あなたのコードのすべてのバージョンで取られた(仮にそれが32ビットの int は図の右側なので、結局やっていることは仮数の下位32ビットを取っているだけなんですね。


さて、マジックナンバーですが、あなたが正しく述べたように、6755399441055744は、2 51  + 2 52 このような数を加えると、強制的に double の間の "スイートレンジ "に入るようにします。 52 と2 53 というように ウィキペディアの説明 という興味深い性質を持っています。

<ブロッククオート

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

これは、仮数の幅が52ビットであることから導かれる。

もう一つの興味深い事実は、2の足し算についてです。 51  + 2 52 仮数に影響を与えるのは上位2ビットだけで、これは下位32ビットだけを取り出すので、どのみち捨てられます。


最後になりますが、符号です。

IEEE754の浮動小数点は大きさと符号の表現を使いますが、「普通の」マシンの整数は2の補数演算を使いますが、ここではどのように扱われているのでしょうか?

正の整数のみについて説明しましたが、今度は32ビットの int よりも小さいので、(絶対値で)(-2) 31  + 1)、これを-aと呼ぶ。このような数は,マジックナンバーを加えることで明らかに正になり,その結果得られる値は2 52  + 2 51  + (-a).

さて、仮数を2の補数表現で解釈すると、どうなるのでしょうか。それは、(2)の2の補数和の結果でなければなりません。 52  + 2 51 )と(-a)を比較します。ここでも、最初の項は上位2ビットだけに影響し、ビット0〜50に残るのは(-a)の2の補数表現です(ここでも上位2ビットを除いたものです)。

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