Pratiques exemplaires pour les opérations de rotation en C++

les opérateurs de quart de gauche et de droite (<>) sont déjà disponibles en c/" class="blnk">C++. Cependant, je n'ai pas pu trouver comment je pouvais effectuer des opérations de changement circulaire ou de rotation.

Comment peut-on effectuer des opérations comme "tourner à gauche" et "tourner à droite"?

tournant à droite deux fois ici

Initial --> 1000 0011 0100 0010

doit aboutir à:

Final   --> 1010 0000 1101 0000

Un exemple serait utile.

(de l'éditeur note: de nombreuses façons courantes d'exprimer les rotations en C souffrent d'un comportement non défini si le nombre de rotations est zéro, ou compilent à plus d'une seule instruction de machine de rotation. La réponse à cette question devrait documenter les pratiques exemplaires.)

72
demandé sur Elroy 2009-04-22 14:20:46

15 réponses

Voir aussi une version antérieure de cette réponse sur un autre pivoter question avec un peu plus de détails sur ce qu'il asm gcc/clang produire pour x86.

la façon la plus conviviale pour un compilateur d'exprimer une rotation en C et C++ qui évite tout comportement non défini semble être l'implémentation de John Regehr . Je l'ai adapté pour tourner par la largeur du type (par exemple en supposant que uint32_t est exactement 32 bits de large, bien que C / C++ garantit seulement qu'il est au moins que large. J'ai essayé de rester lisible en laissant des chèques pour ce genre de chose).

#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 n'importe quel type entier non signé, pas seulement uint32_t , donc vous pouvez faire des versions pour d'autres tailles.

voir aussi un C++11 Modèle version avec beaucoup de contrôles 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 DSPs 24 bits ou ordinateurs centraux 36 bits, par exemple.

Je ne recommande d'utiliser le modèle que comme arrière-plan pour les enveloppes avec des noms qui incluent explicitement la largeur de rotation. entier-règles de promotion signifie que rotl_template(u16 & 0x11UL, 7) ferait une rotation de 32 ou 64 bits, pas 16 (selon la largeur de unsigned long ). Même uint16_t & uint16_t est promu à signed int par les règles de promotion integer de C++, sauf sur plates-formes où int n'est pas plus large que uint16_t .


sur x86 , cette version inlines à un seul rol r32, cl (ou rol r32, imm8 ) avec des compilateurs qui grok il, parce que le compilateur sait que x86 tourner et instructions de changement masquer le changement-compter de la même façon que le C source fait.

soutien de compilateur pour cette UB-éviter idiome sur x86, pour uint32_t x et unsigned int n pour les postes à nombre variable:

  • clang: reconnu pour les rotations à comptage variable depuis clang3.5, plusieurs équipes+ou insns avant cela.
  • gcc: reconnu pour les rotations à comptage variable depuis gcc4.9 , postes multiples+ou insns avant cela. gcc5 et plus tard optimiser la branche et le masque dans la version Wikipédia, aussi, en utilisant juste un ror ou rol instructions pour les comptes variables.
  • cpi: pris en charge pour la variable de comptage tourne depuis ICC13 ou plus tôt . Les rotations à comptage Constant utilisent shld edi,edi,7 qui est plus lent et prend plus d'octets que rol edi,7 sur certains CPU (en particulier AMD, mais aussi certains Intel), quand BMI2 n'est pas disponible pour rorx eax,edi,25 pour sauver un MOV.
  • MSVC: x86-64 CL19: reconnu uniquement pour les rotations à nombre constant. (L'idiome Wikipédia est reconnu, mais la branche et et ne sont pas optimisés à l'extérieur). Utilisez le _rotl / _rotr intrinsics de <intrin.h> sur x86 (y compris x86-64).

gcc pour bras utilise un and r1, r1, #31 pour les rotations à nombre variable, mais fait encore la rotation réelle avec une seule instruction : ror r0, r0, r1 . Ainsi, gcc ne se rend pas compte que les comptes de rotation sont intrinsèquement modulaires. Comme le disent les docs ARM, "ROR with shift length, n , plus de 32 est le même que ROR avec la longueur de poste n-32 . Je pense que gcc se confond ici parce que les décalages gauche / droite sur le bras saturent le compte, donc un décalage de 32 ou plus va effacer le registre. (Contrairement à x86, où les changements masquent le même nombre que les rotations). Il décide probablement qu'il a besoin d'une instruction et avant de reconnaître l'idiome de rotation, en raison de la façon dont les quarts non-circulaires travaillent sur cette cible.

compilateurs x86 actuels utilisent toujours un instruction pour masquer un compte de variable pour 8 et 16-bit tourne, probablement pour la même raison qu'ils n'évitent pas le et sur le bras. Il s'agit d'une optimisation manquée, car la performance ne dépend pas du nombre de rotations sur N'importe quel CPU x86-64. (Le masquage des comptes a été introduit avec 286 pour des raisons de performance parce qu'il gérait les changements de façon itérative, pas avec une latence constante comme les CPU modernes.)

BTW, préfèrent tourner-à droite pour la variable-compte tourne, pour éviter de faire le compilateur faire 32-n pour implémenter une rotation à gauche sur des architectures comme ARM et MIPS qui ne fournissent qu'une rotation à droite. (Ceci optimise la suppression avec des comptes de compilations à temps constant.)

fait Amusant: le BRAS n'a pas vraiment dédié décalage/rotation des instructions, c'est juste MOV avec le source et l'opérande de passer par le barrel shifter en ROR mode : mov r0, r0, ror r1 . Ainsi une rotation peut se plier dans un opérande registre-source pour une instruction EOR ou quelque chose.


assurez-vous d'utiliser des types non signés pour n et la valeur de retour, Ou bien ce ne sera pas une rotation . (gcc pour les cibles x86 fait des déplacements arithmétiques à droite, le déplacement dans les copies du signe-bit plutôt que des zéros, conduisant à un problème quand vous OR les deux valeurs déplacées ensemble. Les déplacements vers la droite des entiers signés négatifs sont définis par la mise en œuvre du comportement en C.)

aussi, assurez-vous que le décompte des équipes est un type non signé , parce que (-n)&31 avec un type signé pourrait être son complément ou signe/magnitude, et pas le même que le 2^n modulaire que vous obtenez avec le complément non signé ou deux. (Voir les commentaires sur le blog de Regehr). unsigned int n'est bien sur chaque compilateur que j'ai regardé, pour chaque largeur de x . Certains autres types en fait battre l'idiom-reconnaissance pour certains compilateurs, donc ne pas utiliser le même type que x .


certains compilateurs fournissent des intrinsèques pour les rotations , ce qui est beaucoup mieux que inline-asm si la version portable ne génère pas de bon code sur le compilateur que vous ciblez. Il n'y a pas d'intrinsèques multiplateformes pour les compilateurs que je connais. Voici quelques-unes des options x86:

  • documents Intel que <immintrin.h> fournit _rotl et _rotl64 intrinsics , idem pour le poste de droite. MSVC exige <intrin.h> , tandis que gcc exige <x86intrin.h> . Un #ifdef s'occupe de gcc vs. icc, mais clang ne semble pas les fournir n'importe où, sauf en mode de compatibilité MSVC avec -fms-extensions -fms-compatibility -fms-compatibility-version=17.00 . Et l'asm qu'il émet pour eux craint (masquage supplémentaire et une CMOV).
  • MSVC: _rotr8 et _rotr16 .
  • de gcc et de la cci (pas de bruit): <x86intrin.h> fournit aussi __rolb / __rorb de 8 bits vers la gauche/droite, __rolw / __rorw (16-peu), __rold / __rord (32-peu), __rolq / __rorq (64-bits, défini uniquement pour les 64 bits cibles). Pour les rotations étroites, l'implémentation utilise __builtin_ia32_rolhi ou ...qi , mais les rotations 32 et 64 bits sont définies en utilisant shift/ or (sans protection contre UB, parce que le code dans ia32intrin.h n'a qu'à travailler 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 si ce n'est pas une seule instruction). La plupart du temps, vous obtenez un bon code de la reconnaissance idiom.

// 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

probablement certains compilateurs non-x86 ont intrinsèques, aussi, mais ne pas étendre cette réponse de communauté-wiki pour les inclure tous. (Peut-être faire cela dans la réponse existante au sujet d'intrinsèques ).


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

Inline asm Bat many optimisations , surtout de type MSVC car il force les entrées à être stockées/rechargées . Une rotation en ligne-asm GNU C soigneusement écrite permettrait d'avoir un comptage immédiat pour les décomptes de décalage en fonction de la constante de temps de compilation, mais elle ne pourrait pas encore être entièrement optimisée si la valeur à décaler est aussi une constante de temps de compilation après l'incrustation. https://gcc.gnu.org/wiki/DontUseInlineAsm .

80
répondu Peter Cordes 2017-06-08 01:46:18

comme C'est C++, utilisez une fonction en ligne:

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
répondu MSalters 2015-04-16 11:26:06

la plupart des compilateurs ont intrinsèquement pour cela. Visual Studio for example _rotr8, _rotr16

18
répondu VolkerK 2013-06-25 13:11:21

définitivement:

template<class T>
T ror(T x, unsigned int moves)
{
  return (x >> moves) | (x << sizeof(T)*8 - moves);
}
16
répondu Dídac Pérez 2013-12-17 13:55:19

comment abt quelque chose comme ça, en utilisant le bitset 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
répondu Abhay 2009-04-22 11:01:21

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

si le patron de bits est 33602 en entier

1000 0011 0100 0010

et vous devez vous retourner avec 2 shifs à droite alors: d'abord faire une copie de bit pattern et ensuite le déplacer à gauche: Length-RightShift c'est à dire la longueur est de 16 maj de droite est égale à 2 16 - 2 = 14

après 14 passages à gauche vous obtenez.

1000 0000 0000 0000

déplacez maintenant à droite la valeur 33602, 2 fois comme requis. Vous obtenez

0010 0000 1101 0000

prend maintenant une ou entre 14 valeurs décalées à gauche et 2 valeurs décalées à droite.

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

et vous obtenez votre valeur de retournement décalée. Rappelez-vous que les opérations de type bit wise sont plus rapides et que cela ne nécessite même pas de boucle.

6
répondu S M Kamran 2010-04-28 03:37:38

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

x=(x>>1 | x<<7);
5
répondu Farhadix 2017-08-23 18:45:32

en supposant que vous voulez déplacer à droite par L bits, et l'entrée x est un nombre avec N bits:

unsigned ror(unsigned x, int L, int N) 
{
    unsigned lsbs = x & ((1 << L) - 1);
    return (x >> L) | (lsbs << (N-L));
}
4
répondu nimrodm 2009-04-22 10:36:29

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
répondu user3102555 2015-12-23 13:28:54

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
répondu kjk 2013-10-31 05:24:14

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
répondu SalemD 2013-11-16 02:13:28

ci-dessous est une version légèrement améliorée de réponse de Dídac Pérez , avec les deux directions mises en œuvre, ainsi qu'une démonstration de ces utilisations de fonctions en utilisant char non signé et non signé valeurs longues. Plusieurs notes:

  1. les fonctions sont allégées pour optimiser les compilateurs
  2. j'ai utilisé un truc cout << +value pour émettre un numéro de code non signé que j'ai 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é char non signé pour le paramètre shiftNum en raison de ce que j'ai trouvé dans la section de détails supplémentaires ici :

le résultat d'une opération de changement n'est pas défini si expression additive est négatif ou si "1519230920 additif"-l'expression est supérieure ou égale à la nombre de bits dans le (promu) "shift-expression .

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
répondu Andrew 2017-05-23 12:34:43
--- 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
répondu MikeZ 2015-08-14 21:12:11

surcharge d'une fonction:

unsigned int rotate_right(unsigned int x)
{
 return (x>>1 | (x&1?0x80000000:0))
}

unsigned short rotate_right(unsigned short x) { /* etc. */ }
-1
répondu graham.reeds 2009-04-22 10:38:09
#define ROTATE_RIGHT(x) ( (x>>1) | (x&1?0x8000:0) )
-1
répondu Dan Byström 2010-04-28 03:36:41