Los operadores de desplazamiento a la izquierda y a la derecha (<< y >>) ya están disponibles en C ++. Sin embargo, no pude averiguar cómo podría realizar operaciones de rotación o desplazamiento circular.
¿Cómo se pueden realizar operaciones como "Girar a la izquierda" y "Girar 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 un comportamiento indefinido si el recuento de rotaciones es cero o se compilan en más de una sola instrucción de máquina de rotación. La respuesta de esta pregunta debe documentar las mejores prácticas).
Respuestas:
Consulte también una versión anterior de esta respuesta en otra pregunta rotativa con algunos detalles más sobre lo que produce asm gcc / clang para x86.
La forma más fácil de compilar de expresar una rotación en C y C ++ que evita cualquier comportamiento indefinido parece ser la implementación de John Regehr . Lo adapté para rotar por el ancho del tipo (usando tipos de ancho fijo como
uint32_t
).Funciona para cualquier tipo de entero sin signo, no solo
uint32_t
, por lo que podría hacer versiones para otros tamaños.Consulte también una versión de plantilla de C ++ 11 con muchas comprobaciones de seguridad (incluida la de
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.Recomendaría usar solo la plantilla como back-end para contenedores 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 deunsigned long
). Evenuint16_t & uint16_t
es promovidosigned int
por las reglas de promoción de enteros de C ++, excepto en plataformas dondeint
no es más ancho queuint16_t
.En x86 , esta versión se integra en un solo
rol r32, cl
(orol r32, imm8
) con compiladores que lo asimilan, porque el compilador sabe que las instrucciones de rotación y desplazamiento x86 enmascaran el recuento de turnos de la misma manera que lo hace la fuente C.Soporte del compilador para este modismo que evita UB en x86, para
uint32_t x
yunsigned int n
para cambios de conteo variable:ror
orol
para recuentos de variables.shld edi,edi,7
que es más lento y ocupa más bytes querol edi,7
en algunas CPU (especialmente AMD, pero también algunas Intel), cuando BMI2 no está disponible pararorx eax,edi,25
guardar un MOV._rotl
/_rotr
intrinsics desde<intrin.h>
x86 (incluido x86-64).GCC para ARM utiliza una
and r1, r1, #31
para gira variable de conteo, pero todavía lo hace la rotación real con una sola instrucción :ror r0, r0, r1
. Entonces, gcc no se da cuenta de que los conteos rotativos son inherentemente modulares. Como dicen los documentos de ARM, "ROR con longitud de turnon
, más de 32 es lo mismo que ROR con longitud de turnon-32
" . Creo que gcc se confunde aquí porque los cambios de izquierda a derecha en ARM saturan el recuento, por lo que un cambio de 32 o más borrará el registro. (A diferencia de x86, donde los cambios enmascaran el recuento de la misma manera que los giros). Probablemente decida que necesita una instrucción AND antes de reconocer el idioma de rotación, 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 recuento de variables para rotaciones de 8 y 16 bits, probablemente por la misma razón por la que no evitan el AND en ARM. Esta es una optimización perdida, porque el rendimiento no depende del recuento de rotaciones en ninguna CPU x86-64. (El enmascaramiento de recuentos se introdujo con 286 por razones de rendimiento porque manejaba los cambios de forma iterativa, no con latencia constante como las CPU modernas).
Por cierto, prefiera rotar a la derecha para rotaciones de recuento variable, para evitar que el compilador
32-n
implemente una rotación a la izquierda en arquitecturas como ARM y MIPS que solo proporcionan una rotación a la derecha. (Esto se optimiza con conteos de constantes de tiempo de compilación).Dato curioso: ARM no tiene realmente turno dedicada / instrucciones de rotación, es sólo MOV con la fuente de operando de pasar por el cañón-palanca de cambios en el modo de ROR :
mov r0, r0, ror r1
. Entonces, 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 firmar para
n
y el valor de retorno, o de lo contrario no será una rotación . (gcc para destinos x86 hace cambios aritméticos a la derecha, cambiando copias del bit de signo en lugar de ceros, lo que genera un problema cuando seOR
cambian los dos valores juntos. Los cambios a la derecha de enteros con signo negativo es un comportamiento definido por la implementación en C.)Además, asegúrese de que el recuento de turnos sea un tipo sin signo , porque
(-n)&31
con un tipo con signo podría ser el complemento de uno o el signo / magnitud, y no el mismo que el 2 ^ n modular que obtiene con el complemento de dos o sin signo. (Ver comentarios en la publicación del blog de Regehr).unsigned int
funciona bien en todos los compiladores que he visto, para cada ancho dex
. Algunos otros tipos en realidad anulan el reconocimiento idiomático de algunos compiladores, así que no use simplemente el mismo tipo quex
.Algunos compiladores proporcionan elementos intrínsecos para rotaciones , que es mucho mejor que inline-asm si la versión portátil no genera un buen código en el compilador al que se dirige. No hay elementos intrínsecos multiplataforma para ningún compilador que yo conozca. Estas son algunas de las opciones de x86:
<immintrin.h>
proporciona_rotl
_rotl64
documentos e intrínsecos , y lo mismo para el turno correcto. MSVC requiere<intrin.h>
, mientras que gcc requiere<x86intrin.h>
. An#ifdef
se encarga de gcc frente a icc, pero clang no parece proporcionarlos en ninguna parte, excepto en el modo de compatibilidad MSVC con-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
. Y el conjunto que emite para ellos apesta (enmascaramiento extra y un CMOV)._rotr8
y_rotr16
.<x86intrin.h>
también proporciona__rolb
/__rorb
para rotación izquierda / derecha de 8 bits,__rolw
/__rorw
(16 bits),__rold
/__rord
(32 bits),__rolq
/__rorq
(64 bits, solo definido para destinos 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 / o (sin protección contra UB, porque el códigoia32intrin.h
solo tiene que funcionar en gcc para x86). GNU C parece no tener ninguna__builtin_rotate
función multiplataforma de la forma en que lo hace__builtin_popcount
(lo que se expande a lo que sea óptimo en la plataforma de destino, incluso si no es una sola instrucción). La mayoría de las veces se obtiene un buen código del reconocimiento de idiomas.Es de suponer que algunos compiladores que no son x86 también tienen elementos intrínsecos, pero no ampliemos esta respuesta de la wiki comunitaria para incluirlos a todos. (Tal vez haga eso en la respuesta existente sobre intrínsecos ).
(La versión anterior de esta respuesta sugería un conjunto en línea específico de 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 el estilo MSVC porque obliga a que las entradas se almacenen / recarguen . Una rotación de ensamblaje en línea de GNU C cuidadosamente escrita permitiría que el recuento sea un operando inmediato para los recuentos de cambios constantes en tiempo de compilación, pero aún así no se podría optimizar por completo si el valor que se va a cambiar es también una constante en tiempo de compilación después de la alineación. https://gcc.gnu.org/wiki/DontUseInlineAsm .
fuente
bits = CHAR_BIT * sizeof(n);
yc &= bits - 1;
yreturn ((n >> c) | (n << (bits - c)))
cuál es el que usaría?bits - c
=32 - 0
. (No recibí un ping de esto porque solo edité la wiki, no la escribí en primer lugar).0 < count < bits
es un requisito constante de casi todas las CPU y lenguajes de programación que implementan la rotación (a veces0 ≤ count < bits
, pero el cambio en la cantidad exacta de bits prácticamente siempre no está definido o se redondea a un nop en lugar de borrar el valor y rotar, bueno).Dado que es C ++, use una función en línea:
Variante de C ++ 11:
fuente
INT
es un entero con signo y el signo está configurado! Pruebe, por ejemplo,rol<std::int32_t>(1 << 31)
cuál debería pasar a 1 pero en realidad se convierte-1
(porque se conserva el signo).std::numeric_limits<INT>::digits
lugar deCHAR_BIT * sizeof
. Olvidé si se permite que los tipos sin firmar tengan relleno sin usar (por ejemplo, enteros de 24 bits almacenados en 32 bits), pero si es así,digits
sería mejor. Consulte también gist.github.com/pabigot/7550454 para obtener una versión con más verificación para un cambio de conteo variable.La mayoría de los compiladores tienen elementos intrínsecos para eso. Visual Studio, por ejemplo _rotr8, _rotr16
fuente
Definitivamente:
fuente
8
un error ortográfico deCHAR_BIT
(que no tiene por qué ser exactamente 8)?std::numeric_limits<T>::digits
.C ++ 20
std::rotl
ystd::rotr
¡Ha llegado! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html y debería agregarlo al
<bit>
encabezado.cppreference dice que el uso será como:
dando salida:
Lo intentaré cuando llegue el soporte a GCC, GCC 9.1.0
g++-9 -std=c++2a
aún no lo admite.La propuesta dice:
y:
También
std::popcount
se agregó A para contar el número de 1 bits: ¿Cómo contar el número de bits establecidos en un entero de 32 bits?fuente
Qué tal algo como esto, usando el conjunto de bits estándar ...
HTH,
fuente
m %= N;
para tener en cuenta los turnos>= N
.Si x es un valor de 8 bits, puede usar esto:
fuente
x
está firmado.En detalle, puede aplicar la siguiente lógica.
Si el patrón de bits es 33602 en entero
y necesita pasar con 2 cambios a la derecha y luego: primero haga una copia del patrón de bits y luego a la izquierda: Longitud - Cambio a la derecha, es decir, la longitud es 16 El valor de cambio a la derecha es 2 16 - 2 = 14
Después de 14 cambios a la izquierda, obtienes.
Ahora cambie a la derecha el valor 33602, 2 veces según sea necesario. Usted obtiene
Ahora tome un OR entre 14 veces el valor desplazado a la izquierda y 2 veces el valor desplazado a la derecha.
Y obtiene su valor de rollover cambiado. Recuerde que las operaciones de bits son más rápidas y esto ni siquiera requiere ningún bucle.
fuente
Suponiendo que desea desplazarse a la derecha en
L
bits, y la entradax
es un número conN
bits:fuente
La respuesta correcta es la siguiente:
fuente
val
está firmado.Código fuente x número de bit
fuente
otra sugerencia
fuente
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 largos sin firmar y sin firmar. Varias notas:
cout << +value
Usé un truco para generar concisamente un carácter sin firmar numéricamente que encontré aquí: https://stackoverflow.com/a/28414758/1599699<put the type here>
sintaxis explícita para mayor claridad y seguridad.Aquí está el código que estoy usando:
fuente
fuente
Sobrecargar una función:
fuente
fuente