Was ist in C / C ++ der einfachste Weg, die Reihenfolge der Bits in einem Byte umzukehren?


110

Obwohl es mehrere Möglichkeiten gibt, die Bitreihenfolge in einem Byte umzukehren, bin ich gespannt, was für einen Entwickler am "einfachsten" zu implementieren ist. Und mit Umkehren meine ich:

1110 -> 0111
0010 -> 0100

Dies ist ähnlich, aber kein Duplikat dieser PHP-Frage.

Dies ist ähnlich, aber kein Duplikat dieser C-Frage. Diese Frage stellt die einfachste Methode dar, die von einem Entwickler implementiert werden kann. Der "beste Algorithmus" befasst sich mit der Speicher- und CPU-Leistung.


Inline-Baugruppe verwenden. Stellen Sie die Funktion besser in eine separate Übersetzungseinheit. Haben Sie ein Assemblersprachenmodul für jede Zielplattform. Lassen Sie den Build-Prozess die Module auswählen.
Thomas Matthews

@
Andreas

Antworten:


102

Wenn Sie über ein einzelnes Byte sprechen, ist eine Tabellensuche wahrscheinlich die beste Wahl, es sei denn, Sie haben aus irgendeinem Grund keine 256 Bytes zur Verfügung.


12
Wenn es sich um etwas handelt, das einfach zu implementieren ist, ohne eine vorgefertigte Lösung zu kopieren, erfordert das Erstellen der Nachschlagetabelle noch eine andere Lösung. (Natürlich könnte man es von Hand machen, aber das ist fehleranfällig und zeitaufwändig ...)
Arkku

7
Sie können das Array auf etwas weniger als 256 Byte komprimieren, wenn Sie Palindrome ignorieren.
Wilhelmtell

8
@wilhelmtell - Sie benötigen eine Tabelle, um zu wissen, welche Palindrome sind.
Mark Ransom

6
@wilhelmtell: Nun, um das Skript zu schreiben, braucht man noch eine andere Lösung, was mein Punkt war - eine Nachschlagetabelle ist einfach zu verwenden, aber nicht einfach zu erstellen. (Außer durch Kopieren einer vorgefertigten Nachschlagetabelle, aber dann könnte man genauso gut jede Lösung kopieren.) Wenn zum Beispiel die „einfachste“ Lösung als eine Lösung betrachtet wird, die in einer Prüfung oder einem Interview auf Papier geschrieben werden könnte, würde ich dies nicht tun Wenn Sie eine Nachschlagetabelle von Hand erstellen und das Programm dazu veranlassen, enthält dies bereits eine andere Lösung (die allein einfacher wäre als die, die sowohl sie als auch die Tabelle enthält).
Arkku

4
@Arkku Ich meinte, ein Skript zu schreiben, das die Tabelle der ersten 256 Bytes und deren umgekehrte Zuordnung ausgibt. Ja, Sie schreiben wieder die umgekehrte Funktion, aber jetzt in Ihrer bevorzugten Skriptsprache, und sie kann so unangenehm sein, wie Sie möchten - Sie werden sie wegwerfen, sobald sie fertig ist und Sie sie einmal ausgeführt haben. Lassen Sie das Skript als C-Code ausgeben, auch : unsigned int rtable[] = {0x800, 0x4000, ...};. Dann werfen Sie das Skript weg und vergessen Sie, dass Sie es jemals hatten. Das Schreiben ist viel schneller als der entsprechende C ++ - Code und wird immer nur einmal ausgeführt, sodass Sie in Ihrem C ++ - Code eine O (1) -Laufzeit erhalten.
Wilhelmtell

227

Das sollte funktionieren:

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

Zuerst werden die linken vier Bits gegen die rechten vier Bits ausgetauscht. Dann werden alle benachbarten Paare ausgetauscht und dann alle benachbarten Einzelbits. Dies führt zu einer umgekehrten Reihenfolge.


26
Ziemlich kurz und schnell, aber nicht einfach.
Mark Ransom

3
Dieser Ansatz verallgemeinert sich auch sauber, um einen Byte-Austausch gegen Endianität durchzuführen.
Boojum

2
Nicht der einfachste Ansatz, aber ich mag es +1.
Nathan

7
Ja, das ist einfach. Es ist eine Art Divide and Conquer-Algorithmus. Ausgezeichnet!
Kiewic

Ist es schneller als die von @Arkku unten vorgeschlagene Methode?
Qed

122

Ich denke, eine Nachschlagetabelle muss eine der einfachsten Methoden sein. Sie benötigen jedoch keine vollständige Nachschlagetabelle.

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

Dies ist ziemlich einfach zu codieren und visuell zu überprüfen.
Letztendlich könnte dies sogar schneller sein als ein voller Tisch. Das Bit Arith ist billig und die Tabelle passt leicht in eine Cache-Zeile.


10
Dies ist eine hervorragende Möglichkeit, die Komplexität der Tabellenlösung zu reduzieren. +1
e.James

3
Schön, aber Sie werden einen Cache verpassen.
Johan Kotlinski

7
@ Kotlinski: Was verursacht einen Cache-Fehler? Ich denke, die kleine Tabellenversion ist möglicherweise cacheeffizienter als die große. Auf meinem Core2 ist eine Cache-Zeile 64 Byte breit, die vollständige Tabelle würde mehrere Zeilen umfassen, während die kleinere Tabelle problemlos in eine einzelne Zeile passt.
Deft_code

4
@kotlinski: Temporal Ort ist wichtiger für Cache - Hits oder Ersatzstrategien, als Adresse Ort
cfi

6
@Harshdeep: Betrachten Sie die binär codierten Indizes der Tabelleneinträge. Index b0000 (0) -> b0000 (0x0) langweilig; b0001(1) -> b1000(0x8), b0010(2) -> b0100(0x4), b1010(10) -> b0101(0x5). Sehen Sie das Muster? Es ist einfach genug, dass Sie es in Ihrem Kopf berechnen können (wenn Sie binär lesen können, benötigen Sie sonst Papier, um es zu erarbeiten). Was den Sprung betrifft, ist das Umkehren einer 8-Bit-Ganzzahl dasselbe wie das Umkehren von 4-Bit-Teilen und das anschließende Vertauschen dieser Teile. Ich beanspruche Erfahrung und Intuition (oder Magie).
Deft_code

46

Sehen Sie sich die kleinen Hacks an viele Lösungen. Copypasting von dort ist offensichtlich einfach zu implementieren. =)

Zum Beispiel (auf einer 32-Bit-CPU):

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

Wenn mit „einfach zu implementieren“ etwas gemeint ist, das ohne Referenz in einer Prüfung oder einem Vorstellungsgespräch durchgeführt werden kann, ist die sicherste Wette wahrscheinlich das ineffiziente Kopieren von Bits nacheinander in eine andere Variable in umgekehrter Reihenfolge (bereits in anderen Antworten gezeigt) ).


1
Von Ihrer URL: 32-Bit-CPU: b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
Joshua

1
@ Joshua: Das ist auch mein persönlicher Favorit. Die Einschränkung (wie auf der verlinkten Seite angegeben) besteht darin, dass sie zugewiesen oder in ein uint8_t umgewandelt werden muss, da sich sonst Müll in den oberen Bits befindet.
Arkku

41

Da niemand eine vollständige Tabellensuchlösung veröffentlicht hat, ist hier meine:

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
Nützlich, danke. Scheint, dass meine langsamere Schaltmethode die Leistung in einer eingebetteten App einschränkte. Tabelle im ROM auf einem PIC platziert (mit Zusatz des Schlüsselworts 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;
}

BEARBEITEN:

Konvertierte es in eine Vorlage mit der optionalen Bitanzahl


@nvl - behoben. Ich fing an, es als Vorlage zu erstellen, entschied mich aber auf halbem Weg, dies nicht zu tun ... zu viele & gt & lt
undund

Für zusätzliche pedenatry, ersetzen sizeof(T)*8mit sizeof(T)*CHAR_BITS.
Pillsy

6
@andand Für zusätzliche Pendantry ersetzen Sie sizeof(T)*CHAR_BITdurch std::numeric_limits<T>::digits(fast 4 Jahre Pedanterie später).
Morwenn

1
Es sollte CHAR_BITnicht sein CHAR_BITS.
Xunie

1
es sollte rv = (rv << 1) | sein (n & amp; 0x01);
Vignesh

16

Zwei Linien:

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

oder falls Sie Probleme mit dem Teil "0b1" haben:

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

"original" ist das Byte, das Sie umkehren möchten. "umgekehrt" ist das Ergebnis, initialisiert auf 0.


14

Obwohl wahrscheinlich nicht portabel, würde ich Assemblersprache verwenden.
Viele Assemblersprachen haben Anweisungen, ein bisschen in das Übertragsflag zu drehen und das Übertragsflag in das Wort (oder Byte) zu drehen.

Der Algorithmus ist:

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

Der Hochsprachencode hierfür ist viel komplizierter, da C und C ++ das Drehen zum Übertragen und das Drehen vom Übertragen nicht unterstützen. Das Carry Flag muss modelliert werden.

Bearbeiten: Assemblersprache zum Beispiel

;  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
Ich denke, diese Antwort ist das Gegenteil von einfach. Nicht portierbar, Assembly und komplex genug, um anstelle der eigentlichen Assembly in Pseudocode geschrieben zu werden.
Deft_code

3
Es ist ganz einfach. Ich habe es in Pseudocode eingefügt, weil Assembly-Mnemonics spezifisch für eine Art von Prozessor sind und es viele Rassen gibt. Wenn Sie möchten, kann ich dies bearbeiten, um die einfache Assemblersprache anzuzeigen.
Thomas Matthews

Man könnte sehen, ob sich eine Compileroptimierung zu einer geeigneten Montageanleitung vereinfacht.
Sparky

12

Ich finde die folgende Lösung einfacher als die anderen Bit-Fiddling-Algorithmen, die ich hier gesehen habe.

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

Es erhält jedes Bit im Byte und verschiebt es entsprechend, beginnend vom ersten zum letzten.

Erläuterung:

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

Wunderschönen! Mein Favorit bisher.
Nick Rameau

Dies ist sicherlich einfach, aber es sollte darauf hingewiesen werden, dass die Ausführungszeit O (n) und nicht O (log₂ n) ist, wobei n die Anzahl der Bits (8, 16, 32, 64 usw.) ist.
Todd Lehman

10

Der einfachste Weg ist wahrscheinlich, über die Bitpositionen in einer Schleife zu iterieren:

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

Es ist CHAR_BIT, ohne ein 's'
ljrk

Warum verwenden, CHAR_BITwenn Sie char8 Bits annehmen ?
Chqrlie

6

Für den sehr begrenzten Fall einer konstanten 8-Bit- Eingabe kostet diese Methode zur Laufzeit weder Speicher noch CPU:

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

Ich habe dies für ARINC-429 verwendet, bei dem die Bitreihenfolge (Endianness) des Labels dem Rest des Wortes gegenüberliegt. Das Etikett ist oft eine Konstante und üblicherweise oktal.

Hier ist, wie ich es verwendet habe, um eine Konstante zu definieren, da die Spezifikation diese Bezeichnung als Big-Endian 205-Oktal definiert.

#define LABEL_HF_COMM MSB2LSB(0205)

Mehr Beispiele:

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

Sie könnten interessiert sein an std::vector<bool>(das ist etwas voll) undstd::bitset

Es sollte das einfachste sein, wie gewünscht.

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

Andere Optionen sind möglicherweise schneller.

EDIT: Ich schulde dir eine Lösung mit 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;
}

Das zweite Beispiel erfordert die Erweiterung c ++ 0x (um das Array mit zu initialisieren {...}). Der Vorteil der Verwendung von a bitsetoder a std::vector<bool>(oder a boost::dynamic_bitset) besteht darin, dass Sie nicht auf Bytes oder Wörter beschränkt sind, sondern eine beliebige Anzahl von Bits umkehren können.

HTH


Wie ist Bitset hier einfacher als ein Pod? Zeigen Sie den Code oder nicht.
Wilhelmtell

Eigentlich denke ich, dass Code das Bitset umkehren und es dann wieder auf das Original zurücksetzen wird. Ändere ii! = Size (); bis ii <Größe () / 2; und es wird einen besseren Job machen =)
Viktor Sehr

(@ viktor-sehr nein, wird es nicht, rev unterscheidet sich von bs). Wie auch immer, ich mag die Antwort selbst nicht: Ich denke, dies ist ein Fall, in dem binäre Arithmetik- und Verschiebungsoperatoren besser geeignet sind. Es bleibt immer noch am einfachsten zu verstehen.
Baol

Wie wäre es std::vector<bool> b = { ... }; std::vector<bool> rb ( b.rbegin(), b.rend()); - Reverse Iteratoren direkt zu verwenden?
MSalters

@ MSalters Ich mag die Unveränderlichkeit davon.
Baol

5

Es gibt viele Möglichkeiten, Bits umzukehren, je nachdem, was Sie als "einfachsten Weg" bezeichnen.


Durch Drehen umkehren

Das wahrscheinlich logischste besteht darin, das Byte zu drehen, während eine Maske auf das erste Bit angewendet wird (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) Da die Länge eines Zeichens ohne Vorzeichen 1 Byte beträgt, was 8 Bit entspricht, bedeutet dies, dass jedes Bit gescannt wird while (byte_len--)

2) Wir prüfen zuerst, ob b ein bisschen ganz rechts mit ist (b & 1); Wenn ja, setzen wir Bit 1 auf r mit |und verschieben es nur 1 Bit nach links, indem wir r mit 2 mit multiplizieren(r << 1)

3) Dann teilen wir unser vorzeichenloses Zeichen b durch 2 mit b >>=1, um das Bit ganz rechts von der Variablen b zu löschen. Zur Erinnerung, b >> = 1; ist äquivalent zu b / = 2;


In einer Zeile umkehren

Diese Lösung wird Rich Schroeppel im Abschnitt Programming Hacks zugeschrieben

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

1) Die Multiplikationsoperation (b * 0x0202020202ULL) erstellt fünf separate Kopien des 8-Bit-Bytemusters, um sie auf einen 64-Bit-Wert aufzufächern.

2) Die UND-Operation (& 0x010884422010ULL) wählt die Bits aus, die sich in der richtigen (umgekehrten) Position befinden, relativ zu jeder 10-Bit-Gruppe von Bits.

3) Zusammen kopieren die Multiplikations- und die UND-Operation die Bits aus dem ursprünglichen Byte, so dass sie jeweils nur in einem der 10-Bit-Sätze erscheinen. Die umgekehrten Positionen der Bits aus dem ursprünglichen Byte stimmen mit ihren relativen Positionen innerhalb eines 10-Bit-Satzes überein.

4) Der letzte Schritt (% 0x3ff), bei dem der Modul durch 2 ^ 10 - 1 geteilt wird, führt dazu, dass jeder Satz von 10 Bits (von den Positionen 0-9, 10-19, 20-29, ...) zusammengeführt wird. im 64-Bit-Wert. Sie überlappen sich nicht, sodass sich die der Modulteilung zugrunde liegenden Additionsschritte wie ODER-Operationen verhalten.


Lösung teilen und erobern

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

Dies ist die am besten bewertete Antwort, und trotz einiger Erklärungen denke ich, dass es für die meisten Menschen schwierig ist, sich vorzustellen, was 0xF0, 0xCC, 0xAA, 0x0F, 0x33 und 0x55 wirklich bedeuten.

'0b', eine GCC-Erweiterung, die seit dem C ++ 14-Standard, der im Dezember 2014 veröffentlicht wurde, enthalten ist, wird nicht genutzt. Eine Weile nach dieser Antwort aus dem April 2010

Ganzzahlige Konstanten können als binäre Konstanten geschrieben werden, die aus einer Folge von '0' und '1' Ziffern mit dem Präfix '0b' oder '0B' bestehen. Dies ist besonders nützlich in Umgebungen, die viel auf Bitebene arbeiten (wie Mikrocontroller).

Bitte überprüfen Sie die folgenden Codeausschnitte, um sich an diese Lösung zu erinnern und sie noch besser zu verstehen, bei der wir uns halb um die Hälfte bewegen:

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: Die >> 4 liegt daran, dass 1 Byte 8 Bits enthält, was ein vorzeichenloses Zeichen ist, also wollen wir die andere Hälfte nehmen und so weiter.

Wir könnten diese Lösung leicht auf 4 Bytes mit nur zwei zusätzlichen Zeilen anwenden und der gleichen Logik folgen. Da sich beide Masken ergänzen, können wir sogar ~ verwenden, um Bits zu wechseln und etwas Tinte zu sparen:

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

[Nur C ++] Alle vorzeichenlosen (Vorlage) umkehren

Die obige Logik kann mit einer Schleife zusammengefasst werden, die für jede Art von vorzeichenlosem funktioniert:

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

Probieren Sie es selbst aus, indem Sie die obige Funktion einbeziehen:

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

Mit asm flüchtig umkehren

Last but not least, wenn das Einfachste weniger Linien bedeutet, warum nicht versuchen, die Inline-Montage durchzuführen?

Sie können das folgende Code-Snippet testen, indem Sie -masm=intelbeim Kompilieren Folgendes hinzufügen :

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

Erklärungen Zeile für Zeile:

        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

Tabellensuche oder

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

bearbeiten

Suchen Sie hier nach anderen Lösungen, die für Sie möglicherweise besser funktionieren


3

eine langsamere aber einfachere Implementierung:

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

Kann das eine schnelle Lösung sein?

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

Beseitigt die Hektik der Verwendung einer for-Schleife! Aber Experten sagen mir bitte, ob dies effizient und schneller ist.


Die Ausführungszeit beträgt O (n) und nicht O (log₂ n), wobei n die Anzahl der Bits (8, 16, 32, 64 usw.) ist. Antworten, die in der Zeit O (log₂ n) ausgeführt werden, finden Sie an anderer Stelle.
Todd Lehman

2

Überprüfen Sie vor der Implementierung einer algorithmischen Lösung die Assemblersprache auf die von Ihnen verwendete CPU-Architektur. Ihre Architektur kann Anweisungen enthalten, die bitweise Manipulationen wie diese handhaben (und was könnte einfacher sein als eine einzelne Montageanweisung?).

Wenn eine solche Anweisung nicht verfügbar ist, würde ich empfehlen, die Route der Nachschlagetabelle zu verwenden. Sie können ein Skript / Programm schreiben, um die Tabelle für Sie zu generieren, und die Suchvorgänge wären schneller als alle hier beschriebenen Bitumkehralgorithmen (auf Kosten der Speicherung der Nachschlagetabelle irgendwo).


2

Diese einfache Funktion verwendet eine Maske, um jedes Bit im Eingangsbyte zu testen und in einen verschiebbaren Ausgang zu übertragen:

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

Maske sollte unsigniert sein, sorry.
Luci88filter

1

Dieser basiert auf dem einen BobStein-VisiBone bereitgestellten

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

Ich mag dieses sehr, weil der Compiler die Arbeit automatisch für Sie erledigt und daher keine weiteren Ressourcen benötigt.

Dies kann auch auf 16-Bit erweitert werden ...

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

Ich würde die bin Klammern setzen, falls es sich um einen komplexeren Ausdruck als eine einzelne Zahl handelt, und das Makro vielleicht auch umbenennen, REVERSE_BYTEum darauf hinzuweisen, dass Sie dort wahrscheinlich keinen komplexeren (Laufzeit-) Ausdruck haben möchten. Oder machen Sie es zu einer Inline-Funktion. (Aber insgesamt mag ich dies als einfach genug, dass Sie es leicht aus dem Gedächtnis mit sehr geringer Wahrscheinlichkeit von Fehlern tun können.)
Arkku

1

Angenommen, Ihr Compiler lässt unsigned long long zu :

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

Hier entdeckt


1

Wenn Sie einen kleinen Mikrocontroller verwenden und eine Hochgeschwindigkeitslösung mit geringem Platzbedarf benötigen, können dies Lösungen sein. Es ist möglich, es für C-Projekte zu verwenden, aber Sie müssen diese Datei als Assembler-Datei * .asm zu Ihrem C-Projekt hinzufügen. Anleitung: Fügen Sie im C-Projekt diese Deklaration hinzu:

extern uint8_t byte_mirror(uint8_t);

Rufen Sie diese Funktion von C aus auf

byteOutput= byte_mirror(byteInput);

Dies ist der Code, er ist nur für 8051 Core geeignet. Im CPU-Register r0 befinden sich Daten von byteInput . Code rechts drehen r0 Cross Carry und dann Carry links nach r1 drehen . Wiederholen Sie diesen Vorgang 8 Mal für jedes Bit. Dann wird das Register r1 als ByteOutput an c zurückgegeben. In 8051 ist der Kern nur möglich, um den Akku a zu drehen .

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

PROS: Es ist klein, es ist schnell. Nachteile: Es ist kein wiederverwendbarer Code, es ist nur für 8051

011101101-> tragen

101101110 <-tragen


Während dieser Code die Frage beantworten kann, ist es besser, einen Kontext einzuschließen, der erklärt, wie er funktioniert und wann er verwendet werden soll. Nur-Code-Antworten sind auf lange Sicht nicht sinnvoll.
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:

Das umgekehrte Byte befindet sich im bl- Register


3
In einem anderen Kontext mag das eine faire Antwort sein, aber die Frage war über C oder C ++, nicht 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;
}

Als stilistische Anmerkung finde ich die Verwendung uint8_tfür die 1-Bit-Felder etwas hässlich, da es zunächst zu sagen scheint, dass es 8 Bit dauern wird, aber dann am Ende der Zeile als nur ein einzelnes Bit definiert wird. Ich würde unsigned b0:1usw. verwenden
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;
}

Bitte fügen Sie eine Erklärung hinzu.
zcui93

0

Ich denke das ist einfach genug

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

oder

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

Ich werde meine Lösung einbinden, da ich so etwas in den Antworten bisher nicht finden kann. Es ist vielleicht etwas überarbeitet, aber es generiert die Nachschlagetabelle mit C ++ 14 std::index_sequencein der Kompilierungszeit.

#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

Hier ist eine einfache und lesbare Lösung, die auf alle konformen Plattformen portierbar ist, einschließlich solcher mit 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

Ich weiß, dass diese Frage veraltet ist, aber ich denke immer noch, dass das Thema für einige Zwecke relevant ist, und hier ist eine Version, die sehr gut funktioniert und lesbar ist. Ich kann nicht sagen, dass es das schnellste oder effizienteste ist, aber es sollte eines der saubersten sein. Ich habe auch eine Hilfsfunktion zum einfachen Anzeigen der Bitmuster hinzugefügt. Diese Funktion verwendet einige der Standardbibliotheksfunktionen, anstatt einen eigenen Bitmanipulator zu schreiben.

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

Und es gibt die folgende Ausgabe.

224
11100000
7
00000111

0

Dieser half mir mit 8x8 Punktmatrix-Arrays.

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
Diese Funktion funktioniert nicht wirklich, die Umkehrung von 0b11001111 sollte 0b11110011 sein, schlägt jedoch mit dieser Funktion fehl. Die gleiche Testmethode funktioniert für viele der anderen hier aufgeführten Funktionen.
Dan

Ja, danke, ich habe meine Antwort korrigiert. Vielen Dank, dass Sie mich über meinen Fehler
informiert haben
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.