Mejores prácticas para operaciones de desplazamiento circular (rotación) en C++


Los operadores de desplazamiento izquierdo y derecho (>) ya están disponibles en C++. Sin embargo, no pude averiguar cómo podía realizar operaciones de cambio circular o rotación.

¿Cómo se pueden realizar operaciones como "Rotar a la Izquierda" y "Rotar a la Derecha"?

Girando a la derecha dos veces aquí

Initial --> 1000 0011 0100 0010

Debería resultar en:

Final   --> 1010 0000 1101 0000

Un ejemplo sería útil.

(nota del editor: Muchas formas comunes de expresar rotaciones en C sufren de un comportamiento indefinido si el recuento de rotaciones es cero, o compilar a más de una sola instrucción de máquina de rotación. La respuesta de esta pregunta debería documentar las mejores prácticas.)

Author: Elroy, 2009-04-22

15 answers

Vea también una versión anterior de esta respuesta en otra pregunta de rotate con algunos detalles más sobre lo que asm gcc/clang produce para x86.

La forma más amigable para el compilador de expresar una rotación en C y C++ que evita cualquier Comportamiento Indefinido parece ser La implementación de John Regehr. Lo he adaptado para rotar por el ancho del tipo (por ejemplo, suponiendo que uint32_t es exactamente de 32 bits de ancho, aunque C/C++ solo garantiza que es al menos ese ancho. Traté de mantenerlo legible dejando fuera cheques para ese tipo de cosas).

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

Funciona para cualquier tipo entero sin signo, no solo uint32_t, por lo que podría hacer versiones para otros tamaños.

Ver también una versión de plantilla de C++11 con muchas comprobaciones de seguridad (incluyendo un static_assert que el ancho del tipo es una potencia de 2), que no es el caso en algunos DSP de 24 bits o mainframes de 36 bits, por ejemplo.

Yo recomendaría solo usar la plantilla como un back-end para envoltorios con nombres que incluyan explícitamente el ancho de rotación. Las reglas de promoción de enteros significan que rotl_template(u16 & 0x11UL, 7) haría una rotación de 32 o 64 bits, no 16 (dependiendo del ancho de unsigned long). Even uint16_t & uint16_t es promovido a signed int por las reglas de promoción de enteros de C++, excepto en plataformas donde int no es más amplio que uint16_t.


En x86, esta versión inline a una sola rol r32, cl (o rol r32, imm8) con compiladores que grok, porque el compilador sabe que x86 girar y shift instructions enmascara el shift-count de la misma manera que lo hace la fuente C.

Soporte de compilador para este modismo que evita UB en x86, para uint32_t x y unsigned int n para cambios de conteo de variables:

  • clang: reconocido para el recuento de variables gira desde clang3.5, múltiples turnos+o insns antes de eso.
  • gcc: reconocido para el conteo de variables gira desde gcc4.9, múltiples turnos+o insns antes de eso. gcc5 y posteriores optimizan la rama y enmascaran en la wikipedia versión, también, usando solo una instrucción ror o rol para conteos de variables.
  • icc: soportado para el conteo de variables gira desde ICC13 o anterior. El conteo constante gira usando shld edi,edi,7 que es más lento y toma más bytes que rol edi,7 en algunas CPU (especialmente AMD, pero también algunos Intel), cuando BMI2 no está disponible para rorx eax,edi,25 para guardar un MOV.
  • MSVC: x86-64 CL19: Solo se reconoce para rotaciones de recuento constante. (El idioma de wikipedia es reconocido, pero la rama y Y no son optimizado lejos). Utilice el _rotl / _rotr intrínsecos de <intrin.h> en x86 (incluyendo x86-64).

Gcc para ARM utiliza un and r1, r1, #31 para el recuento de variables gira, pero todavía hace la rotación real con una sola instrucción: ror r0, r0, r1. Así que gcc no se da cuenta de que los recuentos de rotación son inherentemente modulares. Como dicen los documentos de ARM, " ROR con longitud de desplazamiento, n, más de 32 es lo mismo que ROR con longitud de desplazamienton-32". Creo que gcc se confunde aquí porque los cambios de izquierda / derecha en el BRAZO saturar la cuenta, por lo que un cambio de 32 o más borrará el registro. (A diferencia de x86, donde los cambios enmascaran la cuenta igual que gira). Probablemente decida que necesita una instrucción AND antes de reconocer el modismo rotate, debido a cómo funcionan los turnos no circulares en ese objetivo.

Los compiladores x86 actuales todavía usan una instrucción adicional para enmascarar un conteo de variables para rotaciones de 8 y 16 bits, probablemente por la misma razón que no evitan el AND on ARM. Esta es una optimización perdida, porque el rendimiento no depende de la cuenta de rotación en cualquier CPU x86-64. (El enmascaramiento de conteos se introdujo con 286 por razones de rendimiento porque manejaba los cambios iterativamente, no con latencia constante como las CPU modernas.)

POR cierto, prefiere rotar a la derecha para las rotaciones de recuento de variables, para evitar que el compilador haga 32-n implementar una rotación a la izquierda en arquitecturas como ARM y MIPS que solo proporcionan una rotación a la derecha. (Esto optimiza away con compile-time-constant contar.)

Dato curioso: ARM realmente no tiene instrucciones dedicadas de cambio / rotación, solo es MOV con el operando de origen pasando por el cambio de barril en modo ROR: mov r0, r0, ror r1. Así que una rotación puede plegarse en un operando de fuente de registro para una instrucción EOR o algo así.


Asegúrese de usar tipos sin signo para n y el valor devuelto, o de lo contrario no será un rotate. (gcc para objetivos x86 hace cambios aritméticos a la derecha, cambiando en copias de la signo-bit en lugar de ceros, lo que lleva a un problema cuando OR los dos valores cambiados juntos. Los desplazamientos a la derecha de enteros con signo negativo son comportamientos definidos por la implementación en C.)

También, asegúrese de que el recuento de cambios es un tipo sin signo, porque (-n)&31 con un tipo con signo podría ser el complemento de uno o signo/magnitud, y no lo mismo que el 2^n modular que se obtiene con un signo o complemento de dos. (Ver comentarios en la entrada del blog de Regehr). unsigned int funciona bien en todos los compiladores He mirado, para cada anchura de x. Algunos otros tipos en realidad vencen el reconocimiento de expresiones idiomáticas para algunos compiladores, así que no use el mismo tipo que x.


Algunos compiladores proporcionan intrínsecos para rotates, que es mucho mejor que inline-asm si la versión portable no genera buen código en el compilador al que se dirige. No hay intrínsecos multiplataforma para ningún compilador que yo sepa. Estas son algunas de las opciones de x86:

  • Intel documentos que <immintrin.h> proporciona _rotl y _rotl64 intrínsecos, y lo mismo para el desplazamiento derecho. MSVC requiere <intrin.h>, mientras que gcc requiere <x86intrin.h>. An #ifdef se encarga de gcc vs. icc, pero clang no parece proporcionarlos en ningún lugar, excepto en el modo de compatibilidad MSVC con -fms-extensions -fms-compatibility -fms-compatibility-version=17.00. Y el asm que emite para ellos apesta (enmascaramiento extra y un CMOV).
  • MSVC: _rotr8 y _rotr16.
  • ccg e icc (no clang): <x86intrin.h> también proporciona __rolb/__rorb para 8 bits girar a la izquierda / derecha, __rolw/__rorw (16-bit), __rold/__rord (32-bit), __rolq/__rorq (64-bit, solo definido para objetivos de 64 bits). Para rotaciones estrechas, la implementación usa __builtin_ia32_rolhi o ...qi, pero las rotaciones de 32 y 64 bits se definen usando shift / or (sin protección contra UB, porque el código en ia32intrin.h solo tiene que trabajar en gcc para x86). GNU C parece no tener ninguna función multiplataforma __builtin_rotate como lo hace para __builtin_popcount (que se expande a lo que es óptimo en el objetivo plataforma, incluso si no es una sola instrucción). La mayoría de las veces obtienes buen código del reconocimiento de expresiones idiomáticas.

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

Presumiblemente algunos compiladores no-x86 también tienen intrínsecos, pero no expandamos esta respuesta de community-wiki para incluirlos a todos. (Tal vez hacer eso en la respuesta existente sobre intrínsecos).


(La versión antigua de esta respuesta sugirió asm inline específico para MSVC (que solo funciona para código x86 de 32 bits), o http://www.devx.com/tips/Tip/14043 para una versión C. Los comentarios responden a eso.)

Asm en línea derrota muchas optimizaciones, especialmente al estilo MSVC porque fuerza que las entradas sean almacenadas / recargadas . Una rotación de GNU C inline-asm cuidadosamente escrita permitiría que el conteo sea un operando inmediato para conteos de cambios constantes en tiempo de compilación, pero aún así no podría optimizarse completamente si el valor a ser desplazado es también una constante en tiempo de compilación después de en línea. https://gcc.gnu.org/wiki/DontUseInlineAsm.

 80
Author: Peter Cordes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-08 01:46:18

Dado que es C++, use una función en línea:

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
Author: MSalters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-16 11:26:06

La mayoría de los compiladores tienen intrínsecos para eso. Visual Studio por ejemplo _rotr8, _rotr16

 18
Author: VolkerK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-25 13:11:21

Definitivamente:

template<class T>
T ror(T x, unsigned int moves)
{
  return (x >> moves) | (x << sizeof(T)*8 - moves);
}
 16
Author: Dídac Pérez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-17 13:55:19

Cómo abt algo como esto, usando el bitset estándar ...

#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
Author: Abhay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-22 11:01:21

En detalles puede aplicar la siguiente lógica.

Si el patrón de bits es 33602 en Entero

1000 0011 0100 0010

Y necesitas rodar con 2 cambios a la derecha entonces: primero haga una copia del patrón de bits y luego cambie a la izquierda: Length-RightShift es decir, la longitud es 16 el valor de desplazamiento a la derecha es 2 16 - 2 = 14

Después de 14 veces el cambio a la izquierda se obtiene.

1000 0000 0000 0000

Ahora cambia a la derecha el valor 33602, 2 veces según sea necesario. Usted consigue

0010 0000 1101 0000

Ahora tomar un O entre 14 tiempo izquierda desplazado valor y 2 veces a la derecha desplazado valor.

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

Y obtendrá su valor de rollover desplazado. Recuerde que las operaciones de bit wise son más rápidas y esto ni siquiera requiere ningún bucle.

 6
Author: S M Kamran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-28 03:37:38

Si x es un valor de 8 bits, puede usar esto:

x=(x>>1 | x<<7);
 5
Author: Farhadix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-23 18:45:32

Suponiendo que desea cambiar a la derecha por L bits, y la entrada x es un número con N bits:

unsigned ror(unsigned x, int L, int N) 
{
    unsigned lsbs = x & ((1 << L) - 1);
    return (x >> L) | (lsbs << (N-L));
}
 4
Author: nimrodm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-22 10:36:29

La respuesta correcta es la siguiente:

#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
Author: user3102555,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-23 13:28:54

Código Fuente x número de bit

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
Author: kjk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-31 05:24:14

Otra sugerencia

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
Author: SalemD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-16 02:13:28

A continuación se muestra una versión ligeramente mejorada de La respuesta de Dídac Pérez, con ambas direcciones implementadas, junto con una demostración de los usos de estas funciones utilizando valores unsigned char y unsigned long long. Varias notas:

  1. Las funciones están en línea para optimizaciones del compilador
  2. Usé un truco cout << +value para emitir de manera concisa un carácter sin signo numéricamente que encontré aquí: https://stackoverflow.com/a/28414758/1599699
  3. Recomiendo usar el lenguaje explícito <put the type here> sintaxis para mayor claridad y seguridad.
  4. Usé unsigned char para el parámetro shiftNum debido a lo que encontré en la sección de Detalles Adicionales aquí :

El resultado de una operación de desplazamiento es indefinido si La expresión aditiva es negativo o si la expresión aditiva es mayor o igual a la número de bits en la (promovida) expresión de desplazamiento.

Aquí está el código que estoy usando:

#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
Author: Andrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
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
Author: MikeZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-14 21:12:11

Sobrecarga una función:

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

unsigned short rotate_right(unsigned short x) { /* etc. */ }
 -1
Author: graham.reeds,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-22 10:38:09
#define ROTATE_RIGHT(x) ( (x>>1) | (x&1?0x8000:0) )
 -1
Author: Dan Byström,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-28 03:36:41