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


145

私が(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

何かが完全に欠けているようです。何か案は?


64
0xFFでは256 255ではありません
リシケシラージ

186
@RishikeshRaje:それで?%ありません&どちらか。
usr2564301 2016年

27
@RishikeshRaje:OPはそのことをよく知っていると思います。さまざまな操作で使用されます。
乾杯とhth。-アルフ

28
場合関心のうち、あなたはより良い結果を得るのですかnumありますかunsigned
バトシェバ2016年

20
@RishikeshRaje Bitwiseおよび0xFFは、符号なし整数の2 ^ 8を法とするものと同等です。
2501

回答:


230

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

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

PS-誰かがコメントしました

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

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


コメントは拡張ディスカッション用ではありません。この会話はチャットに移動さました
Martijn Pieters

53

短い答え

-1 % 256収量-1ではなく255どちらです-1 & 0xFF。したがって、最適化は正しくありません。

長い答え

C ++には、という規則が(a/b)*b + a%b == aあります。a/b常に小数部なしで算術結果を返します(0に向かって切り捨て)。結果a%bとしてa、は0または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

参照してくださいこれを

他の言語

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


50

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

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

numあなたの場合は別の問題でしょうunsigned:最近で、コンパイラが引用する最適化を行うことをほとんど期待しています。


6
ほぼ完全ではありませんが。場合はnum否定され、その後、num % 256ゼロまたは負(別名非正)です。
Nayuki、2016年

5
どのIMOが標準の誤りですか。数学的に剰余演算は除数の符号(この場合は256)を取る必要があります。なぜそれを考慮するのかを理解するために(-250+256)%256==6、しかし(-250%256)+(256%256)それは標準によれば「非陽性」でなければならず、したがってそうではない6。たとえば、整数座標で「ズームアウト」レンダリングを計算する場合、すべての座標が負でないように画像をシフトする必要があります。
マイケル

2
@Michael Modulusは、数学の定義に従っても、( "associative"はこのプロパティの間違った名前です!)これまでに配布されたことはありません。たとえばです(128+128)%256==0(128%256)+(128%256)==256。おそらく、指定された振る舞いに良い異議があるかもしれませんが、それがあなたが言ったものであることは私には明らかではありません。
Daniel Wagner

1
@DanielWagner、あなたは正しいです、もちろん、私は「連想」とは間違えました。ただし、除数の符号を保持し、すべてをモジュラー算術で計算する場合、分布特性は保持されます。あなたの例ではあなたが持っているでしょう256==0。重要なのはN、モジュロN演算で正確に可能な値を持つことです。これは、すべての結果が範囲内0,...,(N-1)にある場合にのみ可能です-(N-1),...,(N-1)
Michael

6
@Michael:%が剰余演算子ではないことを除いて、剰余演算子です。
Joren、2016年

11

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

sar符号ビットの値で空いたビットを埋める、「算術右にシフト」のように私に指示音。


0

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

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

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


4
「フロア」と「トランケート」を混同していると思います。初期のコンピューターは切り捨てを使用していました。これは、物事が均等に分割された場合でも、フロア型除算よりも計算が簡単なことが多いためです。切り捨てられた除算がフロアされた除算よりも有用であるケースはほとんどありませんでしたが、多くの言語が切り捨てられた除算を使用するFORTRANの主導に従っています。
スーパーキャット2016年

@supercat数学的に言えば、床切り捨てられます。どちらも同じ効果があります。それらはコンピューターに同じように実装されていないかもしれませんが、同じことをします。
user64742 2016年

5
@TheGreatDuck:負の数では同じではありません。の底は-2.3ですが-3-2.3整数に切り捨てると得られ-2ます。en.wikipedia.org/wiki/Truncationを参照してください。「負の数の場合、切り捨ては、floor関数と同じ方向に丸められません」。そして、%負の数の場合の動作は、OPが記述された動作を見ている理由です。
Mark Dickinson

@MarkDickinson c ++のモジュロが正の約数に正の値を与えることは確かですが、私は議論しません。
user64742 16

1
@TheGreatDuck-例を参照してください:cpp.sh/3g7h (C ++ 98は2つの可能なバリアントのどちらが使用されたかを定義していませんが、最近の標準では定義されているため、C ++の実装を使用した可能性があります以前は違った
やり方でした
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.