Trong C / C ++ cách đơn giản nhất để đảo ngược thứ tự các bit trong một byte là gì?


110

Mặc dù có nhiều cách để đảo ngược thứ tự bit trong một byte, tôi vẫn tò mò về cách "đơn giản nhất" để một nhà phát triển thực hiện là gì. Và bằng cách đảo ngược ý tôi là:

1110 -> 0111
0010 -> 0100

Điều này tương tự như, nhưng không phải là bản sao của câu hỏi PHP này .

Điều này tương tự, nhưng không phải là bản sao của câu hỏi C này . Câu hỏi này yêu cầu một nhà phát triển đưa ra phương pháp dễ thực hiện nhất. "Thuật toán tốt nhất" liên quan đến bộ nhớ và hiệu suất cpu.


Sử dụng lắp ráp nội tuyến. Tốt hơn, hãy đặt chức năng vào một đơn vị dịch riêng biệt. Có một mô-đun hợp ngữ cho mỗi nền tảng đích. Hãy để quá trình xây dựng chọn các mô-đun.
Thomas Matthews

@Andreas Cách triển khai đơn giản nhất
nathan

Câu trả lời:


102

Nếu bạn đang nói về một byte duy nhất, tra cứu bảng có lẽ là cách tốt nhất, trừ khi vì lý do nào đó bạn không có sẵn 256 byte.


12
Nếu chúng ta đang nói về một thứ gì đó đơn giản để thực hiện mà không cần sao chép giải pháp tạo sẵn, thì việc tạo bảng tra cứu vẫn yêu cầu một giải pháp khác. (Tất nhiên người ta có thể làm điều đó bằng tay, nhưng điều đó dễ xảy ra lỗi và tốn thời gian…)
Arkku

7
Bạn có thể ép mảng thành ít hơn 256 byte nếu bạn bỏ qua palindromes.
wilhelmtell

8
@wilhelmtell - bạn cần một bảng để biết những cái nào là palindromes.
Mark Ransom

6
@wilhelmtell: Chà, để viết script, người ta vẫn cần một giải pháp khác, đó là quan điểm của tôi - một bảng tra cứu rất dễ sử dụng nhưng không đơn giản để tạo. (Ngoại trừ bằng cách sao chép một bảng tra cứu làm sẵn, nhưng sau đó người ta cũng có thể sao chép bất kỳ giải pháp nào.) Ví dụ: nếu giải pháp “đơn giản nhất” được coi là giải pháp có thể viết trên giấy trong một kỳ thi hoặc cuộc phỏng vấn, tôi sẽ không bắt đầu tạo một bảng tra cứu bằng tay và làm cho chương trình để thực hiện nó sẽ bao gồm một giải pháp khác (đơn giản hơn là một giải pháp bao gồm cả nó và bảng).
Arkku

4
@Arkku ý tôi là viết một tập lệnh xuất ra bảng 256 byte đầu tiên và ánh xạ ngược của chúng. Có, bạn đang quay lại viết hàm đảo ngược, nhưng bây giờ bằng ngôn ngữ kịch bản yêu thích của bạn và nó có thể khó chịu như bạn muốn - bạn sẽ vứt nó đi ngay sau khi hoàn thành và bạn đã chạy nó một lần. Có đầu ra của kịch bản dưới dạng mã C, thậm chí: unsigned int rtable[] = {0x800, 0x4000, ...};. Sau đó, vứt bỏ tập lệnh và quên mất bạn đã từng có nó. Viết nhanh hơn nhiều so với mã C ++ tương đương và nó sẽ chỉ chạy một lần, vì vậy bạn nhận được thời gian chạy O (1) trong mã C ++ của mình.
wilhelmtell

227

Điều này sẽ hoạt động:

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

Đầu tiên bốn bit bên trái được đổi chỗ với bốn bit bên phải. Sau đó, tất cả các cặp liền kề được đổi chỗ và sau đó là tất cả các bit đơn liền kề. Điều này dẫn đến một thứ tự bị đảo ngược.


26
Hợp lý ngắn gọn và nhanh chóng, nhưng không đơn giản.
Mark Ransom

3
Cách tiếp cận này cũng tổng quát hóa một cách rõ ràng để thực hiện hoán đổi byte để lấy giá trị cuối cùng.
Boojum

2
Không phải là cách tiếp cận đơn giản nhất, nhưng tôi thích nó +1.
nathan

7
Vâng, nó là đơn giản. Đó là một loại thuật toán phân chia và chinh phục. Thông minh!
kiewic

Nó có nhanh hơn phương pháp được @Arkku gợi ý bên dưới không?
qed

122

Tôi nghĩ rằng bảng tra cứu phải là một trong những phương pháp đơn giản nhất. Tuy nhiên, bạn không cần một bảng tra cứu đầy đủ.

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

Điều này khá đơn giản để mã và xác minh trực quan.
Cuối cùng, điều này thậm chí có thể nhanh hơn một bảng đầy đủ. Bit arith rẻ và bảng dễ dàng phù hợp trên một dòng bộ nhớ cache.


10
Đó là một cách tuyệt vời để giảm độ phức tạp của giải pháp bảng. +1
e.James

3
Tốt, nhưng sẽ cung cấp cho bạn một bộ nhớ cache bỏ lỡ.
Johan Kotlinski

7
@kotlinski: điều gì sẽ gây ra lỗi bộ nhớ cache? Tôi nghĩ rằng phiên bản bảng nhỏ có thể bộ nhớ cache hiệu quả hơn phiên bản lớn. Trên Core2 của tôi, một dòng bộ nhớ cache rộng 64 byte, bảng đầy đủ sẽ trải dài nhiều dòng, trong khi bảng nhỏ hơn dễ dàng vừa với một dòng duy nhất.
deft_code

4
@kotlinski: Temporal địa phương là quan trọng hơn với các hit bộ nhớ cache hoặc chiến lược thay thế, hơn địa chỉ địa phương
CFI

6
@Harshdeep: Xem xét các chỉ mục được mã hóa nhị phân của các mục trong bảng. chỉ số b0000 (0) -> b0000 (0x0) nhàm chán; b0001(1) -> b1000(0x8), b0010(2) -> b0100(0x4), b1010(10) -> b0101(0x5). Xem mẫu? Nó đủ đơn giản để bạn có thể tính toán nó trong đầu (nếu bạn có thể đọc hệ nhị phân, nếu không bạn sẽ cần giấy để tính toán). Đối với bước nhảy vọt rằng việc đảo ngược số nguyên 8 bit cũng giống như đảo ngược các phần 4 bit sau đó hoán đổi chúng; Tôi khẳng định kinh nghiệm và trực giác (hoặc ma thuật).
deft_code

46

Xem các bản hack lộn xộn để biết nhiều giải pháp. Copypasting từ đó rõ ràng là đơn giản để thực hiện. =)

Ví dụ (trên CPU 32-bit):

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

Nếu "đơn giản để thực hiện" nghĩa là điều gì đó có thể được thực hiện mà không cần tham chiếu trong kỳ thi hoặc phỏng vấn xin việc, thì cách an toàn nhất có lẽ là sao chép không hiệu quả từng bit một vào một biến khác theo thứ tự ngược lại (đã được trình bày trong các câu trả lời khác ).


1
Từ URL của bạn: CPU 32 bit: b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
Joshua

1
@Joshua: Đó cũng là sở thích cá nhân của tôi. Lưu ý (như đã nêu trên trang được liên kết) là nó cần được gán hoặc ép kiểu thành uint8_t nếu không sẽ có rác ở các bit phía trên.
Arkku

41

Vì không ai đăng giải pháp tra cứu bảng hoàn chỉnh nên đây là của tôi:

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
Hữu ích, cảm ơn. Có vẻ như phương pháp chuyển đổi chậm hơn của tôi đã hạn chế hiệu suất trong một ứng dụng được nhúng. Đã đặt bảng trong ROM trên PIC (có thêm từ khóa rom).
chạy vào

1
Một phương pháp đơn giản hơn: graphics.stanford.edu/~seander/bithacks.html#BitReverseTable
Sleeport

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

BIÊN TẬP:

Đã chuyển đổi nó thành một mẫu với số lượng bit tùy chọn


@nvl - đã sửa. Tôi đã bắt đầu xây dựng nó như một mẫu nhưng đã quyết định giữa chừng để không làm như vậy ... quá nhiều & gt & lt
andand

Để có thêm pedenatry, hãy thay thế sizeof(T)*8bằng sizeof(T)*CHAR_BITS.
Pillsy

6
@andand Để có thêm mặt dây chuyền, hãy thay thế sizeof(T)*CHAR_BITbằng std::numeric_limits<T>::digits(gần 4 năm sau đó).
Morwenn

1
Nó nên được CHAR_BIT, không phải CHAR_BITS.
Xunie

1
nó phải là rv = (rv << 1) | (n & 0x01);
Canh thức

16

Hai dòng:

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

hoặc trong trường hợp bạn gặp sự cố với phần "0b1":

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

"gốc" là byte bạn muốn đảo ngược. "đảo ngược" là kết quả, được khởi tạo thành 0.


14

Mặc dù có lẽ không phải là portable, nhưng tôi sẽ sử dụng hợp ngữ.
Nhiều ngôn ngữ hợp ngữ có hướng dẫn để xoay một bit vào cờ mang và để xoay cờ mang thành từ (hoặc byte).

Thuật toán là:

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

Mã ngôn ngữ cấp cao cho điều này phức tạp hơn nhiều, vì C và C ++ không hỗ trợ xoay để mang và xoay từ mang. Cờ mang phải được mô hình hóa.

Chỉnh sửa: Hợp ngữ chẳng hạn

;  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
Tôi nghĩ rằng câu trả lời này là ngược lại với đơn giản. Không di động, lắp ráp và đủ phức tạp để được viết bằng mã giả thay vì lắp ráp thực tế.
deft_code

3
Nó khá đơn giản. Tôi đặt nó vào mã giả vì kỹ thuật ghi nhớ lắp ráp dành riêng cho một giống bộ xử lý và có rất nhiều giống ngoài đó. Nếu bạn muốn, tôi có thể chỉnh sửa điều này để hiển thị hợp ngữ đơn giản.
Thomas Matthews

Người ta có thể thấy liệu một tối ưu hóa trình biên dịch có đơn giản hóa thành một hướng dẫn lắp ráp phù hợp hay không.
Sparky

12

Tôi thấy giải pháp sau đây đơn giản hơn các thuật toán hơi khó hiểu khác mà tôi đã thấy ở đây.

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

Nó nhận từng bit trong byte và thay đổi nó tương ứng, bắt đầu từ đầu tiên đến cuối cùng.

Giải trình:

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

Xinh đẹp! Yêu thích của tôi cho đến nay.
Nick Rameau

Điều này chắc chắn là đơn giản, nhưng cần chỉ ra rằng thời gian thực hiện là O (n) chứ không phải O (log₂ n), trong đó n là số bit (8, 16, 32, 64, v.v.).
Todd Lehman

10

Cách đơn giản nhất có lẽ là lặp lại các vị trí bit trong một vòng lặp:

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

nó CHAR_BIT, mà không có một 's'
ljrk

Tại sao sử dụng CHAR_BITkhi bạn giả sử charcó 8 bit?
chqrlie

6

Đối với trường hợp đầu vào không đổi, 8 bit rất hạn chế , phương pháp này không tốn bộ nhớ hoặc CPU tại thời điểm chạy:

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

Tôi đã sử dụng điều này cho ARINC-429 trong đó thứ tự bit (endianness) của nhãn đối diện với phần còn lại của từ. Nhãn thường là một hằng số và được quy ước ở dạng bát phân.

Đây là cách tôi sử dụng nó để xác định một hằng số, vì thông số xác định nhãn này là bát phân big-endian 205.

#define LABEL_HF_COMM MSB2LSB(0205)

Các ví dụ khác:

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

Bạn có thể quan tâm đến std::vector<bool>(được đóng gói bit) vàstd::bitset

Nó phải là đơn giản nhất theo yêu cầu.

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

Các tùy chọn khác có thể nhanh hơn.

CHỈNH SỬA: Tôi nợ bạn một giải pháp sử dụng 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;
}

Ví dụ thứ hai yêu cầu phần mở rộng c ++ 0x (để khởi tạo mảng với {...}). Lợi thế của việc sử dụng a bitsethoặc a std::vector<bool>(hoặc aboost::dynamic_bitset ) là bạn không bị giới hạn bởi byte hoặc từ nhưng có thể đảo ngược một số bit tùy ý.

HTH


Bitet đơn giản hơn pod ở đây như thế nào? Hiển thị mã, hoặc nó không.
wilhelmtell

Actu ally, tôi nghĩ rằng đoạn mã đó sẽ đảo ngược bộ bit, và sau đó đảo ngược nó trở lại ban đầu. Thay đổi ii! = Size (); thành ii <size () / 2; và nó sẽ làm công việc tốt hơn =)
Viktor Sehr

(@ viktor-sehr không, nó sẽ không, rev khác với bs). Dù sao thì bản thân tôi cũng không thích câu trả lời: Tôi nghĩ đây là trường hợp mà toán tử số học nhị phân và toán tử dịch chuyển phù hợp hơn. Nó vẫn là cách hiểu đơn giản nhất.
baol

Làm thế nào về std::vector<bool> b = { ... }; std::vector<bool> rb ( b.rbegin(), b.rend()); - sử dụng trình lặp ngược trực tiếp?
MSalters

@MSalters Tôi thích tính bất biến của nó.
baol

5

Có nhiều cách để đảo ngược bit tùy thuộc vào ý của bạn là "cách đơn giản nhất".


Đảo ngược bằng cách xoay

Có lẽ là hợp lý nhất, bao gồm xoay byte trong khi áp dụng mặt nạ trên bit đầu tiên (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) Vì độ dài của một ký tự không dấu là 1 byte, bằng 8 bit, điều đó có nghĩa là chúng tôi sẽ quét từng bit while (byte_len--)

2) Đầu tiên chúng ta kiểm tra xem b có phải là một bit ở cực bên phải với (b & 1); nếu vậy, chúng tôi đặt bit 1 trên r với |và di chuyển nó chỉ 1 bit sang trái bằng cách nhân r với 2 với(r << 1)

3) Sau đó, chúng ta chia char b không dấu của chúng ta cho 2 với b >>=1để xóa bit nằm ở cực bên phải của biến b. Xin nhắc lại, b >> = 1; tương đương với b / = 2;


Đảo ngược trong một dòng

Giải pháp này là do Rich Schroeppel trong phần Lập trình Hacks

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

1) Phép toán nhân (b * 0x0202020202ULL) tạo ra năm bản sao riêng biệt của mẫu byte 8 bit để chuyển thành giá trị 64 bit.

2) Phép toán AND (& 0x010884422010ULL) chọn các bit ở vị trí chính xác (đảo ngược), liên quan đến từng nhóm bit 10 bit.

3) Các phép toán nhân và AND cùng nhau sao chép các bit từ byte ban đầu để mỗi bit chỉ xuất hiện trong một trong các bộ 10 bit. Vị trí đảo ngược của các bit từ byte ban đầu trùng với vị trí tương đối của chúng trong bất kỳ tập hợp 10 bit nào.

4) Bước cuối cùng (% 0x3ff), liên quan đến phép chia mô-đun cho 2 ^ 10 - 1 có tác dụng hợp nhất từng bộ 10 bit với nhau (từ các vị trí 0-9, 10-19, 20-29, ...) ở giá trị 64-bit. Chúng không chồng chéo lên nhau, vì vậy các bước bổ sung bên dưới phép phân chia mô đun hoạt động giống như các phép toán OR.


Giải pháp phân chia và chinh phục

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

Đây là câu trả lời được ủng hộ nhiều nhất và mặc dù có một số giải thích, tôi nghĩ rằng đối với hầu hết mọi người, cảm thấy khó hình dung 0xF0, 0xCC, 0xAA, 0x0F, 0x33 và 0x55 thực sự có ý nghĩa như thế nào.

Nó không tận dụng lợi thế của '0b' là một phần mở rộng GCC và được bao gồm từ tiêu chuẩn C ++ 14, phát hành vào tháng 12 năm 2014, vì vậy một thời gian sau câu trả lời này có từ tháng 4 năm 2010

Hằng số nguyên có thể được viết dưới dạng hằng số nhị phân, bao gồm một chuỗi các chữ số '0' và '1', có tiền tố là '0b' hoặc '0B'. Điều này đặc biệt hữu ích trong các môi trường hoạt động nhiều ở mức bit (như vi điều khiển).

Vui lòng kiểm tra các đoạn mã dưới đây để ghi nhớ và hiểu rõ hơn về giải pháp này khi chúng ta di chuyển một nửa:

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: >> 4Đó là vì có 8 bit trong 1 byte, là một ký tự không dấu nên chúng tôi muốn lấy nửa còn lại, v.v.

Chúng tôi có thể dễ dàng áp dụng giải pháp này cho 4 byte chỉ với hai dòng bổ sung và tuân theo cùng một logic. Vì cả hai mặt nạ bổ sung cho nhau, chúng tôi thậm chí có thể sử dụng ~ để chuyển đổi các bit và tiết kiệm mực:

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

[Chỉ dành cho C ++] Đảo ngược mọi dấu hiệu chưa được ký (Mẫu)

Logic ở trên có thể được tóm tắt bằng một vòng lặp sẽ hoạt động trên bất kỳ loại unsigned nào:

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

Hãy tự mình thử với chức năng trên:

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

Đảo ngược với asm dễ bay hơi

Cuối cùng nhưng không kém phần quan trọng, nếu đơn giản nhất có nghĩa là ít dòng hơn, tại sao không thử lắp ráp nội tuyến?

Bạn có thể kiểm tra đoạn mã bên dưới bằng cách thêm vào -masm=intelkhi biên dịch:

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

Giải thích từng dòng:

        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

Bảng tra cứu hoặc

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

biên tập

Tìm ở đây để biết các giải pháp khác có thể hoạt động tốt hơn cho bạn


3

triển khai chậm hơn nhưng đơn giản hơn:

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

Đây có thể là giải pháp nhanh chóng?

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

Thoát khỏi sự hối hả của việc sử dụng vòng lặp for! nhưng các chuyên gia vui lòng cho tôi biết cách này có hiệu quả và nhanh hơn không?


Điều này có thời gian thực thi là O (n) chứ không phải O (log₂ n), trong đó n là số bit (8, 16, 32, 64, v.v.). Xem phần khác để biết các câu trả lời thực thi trong thời gian O (log₂ n).
Todd Lehman

2

Trước khi thực hiện bất kỳ giải pháp thuật toán nào, hãy kiểm tra hợp ngữ cho bất kỳ kiến ​​trúc CPU nào bạn đang sử dụng. Kiến trúc của bạn có thể bao gồm các lệnh xử lý các thao tác bitwise như thế này (và điều gì có thể đơn giản hơn một lệnh lắp ráp đơn lẻ?).

Nếu một hướng dẫn như vậy không có sẵn, thì tôi khuyên bạn nên đi theo lộ trình bảng tra cứu. Bạn có thể viết một tập lệnh / chương trình để tạo bảng cho bạn và các thao tác tra cứu sẽ nhanh hơn bất kỳ thuật toán đảo ngược bit nào ở đây (với chi phí phải lưu trữ bảng tra cứu ở đâu đó).


2

Hàm đơn giản này sử dụng một mặt nạ để kiểm tra từng bit trong byte đầu vào và chuyển nó thành đầu ra dịch chuyển:

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

Mặt nạ nên không có dấu xin lỗi.
luci88filter 17/02/18

1

Cái này dựa trên cái mà BobStein-VisiBone cung cấp

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

Tôi thực sự thích cái này rất nhiều vì trình biên dịch tự động xử lý công việc cho bạn, do đó không yêu cầu thêm tài nguyên.

điều này cũng có thể được mở rộng đến 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 ) 

Tôi sẽ đặt bdấu ngoặc đơn trong trường hợp nó là một biểu thức phức tạp hơn một số đơn lẻ và có lẽ cũng đổi tên macro thành REVERSE_BYTEnhư một gợi ý rằng bạn có thể không muốn có một biểu thức (thời gian chạy) phức tạp hơn ở đó. Hoặc biến nó thành một hàm nội tuyến. (Nhưng nhìn chung, tôi thích điều này vì nó đủ đơn giản để bạn có thể làm điều đó từ bộ nhớ một cách dễ dàng với rất ít khả năng xảy ra lỗi.)
Arkku

1

Giả sử rằng trình biên dịch của bạn cho phép dài chưa ký :

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

Đã khám phá tại đây


1

Nếu bạn sử dụng vi điều khiển nhỏ và cần giải pháp tốc độ cao với dấu chân nhỏ, đây có thể là giải pháp. Có thể sử dụng nó cho dự án C, nhưng bạn cần thêm tệp này dưới dạng tệp hợp ngữ * .asm, vào dự án C của bạn. Hướng dẫn: Trong dự án C thêm khai báo này:

extern uint8_t byte_mirror(uint8_t);

Gọi hàm này từ C

byteOutput= byte_mirror(byteInput);

Đây là mã, nó chỉ phù hợp với lõi 8051. Trong thanh ghi CPU r0 là dữ liệu từ byteInput . Mã xoay phải r0 mang chéo và sau đó xoay mang sang trái đến r1 . Lặp lại quy trình này 8 lần, cho mỗi bit. Sau đó thanh ghi r1 được trả về hàm c dưới dạng byteOutput. Trong 8051 lõi chỉ có thể thay thế để xoay acumulator 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

PROS: Đó là dấu chân nhỏ, tốc độ cao. LƯU Ý: Đây là mã không thể tái sử dụng, nó chỉ dành cho 8051

011101101-> mang

101101110 <-carry


Mặc dù mã này có thể trả lời câu hỏi, nhưng sẽ tốt hơn nếu bao gồm một số ngữ cảnh, giải thích cách thức hoạt động và khi nào sử dụng nó. Các câu trả lời chỉ có mã không hữu ích về lâu dài.
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:

byte đảo ngược sẽ ở thanh ghi bl


3
Trong một bối cảnh khác mà có thể là một câu trả lời công bằng nhưng câu hỏi là về C hoặc C ++, không 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;
}

Như một lưu ý về phong cách, tôi thấy việc sử dụng uint8_tcho các trường 1 bit hơi xấu, vì đầu tiên có vẻ như nói rằng nó sẽ mất 8 bit nhưng sau đó ở cuối dòng xác định nó chỉ là một bit duy nhất. Tôi sẽ sử dụng, unsigned b0:1v.v.
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;
}

Vui lòng thêm một số giải thích.
zcui93

0

Tôi nghĩ điều này đủ đơn giản

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

hoặc là

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

Tôi sẽ đưa ra giải pháp của mình, vì cho đến nay tôi không thể tìm thấy bất kỳ điều gì như thế này trong các câu trả lời. Có thể nó hơi bị khai thác quá mức, nhưng nó tạo ra bảng tra cứu bằng cách sử dụng C ++ 14 std::index_sequencetrong thời gian biên dịch.

#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

Đây là một giải pháp đơn giản và dễ đọc, có thể di chuyển đến tất cả các nền tảng tuân thủ, bao gồm cả những nền tảng có 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

Tôi biết rằng câu hỏi này có niên đại nhưng tôi vẫn nghĩ rằng chủ đề này có liên quan cho một số mục đích và đây là một phiên bản hoạt động rất tốt và có thể đọc được. Tôi không thể nói rằng nó là nhanh nhất hay hiệu quả nhất, nhưng nó phải là một trong những thứ sạch nhất. Tôi cũng đã bao gồm một chức năng trợ giúp để dễ dàng hiển thị các mẫu bit. Hàm này sử dụng một số hàm thư viện tiêu chuẩn thay vì viết trình điều khiển bit của riêng bạn.

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

Và nó cho kết quả như sau.

224
11100000
7
00000111

0

Cái này đã giúp tôi với bộ mảng ma trận điểm 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
Chức năng này thực sự không hoạt động, đảo ngược của 0b11001111 phải là 0b11110011, nhưng không thành công với chức năng này. Phương pháp kiểm tra tương tự hoạt động đối với nhiều chức năng khác được liệt kê ở đây.
Dan

Vâng, cảm ơn tôi đã sửa câu trả lời của mình. Cảm ơn vì đã cho tôi biết về sai lầm của mình :)
R1S8K
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.