Dabei wurde das Thema ja eher durch Zufall gestreift - im Zusammenhang mit meinen Fragen zu Softwarepatenten.
Ursprünglicher Thread viewtopic.php?p=66488
Es geht darum, den Betrag einer Ganzzahl ohne branching zu ermitteln.
Krishty, du hattest in der Diskussion eine Technik dazu erwähnt:
Code: Alles auswählen
(x ^ 0x80000000) - 0x80000000
Gemeint hattest du wohl Folgendes (laut deiner verlinkten Quelle https://graphics.stanford.edu/~seander/ ... IntegerAbs ):
Code: Alles auswählen
int v; // we want to find the absolute value of v
unsigned int r; // the result goes here
int const mask = v >> sizeof(int) * CHAR_BIT - 1;
r = (v + mask) ^ mask;
(Der Patentversuch durch Sun Microsystems dürfte ungültig sein - siehe Link weiter oben)
Code: Alles auswählen
r = (v ^ mask) - mask;
Ist v negativ, dann hat mask den Wert -1 (alle Bits sind 1).
Ist v positiv, dann hat mask den Wert 0.
Dieser Mechanismus ersetzt quasi das branching.
Doch ob bei right shift auf signed arithmetisch oder logisch geshiftet wird, ist ja bekanntlich Implementation-Defined Behavior - also abhängig vom Compiler (was die Quelle ja auch erwähnt).
In der Praxis dürfte dadurch die "Verwendbarkeit" der Technik ziemlich eingeschränkt sein (oder wie seht ihr das?).
Ich habe dann das Ganze anhand von 4-Bit Beispielen mal durchgerechnet, mit Augenmerk auf die Grenzfälle (4-Bit oder 32-Bit - das Konzept und die möglichen Probleme bleiben ja die selben).
1. Version
Code: Alles auswählen
r = (v + mask) ^ mask;
4-Bit Beispiel
Erster Schritt: Addtion mit mask
Code: Alles auswählen
1000 // 1000 ≙ -8
+ 1111 // 1111 ≙ -1 overflow: (-8) + (-1) = -9
-----
1 0111 // overflow
Code: Alles auswählen
0111 // 1000 ≙ 7
^ 1111 // 1111 ≙ -1
-----
1000 // -8 ≙ 8 (unsigned)
Das Bitmuster bleibt (bei gleicher Bitbreite) von signed zu unsigned (und umgekehrt) ja stets das selbe.
Aus -8 wird also 8. Doch weil das Ganze durch die Addition bereits UB war, ist das Kind in den Brunnen gefallen.
In der Quelle wird das soweit auch erwähnt - doch weiter auch Folgendes:
Diese Aussage kann ich in dieser Form nicht nachvollziehen.Hai Jin complained that the result was signed, so when computing the abs of the most negative value, it was still negative.
2. Version ("Patentversuch")
Code: Alles auswählen
r = (v ^ mask) - mask;
Beide Versionen sind also gleichermassen UB.
4-Bit Beispiel
Erster Schritt: XOR mit mask
Code: Alles auswählen
1000 // 1000 ≙ -8
^ 1111 // 1111 ≙ -1
-----
0111 // 0111 ≙ 7
Code: Alles auswählen
0111 // 1000 ≙ 7
- 1111 // 1111 ≙ -1 overflow: 7 - (-1) = 7 + 1 = 8
-----
1000 // -8 ≙ 8 (unsigned)
Ich glaube allerdings für beide Versionen eine Lösung gefunden zu haben, wodurch diese definiertes Verhalten aufweisen.
aus:
Code: Alles auswählen
int mask = v >> sizeof(int) * CHAR_BIT - 1;
Code: Alles auswählen
unsigned int mask = v >> sizeof(int) * CHAR_BIT - 1;
Weiter passiert dann Folgendes:
Variante 1.
Code: Alles auswählen
r = (v + mask) ^ mask;
Dadurch, das v nun zu unsigned int promoted wurde, tritt jetzt kein overflow, sondern ein wrap-around auf - was bekanntlich wohl definiert ist.
Variante 2.
Code: Alles auswählen
r = (v ^ mask) - mask;
Mit dieser Lösung müssten sich jetzt beide Varianten ebenbürtig sein.
Rechnerisch hat sich nichts geändert, doch das Ganze resultiert jetzt maximal in einem wrap-around und nicht in einem overflow.
Ein expliziter cast ist durch die Regeln der integer promotion nicht nötig.
Vielleicht ist sogar die Variante 1. einen kleinen Tick eleganter, da eine Addition und keine Subtraktion stattfindet.
Ausserdem käme Variante 1. aufgrund der operator precedence auch ohne Klammern aus.
Code: Alles auswählen
r = v + mask ^ mask;
Ich hoffe meine Überlegungen stimmen und ich habe nichts übersehen!
Vielleicht sind euch ja noch weitere Details aufgefallen?