web-dev-qa-db-ja.com

(a%256)が(a&0xFF)と異なるのはなぜですか?

(a % 256)を実行するとき、オプティマイザは、自然に(a & 0xFF)を作成したかのように、効率的なビット演算を使用すると自然に想定していました。

コンパイラーエクスプローラーgcc-6.2(-O3)でテストする場合:

// Type your code here, or load an example.
int mod(int num) {
    return num % 256;
}

mod(int):
    mov     edx, edi
    sar     edx, 31
    shr     edx, 24
    lea     eax, [rdi+rdx]
    movzx   eax, al
    sub     eax, edx
    ret

そして、他のコードを試すとき:

// Type your code here, or load an example.
int mod(int num) {
    return num & 0xFF;
}

mod(int):
    movzx   eax, dil
    ret

私は完全に何かを見逃しているようです。何か案は?

143
Elad Weiss

同じではありません。 num = -79を試すと、両方の操作で異なる結果が得られます。 (-79) % 256 = -79(-79) & 0xffは正の数です。

unsigned intを使用すると、操作は同じになり、コードも同じになります。

PS-誰かがコメントした

それらは同じであってはなりません。a % ba - b * floor (a / b)として定義されています。

これは、C、C++、Objective-C(つまり、問題のコードがコンパイルされるすべての言語)での定義方法ではありません。

228
gnasher729

短い答え

-1 % 256は、-1である255ではなく、-1 & 0xFFを生成します。したがって、最適化は正しくありません。

長い答え

C++には、(a/b)*b + a%b == aという規則がありますが、これは非常に自然に思えます。 a/bは、常に小数部分のない算術結果を返します(0に向かって切り捨てられます)。結果として、a%baと同じ符号を持つか、0です。

除算-1/2560を生成するため、上記の条件(-1%256)を満たすために-1(-1%256)*256 + -1%256 == -1でなければなりません。これは、-1&0xFFである0xFFとは明らかに異なります。そのため、コンパイラは希望する方法を最適化できません。

N4606時点のC++標準[expr.mul§4] の関連セクション:

整数オペランドの場合、/演算子は、小数部分が破棄された代数商を生成します。商a/bが結果の型で表現できる場合、(a/b)*b + a%ba [...]と等しくなります。

最適化を有効にする

ただし、unsigned型を使用すると、最適化は完全に正しいになり、上記の規則を満たします。

unsigned(-1)%256 == 0xFF

this もご覧ください。

他の言語

Wikipedia で調べることができるため、これはプログラミング言語によって大きく異なります。

54
Ralph Tandetzky

C++ 11以降、numが負の場合、num % 256は正でない必要があります。

したがって、ビットパターンはシステムの符号付き型の実装に依存します。最初の引数が負の場合、結果は最下位8ビットの抽出ではありません。

あなたの場合のnumunsignedだった場合、それは別の問題になります:最近では、ほとんどexpectあなたが引用する最適化を行うコンパイラーになります。

50
Bathsheba

コンパイラの推論についてテレパシーの洞察力はありませんが、%の場合、負の値を処理する必要があり(除算はゼロに向かって丸められます)、&では結果は常に下位8ビットです。

sar命令は、「算術右シフト」のように聞こえ、空のビットを符号ビット値で埋めます。

11

数学的に言えば、モジュロは次のように定義されます。

a%b = a-b * floor(a/b)

ここでこれをクリアする必要があります。整数の除算はfloor(a/b)と等しいため、整数の下限を削除できます。ただし、コンパイラがあなたが提案するような一般的なトリックを使用する場合、すべてのaおよびすべてのbで動作する必要があります。残念ながら、これは事実ではありません。数学的に言えば、あなたのトリックは符号なし整数に対して100%正しいです(符号付き整数が壊れていると答えているが、-a%bは正でなければならないのでこれを確認または拒否できます)。ただし、すべてのbに対してこのトリックを行うことはできますか?おそらくない。コンパイラーがそれをしない理由です。結局、モジュロが1つのビット単位の演算として簡単に記述される場合、加算やその他の演算のようなモジュロ回路を追加するだけです。

0
The Great Duck