In C / C ++ qual è il modo più semplice per invertire l'ordine dei bit in un byte?


110

Sebbene ci siano diversi modi per invertire l'ordine dei bit in un byte, sono curioso di sapere quale sia il "più semplice" da implementare per uno sviluppatore. E invertendo intendo:

1110 -> 0111
0010 -> 0100

È simile, ma non è un duplicato, di questa domanda PHP.

Questo è simile, ma non un duplicato, di questa domanda C. Questa domanda richiede il metodo più semplice da implementare da uno sviluppatore. Il "miglior algoritmo" riguarda la memoria e le prestazioni della CPU.


Usa assemblaggio in linea. Meglio, metti la funzione in un'unità di traduzione separata. Avere un modulo in linguaggio assembly per ogni piattaforma di destinazione. Lascia che il processo di compilazione scelga i moduli.
Thomas Matthews

@Andreas Implementazione più semplice
nathan

Risposte:


102

Se stai parlando di un singolo byte, una ricerca in una tabella è probabilmente la soluzione migliore, a meno che per qualche ragione tu non abbia 256 byte disponibili.


12
Se stiamo parlando di qualcosa che è semplice da implementare senza copiare una soluzione già pronta, la creazione della tabella di ricerca richiede comunque un'altra soluzione. (Ovviamente si potrebbe farlo a mano, ma è soggetto a errori e richiede tempo ...)
Arkku

7
Puoi comprimere l'array in un po 'meno di 256 byte se ignori i palindromi.
Wilhelmtell

8
@wilhelmtell - avresti bisogno di un tavolo per sapere quali sono i palindromi.
Mark Ransom

6
@wilhelmtell: Beh, per scrivere lo script è ancora necessaria un'altra soluzione, che era il mio punto: una tabella di ricerca è semplice da usare ma non semplice da creare. (Tranne copiando una tabella di ricerca già pronta, ma allora si potrebbe altrettanto bene copiare qualsiasi soluzione.) Ad esempio, se la soluzione "più semplice" è considerata quella che potrebbe essere scritta su carta in un esame o in un colloquio, non lo farei iniziare a creare manualmente una tabella di ricerca e fare in modo che il programma lo faccia includerebbe già una soluzione diversa (che da sola sarebbe più semplice di quella che include sia essa che la tabella).
Arkku

4
@Arkku quello che volevo dire è scrivere uno script che restituisca la tabella dei primi 256 byte e la loro mappatura inversa. Sì, sei tornato a scrivere la funzione inversa, ma ora nel tuo linguaggio di scripting preferito, e può essere sgradevole quanto vuoi: lo getterai via non appena sarà finito e lo eseguirai una volta. Avere l'uscita dello script come codice C, anche: unsigned int rtable[] = {0x800, 0x4000, ...};. Poi getta via il copione e dimentica di averlo mai avuto. È molto più veloce da scrivere rispetto al codice C ++ equivalente e verrà eseguito solo una volta, quindi ottieni il runtime O (1) nel tuo codice C ++.
Wilhelmtell

227

Questo dovrebbe funzionare:

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

Per prima cosa i quattro bit di sinistra vengono scambiati con i quattro bit di destra. Quindi tutte le coppie adiacenti vengono scambiate e quindi tutti i singoli bit adiacenti. Ciò si traduce in un ordine inverso.


26
Ragionevolmente breve e veloce, ma non semplice.
Mark Ransom

3
Questo approccio generalizza anche chiaramente per eseguire lo scambio di byte per endianness.
Boojum

2
Non è l'approccio più semplice, ma mi piace +1.
nathan

7
Sì, è semplice. È una specie di algoritmo divide et impera. Eccellente!
kiewic

È più veloce del metodo suggerito da @Arkku di seguito?
qed

123

Penso che una tabella di ricerca debba essere uno dei metodi più semplici. Tuttavia, non è necessaria una tabella di ricerca 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]

Questo è abbastanza semplice da codificare e verificare visivamente.
Alla fine questo potrebbe anche essere più veloce di una tabella completa. Il bit arith è economico e la tabella si adatta facilmente a una linea cache.


10
Questo è un modo eccellente per ridurre la complessità della soluzione table. +1
James

3
Bello, ma ti darà una cache miss.
Johan Kotlinski

7
@kotlinski: cosa causerà un errore nella cache? Penso che la versione da tavolo piccolo possa essere più efficiente nella cache di quella grande. Sul mio Core2 una riga della cache è larga 64 byte, la tabella completa si estenderebbe su più righe, mentre la tabella più piccola si adatta facilmente a una singola riga.
deft_code

4
@kotlinski: la località temporale è più importante per i riscontri nella cache o le strategie di sostituzione rispetto alla località dell'indirizzo
cfi

6
@Harshdeep: considera gli indici con codifica binaria delle voci della tabella. indice b0000 (0) -> b0000 (0x0) noioso; b0001(1) -> b1000(0x8), b0010(2) -> b0100(0x4), b1010(10) -> b0101(0x5). Vedi lo schema? È abbastanza semplice da poterlo calcolare nella tua testa (se sai leggere il binario, altrimenti avrai bisogno di carta per elaborarlo). Per quanto riguarda il salto, invertire un intero a 8 bit equivale a invertire parti a 4 bit e poi scambiarle; Affermo esperienza e intuizione (o magia).
deft_code

46

Guarda i trucchi per trovare molte soluzioni. Il copypasting da lì è ovviamente semplice da implementare. =)

Ad esempio (su una CPU a 32 bit):

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

Se per "semplice da implementare" si intende qualcosa che può essere fatto senza un riferimento in un esame o in un colloquio di lavoro, allora la scommessa più sicura è probabilmente l'inefficiente copia dei bit uno per uno in un'altra variabile in ordine inverso (già mostrato in altre risposte ).


1
Dal tuo URL: CPU a 32 bit: b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
Joshua

1
@ Joshua: Questo è anche il mio preferito. L'avvertenza (come indicato nella pagina collegata) è che deve essere assegnato o lanciato in un uint8_t o ci sarà spazzatura nei bit superiori.
Arkku

42

Dal momento che nessuno ha pubblicato una soluzione completa per la ricerca di tabelle, ecco la mia:

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];
}

2
Utile, grazie. Sembra che il mio metodo di cambio più lento stesse limitando le prestazioni in un'app incorporata. Tabella collocata nella ROM su un PIC (con aggiunta della parola chiave 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;
}

MODIFICARE:

Convertito in un modello con il bitcount opzionale


@nvl - risolto. Ho iniziato a costruire come modello, ma ha deciso a metà di non fare così ... troppi & gt & lt
andand

Per pedenatria extra, sostituire sizeof(T)*8con sizeof(T)*CHAR_BITS.
Pillsy

6
@andand Per un'ulteriore pendenza extra, sostituire sizeof(T)*CHAR_BITda std::numeric_limits<T>::digits(quasi 4 anni di pedanteria dopo).
Morwenn

1
Dovrebbe essere CHAR_BIT, no CHAR_BITS.
Xunie

1
dovrebbe essere rv = (rv << 1) | (n & 0x01);
Vignesh

16

Due linee:

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

o in caso di problemi con la parte "0b1":

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

"originale" è il byte che vuoi invertire. "invertito" è il risultato, inizializzato a 0.


14

Sebbene probabilmente non sia portatile, userei il linguaggio assembly.
Molti linguaggi assembly hanno istruzioni per ruotare un po 'nel carry flag e per ruotare il carry flag nella parola (o byte).

L'algoritmo è:

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

Il codice del linguaggio di alto livello per questo è molto più complicato, perché C e C ++ non supportano la rotazione per trasportare e la rotazione dal trasporto. Il carry flag deve essere modellato.

Modifica: linguaggio Assembly per esempio

;  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.

7
Penso che questa risposta sia l'opposto di semplice. Non portabile, assembly e abbastanza complesso da essere scritto in pseudo-codice invece che nell'assembly vero e proprio.
deft_code

3
È abbastanza semplice. L'ho inserito in pseudo-codice perché i mnemonici di assemblaggio sono specifici per una razza di processori e ci sono molte razze là fuori. Se lo desideri, posso modificarlo per mostrare il semplice linguaggio assembly.
Thomas Matthews

Si potrebbe vedere se l'ottimizzazione di un compilatore si semplifica in un'istruzione di assembly adatta.
Sparky

12

Trovo la seguente soluzione più semplice rispetto agli altri algoritmi di manipolazione che ho visto qui.

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

Ottiene ogni bit nel byte e lo sposta di conseguenza, a partire dal primo all'ultimo.

Spiegazione:

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

Bellissimo! Il mio preferito finora.
Nick Rameau

Questo è certamente semplice, ma è bene sottolineare che il tempo di esecuzione è O (n) anziché O (log₂ n), dove n è il numero di bit (8, 16, 32, 64, ecc.).
Todd Lehman

10

Il modo più semplice è probabilmente quello di iterare sulle posizioni dei bit in un ciclo:

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

è CHAR_BIT, senza una 's'
ljrk

Perché usarlo CHAR_BITquando presumi chardi avere 8 bit?
chqrlie

6

Potresti essere interessato a std::vector<bool>(che è pieno di bit) estd::bitset

Dovrebbe essere il più semplice come richiesto.

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

Altre opzioni potrebbero essere più veloci.

EDIT: ti devo una soluzione 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;
}

Il secondo esempio richiede l'estensione c ++ 0x (per inizializzare l'array con {...}). Il vantaggio di utilizzare a bitseto a std::vector<bool>(o aboost::dynamic_bitset ) è che non sei limitato a byte o parole, ma puoi invertire un numero arbitrario di bit.

HTH


In che modo Bitset è più semplice di un pod qui? Mostra il codice o non lo è.
Wilhelmtell

In realtà, penso che il codice invertirà il set di bit e poi lo ripristinerà al suo originale. Cambia ii! = Size (); a ii <size () / 2; e farà un lavoro migliore =)
Viktor Sehr

(@ viktor-sehr no, non lo farà, rev è diverso da bs). Comunque non mi piace la risposta io stesso: penso che questo sia un caso in cui l'aritmetica binaria e gli operatori di scorrimento sono più adatti. Rimane ancora il più semplice da capire.
Baol

Che ne dici di std::vector<bool> b = { ... }; std::vector<bool> rb ( b.rbegin(), b.rend()); usare direttamente gli iteratori inversi?
MSalters

@MSalters Mi piace l'immutabilità di esso.
Baol

6

Per il caso molto limitato di input costante a 8 bit , questo metodo non costa memoria o CPU in fase di esecuzione:

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

L'ho usato per ARINC-429 dove l'ordine dei bit (endianness) dell'etichetta è opposto al resto della parola. L'etichetta è spesso una costante e convenzionalmente in ottale.

Ecco come l'ho usato per definire una costante, perché la specifica definisce questa etichetta come big-endian 205 ottale.

#define LABEL_HF_COMM MSB2LSB(0205)

Altri esempi:

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

5

Ci sono molti modi per invertire i bit a seconda di cosa intendi per "modo più semplice".


Inverti per rotazione

Probabilmente il più logico, consiste nel ruotare il byte applicando una maschera sul primo 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) Poiché la lunghezza di un carattere non firmato è 1 byte, che è uguale a 8 bit, significa che scansioneremo ogni bit while (byte_len--)

2) Per prima cosa controlliamo se b come un po 'all'estrema destra con (b & 1); se è così impostiamo il bit 1 su r con| e lo spostiamo solo di 1 bit a sinistra moltiplicando r per 2 con(r << 1)

3) Quindi dividiamo il nostro carattere senza segno b per 2 con b >>=1per cancellare il bit situato all'estrema destra della variabile b. Come promemoria, b >> = 1; è equivalente a b / = 2;


Inverti in una riga

Questa soluzione è attribuita a Rich Schroeppel nella sezione Programming Hacks

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

1) L'operazione di moltiplicazione (b * 0x0202020202ULL) crea cinque copie separate del modello di byte a 8 bit da creare a ventaglio in un valore a 64 bit.

2) L'operazione AND (& 0x010884422010ULL) seleziona i bit che si trovano nelle posizioni corrette (invertite), rispetto a ciascun gruppo di bit a 10 bit.

3) Insieme, le operazioni di moltiplicazione e AND copiano i bit dal byte originale in modo che appaiano ciascuna in uno solo dei set di 10 bit. Le posizioni invertite dei bit dal byte originale coincidono con le loro posizioni relative all'interno di qualsiasi set di 10 bit.

4) L'ultimo passaggio (% 0x3ff), che prevede la divisione del modulo per 2 ^ 10 - 1 ha l'effetto di fondere insieme ogni set di 10 bit (dalle posizioni 0-9, 10-19, 20-29, ...) nel valore a 64 bit. Non si sovrappongono, quindi i passaggi di addizione alla base della divisione del modulo si comportano come operazioni OR.


Divide and Conquer Solution

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

Questa è la risposta più votata e nonostante alcune spiegazioni, penso che per la maggior parte delle persone sia difficile visualizzare cosa significano veramente 0xF0, 0xCC, 0xAA, 0x0F, 0x33 e 0x55.

Non sfrutta "0b" che è un'estensione GCC ed è inclusa dallo standard C ++ 14, rilasciato a dicembre 2014, quindi un po 'di tempo dopo questa risposta che risale ad aprile 2010

Le costanti intere possono essere scritte come costanti binarie, costituite da una sequenza di cifre "0" e "1", precedute da "0b" o "0B". Ciò è particolarmente utile in ambienti che operano molto a livello di bit (come i microcontrollori).

Si prega di controllare i frammenti di codice di seguito per ricordare e comprendere ancora meglio questa soluzione in cui ci spostiamo metà per metà:

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: Il >> 4motivo è che ci sono 8 bit in 1 byte, che è un carattere senza segno quindi vogliamo prendere l'altra metà, e così via.

Potremmo facilmente applicare questa soluzione a 4 byte con solo due righe aggiuntive e seguendo la stessa logica. Poiché entrambe le maschere si completano a vicenda, possiamo persino usare ~ per cambiare bit e risparmiare inchiostro:

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

[Solo C ++] Inverti qualsiasi non firmato (modello)

La logica di cui sopra può essere riassunta con un ciclo che funzionerebbe su qualsiasi tipo di non firmato:

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

Prova tu stesso con l'inclusione della funzione sopra:

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

Reverse con asm volatile

Ultimo ma non meno importante, se più semplice significa meno righe, perché non provare l'assemblaggio in linea?

Puoi testare lo snippet di codice di seguito aggiungendo -masm=inteldurante la compilazione:

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

Spiegazioni riga per riga:

        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

3

Ricerca tabella o

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

modificare

Cerca qui altre soluzioni che potrebbero funzionare meglio per te


3

un'implementazione più lenta ma più semplice:

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

3

Può essere una soluzione rapida?

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

Elimina il trambusto dell'utilizzo di un ciclo for! ma esperti per favore mi dicono se questo è efficiente e più veloce?


Il tempo di esecuzione è O (n) anziché O (log₂ n), dove n è il numero di bit (8, 16, 32, 64, ecc.). Vedi altrove per le risposte che vengono eseguite in tempo O (log₂ n).
Todd Lehman

2

Prima di implementare qualsiasi soluzione algoritmica, controlla il linguaggio assembly per qualsiasi architettura della CPU che stai utilizzando. La tua architettura può includere istruzioni che gestiscono manipolazioni bit per bit come questa (e cosa potrebbe essere più semplice di una singola istruzione di assemblaggio?).

Se tale istruzione non è disponibile, suggerirei di seguire il percorso della tabella di ricerca. Puoi scrivere uno script / programma per generare la tabella per te e le operazioni di ricerca sarebbero più veloci di qualsiasi algoritmo di inversione di bit qui (a costo di dover memorizzare la tabella di ricerca da qualche parte).


2

Questa semplice funzione utilizza una maschera per testare ogni bit nel byte di ingresso e trasferirlo in un'uscita mobile:

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

La maschera dovrebbe essere non firmata mi dispiace.
luci88filter

1

Questo è basato su quello BobStein-Visibone fornito

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

Questo mi piace molto perché il compilatore gestisce automaticamente il lavoro per te, quindi non richiede ulteriori risorse.

questo può anche essere esteso a 16 bit ...

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

Metterei btra parentesi nel caso in cui si tratti di un'espressione più complessa di un singolo numero, e forse rinominerei anche la macro in REVERSE_BYTEun suggerimento che probabilmente non vuoi avere un'espressione più complessa (runtime) lì. Oppure rendila una funzione inline. (Ma nel complesso mi piace che sia abbastanza semplice da poterlo fare a memoria facilmente con pochissime possibilità di errore.)
Arkku

1

Supponendo che il tuo compilatore consenta unsigned long long :

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

Scoperto qui


1

Se utilizzi un microcontrollore piccolo e hai bisogno di una soluzione ad alta velocità con un ingombro ridotto, queste potrebbero essere le soluzioni. È possibile usarlo per il progetto C, ma è necessario aggiungere questo file come file assembler * .asm, al progetto C. Istruzioni: Nel progetto C aggiungere questa dichiarazione:

extern uint8_t byte_mirror(uint8_t);

Chiama questa funzione da C

byteOutput= byte_mirror(byteInput);

Questo è il codice, è adatto solo per il core 8051. Nel registro della CPU r0 sono i dati da byteInput . Codice ruota a destra r0 cross carry e poi ruota a sinistra r1 . Ripeti questa procedura 8 volte, per ogni bit. Quindi il registro r1 viene restituito alla funzione c come byteOutput. In 8051 core è possibile ruotare solo un accumulatore .

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

PRO: Ha un ingombro ridotto, è ad alta velocità CONTRO: Non è un codice riutilizzabile, è solo per 8051

011101101-> carry

101101110 <-eseguire


Sebbene questo codice possa rispondere alla domanda, sarebbe meglio includere un contesto, spiegando come funziona e quando usarlo. Le risposte di solo codice non sono utili a lungo termine.
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:

il byte invertito sarà nel registro bl


3
In un altro contesto, questa potrebbe essere una risposta equa, ma la domanda riguardava C o C ++, non 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;
}

Come nota stilistica, trovo che l'uso di uint8_tper i campi a 1 bit sia un po 'brutto, dato che sembra dire prima che ci vorranno 8 bit ma poi alla fine della riga lo definisce come un solo bit. Vorrei usare unsigned b0:1ecc
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;
}

Per favore aggiungi qualche spiegazione.
zcui93

0

Penso che sia abbastanza semplice

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

o

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

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

0

Aggiungerò la mia soluzione, dal momento che non riesco a trovare nulla di simile nelle risposte finora. Forse è un po 'troppo ingegnerizzato, ma genera la tabella di ricerca usando C ++ 14 std::index_sequencein fase di compilazione.

#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


0

Ecco una soluzione semplice e leggibile, portabile su tutte le piattaforme conformi, comprese quelle con 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;
}

0

So che questa domanda è datata ma penso ancora che l'argomento sia rilevante per alcuni scopi, ed ecco una versione che funziona molto bene ed è leggibile. Non posso dire che sia il più veloce o il più efficiente, ma dovrebbe essere uno dei più puliti. Ho anche incluso una funzione di supporto per visualizzare facilmente i modelli di bit. Questa funzione utilizza alcune delle funzioni di libreria standard invece di scrivere il proprio manipolatore di bit.

#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 fornisce il seguente output.

224
11100000
7
00000111

0

Questo mi ha aiutato con un set di array a matrice di punti 8x8.

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

1
Questa funzione in realtà non funziona, il contrario di 0b11001111 dovrebbe essere 0b11110011, ma non funziona con questa funzione. Lo stesso metodo di test funziona per molte delle altre funzioni elencate qui.
Dan

Sì, grazie ho corretto la mia risposta. Grazie per avermi informato del mio errore :)
R1S8K
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.