14

私の答えは次の場所にあります。

この式は C プリプロセッサで正しいですか

私はここで少し得意ではないので、この特定の最適化がどのように機能するかを理解しようとしています。

回答で述べたように、gcc は整数除算を 7 で最適化して次のようにします。

mov edx, -1840700269
mov eax, edi
imul    edx
lea eax, [rdx+rdi]
sar eax, 2
sar edi, 31
sub eax, edi

これは次のように C に変換されます。

int32_t divideBySeven(int32_t num) {
    int32_t temp = ((int64_t)num * -015555555555) >> 32;
    temp = (temp + num) >> 2;
    return (temp - (num >> 31));
}

最初の部分を見てみましょう。

int32_t temp = ((int64_t)num * -015555555555) >> 32;

なぜこの数字?

では、2^64 を 7 で割り、何が飛び出すか見てみましょう。

2^64 / 7 = 2635249153387078802.28571428571428571429

ぐちゃぐちゃに見えますが、これを 8 進数に変換するとどうなるでしょうか。

0222222222222222222222.22222222222222222222222

これは非常によく繰り返されるパターンであり、偶然ではありません。つまり、7 は であることを覚えており0b111、99 で割ると底が 10 の繰り返しパターンが得られる傾向があることがわかっています。したがって、7 で割ると底が 8 の繰り返しパターンが得られることは理にかなっています。

では、私たちの番号はどこから来るのでしょうか?

(int32_t)-1840700269と同じです(uint_32t)2454267027

* 7 = 17179869189

そして最後に 17179869184 は2^34

つまり、17179869189 は 7 2^34 の最も近い倍数です。別の言い方をすれば、2454267027 は に収まる最大の数で、uint32_t7 を掛けると 2 の累乗に非常に近くなります。

この数字は 8 進数で何ですか?

0222222222223

何でこれが大切ですか?さて、7 で割りたいと思います。この数は 2^34/7 です... おおよそです。したがって、それを掛けてから 34 回左シフトすると、正確な数に非常に近い数が得られるはずです。

最後の 2 行は、近似誤差を修正するように設計されているように見えます。

おそらく、この分野でもう少し知識や専門知識を持っている人がこれに参加することができます.

>>> magic = 2454267027
>>> def div7(a):
...   if (int(magic * a >> 34) != a // 7):
...     return 0
...   return 1
... 
>>> for a in xrange(2**31, 2**32):
...   if (not div7(a)):
...     print "%s fails" % a
... 

失敗は 3435973841 で始まります。これは、おかしなことに 0b11001100110011001100110011010001 です。

近似が失敗する理由を分類することは私には少し難しいことであり、パッチがそれを修正する理由も同様です。私がここに書いたこと以外に、魔法がどのように機能するか知っている人はいますか?

4

1 に答える 1

9

アルゴリズムの最初の部分は、7の逆数の近似値を乗算することです。この場合、整数の乗算と右ビットシフトを使用して逆数を計算することを近似しています。

まず、値-1840700269(8進数-015555555555)を32ビット整数として表示します。これを符号なし32ビット整数として読み取ると、値2454267027(8進数22222222223)になります。これ2454267027 / 2^34は、に非常に近い整数近似であることがわかり1/7ます。

なぜこの数とこの2の特定の累乗を選ぶのですか?使用する整数が大きいほど、近似は近くなります。この場合、2454267027は、64ビットintをオーバーフローさせることなく、符号付き32ビットintを乗算できる最大の整数(上記のプロパティを満たす)のようです。

次に、すぐに右シフトし>> 34て結果を32ビットintに格納すると、最下位2ビットの精度が失われます。これらのビットは、整数除算の適切なフロアを決定するために必要です。

2行目がx86コードから正しく変換されたかどうかはわかりません。その時点で、tempはおおよそnum * 4/7です。そのためnum * 4/7 + num、ビットシフトを行うと、おおよそnum * 1/7 + num * 1/4、非常に大きなエラーが発生します。

たとえば、入力57として、ここで57 // 7 = 8。以下のコードも確認しました。

  • 57 * 2454267027 = 139893220539
  • 139893220539 >> 32 = 3257 * 4/7 = 32.5714...(この時点で約)
  • 32 + 57 = 89
  • 89 >> 2 = 22(え??8この時点ではどこにも近くありません。)

とにかく、最後の行については、この方法で符号付き整数除算を計算した後に行う調整です。署名された除算に関するハッカーの喜びのセクションから引用します。

コードは最も自然に床分割の結果を計算するため、従来の0に向けて切り捨てられた結果を計算するように修正する必要があります。これは、被除数が負の場合に被除数に追加することにより、3つの計算命令で実行できます。

この場合(他の投稿を参照)、符号付きシフトを実行しているように見えるため-1、負の数の場合は減算されます。の結果を与える+1

これがあなたにできることのすべてではありません。これは、1回の乗算で7で割る方法についてのさらにクレイジーなブログ投稿です。

于 2013-03-07T04:47:05.837 に答える