Warum unterscheidet sich (a% 256) von (a & 0xFF)?

145

Ich bin immer davon ausgegangen, dass (a % 256)der Optimierer natürlich eine effiziente bitweise Operation verwenden würde, als ob ich geschrieben hätte (a & 0xFF).

Beim Testen auf dem Compiler Explorer 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

Und wenn Sie den anderen Code ausprobieren:

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

mod(int):
    movzx   eax, dil
    ret

Scheint, als würde mir etwas völlig fehlen. Irgendwelche Ideen?

Elad Weiss
quelle
64
0xFF ist 255 nicht 256.
Rishikesh Raje
186
@ RishikeshRaje: Also? %ist auch nicht &.
usr2564301
27
@RishikeshRaje: Ich bin sicher, dass das OP sich dessen sehr bewusst ist. Sie werden mit verschiedenen Operationen verwendet.
Prost und hth. - Alf
28
Erhalten Sie aus Interesse bessere Ergebnisse, wenn dies der Fall numist unsigned?
Bathseba
20
@RishikeshRaje Bitweise und 0xFF entsprechen Modulo 2 ^ 8 für vorzeichenlose Ganzzahlen.
2501

Antworten:

230

Es ist nicht das gleiche. Versuchen Sie es num = -79, und Sie erhalten bei beiden Vorgängen unterschiedliche Ergebnisse. (-79) % 256 = -79, während (-79) & 0xffist eine positive Zahl.

Bei Verwendung unsigned intsind die Vorgänge identisch, und der Code ist wahrscheinlich identisch.

PS: Jemand hat kommentiert

Sie sollten nicht gleich sein, a % bist definiert als a - b * floor (a / b).

So ist es nicht in C, C ++, Objective-C definiert (dh in allen Sprachen, in denen der Code in der Frage kompiliert werden würde).

gnasher729
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Martijn Pieters
53

Kurze Antwort

-1 % 256ergibt -1und nicht 255was ist -1 & 0xFF. Daher wäre die Optimierung falsch.

Lange Antwort

C ++ hat die Konvention (a/b)*b + a%b == a, die ganz natürlich erscheint. a/bGibt immer das arithmetische Ergebnis ohne den Bruchteil zurück (Abschneiden in Richtung 0). Infolgedessen a%bhat das gleiche Vorzeichen wie aoder ist 0.

Die Division -1/256ergibt 0und -1%256muss daher sein -1, um die obige Bedingung zu erfüllen ( (-1%256)*256 + -1%256 == -1). Dies ist offensichtlich anders als -1&0xFFdas ist 0xFF. Daher kann der Compiler nicht wie gewünscht optimieren.

Der relevante Abschnitt im C ++ - Standard [Ausdruck § 4] ab N4606 lautet:

Für integrale Operanden /liefert der Operator den algebraischen Quotienten, wobei jeder gebrochene Teil verworfen wird. Wenn der Quotient a/bin der Art des Ergebnisses darstellbar ist, (a/b)*b + a%bist er gleich a[...].

Optimierung aktivieren

Bei Verwendung von unsignedTypen wäre die Optimierung jedoch vollständig korrekt und würde die obige Konvention erfüllen:

unsigned(-1)%256 == 0xFF

Siehe auch dies .

Andere Sprachen

Dies wird in verschiedenen Programmiersprachen sehr unterschiedlich gehandhabt, da Sie auf Wikipedia nachschlagen können .

Ralph Tandetzky
quelle
50

Da C ++ 11, num % 256muss nicht positiv sein, wenn numnegativ ist.

Das Bitmuster hängt also von der Implementierung vorzeichenbehafteter Typen auf Ihrem System ab: Bei einem negativen ersten Argument ist das Ergebnis nicht die Extraktion der niedrigstwertigen 8 Bits.

numIn Ihrem Fall wäre es eine andere Sache unsigned: Heutzutage würde ich fast erwarten, dass ein Compiler die von Ihnen zitierte Optimierung vornimmt.

Bathseba
quelle
6
Fast aber nicht ganz. Wenn numes negativ ist, num % 256ist es null oder negativ (auch bekannt als nicht positiv).
Nayuki
5
Welche IMO ist ein Fehler im Standard: Die mathematisch modulo-Operation sollte das Vorzeichen des Divisors annehmen, in diesem Fall 256. Um zu verstehen, warum sollte man das berücksichtigen (-250+256)%256==6, (-250%256)+(256%256)muss aber nach dem Standard "nicht positiv" sein und daher nicht 6. Eine solche Assoziativität zu brechen hat reale Nebenwirkungen: Wenn man beispielsweise das "Verkleinern" -Rendering in ganzzahligen Koordinaten berechnet, muss das Bild verschoben werden, damit alle Koordinaten nicht negativ sind.
Michael
2
@Michael Modulus war noch nie über Addition verteilt ("assoziativ" ist der falsche Name für diese Eigenschaft!), Selbst wenn Sie der mathematischen Definition bis zum Buchstaben folgen. Zum Beispiel (128+128)%256==0aber (128%256)+(128%256)==256. Vielleicht gibt es einen guten Einwand gegen das angegebene Verhalten, aber mir ist nicht klar, dass es das ist, was Sie gesagt haben.
Daniel Wagner
1
@ DanielWagner, du hast natürlich recht, ich habe mit "assoziativ" falsch geschrieben. Wenn man jedoch das Vorzeichen des Divisors behält und alles in modularer Arithmetik berechnet, gilt die Verteilungseigenschaft; in deinem Beispiel hättest du 256==0. Der Schlüssel ist, genau Nmögliche Werte in der Modulo- NArithmetik zu haben, was nur möglich ist, wenn alle Ergebnisse im Bereich liegen 0,...,(N-1), nicht -(N-1),...,(N-1).
Michael
6
@ Michael: Mit Ausnahme% keine Modulo - Operator ist, es ist ein Rest - Operator.
Joren
11

Ich habe keinen telepathischen Einblick in die Argumentation des Compilers, aber im Fall von %besteht die Notwendigkeit, mit negativen Werten umzugehen (und Divisionsrunden gegen Null), während &das Ergebnis immer die unteren 8 Bits sind.

Der sarBefehl klingt für mich wie "Arithmetik nach rechts verschieben" und füllt die frei gewordenen Bits mit dem Vorzeichenbitwert.

Prost und hth. - Alf
quelle
0

Mathematisch gesehen ist Modulo wie folgt definiert:

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

Dies hier sollte es für Sie klären. Wir können Floor für Ganzzahlen eliminieren, da die Ganzzahldivision dem Floor (a / b) entspricht. Wenn der Compiler jedoch einen allgemeinen Trick verwenden würde, wie Sie ihn vorschlagen, müsste er für alle a und alle b funktionieren. Dies ist leider nicht der Fall. Mathematisch gesehen ist Ihr Trick für vorzeichenlose Ganzzahlen zu 100% korrekt (ich sehe eine Antwort, dass vorzeichenbehaftete Ganzzahlen brechen, aber ich kann dies bestätigen oder leugnen, da -a% b positiv sein sollte). Können Sie diesen Trick jedoch für alle b machen? Wahrscheinlich nicht. Deshalb macht der Compiler das nicht. Wenn Modulo leicht als eine bitweise Operation geschrieben werden könnte, würden wir einfach eine Modulo-Schaltung wie für die Addition und die anderen Operationen hinzufügen.

user64742
quelle
4
Ich denke, Sie verwechseln "Boden" mit "abgeschnitten". Frühe Computer verwendeten das Abschneiden von Teilungen, da es oft einfacher zu berechnen ist als die Bodenunterteilung, selbst wenn die Dinge sich gleichmäßig teilen. Ich habe nur sehr wenige Fälle gesehen, in denen eine abgeschnittene Teilung nützlicher war als eine bodengebundene Teilung, aber viele Sprachen folgen FORTRANs Vorbild, abgeschnittene Teilung zu verwenden.
Supercat
@supercat Mathematisch gesprochen Boden ist gestutzt. Sie haben beide den gleichen Effekt. Sie sind möglicherweise nicht in einem Computer gleich implementiert, aber sie tun dasselbe.
user64742
5
@ TheGreatDuck: Sie sind nicht gleich für negative Zahlen. Der Boden von -2.3ist -3, während, wenn Sie -2.3auf eine ganze Zahl abschneiden , erhalten Sie -2. Siehe en.wikipedia.org/wiki/Truncation . "Bei negativen Zahlen rundet das Abschneiden nicht in die gleiche Richtung wie die Bodenfunktion". Und das Verhalten von %für negative Zahlen ist genau der Grund, warum das OP das beschriebene Verhalten sieht.
Mark Dickinson
@ MarkDickinson Ich bin mir ziemlich sicher, dass Modulo in C ++ positive Werte für positive Teiler liefert, aber ich werde nicht streiten.
user64742
1
@TheGreatDuck - siehe Beispiel: cpp.sh/3g7h (Beachten Sie, dass C ++ 98 nicht definiert hat, welche der beiden möglichen Varianten verwendet wurde, aber dass neuere Standards dies tun, sodass Sie möglicherweise eine Implementierung von C ++ verwendet haben in der Vergangenheit hat das anders gemacht ...)
Periata Breatta