web-dev-qa-db-ger.com

Bewährte Methoden für das Verschieben (Drehen) von Kreisen in C ++

Links- und Rechts-Shift-Operatoren (<< und >>) sind in C++ bereits verfügbar. Ich konnte jedoch nicht herausfinden, wie ich kreisförmige Verschiebungs- oder Rotationsoperationen durchfü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 ergeben:

Final   --> 1010 0000 1101 0000

Ein Beispiel wäre hilfreich.

(Anmerkung des Herausgebers: Viele gebräuchliche Arten, Rotationen in C auszudrücken, weisen ein undefiniertes Verhalten auf, wenn die Anzahl der Rotationen Null ist oder wenn mehr als nur eine einzelne Rotationsmaschinenanweisung kompiliert wird. Die Antwort dieser Frage sollte bewährte Methoden dokumentieren.)

81
Elroy

Siehe auch eine frühere Version von dieser Antwort auf eine andere rotierende Frage mit einigen Details darüber, was asm gcc/clang für x86 produziert.

Die compilerfreundlichste Möglichkeit, eine Rotation in C und C++ auszudrücken, bei der Undefiniertes Verhalten vermieden wird, 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).

#include <stdint.h>   // for uint32_t
#include <limits.h>   // for CHAR_BIT
// #define NDEBUG
#include <assert.h>

static inline uint32_t rotl32 (uint32_t n, unsigned int c)
{
  const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);  // assumes width is a power of 2.

  // assert ( (c<=mask) &&"rotate by type width or more");
  c &= mask;
  return (n<<c) | (n>>( (-c)&mask ));
}

static inline uint32_t rotr32 (uint32_t n, unsigned int c)
{
  const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);

  // assert ( (c<=mask) &&"rotate by type width or more");
  c &= mask;
  return (n>>c) | (n<<( (-c)&mask ));
}

Funktioniert für jeden vorzeichenlosen Integer-Typ, nicht nur für uint32_t, Sodass Sie Versionen für andere Größen erstellen können.

Siehe auch eine C++ 11-Vorlagenversion mit vielen Sicherheitsprüfungen (einschließlich eines 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. Regeln für ganzzahlige Heraufstufung bedeuten, dass rotl_template(u16 & 0x11UL, 7) eine 32- oder 64-Bit-Drehung ausführt, nicht 16 (abhängig von der Breite von unsigned long) . Sogar uint16_t & uint16_t Wird durch C++ 's Regeln zur Heraufstufung von Ganzzahlen auf signed int Hochgestuft, außer auf Plattformen, auf denen int nicht breiter als uint16_t Ist.


Auf x86 ist diese Version in ein einzelnes rol r32, cl (oder rol r32, imm8) Mit Compilern eingebunden, die grok Dies liegt daran, dass 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 Und unsigned int n Für Verschiebungen mit variabler Anzahl:

  • clang: Erkannt für Variable-Count-Rotation seit clang3.5, mehrere Shifts + oder Insns davor.
  • gcc: erkannt für variable Anzahl dreht sich seit gcc4.9 , mehrere Shifts + oder Insns davor. gcc5 und höher optimieren den Zweig und die Maske auch in der Wikipedia-Version, indem Sie nur eine ror - oder rol -Anweisung für die Anzahl der Variablen verwenden.
  • icc: wird für Rotationen mit variabler Anzahl seit ICC13 oder früher unterstützt. Bei Rotationen mit konstanter Anzahl wird shld edi,edi,7 Verwendet, das langsamer ist und mehr Bytes als rol edi,7 Auf einigen CPUs (insbesondere AMD, aber auch einigen Intel) benötigt, wenn BMI2 für rorx eax,edi,25 Nicht verfügbar ist. ], um einen MOV zu speichern.
  • MSVC: x86-64 CL19: Wird nur für Rotationen mit konstanter Anzahl erkannt. (Die Wikipedia-Sprache wird erkannt, aber der Zweig und das UND sind nicht optimiert). Verwenden Sie die Intrinsics _rotl/_rotr Von <intrin.h> Auf x86 (einschließlich x86-64).

gcc für ARM verwendet einen and r1, r1, #31 für Rotationen mit variabler Anzahl, dreht sich aber immer noch mit einer einzigen Anweisung : ror r0, r0, r1. Also erkennt gcc nicht, dass Rotationszählungen von Natur aus modular sind. Wie die ARM= docs sagen, "ROR with shift length, n, mehr als 32 ist das gleiche wie ROR mit Shift-Länge n-32 " . Ich denke, GCC wird hier verwirrt, weil Links-/Rechtsverschiebungen auf ARM sättigen Die Anzahl, also eine Verschiebung um 32 oder mehr, löscht das Register (im Gegensatz zu x86, wo Verschiebungen die Anzahl genauso maskieren, wie sie sich dreht) verschiebt die Arbeit an diesem Ziel.

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 in ARM nicht umgehen. Dies ist eine verpasste Optimierung, da die Leistung nicht von der Anzahl der Rotationen auf einer x86-64-CPU abhängt. (Die Maskierung der Anzahl wurde mit 286 aus Leistungsgründen eingeführt, weil es Verschiebungen iterativ handhabt, nicht mit einer konstanten Latenz wie moderne CPUs.)

Übrigens, bevorzugen Sie Rechtsdrehung für Rotationen mit variabler Anzahl, um zu vermeiden, dass der Compiler 32-n Für die Implementierung einer Linksdrehung auf Architekturen wie ARM und MIPS, die nur eine Rotationsdrehung bereitstellen, ausführt. richtig. (Dies optimiert weg mit Compile-Zeit-Konstanten-Zählungen.)

Unterhaltsame Tatsache: ARM hat keine speziellen Anweisungen zum Verschieben/Drehen, sondern nur MOV mit dem Quelloperanden, der im ROR-Modus durch den Barrel-Shifter geht : mov r0, r0, ror r1. So kann eine Drehung in einen Registerquellenoperanden für einen EOR-Befehl oder so etwas übergehen.


Stellen Sie sicher, dass Sie vorzeichenlose Typen für n und den Rückgabewert verwenden, da es sonst keine Drehung gibt . (gcc für x86-Ziele führt arithmetische Verschiebungen nach rechts durch und verschiebt Kopien des Vorzeichenbits anstelle von Nullen. Dies führt zu einem Problem, wenn Sie die beiden verschobenen Werte zusammen OR verschieben. Die Verschiebung von negativen Ganzzahlen nach rechts ist implementierungsbedingt. definiertes Verhalten in C.)

Stellen Sie außerdem mit sicher, dass es sich bei der Verschiebungszahl um einen vorzeichenlosen Typ handelt , da (-n)&31 Mit einem vorzeichenbehafteten Typ das eigene Komplement oder Vorzeichen/die eigene Größe sein kann und nicht das gleiche wie das modulare 2 ^ n bekommst du mit unsigned oder two's komplement. (Siehe Kommentare zu Regehrs Blogpost). unsigned int Eignet sich für jeden Compiler, den ich mir angesehen habe, für jede Breite von x. Einige andere Typen verhindern die Spracherkennung für einige Compiler. Verwenden Sie also nicht nur den gleichen Typ wie x.


Einige Compiler bieten Intrinsics für Rotates , was weitaus besser ist als Inline-Asm, wenn die portable Version auf dem Compiler, auf den Sie abzielen, keinen guten Code generiert. Es gibt keine plattformübergreifenden Eigenheiten für Compiler, die mir bekannt sind. Dies sind einige der x86-Optionen:

  • Intel-Dokumente, bei denen <immintrin.h> Die Eigenheiten _rotl Und _rotl64 bereitstellt, und dieselben für die Rechtsverschiebung. MSVC benötigt <intrin.h>, Während gcc <x86intrin.h> Benötigt. Ein #ifdef Kümmert sich um gcc vs. icc, aber clang scheint sie nirgendwo bereitzustellen , außer im MSVC-Kompatibilitätsmodus mit -fms-extensions -fms-compatibility -fms-compatibility-version=17.00 . Und der Asm, den es für sie ausgibt, ist zum Kotzen (zusätzliche Maskierung und CMOV).
  • MSVC: _rotr8 Und _rotr16 .
  • gcc und icc (nicht clang): <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 definiert Ziele). Bei engen Drehungen verwendet die Implementierung __builtin_ia32_rolhi Oder ...qi, Aber die 32- und 64-Bit-Drehungen werden mit shift/oder (ohne Schutz gegen UB) definiert, da der Code in ia32intrin.h muss nur auf gcc für x86 funktionieren. GNU C scheint keine plattformübergreifenden __builtin_rotate - Funktionen zu haben, wie dies für __builtin_popcount Der Fall ist Es ist keine einzelne Anweisung.) Meistens erhalten Sie guten Code durch die Spracherkennung.
// For real use, probably use a rotate intrinsic for MSVC, or this idiom for other compilers.  This pattern of #ifdefs may be helpful
#if defined(__x86_64__) || defined(__i386__)

#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>  // Not just <immintrin.h> for compilers other than icc
#endif

uint32_t rotl32_x86_intrinsic(rotwidth_t x, unsigned n) {
  //return __builtin_ia32_rorhi(x, 7);  // 16-bit rotate, GNU C
  return _rotl(x, n);  // gcc, icc, msvc.  Intel-defined.
  //return __rold(x, n);  // gcc, icc.
  // can't find anything for clang
}
#endif

Vermutlich haben auch einige Nicht-x86-Compiler Eigenheiten, aber lassen Sie uns diese Community-Wiki-Antwort nicht so erweitern, dass sie alle einschließt. (Vielleicht tun Sie das in der vorhandenen Antwort zu intrinsics ).


(In der alten Version dieser Antwort wurde MSVC-spezifischer Inline-Asm (der nur für 32-Bit-x86-Code funktioniert) oder http://www.devx.com/tips/Tip/14043 für vorgeschlagen eine C-Version. Die Kommentare antworten darauf.)

Inline asm besiegt viele Optimierungen , insbesondere den MSVC-Stil, da Eingaben gespeichert/neu geladen werden müssen . Ein sorgfältig geschriebenes GNU C Inline-Asm-Drehen würde es ermöglichen, dass die Zählung ein unmittelbarer Operand für die Anzahl der Verschiebungen mit konstanter Kompilierungszeit ist, sie kann jedoch immer noch nicht vollständig optimiert werden, wenn der Wert auf Verschoben wird auch eine Kompilierzeitkonstante nach dem Inlining. https://gcc.gnu.org/wiki/DontUseInlineAsm.

91
AndreasT

Verwenden Sie eine Inline-Funktion, da es sich um C++ handelt:

template <typename INT> 
INT rol(INT val) {
    return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1));
}

C++ 11 Variante:

template <typename INT> 
constexpr INT rol(INT val) {
    static_assert(std::is_unsigned<INT>::value,
                  "Rotate Left only makes sense for unsigned types");
    return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1));
}
33
MSalters

Die meisten Compiler haben dafür eigene Eigenschaften. Visual Studio zum Beispiel _ rotr8, _rotr16

20
VolkerK

Endgültig:

template<class T>
T ror(T x, unsigned int moves)
{
  return (x >> moves) | (x << sizeof(T)*8 - moves);
}
16

Wie wäre es so mit dem Standardbitset ...

#include <bitset> 
#include <iostream> 

template <std::size_t N> 
inline void 
rotate(std::bitset<N>& b, unsigned m) 
{ 
   b = b << m | b >> (N-m); 
} 

int main() 
{ 
   std::bitset<8> b(15); 
   std::cout << b << '\n'; 
   rotate(b, 2); 
   std::cout << b << '\n'; 

   return 0;
}

HTH,

7
Abhay

Im Detail können Sie die folgende Logik anwenden.

Wenn das Bitmuster 33602 in Ganzzahl ist

 1000 0011 0100 0010 

und Sie müssen dann mit 2 rechten Shifs einen Rollover ausführen: erst eine Kopie des Bitmusters erstellen und dann mit der linken Maustaste verschieben: Länge - RightShift, d. h

Nach 14 mal Linksverschiebung kommt man.

 1000 0000 0000 0000 

Verschieben Sie nun den Wert 33602 zweimal nach rechts. Du erhältst

 0010 0000 1101 0000 

Nehmen Sie nun einen OR zwischen dem 14-fach linksverschobenen Wert und dem 2-fach rechtsverschobenen Wert.

 1000 0000 0000 0000 
 0010 0000 1101 0000 
 ================== 
 1010 0000 1101 0000 
 =================== 

Und Sie erhalten Ihren verschobenen Rollover-Wert. Denken Sie daran, bitweise Operationen sind schneller und erfordern nicht einmal eine Schleife.

6
S M Kamran

Wenn x ein 8-Bit-Wert ist, können Sie dies verwenden:

x=(x>>1 | x<<7);
5
Farhadix

Angenommen, Sie möchten um L Bits nach rechts verschieben, und die Eingabe x ist eine Zahl mit N Bits:

unsigned ror(unsigned x, int L, int N) 
{
    unsigned lsbs = x & ((1 << L) - 1);
    return (x >> L) | (lsbs << (N-L));
}
4
nimrodm

Die richtige Antwort lautet wie folgt:

#define BitsCount( val ) ( sizeof( val ) * CHAR_BIT )
#define Shift( val, steps ) ( steps % BitsCount( val ) )
#define ROL( val, steps ) ( ( val << Shift( val, steps ) ) | ( val >> ( BitsCount( val ) - Shift( val, steps ) ) ) )
#define ROR( val, steps ) ( ( val >> Shift( val, steps ) ) | ( val << ( BitsCount( val ) - Shift( val, steps ) ) ) )
3
user3102555

C++ 20 std::rotl Und std::rotr

Es ist angekommen! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html und sollte dem Header <bit> hinzugefügt werden.

cppreference sagt, dass die Verwendung wie folgt aussehen wird:

#include <bit>
#include <bitset>
#include <cstdint>
#include <iostream>

int main()
{
    std::uint8_t i = 0b00011101;
    std::cout << "i          = " << std::bitset<8>(i) << '\n';
    std::cout << "rotl(i,0)  = " << std::bitset<8>(std::rotl(i,0)) << '\n';
    std::cout << "rotl(i,1)  = " << std::bitset<8>(std::rotl(i,1)) << '\n';
    std::cout << "rotl(i,4)  = " << std::bitset<8>(std::rotl(i,4)) << '\n';
    std::cout << "rotl(i,9)  = " << std::bitset<8>(std::rotl(i,9)) << '\n';
    std::cout << "rotl(i,-1) = " << std::bitset<8>(std::rotl(i,-1)) << '\n';
}

ausgabe geben:

i          = 00011101
rotl(i,0)  = 00011101
rotl(i,1)  = 00111010
rotl(i,4)  = 11010001
rotl(i,9)  = 00111010
rotl(i,-1) = 10001110

Ich werde es versuchen, wenn GCC unterstützt wird. GCC 9.1.0 mit g++-9 -std=c++2a Unterstützt es immer noch nicht.

In dem Vorschlag heißt es:

Header:

namespace std {
  // 25.5.5, rotating   
  template<class T>
    [[nodiscard]] constexpr T rotl(T x, int s) noexcept;
  template<class T>
    [[nodiscard]] constexpr T rotr(T x, int s) noexcept;

und:

25.5.5 Drehen [bitops.rot]

In den folgenden Beschreibungen sei N std::numeric_limits<T>::digits.

template<class T>
  [[nodiscard]] constexpr T rotl(T x, int s) noexcept;

Einschränkungen: T ist ein Integer-Typ ohne Vorzeichen (3.9.1 [basic.fundamental]).

Sei r s% N.

Rückgabe: Wenn r 0 ist, x; wenn r positiv ist, (x << r) | (x >> (N - r)); wenn r negativ ist, rotr(x, -r).

template<class T>
  [[nodiscard]] constexpr T rotr(T x, int s) noexcept;

Einschränkungen: T ist ein Integer-Typ ohne Vorzeichen (3.9.1 [basic.fundamental]). Sei r s% N.

Rückgabe: Wenn r 0 ist, x; wenn r positiv ist, (x >> r) | (x << (N - r)); wenn r negativ ist, rotl(x, -r).

Ein std::popcount Wurde ebenfalls hinzugefügt, um die Anzahl von 1 Bits zu zählen: Wie wird die Anzahl der gesetzten Bits in einer 32-Bit-Ganzzahl gezählt?

Nachfolgend finden Sie eine leicht verbesserte Version von Dídac Pérezs Antwort , in der beide Richtungen implementiert sind, sowie eine Demo der Verwendung dieser Funktionen mit vorzeichenlosen Zeichen und vorzeichenlosen langen langen Werten. Einige Notizen:

  1. Die Funktionen sind für Compiler-Optimierungen integriert
  2. Ich habe ein cout << +value Trick für die knappe Ausgabe eines vorzeichenlosen Zeichens, das ich hier gefunden habe: https://stackoverflow.com/a/28414758/1599699
  3. Ich empfehle die Verwendung des expliziten <put the type here> Syntax für Klarheit und Sicherheit.
  4. Ich habe unsigned char für den shiftNum-Parameter verwendet, da ich im Abschnitt "Zusätzliche Details" Folgendes gefunden habe hier :

Das Ergebnis einer Verschiebeoperation ist undefiniert, wenn additiver Ausdruck negativ ist oder wenn additiver Ausdruck ist größer oder gleich der Anzahl der Bits im (hochgestuften) Verschiebungsausdruck .

Hier ist der Code, den ich benutze:

#include <iostream>

using namespace std;

template <typename T>
inline T rotateAndCarryLeft(T rotateMe, unsigned char shiftNum)
{
    static const unsigned char TBitCount = sizeof(T) * 8U;

    return (rotateMe << shiftNum) | (rotateMe >> (TBitCount - shiftNum));
}

template <typename T>
inline T rotateAndCarryRight(T rotateMe, unsigned char shiftNum)
{
    static const unsigned char TBitCount = sizeof(T) * 8U;

    return (rotateMe >> shiftNum) | (rotateMe << (TBitCount - shiftNum));
}

void main()
{
    //00010100 == (unsigned char)20U
    //00000101 == (unsigned char)5U == rotateAndCarryLeft(20U, 6U)
    //01010000 == (unsigned char)80U == rotateAndCarryRight(20U, 6U)

    cout << "unsigned char " << 20U << " rotated left by 6 bits == " << +rotateAndCarryLeft<unsigned char>(20U, 6U) << "\n";
    cout << "unsigned char " << 20U << " rotated right by 6 bits == " << +rotateAndCarryRight<unsigned char>(20U, 6U) << "\n";

    cout << "\n";


    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned char) * 8U; ++shiftNum)
    {
        cout << "unsigned char " << 21U << " rotated left by " << +shiftNum << " bit(s) == " << +rotateAndCarryLeft<unsigned char>(21U, shiftNum) << "\n";
    }

    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned char) * 8U; ++shiftNum)
    {
        cout << "unsigned char " << 21U << " rotated right by " << +shiftNum << " bit(s) == " << +rotateAndCarryRight<unsigned char>(21U, shiftNum) << "\n";
    }


    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned long long) * 8U; ++shiftNum)
    {
        cout << "unsigned long long " << 3457347ULL << " rotated left by " << +shiftNum << " bit(s) == " << rotateAndCarryLeft<unsigned long long>(3457347ULL, shiftNum) << "\n";
    }

    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned long long) * 8U; ++shiftNum)
    {
        cout << "unsigned long long " << 3457347ULL << " rotated right by " << +shiftNum << " bit(s) == " << rotateAndCarryRight<unsigned long long>(3457347ULL, shiftNum) << "\n";
    }

    cout << "\n\n";
    system("pause");
}
0
Andrew
--- Substituting RLC in 8051 C for speed --- Rotate left carry
Here is an example using RLC to update a serial 8 bit DAC msb first:
                               (r=DACVAL, P1.4= SDO, P1.5= SCLK)
MOV     A, r
?1:
MOV     B, #8
RLC     A
MOV     P1.4, C
CLR     P1.5
SETB    P1.5
DJNZ    B, ?1

Here is the code in 8051 C at its fastest:
sbit ACC_7  = ACC ^ 7 ; //define this at the top to access bit 7 of ACC
ACC     =   r;
B       =   8;  
do  {
P1_4    =   ACC_7;  // this assembles into mov c, acc.7  mov P1.4, c 
ACC     <<= 1;
P1_5    =   0;
P1_5    =   1;
B       --  ; 
    } while ( B!=0 );
The keil compiler will use DJNZ when a loop is written this way.
I am cheating here by using registers ACC and B in c code.
If you cannot cheat then substitute with:
P1_4    =   ( r & 128 ) ? 1 : 0 ;
r     <<=   1;
This only takes a few extra instructions.
Also, changing B for a local var char n is the same.
Keil does rotate ACC left by ADD A, ACC which is the same as multiply 2.
It only takes one extra opcode i think.
Keeping code entirely in C keeps things simpler sometimes.
0
MikeZ

Quellcode x Bitnummer

int x =8;
data =15; //input
unsigned char tmp;
for(int i =0;i<x;i++)
{
printf("Data & 1    %d\n",data&1);
printf("Data Shifted value %d\n",data>>1^(data&1)<<(x-1));
tmp = data>>1|(data&1)<<(x-1);
data = tmp;  
}
0
kjk

ein weiterer vorschlag

template<class T>
inline T rotl(T x, unsigned char moves){
    unsigned char temp;
    __asm{
        mov temp, CL
        mov CL, moves
        rol x, CL
        mov CL, temp
    };
    return x;
}
0
SalemD