Links- und Rechtsverschiebungsoperatoren (<< und >>) sind in C ++ bereits verfügbar. Ich konnte jedoch nicht herausfinden, wie ich Kreisverschiebungs- oder Rotationsvorgänge ausführen konnte.
Wie können Operationen wie "Nach links drehen" und "Nach rechts drehen" ausgeführt werden?
Hier zweimal nach rechts drehen
Initial --> 1000 0011 0100 0010
sollte führen zu:
Final --> 1010 0000 1101 0000
Ein Beispiel wäre hilfreich.
(Anmerkung des Herausgebers: Viele gängige Methoden zum Ausdrücken von Rotationen in C weisen ein undefiniertes Verhalten auf, wenn die Rotationszahl Null ist oder zu mehr als nur einer einzelnen Rotationsmaschinenanweisung kompiliert wird. Die Antwort dieser Frage sollte Best Practices dokumentieren.)
Antworten:
Siehe auch eine frühere Version dieser Antwort zu einer anderen Rotationsfrage mit einigen weiteren Details darüber, was asm gcc / clang für x86 produziert.
Die compilerfreundlichste Art, eine Rotation in C und C ++ auszudrücken, die undefiniertes Verhalten vermeidet, scheint die Implementierung von John Regehr zu sein . Ich habe es so angepasst, dass es sich um die Breite des Typs dreht (unter Verwendung von Typen mit fester Breite wie
uint32_t
).Funktioniert für jeden vorzeichenlosen Integer-Typ, nicht nur
uint32_t
, sodass Sie Versionen für andere Größen erstellen können.Siehe auch eine C ++ 11-Vorlagenversion mit vielen Sicherheitsüberprüfungen (einschließlich der Tatsache,
static_assert
dass die Typbreite eine Potenz von 2 ist) , was beispielsweise bei einigen 24-Bit-DSPs oder 36-Bit-Mainframes nicht der Fall ist.Ich würde empfehlen, die Vorlage nur als Back-End für Wrapper mit Namen zu verwenden, die die Drehbreite explizit enthalten. Integer-Promotion-Regeln bedeuten, dass
rotl_template(u16 & 0x11UL, 7)
eine 32- oder 64-Bit-Drehung durchgeführt wird, nicht 16 (abhängig von der Breite vonunsigned long
). Evenuint16_t & uint16_t
wirdsigned int
durch die Ganzzahl-Heraufstufungsregeln von C ++ hochgestuft, außer auf Plattformen, auf denenint
nicht breiter als istuint16_t
.Unter x86 wird diese Version in eine einzelne
rol r32, cl
(oderrol r32, imm8
) Version mit Compilern eingefügt, die sie bearbeiten, da der Compiler weiß, dass x86-Anweisungen zum Drehen und Verschieben die Anzahl der Verschiebungen genauso maskieren wie die C-Quelle.Compiler-Unterstützung für diese UB-vermeidende Redewendung auf x86 für
uint32_t x
undunsigned int n
für Verschiebungen mit variabler Anzahl:ror
oder-rol
Anweisung für die Anzahl der Variablen verwenden.shld edi,edi,7
was langsamer ist und mehr Bytes benötigt alsrol edi,7
bei einigen CPUs (insbesondere AMD, aber auch bei einigen Intel), wenn BMI2 nichtrorx eax,edi,25
zum Speichern eines MOV verfügbar ist ._rotl
/_rotr
intrinsics von<intrin.h>
x86 (einschließlich x86-64).gcc for ARM verwendet eine
and r1, r1, #31
Rotation für variable Anzahl, führt jedoch die eigentliche Rotation mit einer einzigen Anweisung aus :ror r0, r0, r1
. Gcc erkennt also nicht, dass Rotationszählungen von Natur aus modular sind. In den ARM-Dokumenten heißt es: "ROR mit Schichtlängen
, mehr als 32 sind gleich ROR mit Schichtlängen-32
" . Ich denke, gcc wird hier verwirrt, weil Links- / Rechtsverschiebungen auf ARM die Anzahl sättigen, sodass eine Verschiebung um 32 oder mehr das Register löscht. (Im Gegensatz zu x86, bei dem Verschiebungen die Anzahl genauso maskieren wie beim Drehen). Es entscheidet wahrscheinlich, dass es eine UND-Anweisung benötigt, bevor es die Rotationssprache erkennt, da nicht kreisförmige Verschiebungen auf dieses Ziel wirken.Aktuelle x86-Compiler verwenden immer noch einen zusätzlichen Befehl, um eine Variablenanzahl für 8- und 16-Bit-Rotationen zu maskieren, wahrscheinlich aus dem gleichen Grund, aus dem sie das UND auf ARM nicht vermeiden. Dies ist eine verpasste Optimierung, da die Leistung nicht von der Anzahl der Rotationen auf einer x86-64-CPU abhängt. (Die Maskierung von Zählungen wurde aus Leistungsgründen mit 286 eingeführt, da Verschiebungen iterativ und nicht wie bei modernen CPUs mit konstanter Latenz behandelt wurden.)
Übrigens, bevorzugen Sie Rotation nach rechts für Rotationen mit variabler Anzahl, um zu vermeiden, dass der Compiler
32-n
bei Architekturen wie ARM und MIPS, die nur eine Rotation nach rechts bereitstellen, eine Rotation nach links implementiert. (Dies optimiert die Anzahl der Kompilierungszeitkonstanten.)Unterhaltsame Tatsache: ARM verfügt nicht über spezielle Shift / Rotate-Anweisungen, sondern nur über MOV, wobei der Quelloperand im ROR-Modus durch den Barrel-Shifter läuft :
mov r0, r0, ror r1
. So kann eine Drehung für einen EOR-Befehl oder etwas in einen Registerquellenoperanden gefaltet werden.Stellen Sie sicher, dass Sie vorzeichenlose Typen für
n
und den Rückgabewert verwenden, da dies sonst keine Drehung ist . (gcc für x86-Ziele führt arithmetische Rechtsverschiebungen durch, wobei Kopien des Vorzeichenbits anstelle von NullenOR
verschoben werden , was zu einem Problem führt, wenn Sie die beiden Werte zusammen verschieben. Rechtsverschiebungen von Ganzzahlen mit negativem Vorzeichen sind ein implementierungsdefiniertes Verhalten in C.)Auch stellen Sie sicher , die Verschiebungszahl ein Typ ohne Vorzeichen ist , weil
(-n)&31
mit einem signierten Typ könnte Einerkomplement oder Zeichen / Größe, und nicht das gleiche wie die modularen 2 ^ n Sie mit unsigned oder Zweier-Komplement zu bekommen sein. (Siehe Kommentare zu Regehrs Blogbeitrag).unsigned int
funktioniert gut auf jedem Compiler, den ich mir angesehen habe, für jede Breite vonx
. Einige andere Typen besiegen tatsächlich die Redewendungserkennung für einige Compiler. Verwenden Sie also nicht einfach den gleichen Typ wiex
.Einige Compiler bieten Intrinsics für Rotationen , was weitaus besser ist als Inline-Asm, wenn die tragbare Version keinen guten Code auf dem Compiler generiert, auf den Sie abzielen. Es gibt keine plattformübergreifenden Eigenschaften für Compiler, die mir bekannt sind. Dies sind einige der x86-Optionen:
<immintrin.h>
die_rotl
und_rotl64
intrinsics bereitstellen , und dasselbe für die Rechtsverschiebung. MSVC erfordert<intrin.h>
, während gcc erfordern<x86intrin.h>
. A#ifdef
kümmert sich um gcc vs. icc, aber clang scheint sie nirgendwo zu bieten, außer im MSVC-Kompatibilitätsmodus mit-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
. Und der Asm, den es für sie ausstrahlt, ist zum Kotzen (zusätzliche Maskierung und ein CMOV)._rotr8
und_rotr16
.<x86intrin.h>
Bietet auch__rolb
/__rorb
für 8-Bit-Drehung nach links / rechts,__rolw
/__rorw
(16-Bit),__rold
/__rord
(32-Bit),__rolq
/__rorq
(64-Bit, nur für 64-Bit-Ziele definiert). Für enge Drehungen verwendet die Implementierung__builtin_ia32_rolhi
oder...qi
, aber die 32- und 64-Bit-Drehungen werden mit shift / oder definiert (ohne Schutz gegen UB, da der Code inia32intrin.h
nur für x86 auf gcc funktionieren muss). GNU C scheint keine plattformübergreifenden__builtin_rotate
Funktionen zu haben, wie es funktioniert__builtin_popcount
(was sich auf das ausdehnt, was auf der Zielplattform optimal ist, auch wenn es sich nicht um eine einzelne Anweisung handelt). Meistens erhalten Sie guten Code durch die Erkennung von Redewendungen.Vermutlich haben auch einige Nicht-x86-Compiler Eigenschaften, aber erweitern wir diese Community-Wiki-Antwort nicht, um sie alle einzuschließen. (Vielleicht tun Sie das in der vorhandenen Antwort über Intrinsics ).
(Die alte Version dieser Antwort schlug MSVC-spezifischen Inline-Asm vor (der nur für 32-Bit-x86-Code funktioniert) oder http://www.devx.com/tips/Tip/14043 für eine C-Version. Die Kommentare antworten darauf .)
Inline-ASM besiegt viele Optimierungen , insbesondere im MSVC-Stil, da Eingaben gespeichert / neu geladen werden müssen . Eine sorgfältig geschriebene GNU C-Inline-Asm-Drehung würde es dem Zähler ermöglichen, ein sofortiger Operand für Verschiebungszählungen zur Kompilierungszeitkonstante zu sein, aber es könnte immer noch nicht vollständig optimiert werden, wenn der zu verschiebende Wert auch eine Kompilierungszeitkonstante ist nach dem Inlining. https://gcc.gnu.org/wiki/DontUseInlineAsm .
quelle
bits = CHAR_BIT * sizeof(n);
undc &= bits - 1;
undreturn ((n >> c) | (n << (bits - c)))
, was würde ich verwenden?bits - c
=32 - 0
. (Ich habe keinen Ping bekommen, weil ich nur das Wiki bearbeitet habe, es aber nicht geschrieben habe.)0 < count < bits
ist eine konstante Anforderung an fast alle CPUs und Programmiersprachen, die Rotation implementieren (manchmal0 ≤ count < bits
, aber das Verschieben um die exakte Anzahl von Bits ist praktisch immer entweder nicht definiert oder wird auf nop abgerundet, anstatt den Wert zu löschen und gut zu drehen.)Verwenden Sie eine Inline-Funktion, da es sich um C ++ handelt:
C ++ 11 Variante:
quelle
INT
es sich um eine vorzeichenbehaftete Ganzzahl handelt und das Vorzeichen gesetzt ist! Test zum Beispiel,rol<std::int32_t>(1 << 31)
der auf 1 umdrehen sollte, aber tatsächlich wird-1
(weil das Vorzeichen erhalten bleibt).std::numeric_limits<INT>::digits
anstelle von verwendenCHAR_BIT * sizeof
. Ich habe vergessen, ob vorzeichenlose Typen nicht verwendete Auffüllungen haben dürfen (z. B. 24-Bit-Ganzzahlen, die in 32 Bit gespeichert sind), aber wenn ja,digits
wäre dies besser. Siehe auch gist.github.com/pabigot/7550454 für eine Version mit mehr Prüfung für eine Verschiebung mit variabler Anzahl.Die meisten Compiler haben dafür Eigenheiten. Visual Studio zum Beispiel _rotr8, _rotr16
quelle
Endgültig:
quelle
8
eine Rechtschreibfehler vonCHAR_BIT
(die nicht genau 8 sein müssen)?std::numeric_limits<T>::digits
.C ++ 20
std::rotl
undstd::rotr
Es ist angekommen! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html und sollte es dem
<bit>
Header hinzufügen .cppreference sagt, dass die Verwendung wie folgt sein wird:
Ausgabe geben:
Ich werde es versuchen, wenn der Support bei GCC eintrifft. GCC 9.1.0
g++-9 -std=c++2a
unterstützt ihn immer noch nicht.Der Vorschlag lautet:
und:
A
std::popcount
wurde ebenfalls hinzugefügt, um die Anzahl der 1 Bits zu zählen : Wie wird die Anzahl der gesetzten Bits in einer 32-Bit-Ganzzahl gezählt?quelle
Wie wäre es mit so etwas mit dem Standard-Bitset ...
HTH,
quelle
m %= N;
Zur Berücksichtigung von Schichten hinzugefügt>= N
.Wenn x ein 8-Bit-Wert ist, können Sie Folgendes verwenden:
quelle
x
unterschrieben ist.Im Detail können Sie die folgende Logik anwenden.
Wenn das Bitmuster 33602 in Integer ist
und Sie müssen dann mit 2 rechten Shifs rollen: Erstellen Sie zuerst eine Kopie des Bitmusters und verschieben Sie es dann nach links: Länge - Rechtsverschiebung, dh Länge ist 16, Rechtsverschiebungswert ist 2 16 - 2 = 14
Nach 14 mal Linksschaltung bekommst du.
Verschieben Sie nun den Wert 33602 nach Bedarf zweimal nach rechts. Du erhältst
Nehmen Sie nun ein ODER zwischen dem 14-mal nach links verschobenen Wert und dem 2-mal nach rechts verschobenen Wert.
Und Sie erhalten Ihren verschobenen Rollover-Wert. Denken Sie daran, dass bitweise Operationen schneller sind und dies nicht einmal eine Schleife erfordert.
quelle
Angenommen, Sie möchten um
L
Bits nach rechts verschieben , und die Eingabex
ist eine Zahl mitN
Bits:quelle
Die richtige Antwort lautet wie folgt:
quelle
val
unterschrieben ist.Quellcode x Bitnummer
quelle
ein weiterer Vorschlag
quelle
Im Folgenden finden Sie eine leicht verbesserte Version der Antwort von Dídac Pérez , in der beide Richtungen implementiert sind, sowie eine Demo der Verwendung dieser Funktionen unter Verwendung von vorzeichenlosen Zeichen und vorzeichenlosen langen langen Werten. Mehrere Anmerkungen:
cout << +value
Trick verwendet, um ein vorzeichenloses Zeichen, das ich hier gefunden habe, knapp numerisch auszugeben: https://stackoverflow.com/a/28414758/1599699<put the type here>
Syntax für Klarheit und Sicherheit.Hier ist der Code, den ich verwende:
quelle
quelle
Funktion überladen:
quelle
quelle