Ich habe C ++ - Code durchsucht und so etwas gefunden:
(a + (b & 255)) & 255
Das doppelte UND ärgerte mich, also dachte ich an:
(a + b) & 255
( a
und b
sind 32-Bit-Ganzzahlen ohne Vorzeichen)
Ich schrieb schnell ein Testskript (JS), um meine Theorie zu bestätigen:
for (var i = 0; i < 100; i++) {
var a = Math.ceil(Math.random() * 0xFFFF),
b = Math.ceil(Math.random() * 0xFFFF);
var expr1 = (a + (b & 255)) & 255,
expr2 = (a + b) & 255;
if (expr1 != expr2) {
console.log("Numbers " + a + " and " + b + " mismatch!");
break;
}
}
Obwohl das Skript meine Hypothese bestätigt hat (beide Operationen sind gleich), vertraue ich ihr immer noch nicht, weil 1) zufällig und 2) ich kein Mathematiker bin, ich habe keine Ahnung, was ich tue .
Entschuldigen Sie auch den Lisp-y-Titel. Fühlen Sie sich frei, es zu bearbeiten.
Math.random()
eine Ganzzahl oder ein Doppel bei [0,1) zurück? Ich glaube nicht, dass Ihr Skript (so gut ich es beurteilen kann) das Problem widerspiegelt, das Sie überhaupt gestellt haben.&
und+
auf vorzeichenlosen Ganzzahlen in C und C ++.Antworten:
Sie sind gleich. Hier ist ein Beweis:
Notieren Sie zuerst die Identität
(A + B) mod C = (A mod C + B mod C) mod C
Lassen Sie sich neu formuliert das Problem , indem in Bezug auf
a & 255
wie in Vertretung vona % 256
. Dies ist wahr, daa
es nicht signiert ist.So
(a + (b & 255)) & 255
ist es auch(a + (b % 256)) % 256
Dies ist dasselbe wie
(a % 256 + b % 256 % 256) % 256
(Ich habe die oben angegebene Identität angewendet: Beachten Sie, dassmod
und%
für nicht signierte Typen gleichwertig sind.)Dies vereinfacht das,
(a % 256 + b % 256) % 256
worauf es ankommt(a + b) % 256
(erneutes Anwenden der Identität). Sie können dann den bitweisen Operator zurücksetzen, um zu geben(a + b) & 255
den Beweis vervollständigen.
quelle
A=0xFFFFFFFF, B=1, C=3
. Die erste Identität gilt nicht. (Überlauf wird kein Problem für vorzeichenlose Arithmetik sein, aber es ist eine etwas andere Sache.)(a + (b & 255)) & 255
ist es dasselbe wie(a + (b % 256)) % N % 256
, wobeiN
eins größer als der maximale vorzeichenlose Wert ist. (Die letztere Formel soll als Arithmetik mathematischer Ganzzahlen interpretiert werden)Bei der Positionsaddition, Subtraktion und Multiplikation von vorzeichenlosen Zahlen, um vorzeichenlose Ergebnisse zu erzielen, wirken sich signifikantere Ziffern der Eingabe nicht auf weniger signifikante Ziffern des Ergebnisses aus. Dies gilt für die binäre Arithmetik ebenso wie für die Dezimalarithmetik. Dies gilt auch für vorzeichenbehaftete Arithmetik mit "Zweierkomplement", nicht jedoch für vorzeichenbehaftete arithmetische Arithmetik.
Wir müssen jedoch vorsichtig sein, wenn wir Regeln aus der binären Arithmetik nehmen und auf C anwenden (ich glaube, C ++ hat die gleichen Regeln wie C für dieses Zeug, aber ich bin nicht 100% sicher), weil C-Arithmetik einige arkane Regeln hat, die uns auslösen können oben. Die vorzeichenlose Arithmetik in C folgt einfachen binären Umlaufregeln, aber der vorzeichenbehaftete arithmetische Überlauf ist ein undefiniertes Verhalten. Schlimmer noch, unter bestimmten Umständen "befördert" C automatisch einen nicht signierten Typ zu (signiertem) int.
Undefiniertes Verhalten in C kann besonders heimtückisch sein. Ein dummer Compiler (oder ein Compiler auf einer niedrigen Optimierungsstufe) wird wahrscheinlich das tun, was Sie aufgrund Ihres Verständnisses der binären Arithmetik erwarten, während ein optimierender Compiler Ihren Code auf seltsame Weise beschädigen kann.
Zurück zur Formel in der Frage: Die Äquivalenz hängt von den Operandentypen ab.
Wenn es sich um vorzeichenlose Ganzzahlen handelt, deren Größe größer oder gleich der Größe von ist,
int
ist das Überlaufverhalten des Additionsoperators als einfacher binärer Wraparound definiert. Ob wir die hohen 24 Bits eines Operanden vor der Additionsoperation maskieren oder nicht, hat keinen Einfluss auf die niedrigen Bits des Ergebnisses.Wenn es sich um Ganzzahlen ohne Vorzeichen handelt, deren Größe geringer
int
ist, werden sie zu (mit Vorzeichen) heraufgestuftint
. Der Überlauf von vorzeichenbehafteten Ganzzahlen ist ein undefiniertes Verhalten, aber zumindest auf jeder Plattform, auf die ich gestoßen bin, ist der Größenunterschied zwischen verschiedenen Ganzzahltypen groß genug, dass eine einzelne Addition von zwei heraufgestuften Werten keinen Überlauf verursacht. Wir können also wieder auf das einfach binäre arithmetische Argument zurückgreifen, um die Aussagen als äquivalent zu betrachten.Wenn es sich um vorzeichenbehaftete Ganzzahlen handelt, deren Größe kleiner als int ist, kann es erneut nicht zu einem Überlauf kommen. Bei Implementierungen mit zwei Komplementen können wir uns auf das Standardargument der binären Arithmetik verlassen, um zu sagen, dass sie gleichwertig sind. Bei Vorzeichengrößen oder solchen, die Implementierungen ergänzen, wären sie nicht gleichwertig.
OTOH wenn
a
undb
vorzeichenbehaftete Ganzzahlen deren Größe größer oder gleich der Größe von int war, dann gibt es sogar bei Implementierungen mit zwei Komplementen Fälle, in denen eine Anweisung gut definiert wäre, während die andere ein undefiniertes Verhalten wäre.quelle
Lemma:
a & 255 == a % 256
für unsigniertea
.Unsigned
a
neu geschrieben werden können , wiem * 0x100 + b
einige unsignedm
,b
,0 <= b < 0xff
,0 <= m <= 0xffffff
. Aus beiden Definitionen folgt, dassa & 255 == b == a % 256
.Zusätzlich brauchen wir:
(a + b) mod n = [(a mod n) + (b mod n)] mod n
(a + b) ==> (a + b) % (2 ^ 32)
So:
Also ja, es ist wahr. Für 32-Bit-Ganzzahlen ohne Vorzeichen.
Was ist mit anderen Integer-Typen?
2^64
zu2^32
.int
. Diesint
wird bei keinem dieser Vorgänge überlaufen oder negativ sein, sodass alle gültig bleiben.a+b
odera+(b&255)
Überlauf, dann ist es nicht definiertes Verhalten. Die Gleichheit kann also nicht gelten - es gibt Fälle, in denen(a+b)&255
undefiniertes Verhalten vorliegt, dies jedoch(a+(b&255))&255
nicht der Fall ist.quelle
Ja,
(a + b) & 255
ist in Ordnung.Erinnerst du dich an den Zusatz in der Schule? Sie fügen Zahlen Ziffer für Ziffer hinzu und fügen der nächsten Ziffernspalte einen Übertragswert hinzu. Es gibt keine Möglichkeit für eine spätere (signifikantere) Ziffernspalte, eine bereits verarbeitete Spalte zu beeinflussen. Aus diesem Grund macht es keinen Unterschied, ob Sie die Ziffern nur im Ergebnis oder auch zuerst in einem Argument auf Null setzen.
Das Obige ist nicht immer wahr, der C ++ - Standard erlaubt eine Implementierung, die dies brechen würde.
Eine solche Deathstation 9000 : - ) müsste ein 33-Bit verwenden
int
, wenn das OPunsigned short
mit "32-Bit-Ganzzahlen ohne Vorzeichen" gemeint wäre . Wennunsigned int
gemeint wäre, müsste der DS9K ein 32-Bitint
und ein 32-Bitunsigned int
mit einem Auffüllbit verwenden. (Die vorzeichenlosen Ganzzahlen müssen dieselbe Größe wie ihre vorzeichenbehafteten Gegenstücke gemäß §3.9.1 / 3 haben, und Füllbits sind in §3.9.1 / 1 zulässig.) Andere Kombinationen von Größen und Füllbits würden ebenfalls funktionieren.Soweit ich das beurteilen kann, ist dies der einzige Weg, es zu brechen, weil:
int
Heraufstufung ist nur zulässig, wenn alle Werte des Quelltyps (§4.5 / 1) dargestellt werden können. Daherint
müssen mindestens 32 Bits zum Wert beitragen, plus ein Vorzeichenbit.int
kann nicht mehr Wertbits haben (ohne das Vorzeichenbit) als 32, da sonst eine Addition nicht überlaufen kann.quelle
2^N-1
. (N muss nicht einmal ein Vielfaches von CHAR_BIT sein, ich vergesse, aber ich bin mir ziemlich sicher, dass der Standard erfordert, dass der Wraparound modulo mit einer Potenz von 2 erfolgt.) Ich denke, der einzige Weg, um Verrücktheit zu erlangen, ist die Beförderung zu a signierter Typ, der breit genug ist, um zu halten,a
oderb
aber nicht breit genug, uma+b
in allen Fällen zu halten .Sie haben bereits die kluge Antwort: Vorzeichenlose Arithmetik ist Modulo-Arithmetik und daher gelten die Ergebnisse, Sie können es mathematisch beweisen ...
Eine coole Sache an Computern ist jedoch, dass Computer schnell sind. In der Tat sind sie so schnell, dass die Aufzählung aller gültigen Kombinationen von 32 Bit in angemessener Zeit möglich ist (versuchen Sie es nicht mit 64 Bit).
In Ihrem Fall mag ich es persönlich, es einfach auf einen Computer zu werfen. Ich brauche weniger Zeit, um mich davon zu überzeugen, dass das Programm korrekt ist, als um mich selbst zu überzeugen, als der mathematische Beweis korrekt ist und dass ich kein Detail in der Spezifikation 1 übersehen habe :
Dies listet alle möglichen Werte von
a
undb
im 32-Bit-Raum auf und prüft, ob die Gleichheit gilt oder nicht. Wenn dies nicht der Fall ist, wird der Fall gedruckt, der nicht funktioniert hat, und der als Überprüfung der geistigen Gesundheit verwendet werden kann.Und laut Clang : Gleichheit gilt .
Da die arithmetischen Regeln bitbreitenunabhängig sind (über der
int
Bitbreite), gilt diese Gleichheit für jeden vorzeichenlosen Integer-Typ von 32 Bit oder mehr, einschließlich 64 Bit und 128 Bit.Hinweis: Wie kann ein Compiler alle 64-Bit-Muster in einem angemessenen Zeitrahmen auflisten? Es kann nicht. Die Schleifen wurden optimiert. Sonst wären wir alle gestorben, bevor die Hinrichtung beendet war.
Ich habe es zunächst nur für 16-Bit-Ganzzahlen ohne Vorzeichen bewiesen. Leider ist C ++ eine verrückte Sprache, in die zuerst kleine Ganzzahlen (kleinere Bitbreiten als
int
) konvertiert werdenint
.Und noch einmal laut Clang : Gleichheit gilt .
Na siehst du :)
1 Wenn ein Programm jemals versehentlich undefiniertes Verhalten auslöst, würde es natürlich nicht viel beweisen.
quelle
int
: kleine Ganzzahlen werden zuerst konvertiertint
(eine seltsame Regel). Also muss ich die Demonstration tatsächlich mit 32 Bit machen (und danach erstreckt sie sich auf 64 Bit, 128 Bit, ...).int
das alles Mögliche darstellen kannuint16_t
, aber woa+b
es überlaufen kann. Das ist nur ein Problem für vorzeichenlose Typen, die schmaler sind alsint
; C erfordert, dassunsigned
Typen binäre Ganzzahlen sind, so dass Wraparound modulo eine Potenz von 2Die schnelle Antwort lautet: Beide Ausdrücke sind gleichwertig
a
undb
32-Bit-Ganzzahlen ohne Vorzeichen sind, ist das Ergebnis auch im Falle eines Überlaufs dasselbe. Die vorzeichenlose Arithmetik garantiert dies: Ein Ergebnis, das nicht durch den resultierenden vorzeichenlosen Ganzzahltyp dargestellt werden kann, wird modulo um die Zahl reduziert, die eins größer ist als der größte Wert, der durch den resultierenden Typ dargestellt werden kann.Die lange Antwort lautet: Es gibt keine bekannten Plattformen, auf denen sich diese Ausdrücke unterscheiden würden, aber der Standard garantiert dies aufgrund der Regeln der integralen Werbung nicht.
Wenn der Typ von
a
undb
(vorzeichenlose 32-Bit-Ganzzahlen) einen höheren Rang als hatint
, wird die Berechnung als vorzeichenloses Modulo 2 32 ausgeführt und liefert für beide Ausdrücke für alle Werte vona
und das gleiche definierte Ergebnisb
.Wenn umgekehrt der Typ
a
undb
kleiner als istint
, werden beide heraufgestuftint
und die Berechnung wird unter Verwendung einer vorzeichenbehafteten Arithmetik durchgeführt, wobei ein Überlauf ein undefiniertes Verhalten hervorruft.Wenn
int
mindestens 33 Wertbits vorhanden sind, kann keiner der oben genannten Ausdrücke überlaufen, sodass das Ergebnis perfekt definiert ist und für beide Ausdrücke den gleichen Wert hat.Wenn
int
genau 32 Wert Bits hat, die Berechnung kann für Überlauf beiden Ausdrücke, zum Beispiel Wertena=0xFFFFFFFF
undb=1
würde einen Überlauf in den beiden Ausdrücken führen. Um dies zu vermeiden, müssten Sie schreiben((a & 255) + (b & 255)) & 255
.Die gute Nachricht ist, dass es keine solchen Plattformen gibt 1 .
1 Genauer gesagt gibt es keine solche reale Plattform, aber man könnte einen DS9K so konfigurieren, dass er ein solches Verhalten aufweist und dennoch dem C-Standard entspricht.
quelle
a
ist kleiner alsint
(2)int
hat 32 Wertbits (3)a=0xFFFFFFFF
. Das kann nicht alles wahr sein.int
, wobei es 32 Wertbits und ein Vorzeichenbit gibt.Identisch unter der Annahme, dass kein Überlauf vorliegt . Keine der Versionen ist wirklich immun gegen Überlauf, aber die Doppel- und Version ist widerstandsfähiger dagegen. Mir ist kein System bekannt, bei dem ein Überlauf in diesem Fall ein Problem darstellt, aber ich kann sehen, dass der Autor dies tut, falls es eines gibt.
quelle
int
es nicht 33 Bit breit ist, ist das Ergebnis auch bei Überlauf das gleiche . Die vorzeichenlose Arithmetik garantiert dies: Ein Ergebnis, das nicht durch den resultierenden vorzeichenlosen Ganzzahltyp dargestellt werden kann, wird modulo um die Zahl reduziert, die eins größer ist als der größte Wert, der durch den resultierenden Typ dargestellt werden kann.Ja, Sie können es mit Arithmetik beweisen, aber es gibt eine intuitivere Antwort.
Beim Hinzufügen beeinflusst jedes Bit nur diejenigen, die wichtiger sind als es selbst; niemals die weniger bedeutenden.
Was auch immer Sie mit den höheren Bits vor dem Hinzufügen tun, ändert daher nichts am Ergebnis, solange Sie nur Bits beibehalten, die weniger wichtig sind als das niedrigste modifizierte Bit.
quelle
Der Beweis ist trivial und als Übung für den Leser hinterlassen
Aber um dies tatsächlich als Antwort zu legitimieren, heißt es in Ihrer ersten Codezeile, nehmen Sie die letzten 8 Bits von
b
** (alle höheren Bits vonb
auf Null gesetzt) und fügen Sie diese hinzua
und nehmen Sie dann nur die letzten 8 Bits der Ergebnismenge, die alle höher sind Bits auf Null.In der zweiten Zeile steht add
a
undb
und nimm die letzten 8 Bits mit allen höheren Bits Null.Nur die letzten 8 Bits sind im Ergebnis von Bedeutung. Daher sind nur die letzten 8 Bits in den Eingängen von Bedeutung.
** letzte 8 Bits = 8 LSB
Es ist auch interessant festzustellen, dass die Ausgabe äquivalent zu wäre
Wie oben sind nur die 8 LSB signifikant, aber das Ergebnis ist eine
unsigned int
mit allen anderen Bits Null. Dera + b
Wille läuft über und erzeugt das erwartete Ergebnis.quelle