web-dev-qa-db-fra.com

Méthodes conseillées pour les opérations de décalage circulaire (rotation) en C ++

Les opérateurs de décalage gauche et droit (<< et >>) sont déjà disponibles en C++. Cependant, je ne pouvais pas savoir comment je pouvais effectuer des opérations de décalage circulaire ou de rotation.

Comment des opérations telles que "Rotation à gauche" et "Rotation à droite" peuvent-elles être effectuées?

Rotation deux fois ici

Initial --> 1000 0011 0100 0010

devrait aboutir à:

Final   --> 1010 0000 1101 0000

Un exemple serait utile.

(Remarque de l'éditeur: de nombreuses façons courantes d'exprimer les rotations en C souffrent d'un comportement indéfini si le nombre de rotations est égal à zéro ou si elles ne compilent pas plus d'une instruction machine en rotation. La réponse à cette question devrait documenter les meilleures pratiques.)

81
Elroy

Voir aussi une version antérieure de cette réponse sur une autre question de rotation avec plus de détails sur ce que asm gcc/clang produit pour x86.

Le moyen le plus convivial pour le compilateur d'exprimer une rotation en C et C++ qui évite tout comportement indéfini semble être implémentation de John Regehr . Je l'ai adapté pour pivoter selon la largeur du type (en utilisant des types à largeur fixe tels que _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 ));
}
_

Fonctionne pour tout type d'entier non signé, pas seulement _uint32_t_, afin que vous puissiez créer des versions pour d'autres tailles.

Voir également une version de modèle C++ 11 avec de nombreuses vérifications de sécurité (y compris un _static_assert_ que la largeur du type est une puissance de 2) , ce qui n'est pas le cas sur certains DSP 24 bits ou grands systèmes 36 bits, par exemple.

Je vous recommande d'utiliser uniquement le modèle en tant que back-end pour les wrappers dont les noms incluent explicitement la largeur de rotation. Les règles de promotion d’entier signifient que rotl_template(u16 & 0x11UL, 7) ferait une rotation de 32 ou 64 bits, et non de 16 (en fonction de la largeur de _unsigned long_). Même _uint16_t & uint16_t_ est promu à _signed int_ par les règles de promotion de l'entier de C++, sauf sur les plates-formes où int n'est pas plus large que _uint16_t_.


Sur x86 , cette version insère un seul _rol r32, cl_ (ou _rol r32, imm8_) avec des compilateurs qui grok it, car le compilateur sait que instructions de rotation et de décalage x86 masque le compte-décalage de la même manière que la source C.

Prise en charge du compilateur pour cet idiome évitant UB sous x86, pour _uint32_t x_ et _unsigned int n_ pour les décalages à compte variable:

  • clang: reconnu pour le nombre variable tourne depuis clang3.5, plusieurs décalages + ou insins auparavant.
  • gcc: reconnu pour le nombre variable tourne depuis gcc4.9 , plusieurs décalages + ou insns avant. gcc5 et versions ultérieures optimisent également la branche et le masque dans la version wikipedia, en utilisant seulement une instruction ror ou rol pour les nombres de variables.
  • icc: pris en charge pour le nombre de variables variable depuis ICC13 ou une version antérieure . Le nombre de tours constant utilise _shld edi,edi,7_ qui est plus lent et prend plus d'octets que _rol edi,7_ sur certains processeurs (en particulier AMD, mais aussi certains Intel), lorsque BMI2 n'est pas disponible pour _rorx eax,edi,25_ un MOV.
  • MSVC: x86-64 CL19: Uniquement reconnu pour les rotations à compte constant. (L'idiome de Wikipédia est reconnu, mais la branche et AND ne sont pas optimisés). Utilisez les éléments intrinsèques __rotl_/__rotr_ à partir de _<intrin.h>_ sur x86 (y compris x86-64).

gcc pour ARM utilise un _and r1, r1, #31_ pour la rotation du nombre de variables, mais effectue toujours la rotation avec une seule instruction : _ror r0, r0, r1_. Donc, gcc ne réalise pas que les rotations sont intrinsèquement modulaires. Comme le disent les docs ARM, "ROR avec longueur de décalage, n, plus de 32 correspond à ROR avec longueur de décalage _n-32_" . Je pense que gcc est confus ici parce que les décalages gauche/droite sur ARM saturent le compte, donc un décalage de 32 ou plus effacera le registre. (Contrairement à x86, où les décalages masquent le compte identique à la rotation). Il a probablement décidé de requérir une instruction AND avant de reconnaître le rot rotatif, en raison de la manière dont les décalages non circulaires fonctionnent sur cette cible.

Les compilateurs x86 actuels utilisent toujours une instruction supplémentaire pour masquer un nombre de variables pour les rotations de 8 et 16 bits, probablement pour la même raison, car ils n'évitent pas le AND sur ARM. Il s'agit d'une optimisation manquée, car les performances ne dépendent pas du nombre de rotations de tous les processeurs x86-64. (Le masquage des comptes a été introduit avec 286 pour des raisons de performances car il gérait les décalages de manière itérative, et non avec une latence constante comme les processeurs modernes.)

BTW, préférez rotation à droite pour les rotations à nombre variable, pour éviter que le compilateur fasse _32-n_ pour implémenter une rotation à gauche sur des architectures comme ARM et MIPS qui fournissent uniquement une rotation à droite. (Ceci optimise l'abandon avec des comptes de constante de temps de compilation.)

Anecdote: ARM ne dispose pas d'instructions de décalage/rotation dédiées, il s'agit simplement de MOV avec l'opérateur l'opérande source passant par le sélecteur de barils en mode ROR : _mov r0, r0, ror r1_ . Ainsi, une rotation peut se transformer en un opérande source de registre pour une instruction EOR ou autre.


Assurez-vous d'utiliser des types non signés pour n et la valeur de retour, sinon ce ne sera pas une rotation . (gcc pour les cibles x86 effectue des décalages arithmétiques corrects, décalant les copies du bit de signe plutôt que les zéros, ce qui pose un problème lorsque vous OR les deux valeurs décalées ensemble. Les décalages à droite des entiers signés négatifs sont implémentés comportement défini dans C.)

En outre, assurez-vous que le nombre d'équipes est un type non signé , car _(-n)&31_ avec un type signé pourrait être son complément ou son signe/magnitude, et pas le même que le 2 ^ n modulaire que vous obtenez avec un complément non signé ou à deux. (Voir les commentaires sur le blog de Regehr). _unsigned int_ réussit bien sur tous les compilateurs que j'ai consultés, pour chaque largeur de x. Certains autres types déjouent la reconnaissance idiomatique de certains compilateurs, aussi n'utilisez pas le même type que x.


Certains compilateurs fournissent des éléments intrinsèques pour les rotations , ce qui est bien meilleur que inline-asm si la version portable ne génère pas un bon code sur le compilateur que vous ciblez. . Il n'y a pas d'intrinsèques multiplateformes pour les compilateurs que je connaisse. Voici certaines des options x86:

  • Intel documente que _<immintrin.h>_ fournit les éléments intrinsèques __rotl_ et __rotl64_ , et identique pour le décalage à droite. MSVC requiert _<intrin.h>_, tandis que gcc nécessite _<x86intrin.h>_. Un _#ifdef_ prend en charge gcc vs. icc, mais clang ne semble pas les fournir nulle part, sauf en mode de compatibilité MSVC avec _-fms-extensions -fms-compatibility -fms-compatibility-version=17.00_ . Et l'ASM qu'il émet pour eux est nul (masquage supplémentaire et CMOV).
  • MSVC: __rotr8_ et __rotr16_ .
  • gcc et icc (pas clang): _<x86intrin.h>_ fournit également ___rolb_/___rorb_ pour 8 bits tourner à gauche/droite, ___rolw_/___rorw_ (16 bits ), ___rold_/___rord_ (32 bits), ___rolq_/___rorq_ (64 bits, défini uniquement pour les cibles 64 bits). Pour les rotations étroites, l’implémentation utilise ___builtin_ia32_rolhi_ ou _...qi_, mais les rotations 32 et 64 bits sont définies à l’aide de shift/ou (sans protection contre UB, car le code entre _ia32intrin.h_ ne doit fonctionner que sur gcc pour x86). GNU C ne semble pas avoir de fonctions multiplateformes ___builtin_rotate_ comme il le fait pour ___builtin_popcount_ (qui s'étend à tout ce qui est optimal sur la plate-forme cible, même s'il ne s'agit pas d'une instruction unique ). La plupart du temps, vous obtenez un bon code grâce à la reconnaissance des idiomes.
_// 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
_

On peut supposer que certains compilateurs non-x86 ont également des propriétés intrinsèques, mais ne développons pas cette réponse wiki de la communauté pour les inclure tous. (Peut-être que faire dans la réponse existante sur les intrinsèques ).


(L'ancienne version de cette réponse suggérait un asm en ligne spécifique à MSVC (qui ne fonctionne que pour du code 32 bits x86), ou http://www.devx.com/tips/Tip/1404 pour une version C Les commentaires répondent à cela.)

Inline asm annule de nombreuses optimisations , surtout dans le style MSVC car il oblige les entrées à être stockées/rechargées . Une rotation GNU C inline-as soigneusement écrite permettrait au décompte d'être un opérande immédiat pour les décomptes de décalage constant à la compilation, mais il ne pourrait toujours pas être optimisé complètement si la valeur à décaler est également une constante de compilation après l’inclusion. https://gcc.gnu.org/wiki/DontUseInlineAsm.

91
AndreasT

Comme c'est C++, utilisez une fonction inline:

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

Variante C++ 11:

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

La plupart des compilateurs ont des éléments intrinsèques pour cela. Visual Studio par exemple _ rotr8, _rotr16

20
VolkerK

Définitivement:

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

Comment quelque chose comme ça, en utilisant le bitet standard ...

#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

Dans les détails, vous pouvez appliquer la logique suivante.

Si le motif binaire est égal à 33602 en entier

 1000 0011 0100 0010 

et vous avez besoin de survoler avec 2 décalages droits, puis: d’abord faire une copie du motif de bits, puis de le déplacer à gauche: Longueur - Décalage à droite, c’est-à-dire que la longueur est de 16, la valeur de décalage à droite est de 2 16 - 2 = 14

Après 14 fois à gauche, vous obtenez.

 1000 0000 0000 0000 

Maintenant, déplacez à droite la valeur 33602, 2 fois si nécessaire. Vous recevez

 0010 0000 1101 0000 

Maintenant, prenons un OR entre 14 valeurs décalées à gauche et 2 fois la valeur décalée à droite.

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

Et vous obtenez votre valeur de survol décalée. Rappelez-vous que les opérations par bits sont plus rapides et que cela n’exige aucune boucle.

6
S M Kamran

Si x est une valeur de 8 bits, vous pouvez utiliser ceci:

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

En supposant que vous souhaitiez passer à droite de L bits, et que l’entrée x soit un nombre contenant N bits:

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

La bonne réponse est la suivante:

#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 Et std::rotr

C'est arrivé! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html et devrait l'ajouter à l'en-tête <bit>.

cppreference dit que l'utilisation sera comme:

#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';
}

donnant la sortie:

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

Je vais essayer quand le support arrive à GCC, GCC 9.1.0 avec g++-9 -std=c++2a Ne le supporte toujours pas.

La proposition dit:

Entête:

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;

et:

25.5.5 Rotation [bitops.rot]

Dans les descriptions suivantes, notons N std::numeric_limits<T>::digits.

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

Contraintes: T est un type entier non signé (3.9.1 [basic.fundamental]).

Soit r s% N.

Retourne: Si r est 0, x; si r est positif, (x << r) | (x >> (N - r)); si r est négatif, rotr(x, -r).

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

Contraintes: T est un type entier non signé (3.9.1 [basic.fundamental]). Soit r s% N.

Retourne: Si r est 0, x; si r est positif, (x >> r) | (x << (N - r)); si r est négatif, rotl(x, -r).

Un std::popcount A également été ajouté pour compter le nombre de 1 bits: Comment compter le nombre de bits définis dans un entier de 32 bits?

Vous trouverez ci-dessous une version légèrement améliorée de réponse de Dídac Pérez , avec les deux instructions implémentées, ainsi qu'une démonstration des utilisations de ces fonctions à l'aide de chars non signés et de valeurs longues non signées. Plusieurs notes:

  1. Les fonctions sont intégrées aux optimisations du compilateur
  2. J'ai utilisé une astuce cout << +value Pour produire de manière concise un caractère non signé numériquement trouvé ici: https://stackoverflow.com/a/28414758/1599699
  3. Je recommande d'utiliser la syntaxe explicite <put the type here> Pour plus de clarté et de sécurité.
  4. J'ai utilisé unsigned char pour le paramètre shiftNum à cause de ce que j'ai trouvé dans la section Détails supplémentaires ici :

Le résultat d'une opération de décalage n'est pas défini si additive-expression est négatif ou si additive-expression est supérieur ou égal au nombre de bits contenus dans l'expression-décalage .

Voici le code que j'utilise:

#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

Code source x nombre de bits

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

une autre suggestion

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