Les opérateurs de décalage gauche et droit (<< et >>) sont déjà disponibles en C ++. Cependant, je ne savais pas comment effectuer des opérations de décalage circulaire ou de rotation.
Comment effectuer des opérations telles que «Rotation à gauche» et «Rotation à droite»?
Rotation à droite deux fois ici
Initial --> 1000 0011 0100 0010
devrait aboutir à:
Final --> 1010 0000 1101 0000
Un exemple serait utile.
(Note 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 compilent plus qu'une seule instruction de rotation de la machine. La réponse à cette question doit documenter les meilleures pratiques.)
Réponses:
Voir également une version antérieure de cette réponse sur une autre question de rotation avec quelques détails supplémentaires sur ce que produit asm gcc / clang 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 l'implémentation de John Regehr . Je l'ai adapté pour qu'il tourne selon la largeur du type (en utilisant des types à largeur fixe comme
uint32_t
).Fonctionne pour tout type entier non signé, pas seulement
uint32_t
, vous pouvez donc créer des versions pour d'autres tailles.Voir aussi une version de modèle C ++ 11 avec de nombreuses vérifications de sécurité (y compris un fait
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 mainframes 36 bits, par exemple.Je recommanderais uniquement d'utiliser le modèle comme back-end pour les wrappers avec des noms qui incluent explicitement la largeur de rotation. Les règles de promotion d'entiers signifient que
rotl_template(u16 & 0x11UL, 7)
cela ferait une rotation 32 ou 64 bits, et non 16 (selon la largeur deunsigned long
). Evenuint16_t & uint16_t
est promusigned int
par les règles de promotion d'entiers de C ++, sauf sur les plates-formes oùint
n'est pas plus large queuint16_t
.Sur x86 , cette version s'aligne sur un seul
rol r32, cl
(ourol r32, imm8
) avec des compilateurs qui le grokent, car le compilateur sait que les instructions de rotation et de décalage x86 masquent le nombre de décalage de la même manière que la source C.Prise en charge du compilateur pour cet idiome évitant UB sur x86, pour
uint32_t x
etunsigned int n
pour les décalages à nombre variable:ror
ourol
pour le nombre de variables.shld edi,edi,7
qui est plus lente et prend plus d'octets querol edi,7
sur certains processeurs (en particulier AMD, mais aussi certains Intel), lorsque BMI2 n'est pas disponible pourrorx eax,edi,25
enregistrer un MOV._rotl
/_rotr
intrinsèques de<intrin.h>
de x86 (y compris x86-64).gcc pour ARM utilise un
and r1, r1, #31
pour tourne-count variable, mais fait encore la rotation réelle avec une seule instruction :ror r0, r0, r1
. Donc gcc ne se rend pas compte que le nombre de tours est intrinsèquement modulaire. Comme le dit ARM, "ROR avec longueur de décalagen
, plus de 32 est identique à ROR avec longueur de décalagen-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 nombre de la même manière que les rotations). Il décide probablement qu'il a besoin d'une instruction AND avant de reconnaître l'idiome de rotation, en raison de la façon 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 8 et 16 bits, probablement pour la même raison qu'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 sur n'importe quel processeur x86-64. (Le masquage des décomptes a été introduit avec 286 pour des raisons de performances car il gérait les changements de manière itérative, pas avec une latence constante comme les processeurs modernes.)
BTW, préférez la rotation à droite pour les rotations à nombre variable, pour éviter de forcer le compilateur
32-n
à implémenter une rotation à gauche sur des architectures comme ARM et MIPS qui ne fournissent qu'une rotation à droite. (Cela s'optimise avec des comptages constants de temps de compilation.)Fait amusant: ARM n'a pas vraiment d'instructions de changement / rotation dédiées, c'est juste MOV avec le Opérande source en passant par le canon-levier de vitesses en mode ROR :
mov r0, r0, ror r1
. Ainsi, une rotation peut se plier en un opérande source de registre pour une instruction EOR ou quelque chose.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 vers la droite, se déplaçant en copies du bit de signe plutôt que des zéros, ce qui entraîne un problème lorsque vousOR
déplacez les deux valeurs ensemble. Les décalages à droite des entiers signés négatifs sont un comportement défini par l'implémentation en C.)Assurez-vous également que le nombre de décalages est un type non signé , car
(-n)&31
avec un type signé peut être le complément à un ou le signe / la 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 billet de blog de Regehr).unsigned int
fonctionne bien sur tous les compilateurs que j'ai consultés, pour chaque largeur dex
. Certains autres types détruisent la reconnaissance d'idiomes pour certains compilateurs, alors n'utilisez pas simplement le même type quex
.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 quelques-unes des options x86:
<immintrin.h>
fournissent_rotl
et_rotl64
intrinsèques , et même pour le décalage à droite. MSVC nécessite<intrin.h>
, tandis que gcc nécessite<x86intrin.h>
. An#ifdef
s'occupe de gcc contre 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 une rotation 8 bits 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 / or (sans protection contre UB, car le codeia32intrin.h
ne doit fonctionner que sur gcc pour x86). GNU C semble ne pas avoir de__builtin_rotate
fonctions multiplateformes comme il le fait__builtin_popcount
(qui s'étend à 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 grâce à la reconnaissance d'idiomes.Vraisemblablement, certains compilateurs non-x86 ont aussi des éléments intrinsèques, mais n'élargissons pas cette réponse de communauté-wiki pour les inclure tous. (Peut-être faites-le 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 le code x86 32 bits), ou http://www.devx.com/tips/Tip/14043 pour une version C. Les commentaires répondent à cela .)
Inline asm défait de nombreuses optimisations , en particulier de style MSVC car il force les entrées à être stockées / rechargées . Une rotation en ligne-asm GNU C soigneusement écrite permettrait au compte d'être un opérande immédiat pour les décomptes de décalage à constante de temps de compilation, mais il ne pourrait toujours pas s'optimiser entièrement si la valeur à déplacer est également une constante de temps de compilation après l'inlining. https://gcc.gnu.org/wiki/DontUseInlineAsm .
la source
bits = CHAR_BIT * sizeof(n);
etc &= bits - 1;
etreturn ((n >> c) | (n << (bits - c)))
, quel est ce que j'utiliserais?bits - c
=32 - 0
. (Je n'ai pas reçu de ping parce que j'ai seulement édité le wiki, pas écrit en premier lieu.)0 < count < bits
est une exigence constante de presque tous les processeurs et langages de programmation implémentant la rotation (parfois0 ≤ count < bits
, mais le décalage de la quantité exacte de bits n'est pratiquement toujours pas défini ou s'arrondit à un nop au lieu d'effacer la valeur, et de tourner, bien.)Puisqu'il s'agit de C ++, utilisez une fonction en ligne:
Variante C ++ 11:
la source
INT
s'agit d'un entier signé et que le signe est défini! Testez par exemplerol<std::int32_t>(1 << 31)
qui devrait retourner à 1 mais qui devient réellement-1
(car le signe est conservé).std::numeric_limits<INT>::digits
place deCHAR_BIT * sizeof
. J'oublie si les types non signés sont autorisés à avoir un remplissage inutilisé (par exemple, des entiers 24 bits stockés en 32 bits), mais si c'est le cas, cedigits
serait mieux. Voir aussi gist.github.com/pabigot/7550454 pour une version avec plus de contrôle pour un décalage à nombre variable.La plupart des compilateurs ont des éléments intrinsèques pour cela. Visual Studio par exemple _rotr8, _rotr16
la source
Définitivement:
la source
8
une faute d'orthographe deCHAR_BIT
(qui ne doit pas nécessairement être exactement 8)?std::numeric_limits<T>::digits
.C ++ 20
std::rotl
etstd::rotr
C'est arrivé! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html et doit l'ajouter à l'en-
<bit>
tête.cppreference dit que l'utilisation sera comme:
donnant la sortie:
Je vais essayer lorsque le support arrivera à GCC, GCC 9.1.0 avec
g++-9 -std=c++2a
ne le supporte toujours pas.La proposition dit:
et:
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?la source
Comment quelque chose comme ça, en utilisant l'ensemble de bits standard ...
HTH,
la source
m %= N;
pour tenir compte des quarts de travail>= N
.Si x est une valeur de 8 bits, vous pouvez utiliser ceci:
la source
x
est signé.Dans les détails, vous pouvez appliquer la logique suivante.
Si le motif de bits est 33602 en nombre entier
et vous devez Roll over avec 2 décalages vers la droite, puis: faites d'abord une copie du motif de bits, puis décalez-le à gauche: Longueur - Décalage à droite c.-à-d. que la longueur est de 16 La valeur de décalage à droite est de 2 16 - 2 = 14
Après 14 passages à gauche, vous obtenez.
Maintenant, décalez à droite la valeur 33602, 2 fois au besoin. Vous obtenez
Maintenant, prenez un OU entre 14 fois la valeur décalée à gauche et 2 fois la valeur décalée à droite.
Et vous obtenez votre valeur de roulement décalée. Rappelez-vous que les opérations par bit sont plus rapides et cela ne nécessite même aucune boucle.
la source
En supposant que vous vouliez décaler vers la droite de
L
bits, et que l'entréex
soit un nombre avec desN
bits:la source
La bonne réponse est la suivante:
la source
val
est signé.Code source x numéro de bit
la source
une autre suggestion
la source
Vous trouverez ci-dessous une version légèrement améliorée de la réponse de Dídac Pérez , avec les deux directions implémentées, ainsi qu'une démonstration de l'utilisation de ces fonctions utilisant des caractères non signés et des valeurs longues longues non signées. Plusieurs remarques:
cout << +value
astuce pour générer de manière laconique un caractère non signé que j'ai trouvé ici: https://stackoverflow.com/a/28414758/1599699<put the type here>
syntaxe pour plus de clarté et de sécurité.Voici le code que j'utilise:
la source
la source
Surcharger une fonction:
la source
la source