Em C / C ++, qual é a maneira mais simples de inverter a ordem dos bits em um byte?

110

Embora existam várias maneiras de inverter a ordem dos bits em um byte, estou curioso para saber qual é a "mais simples" para um desenvolvedor implementar. E ao inverter, quero dizer:

1110 -> 0111
0010 -> 0100

Isso é semelhante, mas não uma duplicata deste questão do PHP.

Isso é semelhante, mas não uma duplicata, desta questão C. Esta questão está pedindo o método mais fácil de implementar por um desenvolvedor. O "Melhor Algoritmo" se preocupa com a memória e o desempenho da CPU.

Nathan
fonte
Use montagem embutida. Melhor, coloque a função em uma unidade de tradução separada. Tenha um módulo de linguagem assembly para cada plataforma de destino. Deixe o processo de construção escolher os módulos.
Thomas Matthews,
Implementação mais simples do @Andreas
nathan

Respostas:

102

Se você está falando sobre um único byte, uma consulta de tabela é provavelmente a melhor aposta, a menos que por algum motivo você não tenha 256 bytes disponíveis.

e.James
fonte
12
Se estivermos falando sobre algo que é simples de implementar sem copiar uma solução pronta, criar a tabela de pesquisa ainda requer outra solução. (Claro que pode ser feito à mão, mas isso é passível de erros e demorado ...)
Arkku
7
Você pode comprimir a matriz em um pouco menos de 256 bytes se ignorar os palíndromos.
wilhelmtell
8
@wilhelmtell - você precisaria de uma tabela para saber quais são os palíndromos.
Mark Ransom,
6
@wilhelmtell: Bem, para escrever o script ainda é necessária outra solução, que era o meu ponto - uma tabela de pesquisa é simples de usar, mas não simples de criar. (Exceto copiando uma tabela de pesquisa pronta, mas então alguém pode muito bem copiar qualquer solução.) Por exemplo, se a solução "mais simples" for considerada uma que possa ser escrita no papel em um exame ou entrevista, eu não faria começar a fazer uma tabela de pesquisa à mão e fazer o programa para fazê-lo já incluiria uma solução diferente (que seria mais simples por si só do que aquela que inclui a tabela e a tabela).
Arkku
4
@Arkku, o que eu quis dizer é escrever um script que produza a tabela dos primeiros 256 bytes e seu mapeamento reverso. Sim, você voltou a escrever a função reversa, mas agora em sua linguagem de script favorita, e pode ser tão desagradável quanto você quiser - você vai jogá-la fora assim que terminar e executá-la uma vez. Tem a saída do script como código C, mesmo: unsigned int rtable[] = {0x800, 0x4000, ...};. Então jogue fora o roteiro e esqueça que você o teve. É muito mais rápido de escrever do que o código C ++ equivalente e só será executado uma vez, portanto, você obtém o tempo de execução O (1) em seu código C ++.
wilhelmtell
227

Isso deve funcionar:

unsigned char reverse(unsigned char b) {
   b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
   b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
   b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
   return b;
}

Primeiro, os quatro bits da esquerda são trocados pelos quatro bits da direita. Em seguida, todos os pares adjacentes são trocados e, em seguida, todos os bits únicos adjacentes. Isso resulta em uma ordem reversa.

sth
fonte
26
Razoavelmente curto e rápido, mas não simples.
Mark Ransom,
3
Essa abordagem também generaliza de forma limpa para realizar a troca de bytes por endianness.
Boojum
2
Não é a abordagem mais simples, mas eu gosto +1.
nathan,
7
Sim, é simples. É uma espécie de algoritmo de divisão para conquistar. Excelente!
kiewic
É mais rápido do que o método sugerido por @Arkku abaixo?
qed
123

Acho que uma tabela de consulta deve ser um dos métodos mais simples. No entanto, você não precisa de uma tabela de pesquisa completa.

//Index 1==0b0001 => 0b1000
//Index 7==0b0111 => 0b1110
//etc
static unsigned char lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };

uint8_t reverse(uint8_t n) {
   // Reverse the top and bottom nibble then swap them.
   return (lookup[n&0b1111] << 4) | lookup[n>>4];
}

// Detailed breakdown of the math
//  + lookup reverse of bottom nibble
//  |       + grab bottom nibble
//  |       |        + move bottom result into top nibble
//  |       |        |     + combine the bottom and top results 
//  |       |        |     | + lookup reverse of top nibble
//  |       |        |     | |       + grab top nibble
//  V       V        V     V V       V
// (lookup[n&0b1111] << 4) | lookup[n>>4]

É bastante simples de codificar e verificar visualmente.
No final das contas, isso pode até ser mais rápido do que uma mesa cheia. O bit arith é barato e a tabela cabe facilmente em uma linha de cache.

deft_code
fonte
10
Essa é uma excelente forma de reduzir a complexidade da solução da mesa. +1
e.James,
3
Legal, mas irá causar uma falha no cache.
Johan Kotlinski
7
@kotlinski: o que causará uma falha no cache? Acho que a versão de mesa pequena pode ser mais eficiente em cache do que a grande. No meu Core2, uma linha de cache tem 64 bytes de largura, a tabela completa ocuparia várias linhas, enquanto a tabela menor cabe facilmente em uma única linha.
deft_code
4
@kotlinski: A localidade temporal é mais importante para acertos de cache ou estratégias de substituição do que a localidade do endereço
cfi
6
@Harshdeep: Considere os índices codificados em binários das entradas da tabela. índice b0000 (0) -> b0000 (0x0) chato; b0001(1) -> b1000(0x8), b0010(2) -> b0100(0x4), b1010(10) -> b0101(0x5). Veja o padrão? É simples o suficiente para que você possa calculá-lo em sua cabeça (se você pode ler binário, caso contrário, você precisará de papel para resolvê-lo). Quanto ao salto, reverter um número inteiro de 8 bits é o mesmo que reverter partes de 4 bits e depois trocá-las; Eu reivindico experiência e intuição (ou magia).
deft_code
46

Veja os pequenos truques de hacks para muitas soluções. Copiar de lá é obviamente simples de implementar. =)

Por exemplo (em uma CPU de 32 bits):

uint8_t b = byte_to_reverse;
b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;

Se por "simples de implementar" se entende algo que pode ser feito sem uma referência em um exame ou entrevista de emprego, então a aposta mais segura é provavelmente a cópia ineficiente de bits um a um em outra variável na ordem inversa (já mostrado em outras respostas )

Arkku
fonte
1
Do seu URL: CPU de 32 bits: b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
Josué,
1
@Joshua: Esse também é o meu favorito. A advertência (conforme declarado na página vinculada) é que ele precisa ser atribuído ou convertido em um uint8_t ou haverá lixo nos bits superiores.
Arkku
42

Como ninguém postou uma solução completa de pesquisa de tabela, aqui está a minha:

unsigned char reverse_byte(unsigned char x)
{
    static const unsigned char table[] = {
        0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
        0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
        0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
        0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
        0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
        0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
        0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
        0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
        0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
        0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
        0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
        0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
        0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
        0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
        0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
        0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
        0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
        0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
        0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
        0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
        0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
        0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
        0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
        0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
        0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
        0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
        0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
        0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
        0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
        0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
        0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
        0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
    };
    return table[x];
}
fredoverflow
fonte
2
Útil, obrigado. Parece que meu método de mudança mais lento estava limitando o desempenho em um aplicativo integrado. Tabela colocada na ROM em um PIC (com adição da palavra-chave rom).
flend
25
template <typename T>
T reverse(T n, size_t b = sizeof(T) * CHAR_BIT)
{
    assert(b <= std::numeric_limits<T>::digits);

    T rv = 0;

    for (size_t i = 0; i < b; ++i, n >>= 1) {
        rv = (rv << 1) | (n & 0x01);
    }

    return rv;
}

EDITAR:

Convertido em um modelo com o bitcount opcional

e e
fonte
@nvl - corrigido. Comecei a criá-lo como um modelo, mas decidi no meio do caminho não fazer isso ... muitos & gt & lt
andand
Para pedenatria extra, substitua sizeof(T)*8por sizeof(T)*CHAR_BITS.
Pillsy,
6
@andand Para pendant extra extra, substitua sizeof(T)*CHAR_BITpor std::numeric_limits<T>::digits(quase 4 anos de pedantismo depois).
Morwenn
1
Deveria ser CHAR_BIT, não CHAR_BITS.
Xunie
1
deve ser rv = (rv << 1) | (n & 0x01);
Vignesh
16

Duas linhas:

for(i=0;i<8;i++)
     reversed |= ((original>>i) & 0b1)<<(7-i);

ou caso você tenha problemas com a parte "0b1":

for(i=0;i<8;i++)
     reversed |= ((original>>i) & 1)<<(7-i);

"original" é o byte que você deseja reverter. "reverso" é o resultado, inicializado com 0.

Daniel
fonte
14

Embora provavelmente não seja portátil, eu usaria a linguagem assembly.
Muitas linguagens assembly têm instruções para girar um pouco na bandeira de carry e girar a bandeira de carry para a palavra (ou byte).

O algoritmo é:

for each bit in the data type:
  rotate bit into carry flag
  rotate carry flag into destination.
end-for

O código de linguagem de alto nível para isso é muito mais complicado, porque C e C ++ não suportam rotação para transportar e rotação para transporte. A bandeira de transporte deve ser modelada.

Editar: linguagem de montagem, por exemplo

;  Enter with value to reverse in R0.
;  Assume 8 bits per byte and byte is the native processor type.
   LODI, R2  8       ; Set up the bit counter
Loop:
   RRC, R0           ; Rotate R0 right into the carry bit.
   RLC, R1           ; Rotate R1 left, then append carry bit.
   DJNZ, R2  Loop    ; Decrement R2 and jump if non-zero to "loop"
   LODR, R0  R1      ; Move result into R0.
Thomas Matthews
fonte
7
Acho que essa resposta é o oposto de simples. Não portável, assembly e complexo o suficiente para ser escrito em pseudocódigo em vez do assembly real.
deft_code
3
É muito simples. Eu coloquei em pseudocódigo porque os mnemônicos de montagem são específicos para uma raça de processador e existem muitas raças por aí. Se desejar, posso editar isso para mostrar a linguagem assembly simples.
Thomas Matthews,
Pode-se ver se uma otimização do compilador simplifica em uma instrução de montagem adequada.
Sparky
12

Acho a solução a seguir mais simples do que os outros algoritmos de manipulação de bits que vi aqui.

unsigned char reverse_byte(char a)
{

  return ((a & 0x1)  << 7) | ((a & 0x2)  << 5) |
         ((a & 0x4)  << 3) | ((a & 0x8)  << 1) |
         ((a & 0x10) >> 1) | ((a & 0x20) >> 3) |
         ((a & 0x40) >> 5) | ((a & 0x80) >> 7);
}

Ele obtém cada bit do byte e o desloca de acordo, começando do primeiro ao último.

Explicação:

   ((a & 0x1) << 7) //get first bit on the right and shift it into the first left position 
 | ((a & 0x2) << 5) //add it to the second bit and shift it into the second left position
  //and so on
dau_sama
fonte
Lindo! Meu favorito até agora.
Nick Rameau
Certamente isso é simples, mas deve-se apontar que o tempo de execução é O (n) ao invés de O (log₂ n), onde n é o número de bits (8, 16, 32, 64, etc.).
Todd Lehman
10

A maneira mais simples é provavelmente iterar sobre as posições dos bits em um loop:

unsigned char reverse(unsigned char c) {
   int shift;
   unsigned char result = 0;
   for (shift = 0; shift < CHAR_BIT; shift++) {
      if (c & (0x01 << shift))
         result |= (0x80 >> shift);
   }
   return result;
}
sth
fonte
é CHAR_BIT, sem um 's'
ljrk
Por que usar CHAR_BITquando você assume charque tem 8 bits?
chqrlie
6

Você pode estar interessado em std::vector<bool>(que é um pacote de bits) estd::bitset

Deve ser o mais simples conforme solicitado.

#include <iostream>
#include <bitset>
using namespace std;
int main() {
  bitset<8> bs = 5;
  bitset<8> rev;
  for(int ii=0; ii!= bs.size(); ++ii)
    rev[bs.size()-ii-1] = bs[ii];
  cerr << bs << " " << rev << endl;
}

Outras opções podem ser mais rápidas.

EDIT: Devo-lhe uma solução usando std::vector<bool>

#include <algorithm>
#include <iterator>
#include <iostream>
#include <vector>
using namespace std;
int main() {
  vector<bool> b{0,0,0,0,0,1,0,1};
  reverse(b.begin(), b.end());
  copy(b.begin(), b.end(), ostream_iterator<int>(cerr));
  cerr << endl;
}

O segundo exemplo requer a extensão c ++ 0x (para inicializar a matriz com {...}). A vantagem de usar a bitsetou a std::vector<bool>(ou a boost::dynamic_bitset) é que você não está limitado a bytes ou palavras, mas pode reverter um número arbitrário de bits.

HTH

baol
fonte
Como o bitset é mais simples do que um pod aqui? Mostre o código ou não é.
wilhelmtell
Na verdade, acho que o código reverterá o bitset e, em seguida, reverterá de volta ao original. Alterar ii! = Tamanho (); para ii <tamanho () / 2; e fará um trabalho melhor =)
Viktor Sehr,
(@viktor-sehr não, não vai, rev é diferente de bs). De qualquer forma, não gosto da resposta: acho que este é um caso em que aritmética binária e operadores de deslocamento são mais adequados. Ainda é o mais simples de entender.
baol
Que tal std::vector<bool> b = { ... }; std::vector<bool> rb ( b.rbegin(), b.rend()); - usando iteradores reversos diretamente?
MSalters
@MSalters Eu gosto da imutabilidade disso.
baol 01 de
6

Para o caso muito limitado de entrada constante de 8 bits , este método não custa memória ou CPU em tempo de execução:

#define MSB2LSB(b) (((b)&1?128:0)|((b)&2?64:0)|((b)&4?32:0)|((b)&8?16:0)|((b)&16?8:0)|((b)&32?4:0)|((b)&64?2:0)|((b)&128?1:0))

Usei isso para ARINC-429, em que a ordem dos bits (endianidade) do rótulo é oposta ao resto da palavra. O rótulo costuma ser uma constante e, convencionalmente, em octal.

É assim que eu usei para definir uma constante, porque a especificação define esse rótulo como big-endian 205 octal.

#define LABEL_HF_COMM MSB2LSB(0205)

Mais exemplos:

assert(0b00000000 == MSB2LSB(0b00000000));
assert(0b10000000 == MSB2LSB(0b00000001));
assert(0b11000000 == MSB2LSB(0b00000011));
assert(0b11100000 == MSB2LSB(0b00000111));
assert(0b11110000 == MSB2LSB(0b00001111));
assert(0b11111000 == MSB2LSB(0b00011111));
assert(0b11111100 == MSB2LSB(0b00111111));
assert(0b11111110 == MSB2LSB(0b01111111));
assert(0b11111111 == MSB2LSB(0b11111111));
assert(0b10101010 == MSB2LSB(0b01010101));
Bob Stein
fonte
5

Existem muitas maneiras de reverter bits, dependendo do que você quer dizer, a "maneira mais simples".


Reverter por rotação

Provavelmente o mais lógico, consiste em girar o byte enquanto aplica uma máscara no primeiro bit (n & 1):

unsigned char reverse_bits(unsigned char b)
{
    unsigned char   r = 0;
    unsigned        byte_len = 8;

    while (byte_len--) {
        r = (r << 1) | (b & 1);
        b >>= 1;
    }
    return r;
}

1) Como o comprimento de um unsigner char é de 1 byte, que é igual a 8 bits, isso significa que iremos escanear cada bit while (byte_len--)

2) Primeiro verificamos se b está um pouco na extrema direita com (b & 1); se for assim, definimos o bit 1 em r com |e o movemos apenas 1 bit para a esquerda multiplicando r por 2 com(r << 1)

3) Em seguida, dividimos nosso unsigned char b por 2 com b >>=1para apagar o bit localizado na extrema direita da variável b. Como um lembrete, b >> = 1; é equivalente a b / = 2;


Reverse em uma linha

Esta solução é atribuída a Rich Schroeppel na seção Programming Hacks

unsigned char reverse_bits3(unsigned char b)
{
    return (b * 0x0202020202ULL & 0x010884422010ULL) % 0x3ff;
}

1) A operação de multiplicação (b * 0x0202020202ULL) cria cinco cópias separadas do padrão de bytes de 8 bits para se espalhar em um valor de 64 bits.

2) A operação AND (& 0x010884422010ULL) seleciona os bits que estão nas posições corretas (invertidas), em relação a cada grupo de bits de 10 bits.

3) Juntas, as operações de multiplicação e AND copiam os bits do byte original de forma que cada um apareça em apenas um dos conjuntos de 10 bits. As posições invertidas dos bits do byte original coincidem com suas posições relativas em qualquer conjunto de 10 bits.

4) A última etapa (% 0x3ff), que envolve a divisão do módulo por 2 ^ 10-1 tem o efeito de mesclar cada conjunto de 10 bits (das posições 0-9, 10-19, 20-29, ...) no valor de 64 bits. Eles não se sobrepõem, portanto, as etapas de adição subjacentes à divisão do módulo se comportam como operações OR.


Solução para dividir e conquistar

unsigned char reverse(unsigned char b) {
   b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
   b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
   b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
   return b;
}

Esta é a resposta mais votada e apesar de algumas explicações, acho que para a maioria das pessoas parece difícil visualizar o que 0xF0, 0xCC, 0xAA, 0x0F, 0x33 e 0x55 realmente significam.

Ele não tira proveito de '0b', que é uma extensão do GCC e está incluído desde o padrão C ++ 14, lançado em dezembro de 2014, portanto, algum tempo depois desta resposta datada de abril de 2010

As constantes inteiras podem ser escritas como constantes binárias, consistindo em uma sequência de dígitos '0' e '1', prefixadas por '0b' ou '0B'. Isso é particularmente útil em ambientes que operam muito no nível de bits (como microcontroladores).

Verifique os trechos de código abaixo para lembrar e compreender ainda melhor esta solução em que avançamos meio a meio:

unsigned char reverse(unsigned char b) {
   b = (b & 0b11110000) >> 4 | (b & 0b00001111) << 4;
   b = (b & 0b11001100) >> 2 | (b & 0b00110011) << 2;
   b = (b & 0b10101010) >> 1 | (b & 0b01010101) << 1;
   return b;
}

NB: O >> 4é porque existem 8 bits em 1 byte, que é um caractere unsigned, então queremos pegar a outra metade, e assim por diante.

Poderíamos facilmente aplicar esta solução a 4 bytes com apenas duas linhas adicionais e seguindo a mesma lógica. Como as duas máscaras se complementam, podemos até usar ~ para trocar bits e economizar tinta:

uint32_t reverse_integer_bits(uint32_t b) {
   uint32_t mask = 0b11111111111111110000000000000000;
   b = (b & mask) >> 16 | (b & ~mask) << 16;
   mask = 0b11111111000000001111111100000000;
   b = (b & mask) >> 8 | (b & ~mask) << 8;
   mask = 0b11110000111100001111000011110000;
   b = (b & mask) >> 4 | (b & ~mask) << 4;
   mask = 0b11001100110011001100110011001100;
   b = (b & mask) >> 2 | (b & ~mask) << 2;
   mask = 0b10101010101010101010101010101010;
   b = (b & mask) >> 1 | (b & ~mask) << 1;
   return b;
}

[Somente C ++] Reverter qualquer não assinado (modelo)

A lógica acima pode ser resumida com um loop que funcionaria em qualquer tipo de sem sinal:

template <class T>
T reverse_bits(T n) {
    short bits = sizeof(n) * 8; 
    T mask = ~T(0); // equivalent to uint32_t mask = 0b11111111111111111111111111111111;

    while (bits >>= 1) {
        mask ^= mask << (bits); // will convert mask to 0b00000000000000001111111111111111;
        n = (n & ~mask) >> bits | (n & mask) << bits; // divide and conquer
    }

    return n;
}

Experimente você mesmo com a inclusão da função acima:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

template <class T>
void print_binary(T n)
{   T mask = 1ULL << ((sizeof(n) * 8) - 1);  // will set the most significant bit
    for(; mask != 0; mask >>= 1) putchar('0' | !!(n & mask));
    putchar('\n');
}

int main() {
    uint32_t n = 12;
    print_binary(n);
    n = reverse_bits(n); 
    print_binary(n);
    unsigned char c = 'a';
    print_binary(c);
    c = reverse_bits(c);
    print_binary(c);
    uint16_t s = 12;
    print_binary(s);
    s = reverse_bits(s);
    print_binary(s);
    uint64_t l = 12;
    print_binary(l);
    l = reverse_bits(l);
    print_binary(l);
    return 0;
}

Reverter com asm volátil

Por último, mas não menos importante, se mais simples significa menos linhas, por que não experimentar a montagem embutida?

Você pode testar o snippet de código abaixo adicionando -masm=inteldurante a compilação:

unsigned char reverse_bits(unsigned char c) {
    __asm__ __volatile__ (R"(
        mov cx, 8       
    daloop:                   
        ror di          
        adc ax, ax      
        dec cx          
        jnz short daloop  
    ;)");
}

Explicações linha por linha:

        mov cx, 8       ; we will reverse the 8 bits contained in one byte
    daloop:             ; while loop
        shr di          ; Shift Register `di` (containing value of the first argument of callee function) to the Right
        rcl ax          ; Rotate Carry Left: rotate ax left and add the carry from shr di, the carry is equal to 1 if one bit was "lost" from previous operation 
        dec cl          ; Decrement cx
        jnz short daloop; Jump if cx register is Not equal to Zero, else end loop and return value contained in ax register
Antonin GAVREL
fonte
3

Consulta de tabela ou

uint8_t rev_byte(uint8_t x) {
    uint8_t y;
    uint8_t m = 1;
    while (m) {
       y >>= 1;
       if (m&x) {
          y |= 0x80;
       }
       m <<=1;
    }
    return y;
}

editar

Procure aqui outras soluções que podem funcionar melhor para você

nategoose
fonte
3

uma implementação mais lenta, porém mais simples:

static int swap_bit(unsigned char unit)
{
    /*
     * swap bit[7] and bit[0]
     */
    unit = (((((unit & 0x80) >> 7) ^ (unit & 0x01)) << 7) | (unit & 0x7f));
    unit = (((((unit & 0x80) >> 7) ^ (unit & 0x01))) | (unit & 0xfe));
    unit = (((((unit & 0x80) >> 7) ^ (unit & 0x01)) << 7) | (unit & 0x7f));

    /*
     * swap bit[6] and bit[1]
     */
    unit = (((((unit & 0x40) >> 5) ^ (unit & 0x02)) << 5) | (unit & 0xbf));
    unit = (((((unit & 0x40) >> 5) ^ (unit & 0x02))) | (unit & 0xfd));
    unit = (((((unit & 0x40) >> 5) ^ (unit & 0x02)) << 5) | (unit & 0xbf));

    /*
     * swap bit[5] and bit[2]
     */
    unit = (((((unit & 0x20) >> 3) ^ (unit & 0x04)) << 3) | (unit & 0xdf));
    unit = (((((unit & 0x20) >> 3) ^ (unit & 0x04))) | (unit & 0xfb));
    unit = (((((unit & 0x20) >> 3) ^ (unit & 0x04)) << 3) | (unit & 0xdf));

    /*
     * swap bit[4] and bit[3]
     */
    unit = (((((unit & 0x10) >> 1) ^ (unit & 0x08)) << 1) | (unit & 0xef));
    unit = (((((unit & 0x10) >> 1) ^ (unit & 0x08))) | (unit & 0xf7));
    unit = (((((unit & 0x10) >> 1) ^ (unit & 0x08)) << 1) | (unit & 0xef));

    return unit;
}
Wenlujon
fonte
3

Essa pode ser uma solução rápida?

int byte_to_be_reversed = 
    ((byte_to_be_reversed>>7)&0x01)|((byte_to_be_reversed>>5)&0x02)|      
    ((byte_to_be_reversed>>3)&0x04)|((byte_to_be_reversed>>1)&0x08)| 
    ((byte_to_be_reversed<<7)&0x80)|((byte_to_be_reversed<<5)&0x40)|
    ((byte_to_be_reversed<<3)&0x20)|((byte_to_be_reversed<<1)&0x10);

Livre-se da confusão de usar um loop for! mas os especialistas, por favor, me digam se isso é eficiente e rápido?

Jamboree
fonte
O tempo de execução é O (n) ao invés de O (log₂ n), onde n é o número de bits (8, 16, 32, 64, etc.). Veja em outro lugar as respostas que são executadas em tempo O (log₂ n).
Todd Lehman
2

Antes de implementar qualquer solução algorítmica, verifique a linguagem assembly para qualquer arquitetura de CPU que você está usando. Sua arquitetura pode incluir instruções que tratam de manipulações bit a bit como esta (e o que poderia ser mais simples do que uma única instrução de montagem?).

Se tal instrução não estiver disponível, então eu sugeriria ir com a rota da tabela de pesquisa. Você pode escrever um script / programa para gerar a tabela para você, e as operações de pesquisa seriam mais rápidas do que qualquer um dos algoritmos de reversão de bits aqui (ao custo de ter que armazenar a tabela de pesquisa em algum lugar).

bta
fonte
2

Esta função simples usa uma máscara para testar cada bit no byte de entrada e transferi-lo para uma saída de deslocamento:

char Reverse_Bits(char input)
{    
    char output = 0;

    for (unsigned char mask = 1; mask > 0; mask <<= 1)
    {
        output <<= 1;

        if (input & mask)
            output |= 1;
    }

    return output;
}
luci88filter
fonte
A máscara não deve ser assinada, desculpe.
luci88filter
1

Este é baseado em um BobStein-VisiBone fornecido

#define reverse_1byte(b)    ( ((uint8_t)b & 0b00000001) ? 0b10000000 : 0 ) | \
                            ( ((uint8_t)b & 0b00000010) ? 0b01000000 : 0 ) | \
                            ( ((uint8_t)b & 0b00000100) ? 0b00100000 : 0 ) | \
                            ( ((uint8_t)b & 0b00001000) ? 0b00010000 : 0 ) | \
                            ( ((uint8_t)b & 0b00010000) ? 0b00001000 : 0 ) | \
                            ( ((uint8_t)b & 0b00100000) ? 0b00000100 : 0 ) | \
                            ( ((uint8_t)b & 0b01000000) ? 0b00000010 : 0 ) | \
                            ( ((uint8_t)b & 0b10000000) ? 0b00000001 : 0 ) 

Eu realmente gosto muito deste porque o compilador automaticamente lida com o trabalho para você, portanto, não requer mais recursos.

isso também pode ser estendido para 16 bits ...

#define reverse_2byte(b)    ( ((uint16_t)b & 0b0000000000000001) ? 0b1000000000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000000010) ? 0b0100000000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000000100) ? 0b0010000000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000001000) ? 0b0001000000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000010000) ? 0b0000100000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000000100000) ? 0b0000010000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000001000000) ? 0b0000001000000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000010000000) ? 0b0000000100000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000000100000000) ? 0b0000000010000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000001000000000) ? 0b0000000001000000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000010000000000) ? 0b0000000000100000 : 0 ) | \
                            ( ((uint16_t)b & 0b0000100000000000) ? 0b0000000000010000 : 0 ) | \
                            ( ((uint16_t)b & 0b0001000000000000) ? 0b0000000000001000 : 0 ) | \
                            ( ((uint16_t)b & 0b0010000000000000) ? 0b0000000000000100 : 0 ) | \
                            ( ((uint16_t)b & 0b0100000000000000) ? 0b0000000000000010 : 0 ) | \
                            ( ((uint16_t)b & 0b1000000000000000) ? 0b0000000000000001 : 0 ) 
Natthapol Vanasrivilai
fonte
Eu colocaria o bentre parênteses no caso de ser uma expressão mais complexa do que um único número, e talvez também renomeie a macro REVERSE_BYTEcomo uma dica de que você provavelmente não deseja ter uma expressão mais complexa (tempo de execução) lá. Ou torne-a uma função embutida. (Mas no geral eu gosto disso por ser simples o suficiente para que você possa fazer isso facilmente de memória com muito pouca chance de erro.)
Arkku
1

Supondo que seu compilador permite não assinado por muito tempo :

unsigned char reverse(unsigned char b) {
  return (b * 0x0202020202ULL & 0x010884422010ULL) % 1023;
}

Descoberto aqui

mascIT
fonte
1

Se você usa um microcontrolador pequeno e precisa de uma solução de alta velocidade com pegada pequena, esta pode ser uma solução. É possível usá-lo para projeto C, mas você precisa adicionar este arquivo como um arquivo assembler * .asm, ao seu projeto C. Instruções: No projeto C, adicione esta declaração:

extern uint8_t byte_mirror(uint8_t);

Chame esta função de C

byteOutput= byte_mirror(byteInput);

Este é o código, só é adequado para 8051 core. No registro da CPU, r0 são dados de byteInput . O código gira para a direita r0 cross carry e, em seguida, gire o carry para a esquerda para r1 . Repita este procedimento 8 vezes, para cada bit. Em seguida, o registrador r1 é retornado para a função c como byteOutput. Em 8051 o núcleo só é possível girar o acumulador a .

NAME     BYTE_MIRROR
RSEG     RCODE
PUBLIC   byte_mirror              //8051 core        

byte_mirror
    mov r3,#8;
loop:   
    mov a,r0;
    rrc a;
    mov r0,a;    
    mov a,r1;
    rlc a;   
    mov r1,a;
    djnz r3,loop
    mov r0,a
    ret

PRÓS: é pequeno, é de alta velocidade. CONTRAS: Não é um código reutilizável, é apenas para 8051

011101101-> carry

101101110 <-carry

Josko Marsic
fonte
Embora este código possa responder à pergunta, seria melhor incluir algum contexto, explicando como funciona e quando usá-lo. Respostas apenas em código não são úteis a longo prazo.
fNek
0
  xor ax,ax
  xor bx,bx
  mov cx,8
  mov al,original_byte!
cycle:   shr al,1
  jnc not_inc
  inc bl
not_inc: test cx,cx
  jz,end_cycle
  shl bl,1
  loop cycle
end_cycle:

o byte invertido estará no registro bl

asm_fan
fonte
3
Em outro contexto, essa pode ser uma resposta justa, mas a pergunta era sobre C ou C ++, não asm ...
jadsq
0
typedef struct
{
    uint8_t b0:1;
    uint8_t b1:1;
    uint8_t b2:1;
    uint8_t b3:1;
    uint8_t b4:1;
    uint8_t b5:1;
    uint8_t b6:1;
    uint8_t b7:1;
} bits_t;

uint8_t reverse_bits(uint8_t src)
{
    uint8_t dst = 0x0;
    bits_t *src_bits = (bits_t *)&src;
    bits_t *dst_bits = (bits_t *)&dst;

    dst_bits->b0 = src_bits->b7;
    dst_bits->b1 = src_bits->b6;
    dst_bits->b2 = src_bits->b5;
    dst_bits->b3 = src_bits->b4;
    dst_bits->b4 = src_bits->b3;
    dst_bits->b5 = src_bits->b2;
    dst_bits->b6 = src_bits->b1;
    dst_bits->b7 = src_bits->b0;

    return dst;
}
Tai-Yuan Fang
fonte
Como uma nota estilística, acho o uso de uint8_tpara os campos de 1 bit um pouco feio, já que parece primeiro dizer que levará 8 bits, mas no final da linha o define como apenas um único bit. Eu usaria unsigned b0:1etc.
Arkku
0
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i;
    unsigned char rev = 0x70 ; // 0b01110000
    unsigned char tmp = 0;

    for(i=0;i<8;i++)
    {
    tmp |= ( ((rev & (1<<i))?1:0) << (7-i));
    }
    rev = tmp;

    printf("%x", rev);       //0b00001110 binary value of given number
    return 0;
}
AHMED ANWAR
fonte
Adicione alguma explicação.
zcui93,
0

Eu acho que isso é bastante simples

uint8_t reverse(uint8_t a)
{
  unsigned w = ((a << 7) & 0x0880) | ((a << 5) & 0x0440) | ((a << 3) & 0x0220) | ((a << 1) & 0x0110);
  return static_cast<uint8_t>(w | (w>>8));
}

ou

uint8_t reverse(uint8_t a)
{
  unsigned w = ((a & 0x11) << 7) | ((a & 0x22) << 5) | ((a & 0x44) << 3) | ((a & 0x88) << 1);
  return static_cast<uint8_t>(w | (w>>8));
}
agbinfo
fonte
0
unsigned char c ; // the original
unsigned char u = // the reversed
c>>7&0b00000001 |
c<<7&0b10000000 |
c>>5&0b00000010 |
c<<5&0b01000000 |
c>>3&0b00000100 |
c<<3&0b00100000 |
c>>1&0b00001000 |
c<<1&0b00010000 ;

Explanation: exchanged bits as per the arrows below.
01234567
<------>
#<---->#
##<-->##
###<>###
Mahen
fonte
0

Vou adicionar minha solução, já que não consigo encontrar nada parecido com isso nas respostas até agora. Talvez seja um pouco sobrecarregado, mas gera a tabela de pesquisa usando C ++ 14 std::index_sequenceem tempo de compilação.

#include <array>
#include <utility>

constexpr unsigned long reverse(uint8_t value) {
    uint8_t result = 0;
    for (std::size_t i = 0, j = 7; i < 8; ++i, --j) {
        result |= ((value & (1 << j)) >> j) << i;
    }
    return result;
}

template<size_t... I>
constexpr auto make_lookup_table(std::index_sequence<I...>)
{
    return std::array<uint8_t, sizeof...(I)>{reverse(I)...};   
}

template<typename Indices = std::make_index_sequence<256>>
constexpr auto bit_reverse_lookup_table()
{
    return make_lookup_table(Indices{});
}

constexpr auto lookup = bit_reverse_lookup_table();

int main(int argc)
{
    return lookup[argc];
}

https://godbolt.org/z/cSuWhF

K. Kirsz
fonte
0

Aqui está uma solução simples e legível, portátil para todas as plataformas em conformidade, incluindo aquelas com sizeof(char) == sizeof(int):

#include <limits.h>

unsigned char reverse(unsigned char c) {
    int shift;
    unsigned char result = 0;

    for (shift = 0; shift < CHAR_BIT; shift++) {
        result <<= 1;
        result |= c & 1;
        c >>= 1;
    }
    return result;
}
chqrlie
fonte
0

Sei que esta pergunta está desatualizada, mas ainda acho que o assunto é relevante para alguns fins, e aqui está uma versão que funciona muito bem e é legível. Não posso dizer que seja o mais rápido ou o mais eficiente, mas deve ser um dos mais limpos. Também incluí uma função auxiliar para exibir facilmente os padrões de bits. Esta função usa algumas das funções de biblioteca padrão em vez de escrever seu próprio manipulador de bits.

#include <algorithm>
#include <bitset>
#include <exception>
#include <iostream>
#include <limits>
#include <string>

// helper lambda function template
template<typename T>
auto getBits = [](T value) {
    return std::bitset<sizeof(T) * CHAR_BIT>{value};
};

// Function template to flip the bits
// This will work on integral types such as int, unsigned int,
// std::uint8_t, 16_t etc. I did not test this with floating
// point types. I chose to use the `bitset` here to convert
// from T to string as I find it easier to use than some of the
// string to type or type to string conversion functions,
// especially when the bitset has a function to return a string. 
template<typename T>
T reverseBits(T& value) {
    static constexpr std::uint16_t bit_count = sizeof(T) * CHAR_BIT;

    // Do not use the helper function in this function!
    auto bits = std::bitset<bit_count>{value};
    auto str = bits.to_string();
    std::reverse(str.begin(), str.end());
    bits = std::bitset<bit_count>(str);
    return static_cast<T>( bits.to_ullong() );
}

// main program
int main() {
    try {
        std::uint8_t value = 0xE0; // 1110 0000;
        std::cout << +value << '\n'; // don't forget to promote unsigned char
        // Here is where I use the helper function to display the bit pattern
        auto bits = getBits<std::uint8_t>(value);
        std::cout << bits.to_string() << '\n';

        value = reverseBits(value);
        std::cout << +value << '\n'; // + for integer promotion

        // using helper function again...
        bits = getBits<std::uint8_t>(value);
        std::cout << bits.to_string() << '\n';

    } catch(const std::exception& e) {  
        std::cerr << e.what();
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

E dá a seguinte saída.

224
11100000
7
00000111
Francis Cugler
fonte
0

Este me ajudou com o conjunto de matrizes de 8x8 pontos.

uint8_t mirror_bits(uint8_t var)
{
    uint8_t temp = 0;
    if ((var & 0x01))temp |= 0x80;
    if ((var & 0x02))temp |= 0x40;
    if ((var & 0x04))temp |= 0x20;
    if ((var & 0x08))temp |= 0x10;

    if ((var & 0x10))temp |= 0x08;
    if ((var & 0x20))temp |= 0x04;
    if ((var & 0x40))temp |= 0x02;
    if ((var & 0x80))temp |= 0x01;

    return temp;
}
R1S8K
fonte
1
Esta função não funciona realmente, o reverso de 0b11001111 deveria ser 0b11110011, mas falha com esta função. O mesmo método de teste funciona para muitas das outras funções listadas aqui.
Dan
Sim, obrigado, corrigi minha resposta. Obrigado por me avisar sobre meu erro :)
R1S8K