(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
私は完全に何かを見逃しているようです。何か案は?
同じではありません。 num = -79
を試すと、両方の操作で異なる結果が得られます。 (-79) % 256 = -79
、(-79) & 0xff
は正の数です。
unsigned int
を使用すると、操作は同じになり、コードも同じになります。
PS-誰かがコメントした
それらは同じであってはなりません。
a % b
はa - b * floor (a / b)
として定義されています。
これは、C、C++、Objective-C(つまり、問題のコードがコンパイルされるすべての言語)での定義方法ではありません。
-1 % 256
は、-1
である255
ではなく、-1 & 0xFF
を生成します。したがって、最適化は正しくありません。
C++には、(a/b)*b + a%b == a
という規則がありますが、これは非常に自然に思えます。 a/b
は、常に小数部分のない算術結果を返します(0に向かって切り捨てられます)。結果として、a%b
はa
と同じ符号を持つか、0です。
除算-1/256
は0
を生成するため、上記の条件(-1%256
)を満たすために-1
は(-1%256)*256 + -1%256 == -1
でなければなりません。これは、-1&0xFF
である0xFF
とは明らかに異なります。そのため、コンパイラは希望する方法を最適化できません。
N4606時点のC++標準[expr.mul§4] の関連セクション:
整数オペランドの場合、
/
演算子は、小数部分が破棄された代数商を生成します。商a/b
が結果の型で表現できる場合、(a/b)*b + a%b
はa
[...]と等しくなります。
ただし、unsigned
型を使用すると、最適化は完全に正しいになり、上記の規則を満たします。
unsigned(-1)%256 == 0xFF
this もご覧ください。
Wikipedia で調べることができるため、これはプログラミング言語によって大きく異なります。
C++ 11以降、num
が負の場合、num % 256
は正でない必要があります。
したがって、ビットパターンはシステムの符号付き型の実装に依存します。最初の引数が負の場合、結果は最下位8ビットの抽出ではありません。
あなたの場合のnum
がunsigned
だった場合、それは別の問題になります:最近では、ほとんどexpectあなたが引用する最適化を行うコンパイラーになります。
コンパイラの推論についてテレパシーの洞察力はありませんが、%
の場合、負の値を処理する必要があり(除算はゼロに向かって丸められます)、&
では結果は常に下位8ビットです。
sar
命令は、「算術右シフト」のように聞こえ、空のビットを符号ビット値で埋めます。
数学的に言えば、モジュロは次のように定義されます。
a%b = a-b * floor(a/b)
ここでこれをクリアする必要があります。整数の除算はfloor(a/b)と等しいため、整数の下限を削除できます。ただし、コンパイラがあなたが提案するような一般的なトリックを使用する場合、すべてのaおよびすべてのbで動作する必要があります。残念ながら、これは事実ではありません。数学的に言えば、あなたのトリックは符号なし整数に対して100%正しいです(符号付き整数が壊れていると答えているが、-a%bは正でなければならないのでこれを確認または拒否できます)。ただし、すべてのbに対してこのトリックを行うことはできますか?おそらくない。コンパイラーがそれをしない理由です。結局、モジュロが1つのビット単位の演算として簡単に記述される場合、加算やその他の演算のようなモジュロ回路を追加するだけです。