Wenn ich eine vorzeichenlose Ganzzahl auf die nächste kleinere oder gleich gerade ganze Zahl runden möchte, kann ich dann durch 2 dividieren und dann mit 2 multiplizieren?

68

Zum Beispiel :

f(8)=8
f(9)=8

Kann ich tun x = x/2*2;? Besteht das Risiko, dass der Compiler einen solchen Ausdruck optimiert?

user2370139
quelle
Optimierungen dürfen die Ergebnisse einer genau definierten Operation nicht ändern.
Barmar
2
Es wird x >>= 1; x <<= 1;
dringend empfohlen, Folgendes zu
5
@ user3629249 Warum dringend empfehlen? Es drückt die Absicht weniger klar aus als der bereits in der Frage enthaltene Code, es wird zu identischem Maschinencode kompiliert, sodass es nicht schneller ist, und es gibt kein Analogon, wenn Sie auf das nächste Vielfache einer anderen Zahl als 2 runden möchten (es sei denn, diese Zahl auch zufällig eine Potenz von 2).
Arthur Tacca
weil es schneller ist, nicht multiplizieren und teilen. Ein VIEL einfacherer Weg ist die einfache Verwendung von: x &= ~(1)' Where the 1` könnte eine Zeichenfolge von 1s der gewünschten Länge sein. Zum Beispiel: x &= ~(0x07);für drei Bits usw.
user3629249
1
Wenn beide Methoden zu denselben Maschinenanweisungen führen, muss die Optimierung auf max.
user3629249

Antworten:

86

Der Compiler darf beliebige Optimierungen vornehmen, solange er keine Nebenwirkungen in das Programm einbringt. In Ihrem Fall kann es die 2 nicht aufheben, da der Ausdruck dann einen anderen Wert für ungerade Zahlen hat.

x / 2 * 2wird streng als ausgewertet (x / 2) * 2und x / 2in ganzzahliger Arithmetik ausgeführt, wenn xes sich um einen ganzzahligen Typ handelt.

Dies ist in der Tat eine idiomatische Rundungstechnik.

Bathseba
quelle
4
Ich mag die Notiz darüber, dass dies idiomatisch ist. Es ist auch etwas intuitiver als eine Maske IMO.
Geschichtenerzähler - Unslander Monica
29
Natürlich ist der Compiler ist dies richtig zu optimieren erlaubt, zum Beispiel indem sie sie mit ersetzen (x>>1)<<1. IIRC gibt es Compiler, die dies auch dann tun, wenn Optimierungen technisch deaktiviert sind.
MSalters
2
@ MSalters: Ja, es war eine dumme Sache zu sagen. Ich habe geändert.
Bathsheba
31
@ MSalters: Oder im Fall von unsignierten (x & 0xfffffffe):)
Matthieu M.
3
@ Voo: Warum? Es ist eine vorzeichenlose Ganzzahl. Das Verhalten ist per Definition identisch.
MSalters
53

Da Sie angegeben haben, dass die Ganzzahlen ohne Vorzeichen sind, können Sie dies mit einer einfachen Maske tun:

x & (~1u)

Dadurch wird das LSB auf Null gesetzt, wodurch die unmittelbare gerade Zahl erzeugt wird, die nicht größer als ist x. Das heißt, wenn xes einen Typ gibt, der nicht breiter als ein ist unsigned int.

Sie können natürlich erzwingen 1, dass derselbe Typ wie ein breiterer ist x, wie folgt:

x & ~((x & 1u) | 1u)

Aber an diesem Punkt sollten Sie diesen Ansatz wirklich als eine Übung zur Verschleierung betrachten und Bathshebas Antwort akzeptieren.


Ich habe natürlich die Standardbibliothek vergessen. Wenn Sie einschließen stdint.h(oder cstdint, wie Sie es in C ++ - Code sollten). Sie können die Implementierung sich um die Details kümmern lassen:

uintmax_t const lsb = 1;
x & ~lsb;

oder

x & ~UINTMAX_C(1)
Geschichtenerzähler - Unslander Monica
quelle
3
Benötigen Sie x & (~static_cast<decltype(x)>(1))keinen Kaffee oder brauche ich ihn?
Bathseba
2
@Bathsheba - Es ist definitiv ich, der einen Kaffee braucht, als. Mmm ... Ich habe nicht viel darüber gesagt, mit einem vorzeichenlosen Integer-Typ zu arbeiten.
Geschichtenerzähler - Unslander Monica
1
@Bathsheba - Ich würde, aber dieses C-Tag ... Ich möchte ein bisschen darüber nachdenken und cool sein, indem ich eine Lösung produziere, die sowohl in C als auch in C ++ funktioniert.
Geschichtenerzähler - Unslander Monica
1
@Bathsheba - Aber nur bis UINT_MAX, nein? Wer'e noch im gleichen Boot , wenn xist unsigned long, ich denke .
Geschichtenerzähler - Unslander Monica
4
Ich fürchte, x & (~1u)funktioniert nicht, wenn der Typ xgrößer ist alsunsigned int . Umgekehrt x & ~1würde sich für alle Typen wie erwartet verhalten. Dies ist eine kontraintuitive Falle. Wenn Sie darauf bestehen, eine vorzeichenlose Konstante zu verwenden, müssen Sie schreiben, x & ~(uintmax_t)1da x & ~1ULLdies bei xeinem größeren Typ als sogar fehlschlagen würde unsigned long long. Erschwerend kommt hinzu, dass viele Plattformen jetzt größere Ganzzahltypen haben als uintmax_tz __uint128_t.
Chqrlie
36

C und C ++ verwenden bei der Optimierung im Allgemeinen die "als ob" -Regel. Das Berechnungsergebnis muss so sein, als ob der Compiler Ihren Code nicht optimiert.

In diesem Fall 9/2*2=8. Der Compiler kann eine beliebige Methode verwenden, um das Ergebnis 8 zu erzielen. Dazu gehören Bitmasken, Bitverschiebungen oder CPU-spezifische Hacks, die dieselben Ergebnisse liefern (x86 verfügt über einige Tricks, die darauf beruhen, dass nicht zwischen Zeigern unterschieden wird und ganze Zahlen, im Gegensatz zu C und C ++).

MSalters
quelle
11
Fall und Punkt . Damit produziert GCC -O1. Alle Ansätze werden auf den gleichen Maschinencode destilliert.
Geschichtenerzähler - Unslander Monica
Und wie ich vermutet habe, wird GCC die and RAX, -2Methode für alle 3 Varianten sogar anwenden -O0.
MSalters
7
GCC ist insofern äußerst ungewöhnlich, als es diese Art von mathematischen / Bit-Twiddling-Gucklochoptimierungen anwendet, selbst wenn Optimierungen deaktiviert sind. Das ist eine interessante und bemerkenswerte Design-Eigenart. Andere Compiler führen eine viel wörtlichere Übersetzung des C-Codes in Maschinenanweisungen mit deaktivierter Optimierung durch. Sobald Sie den Optimierer aktivieren, ist die Ausgabe jedoch gleich. Aus diesem Grund sollten Sie den Code zur besseren Lesbarkeit schreiben, es sei denn, Sie wissen genau, dass Ihr Code vorhanden ist Der Compiler ist hirntot.
Cody Gray
1
Die Umwandlung einer Division in eine Verschiebung ist eine Standardoptimierungstechnik, die als "Festigkeitsreduzierung" bekannt ist. Ja, es ist sehr normal und äußerst unkompliziert, aber ich bin anderer Meinung, dass es keine Optimierung darstellt. @ Supercat
Cody Gray
1
@supercat: Ich stimme Cody Gray hier größtenteils zu. Es ist nicht so sehr die Verringerung der Stärke; Es ist die Tatsache, dass GCC keinen Code für einzelne Ausdrücke generiert. x/2*2Kompiliert zu einer einzelnen Anweisung. Im Allgemeinen wird das Kompilieren ohne Optimierungen zu Debugging-Zwecken durchgeführt, und dann möchten Sie eine 1: N-Entsprechung zwischen Quellcode und Assembly.
MSalters
21

Sie können schreiben x / 2 * 2und der Compiler erzeugt sehr effizienten Code, um das niedrigstwertige Bit zu löschen, wennx einen vorzeichenlosen Typ hat.

Umgekehrt könnte man schreiben:

x = x & ~1;

Oder wahrscheinlich weniger lesbar:

x = x & -2;

Oder auch

x = (x >> 1) << 1;

Oder auch das:

x = x - (x & 1);

Oder dieser letzte, von Supercat vorgeschlagene, der für positive Werte aller ganzzahligen Typen und Darstellungen funktioniert:

x = (x | 1) ^ 1;

Alle oben genannten Vorschläge funktionieren korrekt für alle vorzeichenlosen Ganzzahltypen auf den Komplementarchitekturen von 2. Ob der Compiler optimalen Code erzeugt, ist eine Frage der Konfiguration und der Qualität der Implementierung.

Beachten Sie jedoch, dass x & (~1u)dies nicht funktioniert, wenn der Typ xgrößer als ist unsigned int. Dies ist eine kontraintuitive Falle. Wenn Sie darauf bestehen, eine vorzeichenlose Konstante zu verwenden, müssen Sie schreiben, x & ~(uintmax_t)1da x & ~1ULLdies bei xeinem größeren Typ als sogar fehlschlagen würde unsigned long long. Erschwerend kommt hinzu, dass viele Plattformen jetzt ganzzahlige Typen haben, die größer als sinduintmax_t z __uint128_t.

Hier ist ein kleiner Maßstab:

typedef unsigned int T;

T test1(T x) {
    return x / 2 * 2;
}

T test2(T x) {
    return x & ~1;
}

T test3(T x) {
    return x & -2;
}

T test4(T x) {
    return (x >> 1) << 1;
}

T test5(T x) {
    return x - (x & 1);
}

T test6(T x) {  // suggested by supercat
    return (x | 1) ^ 1;
}

T test7(T x) {  // suggested by Mehrdad
    return ~(~x | 1);
}

T test1u(T x) {
    return x & ~1u;
}

Wie von Ruslan vorgeschlagen, zeigt das Testen im Compiler-Explorer von Godbolt , dass für alle oben genannten Alternativen gcc -O1derselbe exakte Code für erzeugt unsigned intwird, der Typ Tjedoch unsigned long longgeändert wird, um unterschiedlichen Code für anzuzeigen test1u.

chqrlie
quelle
Hier ist ein Beispiel dafür, was GCC mit x/2*2unsignierten macht x: godbolt.org/g/Nee7cJ
Ruslan
@ Ruslan: Danke für Ihren Kommentar, gcc erzeugt tatsächlich den gleichen Code für alle vorgeschlagenen Alternativen.
Chqrlie
@MM: Ich glaube, es funktioniert für alle ganzzahligen Typen und Darstellungen, aber nur für positive Werte von x. Können Sie zeigen, für welchen Wert es nicht funktionieren würde?
Chqrlie
1
@ MM: Wie so? 1ist positiv und wird immer als dargestellt 00...01. |1stellt das LSB ein. ^1schaltet das gleiche Bit um. Das Komplement beeinflusst den Wert des MSB cq den Wert von, INT_MINaber wir berühren das MSB nicht. IOW, Zahlendarstellungen spielen eine Rolle, wenn Sie arithmetische und numerische Operationen mischen, und das tun wir nicht. (Aber die Frage lässt offen, wie man negative Zahlen rundet,
abrundet
1
Was nützt ein Puzzle der Bitmanipulation ohne jedermanns Liebling, Modulo! x = x - (x%2)? Dies hat den Vorteil der Skalierung auf jeden nworoundToMultiple(int x, int n) { return x - (x % n); }
CORSIKA
7

Wenn Ihre Werte von einem vorzeichenlosen Typ sind, wie Sie sagen, ist dies am einfachsten

x & -2;

Die Wunder der vorzeichenlosen Arithmetik machen es so, dass -2es in den Typ von konvertiert xwird und ein Bitmuster hat, das alle hat, aber für das niedrigstwertige Bit, das ist 0.

Im Gegensatz zu einigen anderen vorgeschlagenen Lösungen sollte dies mit jedem vorzeichenlosen Ganzzahltyp funktionieren, der mindestens so breit ist wie unsigned. (Und Sie sollten sowieso nicht mit engeren Typen rechnen.)

Zusätzlicher Bonus, wie von Supercat bemerkt, verwendet nur die Konvertierung eines signierten Typs in einen nicht signierten Typ. Dies wird durch den Standard als Modulo-Arithmetik definiert. Das Ergebnis ist also immer UTYPE_MAX-1für UTYPEden vorzeichenlosen Typ von x. Insbesondere ist es unabhängig von der Vorzeichendarstellung der Plattform für vorzeichenbehaftete Typen.

Jens Gustedt
quelle
4
Es kann erwähnenswert sein, nur um Verwirrung zu vermeiden, dass die Konvertierung von -2 in "vorzeichenlos" den "vorzeichenlosen Wert" ergibt, der bei Addition zu 2 0 ergibt, unabhängig davon, ob ein System vorzeichenbehaftete Zwei-Komplement-Mathematik verwendet . Wenn man dagegen ein Einsen-Komplement-System hätte, wäre ~ 1 gleich -1, was bei Umwandlung in vorzeichenloses System einen Wert ergeben würde, bei dem alle Bits gesetzt sind.
Supercat
@ Supercat, danke, ja genau. Ich habe versucht, diese Ideen in meine Antwort zu integrieren.
Jens Gustedt
Um dies für vorzeichenlose Typen kleiner als unsigned int zu machen, können Sie (1u * x) & -2
plugwash
6

Eine Option, von der ich überrascht bin, dass sie bisher nicht erwähnt wurde, ist die Verwendung des Modulo-Operators. Ich würde argumentieren, dass dies Ihre Absicht mindestens genauso gut darstellt wie Ihr ursprüngliches Snippet und vielleicht sogar noch besser.

x = x - x % 2

Wie andere bereits gesagt haben, wird der Optimierer des Compilers jeden vernünftigen Ausdruck gleichwertig behandeln. Machen Sie sich also Gedanken darüber, was klarer ist als was Sie für am schnellsten halten. Alle Bit-Tweaking-Antworten sind interessant, aber Sie sollten definitiv keine von ihnen anstelle von arithmetischen Operatoren verwenden (vorausgesetzt, die Motivation ist eher arithmetisch als Bit-Tweaking).

Arthur Tacca
quelle
1
Ja, die Optimierer des Compilers sind klug genug: godbolt.org/g/U9zsiL
Bob__
1
Während das OP unsigned angegeben hat, sollte beachtet werden, dass das oben Gesagte möglicherweise überraschende Ergebnisse liefert, wenn Sie ihm eine vorzeichenbehaftete Ganzzahl geben.
Yakk - Adam Nevraumont
@Yakk In C ist Modulo so definiert x % d = x - x / d * d, dass mein Snippet garantiert identische Ergebnisse wie die x = x/2*2in der Frage genannten liefert . Angenommen, xSie befürchten, negativ zu sein (und nicht als negativer Ersatz für 2), dann gibt dies das, was OP wahrscheinlich will: wenn x=-7dann x/2*2 == x - x%2 == -6.
Arthur Tacca
Aber die Tatsache, dass Sie diese Sorge hatten und ich war mir nicht sicher, bis ich sie überprüft habe, wirft mein Argument darüber zurück, dass es leichter zu lesen ist (im Fall von negativen Zahlen).
Arthur Tacca
@ArthurTacca Ich würde behaupten, dass sogar die OPs x/2*2mit negativen ganzen Zahlen überraschend sind. Ich bin mir also nicht sicher, wie ich vermeiden soll, Code zu haben, der sich für jemanden mit negativen ganzen Zahlen überraschend verhält!
Yakk - Adam Nevraumont
-2

Verwenden Sie einfach Folgendes:

template<class T>
inline T f(T v)
{
    return v & (~static_cast<T>(1));
}

Keine Angst, dass dies eine Funktion ist, der Compiler sollte dies schließlich in nur v & (~ 1) mit dem entsprechenden Typ 1 optimieren.

ivan.ukr
quelle