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.)
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:
ror
ou rol
pour les nombres de variables.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._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:
<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)._rotr8
_ et __rotr16
_ .<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.
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));
}
La plupart des compilateurs ont des éléments intrinsèques pour cela. Visual Studio par exemple _ rotr8, _rotr16
Définitivement:
template<class T>
T ror(T x, unsigned int moves)
{
return (x >> moves) | (x << sizeof(T)*8 - moves);
}
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,
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.
Si x est une valeur de 8 bits, vous pouvez utiliser ceci:
x=(x>>1 | x<<7);
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));
}
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 ) ) ) )
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:
cout << +value
Pour produire de manière concise un caractère non signé numériquement trouvé ici: https://stackoverflow.com/a/28414758/1599699<put the type here>
Pour plus de clarté et de sécurité.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");
}
--- 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.
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;
}
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;
}