Operadores shift para a esquerda e direita (<< e >>) já estão disponíveis em C ++. No entanto, não consegui descobrir como realizar operações de deslocamento circular ou rotação.
Como operações como "Girar para a esquerda" e "Girar para a direita" podem ser realizadas?
Girando bem duas vezes aqui
Initial --> 1000 0011 0100 0010
deve resultar em:
Final --> 1010 0000 1101 0000
Um exemplo seria útil.
(nota do editor: muitas maneiras comuns de expressar rotações em C sofrem de comportamento indefinido se a contagem de rotação for zero ou compilar para mais do que apenas uma única instrução de rotação da máquina. A resposta desta pergunta deve documentar as melhores práticas.)
Respostas:
Veja também uma versão anterior desta resposta em outra pergunta rotativa com mais alguns detalhes sobre o que o asm gcc / clang produz para x86.
A maneira mais amigável do compilador de expressar uma rotação em C e C ++ que evita qualquer comportamento indefinido parece ser a implementação de John Regehr . Eu o adaptei para girar pela largura do tipo (usando tipos de largura fixa como
uint32_t
).Funciona para qualquer tipo de inteiro sem sinal, não apenas
uint32_t
, para que você possa fazer versões para outros tamanhos.Veja também uma versão do modelo C ++ 11 com muitas verificações de segurança (incluindo a de
static_assert
que a largura do tipo é uma potência de 2) , o que não é o caso em alguns DSPs de 24 bits ou mainframes de 36 bits, por exemplo.Eu recomendo usar apenas o modelo como back-end para wrappers com nomes que incluem a largura de rotação explicitamente. As regras de promoção de inteiros significam que
rotl_template(u16 & 0x11UL, 7)
faria uma rotação de 32 ou 64 bits, não 16 (dependendo da largura deunsigned long
). Evenuint16_t & uint16_t
é promovidosigned int
pelas regras de promoção de inteiros do C ++, exceto em plataformas ondeint
não é mais largo queuint16_t
.No x86 , esta versão se alinha a um único
rol r32, cl
(ourol r32, imm8
) com compiladores que o agrupam, porque o compilador sabe que as instruções de rotação e deslocamento do x86 mascaram a contagem de deslocamento da mesma forma que o código-fonte C faz.Suporte de compilador para este idioma que evita UB em x86, para
uint32_t x
eunsigned int n
para mudanças de contagem de variável:ror
ourol
para contagens de variáveis.shld edi,edi,7
que é mais lento e leva mais bytes do querol edi,7
em alguns processadores (especialmente AMD, mas também alguns Intel), quando o BMI2 não está disponível pararorx eax,edi,25
salvar um MOV._rotl
/_rotr
intrinsics do<intrin.h>
x86 (incluindo x86-64).gcc para ARM utiliza um
and r1, r1, #31
para rodar variável de contagem, mas ainda faz a rotação real com uma única instrução :ror r0, r0, r1
. Portanto, o gcc não percebe que as contagens de rotação são inerentemente modulares. Como os documentos do ARM dizem, "ROR com comprimento de deslocamenton
, mais de 32 é o mesmo que ROR com comprimento de deslocamenton-32
" . Acho que o gcc fica confuso aqui porque os deslocamentos para a esquerda / direita no ARM saturam a contagem, portanto, um deslocamento de 32 ou mais limpará o registro. (Ao contrário do x86, em que as mudanças mascaram a contagem da mesma forma que as rotações). Ele provavelmente decide que precisa de uma instrução AND antes de reconhecer o idioma de rotação, por causa de como as mudanças não circulares funcionam naquele destino.Os compiladores x86 atuais ainda usam uma instrução extra para mascarar uma contagem de variável para rotações de 8 e 16 bits, provavelmente pela mesma razão que eles não evitam o AND no ARM. Esta é uma otimização perdida, porque o desempenho não depende da contagem de rotação em qualquer CPU x86-64. (O mascaramento de contagens foi introduzido no 286 por motivos de desempenho, porque ele lida com os turnos de forma iterativa, não com latência constante como as CPUs modernas.)
BTW, prefira girar para a direita para rotações de contagem variável, para evitar que o compilador
32-n
implemente uma rotação para a esquerda em arquiteturas como ARM e MIPS que fornecem apenas uma rotação para a direita. (Isso otimiza com contagens de constantes de tempo de compilação.)Curiosidade: ARM realmente não tem mudança dedicado / instruções de rotação, é apenas MOV com a fonte operando a atravessar o cano-shifter em modo ROR :
mov r0, r0, ror r1
. Portanto, um rotate pode dobrar em um operando de fonte de registro para uma instrução EOR ou algo assim.Certifique-se de usar tipos não assinados para
n
e o valor de retorno, ou então não será uma rotação . (gcc para x86 alvos faz deslocamentos aritméticos para a direita, deslocando as cópias do bit de sinal em vez de zeros, levando a um problema quando vocêOR
os dois valores deslocados juntos. Deslocamentos para a direita de inteiros com sinal negativo é um comportamento definido pela implementação em C.)Além disso, certifique-se de que a contagem de deslocamento seja um tipo sem sinal , porque
(-n)&31
um tipo com sinal pode ser o complemento ou sinal / magnitude de um, e não o mesmo que o 2 ^ n modular que você obtém com o complemento sem sinal ou de dois. (Veja os comentários na postagem do blog de Regehr).unsigned int
funciona bem em todos os compiladores que eu examinei, para cada largura dex
. Alguns outros tipos realmente derrotam o reconhecimento de idioma para alguns compiladores, portanto, não use apenas o mesmo tipo quex
.Alguns compiladores fornecem intrínsecos para rotações , o que é muito melhor do que inline-asm se a versão portátil não gerar um bom código no compilador que você está almejando. Não há intrínsecos de plataforma cruzada para nenhum compilador que eu conheça. Estas são algumas das opções do x86:
<immintrin.h>
fornecem_rotl
e_rotl64
intrínsecos , e o mesmo para o turno certo. MSVC requer<intrin.h>
, enquanto gcc requer<x86intrin.h>
. An#ifdef
cuida do gcc vs. icc, mas o clang não parece fornecê-los em lugar nenhum, exceto no modo de compatibilidade MSVC com-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
. E o conjunto que emite para eles é uma merda (máscara extra e um CMOV)._rotr8
e_rotr16
.<x86intrin.h>
também fornece__rolb
/__rorb
para rotação de 8 bits para a esquerda / direita,__rolw
/__rorw
(16 bits),__rold
/__rord
(32 bits),__rolq
/__rorq
(64 bits, definido apenas para destinos de 64 bits). Para rotações estreitas, a implementação usa__builtin_ia32_rolhi
ou...qi
, mas as rotações de 32 e 64 bits são definidas usando shift / ou (sem proteção contra UB, porque o códigoia32intrin.h
só precisa funcionar no gcc para x86). GNU C parece não ter nenhuma__builtin_rotate
função de plataforma cruzada da maneira que tem__builtin_popcount
(o que se expande para o que for ideal na plataforma de destino, mesmo se não for uma única instrução). Na maioria das vezes, você obtém um bom código de reconhecimento de idioma.Presumivelmente, alguns compiladores não x86 também possuem intrínsecos, mas não vamos expandir esta resposta wiki da comunidade para incluir todos eles. (Talvez faça isso na resposta existente sobre intrínsecos ).
(A versão antiga desta resposta sugeria asm embutidas específicas do MSVC (que funciona apenas para código x86 de 32 bits) ou http://www.devx.com/tips/Tip/14043 para uma versão C. Os comentários estão respondendo a isso .)
O conjunto embutido derrota muitas otimizações , especialmente no estilo MSVC, porque força as entradas a serem armazenadas / recarregadas . Uma rotação embutida asm do GNU C cuidadosamente escrita permitiria que a contagem fosse um operando imediato para contagens de deslocamento da constante de tempo de compilação, mas ainda não poderia otimizar totalmente se o valor a ser deslocado também fosse uma constante de tempo de compilação após inlining. https://gcc.gnu.org/wiki/DontUseInlineAsm .
fonte
bits = CHAR_BIT * sizeof(n);
ec &= bits - 1;
ereturn ((n >> c) | (n << (bits - c)))
, que é o que eu usaria?bits - c
=32 - 0
. (Não recebi um ping disso porque apenas editei o wiki, não o escrevi em primeiro lugar.)0 < count < bits
é um requisito constante de quase todas as CPUs e linguagens de programação que implementam a rotação (às vezes0 ≤ count < bits
, mas a mudança pela quantidade exata de bits quase sempre não é definida ou é arredondada para um nop em vez de limpar o valor e girar, bem).Por ser C ++, use uma função embutida:
Variante C ++ 11:
fonte
INT
for um inteiro com sinal e o sinal estiver definido! Teste, por exemplo,rol<std::int32_t>(1 << 31)
qual deveria mudar para 1, mas realmente se torna-1
(porque o sinal é retido).std::numeric_limits<INT>::digits
vez deCHAR_BIT * sizeof
. Eu esqueci se os tipos sem sinal podem ter preenchimento não usado (por exemplo, inteiros de 24 bits armazenados em 32 bits), mas se sim,digits
seria melhor. Consulte também gist.github.com/pabigot/7550454 para uma versão com mais verificação para uma mudança de contagem de variável.A maioria dos compiladores tem intrínsecos para isso. Visual Studio por exemplo _rotr8, _rotr16
fonte
Definitivamente:
fonte
8
um erro ortográfico deCHAR_BIT
(que não precisa ser exatamente 8)?std::numeric_limits<T>::digits
.C ++ 20
std::rotl
estd::rotr
Chegou! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html e deve adicioná-lo ao
<bit>
cabeçalho.cppreference diz que o uso será como:
dando saída:
Vou tentar quando o suporte chegar ao GCC, o GCC 9.1.0
g++-9 -std=c++2a
ainda não oferece suporte.A proposta diz:
e:
A
std::popcount
também foi adicionado para contar o número de bits 1: Como contar o número de bits definidos em um inteiro de 32 bits?fonte
Que tal algo assim, usando o bitset padrão ...
HTH,
fonte
m %= N;
para contabilizar mudanças>= N
.Se x for um valor de 8 bits, você pode usar isto:
fonte
x
estiver assinado.Em detalhes, você pode aplicar a seguinte lógica.
Se o padrão de bits for 33602 em número inteiro
e você precisa rolar com 2 deslocamentos para a direita então: primeiro faça uma cópia do padrão de bits e depois desloque para a esquerda: Comprimento - Deslocamento para a direita, ou seja, o comprimento é 16, o valor do deslocamento para a direita é 2 16 - 2 = 14
Depois de 14 vezes à esquerda, você consegue.
Agora mude para a direita o valor 33602, 2 vezes conforme necessário. Você consegue
Agora tome um OR entre 14 vezes o valor deslocado para a esquerda e 2 vezes o valor deslocado para a direita.
E você obtém seu valor de rollover alterado. Lembre-se de que as operações bit wise são mais rápidas e isso nem mesmo requer nenhum loop.
fonte
Supondo que você deseja mudar os
L
bits à direita e a entradax
é um número comN
bits:fonte
A resposta correta é a seguinte:
fonte
val
estiver assinado.Código fonte x número de bits
fonte
outra sugestão
fonte
Abaixo está uma versão ligeiramente melhorada da resposta de Dídac Pérez , com ambas as direções implementadas, junto com uma demonstração dos usos dessas funções usando unsigned char e unsigned long long values. Várias notas:
cout << +value
truque para gerar um caractere sem sinal numericamente que encontrei aqui: https://stackoverflow.com/a/28414758/1599699<put the type here>
sintaxe explícita para clareza e segurança.Este é o código que estou usando:
fonte
fonte
Sobrecarregar uma função:
fonte
fonte