繁体   English   中英

在 C/C++ 中,反转字节中位顺序的最简单方法是什么?

[英]In C/C++ what's the simplest way to reverse the order of bits in a byte?

虽然有多种方法可以反转字节中的位顺序,但我很好奇开发人员实现的“最简单”方法是什么。 反过来我的意思是:

1110 -> 0111
0010 -> 0100

与 PHP 问题类似,但不是重复的。

与 C 问题类似,但不是重复的。 这个问题要求开发人员实现最简单的方法。 “最佳算法”与 memory 和 cpu 性能有关。

这应该有效:

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

首先左四位与右四位交换。 然后交换所有相邻的对,然后交换所有相邻的单个位。 这导致相反的顺序。

我认为查找表必须是最简单的方法之一。 但是,您不需要完整的查找表。

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

这相当容易编码和视觉验证。
最终,这甚至可能比全表更快。 位算法很便宜,并且该表很容易适合缓存行。

如果您谈论的是单个字节,那么查找表可能是最好的选择,除非由于某种原因您没有 256 个字节可用。

有关许多解决方案,请参阅bit twiddling hacks 从那里复制粘贴显然很容易实现。 =)

例如(在 32 位 CPU 上):

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

如果“简单实施”意味着在考试或工作面试中无需参考就可以完成的事情,那么最安全的选择可能是将比特以相反的顺序一个接一个地低效复制到另一个变量中(已在其他答案中显示) )。

由于没有人发布完整的查表解决方案,这里是我的:

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

编辑:

使用可选的 bitcount 将其转换为模板

两行:

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

或者如果您对“0b1”部分有问题:

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

“原始”是您要反转的字节。 “反转”是结果,初始化为 0。

虽然可能不可移植,但我会使用汇编语言。
许多汇编语言都有将位旋转到进位标志并将进位标志旋转到字(或字节)的指令。

算法是:

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

用于此的高级语言代码要复杂得多,因为 C 和 C++ 不支持旋转进位和从进位旋转。 进位标志必须建模。

编辑:例如汇编语言

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

我发现以下解决方案比我在这里看到的其他位摆弄算法更简单。

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

它获取字节中的每一位,并相应地移动它,从第一个到最后一个。

解释:

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

根据您的意思是“最简单的方法”,有很多方法可以反转位。


旋转反转

可能是最合乎逻辑的,包括在对第一位(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. 由于 unsigner char 的长度为 1 个字节,等于 8 位,这意味着我们将while (byte_len--)

  2. 我们首先用(b & 1)检查 b 是否在最右边; 如果是这样,我们在 r 上设置位 1 | 并通过将 r 乘以 2 与(r << 1)将其向左移动 1 位

  3. 然后我们用b >>=1除以 2 的无符号字符 b 以擦除位于变量 b 最右侧的位。 提醒一下,b >>= 1; 相当于 b /= 2;


一行反转

此解决方案归功于“编程黑客”部分中的 Rich Schroeppel

unsigned char reverse_bits3(unsigned char b)
{
    return (b * 0x0202020202ULL & 0x010884422010ULL) % 0x3ff;
}
  1. 乘法运算 (b * 0x0202020202ULL) 创建 8 位字节模式的五个单独副本,以扇出为 64 位值。

  2. AND 运算 (& 0x010884422010ULL) 选择相对于每个 10 位位组处于正确(反转)位置的位。

  3. 乘法和与运算一起从原始字节复制位,因此它们每个都只出现在 10 位集合中的一个中。 与原始字节相反的位的位置与它们在任何 10 位集合中的相对位置一致。

  4. 最后一步 (% 0x3ff) 涉及模数除以 2^10 - 1 的作用是将每组 10 位(从位置 0-9、10-19、20-29、...)合并在一起。 64 位值。 它们不重叠,因此模数除法基础的加法步骤的行为类似于 OR 运算。


分而治之的解决方案

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

这是最受欢迎的答案,尽管有一些解释,但我认为对于大多数人来说,很难想象 0xF0、0xCC、0xAA、0x0F、0x33 和 0x55 的真正含义。

它没有利用'0b',它是一个GCC 扩展,自 2014 年 12 月发布的 C++14 标准以来就包含在内,所以在这个答案可以追溯到 2010 年 4 月之后的一段时间

整数常量可以写成二进制常量,由“0”和“1”数字序列组成,前缀为“0b”或“0B”。 这在位级(如微控制器)大量运行的环境中特别有用。

请检查下面的代码片段,以记住并更好地理解我们将一半移动的解决方案:

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

注意: >> 4是因为 1 字节中有 8 位,这是一个无符号字符,所以我们要取另一半,依此类推。

我们可以轻松地将此解决方案应用于 4 个字节,只需添加两行并遵循相同的逻辑。 由于两个掩码相互补充,我们甚至可以使用 ~ 来切换位并节省一些墨水:

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

[仅限 C++] 反转任何无符号(模板)

上面的逻辑可以用一个循环来总结,该循环适用于任何类型的无符号:

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

仅限 C++ 17

您可以使用一个表来存储每个字节反向值(i * 0x0202020202ULL & 0x010884422010ULL) % 0x3ff ,通过 lambda 初始化(您需要使用g++ -std=c++1z编译它,因为它只在 C 之后工作++17),然后返回表中的值会给你相应的反转位:

#include <cstdint>
#include <array>

uint8_t reverse_bits(uint8_t n) {
        static constexpr array<uint8_t, 256> table{[]() constexpr{
                constexpr size_t SIZE = 256;
                array<uint8_t, SIZE> result{};

                for (size_t i = 0; i < SIZE; ++i)
                    result[i] = (i * 0x0202020202ULL & 0x010884422010ULL) % 0x3ff;
                return result;
        }()};

    return table[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;
}

与 asm volatile 反转

最后但并非最不重要的一点,如果最简单意味着更少的行,为什么不尝试内联汇编?

您可以通过在编译时添加-masm=intel来测试以下代码片段:

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

逐行解释:

        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

最简单的方法可能是在循环中迭代位位置:

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

对于常量 8 位输入的非常有限的情况,此方法在运行时不消耗内存或 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))

我将它用于 ARINC-429,其中标签的位顺序(字节序)与单词的其余部分相反。 标签通常是一个常数,通常是八进制。

这是我如何使用它来定义一个常量,因为规范将这个标签定义为 big-endian 205 八进制。

#define LABEL_HF_COMM MSB2LSB(0205)

更多例子:

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

您可能对std::vector<bool> (即位打包)和std::bitset感兴趣

它应该是最简单的要求。

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

其他选项可能更快。

编辑:我欠你一个使用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;
}

第二个示例需要 c++0x 扩展(用{...}初始化数组)。 使用bitsetstd::vector<bool> (或boost::dynamic_bitset )的优点是您不仅限于字节或字,还可以反转任意数量的位。

HTH

这可以是快速解决方案吗?

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

摆脱使用 for 循环的麻烦! 但请专家告诉我这是否有效且更快?

查表或

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

编辑

在此处查看可能更适合您的其他解决方案

一个更慢但更简单的实现:

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

在实施任何算法解决方案之前,请检查您使用的任何 CPU 架构的汇编语言。 您的架构可能包含处理这样的按位操作的指令(还有什么比单个汇编指令更简单?)。

如果这样的指令不可用,那么我建议使用查找表路由。 您可以编写一个脚本/程序来为您生成表,并且查找操作将比此处的任何位反转算法都快(代价是必须将查找表存储在某处)。

这个简单的函数使用掩码来测试输入字节中的每一位并将其传输到移位输出中:

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

假设您的编译器允许unsigned long long

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

在这里发现

这个是基于BobStein-VisiBone提供的

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

我真的很喜欢这个,因为编译器会自动为你处理工作,因此不需要更多的资源。

这也可以扩展到 16 位...

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

我知道这个问题已经过时了,但我仍然认为该主题与某些目的相关,这是一个运行良好且可读的版本。 我不能说它是最快或最有效的,但它应该是最干净的之一。 我还包含了一个辅助函数,用于轻松显示位模式。 此函数使用一些标准库函数,而不是编写您自己的位操纵器。

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

它给出了以下输出。

224
11100000
7
00000111

如果您使用小型微控制器并需要占用空间小的高速解决方案,这可能是解决方案。 可以将它用于 C 项目,但您需要将此文件作为汇编文件 *.asm 添加到您的 C 项目中。 说明:在 C 项目中添加此声明:

extern uint8_t byte_mirror(uint8_t);

从 C 调用这个函数

byteOutput= byte_mirror(byteInput);

这是代码,它只适用于 8051 内核。 在 CPU 寄存器r0 中是来自byteInput 的数据。 代码向右旋转r0交叉进位,然后向左旋转进位到r1 对每一位重复此过程 8 次。 然后寄存器 r1 作为 byteOutput 返回给 c 函数。 在 8051 内核中,只能旋转累加器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

优点:占用空间小,速度高缺点:不可重用代码,仅适用于 8051

011101101->携带

101101110<-携带

它简单快捷:

无符号字符反向(无符号字符 rv)
{
无符号字符 tmp=0;
如果(rv&0x01)tmp = 0x80;
if( rv&0x02 ) tmp |= 0x40;
如果( rv&0x04 ) tmp |= 0x20;
如果( rv&0x08 ) tmp |= 0x10;
if( rv&0x10 ) tmp |= 0x08;
if( rv&0x20 ) tmp |= 0x04;
if( rv&0x40 ) tmp |= 0x02;
if( rv&0x80 ) tmp |= 0x01;
返回 tmp;
}

这是与 sth 的优秀答案类似的方法,但进行了优化,最多支持 64 位整数,以及其他小的改进。

我使用 C++ 模板 function reverse_bits()让编译器针对可能传递给 function 的整数的各种字长进行优化。 function 应该在任何字长为 8 位的倍数(最多 64 位)的情况下正常工作。 如果您的编译器支持超过 64 位的字,则该方法很容易扩展。

这是一个完整的、可立即编译的示例,带有必要的头文件。 有一个方便的模板 function to_binary_str()用于创建二进制数的 std::string 表示,以及一些具有各种字长的调用来演示所有内容。

如果去掉注释和空行,function 相当紧凑且视觉上令人愉悦。

你可以在这里的 labstack 上试用它。

// this is the only header used by the reverse_bits() function
#include <type_traits>

// these headers are only used by demonstration code
#include <string>
#include <iostream>
#include <cstdint>


template<typename T>
T reverse_bits( T n ) {
    // we force the passed-in type to its unsigned equivalent, because C++ may
    // perform arithmetic right shift instead of logical right shift, depending
    // on the compiler implementation.
    typedef typename std::make_unsigned<T>::type unsigned_T;
    unsigned_T v = (unsigned_T)n;

    // swap every bit with its neighbor
    v = ((v & 0xAAAAAAAAAAAAAAAA) >> 1)  | ((v & 0x5555555555555555) << 1);

    // swap every pair of bits
    v = ((v & 0xCCCCCCCCCCCCCCCC) >> 2)  | ((v & 0x3333333333333333) << 2);

    // swap every nybble
    v = ((v & 0xF0F0F0F0F0F0F0F0) >> 4)  | ((v & 0x0F0F0F0F0F0F0F0F) << 4);
    // bail out if we've covered the word size already
    if( sizeof(T) == 1 ) return v;

    // swap every byte
    v = ((v & 0xFF00FF00FF00FF00) >> 8)  | ((v & 0x00FF00FF00FF00FF) << 8);
    if( sizeof(T) == 2 ) return v;

    // etc...
    v = ((v & 0xFFFF0000FFFF0000) >> 16) | ((v & 0x0000FFFF0000FFFF) << 16);
    if( sizeof(T) <= 4 ) return v;

    v = ((v & 0xFFFFFFFF00000000) >> 32) | ((v & 0x00000000FFFFFFFF) << 32);

    // explictly cast back to the original type just to be pedantic
    return (T)v;
}


template<typename T>
std::string to_binary_str( T n ) {
    const unsigned int bit_count = sizeof(T)*8;
    char s[bit_count+1];
    typedef typename std::make_unsigned<T>::type unsigned_T;
    unsigned_T v = (unsigned_T)n;
    for( int i = bit_count - 1; i >= 0; --i ) {
        if( v & 1 )
            s[i] = '1';
        else
            s[i] = '0';

        v >>= 1;
    }
    s[bit_count] = 0;  // string null terminator
    return s;
}


int main() {
    {
        char x = 0xBA;
        std::cout << to_binary_str( x ) << std::endl;

        char y = reverse_bits( x );
        std::cout << to_binary_str( y ) << std::endl;
    }
    {
        short x = 0xAB94;
        std::cout << to_binary_str( x ) << std::endl;

        short y = reverse_bits( x );
        std::cout << to_binary_str( y ) << std::endl;
    }
    {
        uint64_t x = 0xFEDCBA9876543210;
        std::cout << to_binary_str( x ) << std::endl;

        uint64_t y = reverse_bits( x );
        std::cout << to_binary_str( y ) << std::endl;
    }
    return 0;
}

在各种在线资源的帮助下,我自己记下了这些(不确定它们是否 100% 准确):

#                 octal       hex

# bit-orig    : 01234567    01234567:89ABCDEF
# bit-invert  : 76543210    FEDCBA98:76543210
#
# clz         : 32110000    43221111:00000000
# clo/ffs     : 00001123    00000000:11112234

位反转: [ 0 4 2 6 1 5 3 7 ] [ 0 8 4 C 2 A 6 E 1 9 5 D 3 B 7 F ]

# cto         : 01020103    01020103:01020104
# ctz         : 30102010    40102010:30102010

但这仅在您的输入已经是十六进制或八进制时才方便。

在这两种格式(8 或 16)中,您会注意到在位反射之后,所有偶数索引都在前半部分。 我还在十六进制侧突出显示了相同的 0-7,以帮助对其进行可视化。

事实上,甚至不必做一个双 substring。 查找字符串既可以用作查找所需的字母,也可以简单地将其用作索引查找。 这就是我自己反映 CRC32 多项式的方式:

(z is the input polynomial (or just any hex string)

xn = 0 ^ (x = length(z));          # initialize to numeric 0,
                                   # foo^bar in awk means 
                                   # foo-to-bar-th-power. 
                                   # same as foo**bar in other langs

 y = substr(_REF_bitREV_hex, 2);   # by pre-trimming the lookup str,
                                   # it allows skipping the + 1 at            
                                   # every cycle of the loop
 do { 
     xn *= 16
     xn += index(y, substr(z,x,1)) # keep in mind that this is awk syntax, 
                                   # where strings start at index-1, not zero.
 } while ( 1 < x—- );

使用基于十六进制或八进制的方法的一个优点是它允许任何长度的输入,无需使用适当的 BigInteger 或 BigFloat 库即可实现任意精度操作。 为此,您必须将 substring 取出新的数字/字母并进行字符串连接,而不是每次都简单地添加。

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

反转字节将在bl寄存器

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

我觉得这很简单

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

或者

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));
}
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
<------>
#<---->#
##<-->##
###<>###
#include <stdio.h>
#include <stdlib.h>

#define BIT0 (0x01)
#define BIT1 (0x02)
#define BIT2 (0x04)
#define BIT3 (0x08)
#define BIT4 (0x10)
#define BIT5 (0x20)
#define BIT6 (0x40)
#define BIT7 (0x80)

#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c\n"

#define BITETOBINARY(byte) \
(byte & BIT7 ? '1' : '0'), \
(byte & BIT6 ? '1' : '0'), \
(byte & BIT5 ? '1' : '0'), \
(byte & BIT4 ? '1' : '0'), \
(byte & BIT3 ? '1' : '0'), \
(byte & BIT2 ? '1' : '0'), \
(byte & BIT1 ? '1' : '0'), \
(byte & BIT0 ? '1' : '0') \

#define BITETOBINARYREVERSE(byte) \
(byte & BIT0 ? '1' : '0'), \
(byte & BIT1 ? '1' : '0'), \
(byte & BIT2 ? '1' : '0'), \
(byte & BIT3 ? '1' : '0'), \
(byte & BIT4 ? '1' : '0'), \
(byte & BIT5 ? '1' : '0'), \
(byte & BIT6 ? '1' : '0'), \
(byte & BIT7 ? '1' : '0') \



int main()
{

    int i,j,c;

    i |= BIT2|BIT7;

    printf("0x%02X\n",i);    

    printf(BYTE_TO_BINARY_PATTERN,BITETOBINARY(i));

    printf("Reverse");

    printf(BYTE_TO_BINARY_PATTERN,BITETOBINARYREVERSE(i));

   return 0;
}

我会加入我的解决方案,因为到目前为止我在答案中找不到这样的东西。 它可能有点过度设计,但它在编译时使用 C++14 std::index_sequence生成查找表。

#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

这是一个简单易读的解决方案,可移植到所有符合标准的平台,包括那些具有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;
}

这个帮助我处理了 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;
}

关于所有 256 个字节的位反射查找表,只需几个循环,您就可以非常快速地从头开始生成它(从十六进制到字节的映射应该很简单):

 #  gawk profile,created Tue Jul 26 22:22:18 2022

 #  BEGIN rule(s)
    BEGIN {
 1      print initREF()
    }
 # Functions,listed alphabetically
 1  function initREF(_,__,___,____,_____,______,_______)
    {
 1     ______=(_+=_^=_<_)^++_-(_=\
           __=(+(___=____="."))(_~_))

 1     gsub(___,"&\\&",_)
 1     _____[_<_]
       _____[ +_]

 7     do {
 7         gsub(___,_,__)
 7         ___=___""____
       } while (—______)

 1     gsub("....","=&", __)
 1     _+=_^=_<_;_______=__;

 2     for(______ in _____) { ______*=_*_*_
 4        for(____ in _____) {  ____*=_+_
 8           for(___ in _____) { ___*= +_
16              for(__ in _____) {

16                    gsub("=" (_<______)(_<____) (_~___)__,
                   sprintf("%X", __+___+____+______),_______)
        } } } }

 1      __=_______
 1       _="[^ ]+[ ]"
 1      gsub(".",_,_)

 1      gsub("..","0x&, ",__)
 1      gsub((_)_,  "&\n",__)
 1      sub("[\1-@]+$","",__)
 1            gsub(" ","",__)

 1      return __
    }

|

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

作为开发人员,这是我最容易记住的方法:

unsigned char   reverse_bits(unsigned char octet)
    {
        return  (((octet >> 0) & 1) << 7) | \
                (((octet >> 1) & 1) << 6) | \
                (((octet >> 2) & 1) << 5) | \
                (((octet >> 3) & 1) << 4) | \
                (((octet >> 4) & 1) << 3) | \
                (((octet >> 5) & 1) << 2) | \
                (((octet >> 6) & 1) << 1) | \
                (((octet >> 7) & 1) << 0);
    }
 

如果有人想知道如何使用编程技巧部分中Rich Schroeppel的解决方案来反转uint16_tuint32_t

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

它可以通过在 X86/X64 平台(小端)上的以下分而治之代码来完成:

uint16_t reverse_bits3(uint16_t s)
{
    uint8_t &b0 = ((uint8_t*)&s)[0];
    uint8_t &b1 = ((uint8_t*)&s)[1];
    return (((uint16_t)reverse_bits3(b0))<<8) + reverse_bits3(b1);
}

uint32_t reverse_bits3(uint32_t u)
{
    uint16_t &s0 = ((uint16_t*)&u)[0];
    uint16_t &s1 = ((uint16_t*)&u)[1];
    return (((uint16_t)reverse_bits3(s0))<<16) + reverse_bits3(s1);
}
#define BITS_SIZE 8

int
reverseBits ( int a )
{
  int rev = 0;
  int i;

  /* scans each bit of the input number*/
  for ( i = 0; i < BITS_SIZE - 1; i++ )
  {
    /* checks if the bit is 1 */
    if ( a & ( 1 << i ) )
    {
      /* shifts the bit 1, starting from the MSB to LSB
       * to build the reverse number 
      */
      rev |= 1 << ( BITS_SIZE - 1 ) - i;
    }
  }

  return rev;
}

这个怎么样...

int value = 0xFACE;

value = ((0xFF & value << 8) | (val >> 8);

这是一个老问题,但似乎没有人表现出明确的简单方法(最接近edW)。 我使用C#来测试这个,但是在这个例子中没有任何东西在C中无法轻易完成。

void PrintBinary(string prompt, int num, int pad = 8)
{
    Debug.WriteLine($"{prompt}: {Convert.ToString(num, 2).PadLeft(pad, '0')}");
}

int ReverseBits(int num)
{
    int result = 0;
    int saveBits = num;
    for (int i = 1; i <= 8; i++)
    {
        // Move the result one bit to the left
        result = result << 1;

        //PrintBinary("saveBits", saveBits);

        // Extract the right-most bit
        var nextBit = saveBits & 1;

        //PrintBinary("nextBit", nextBit, 1);

        // Add our extracted bit to the result
        result = result | nextBit;

        //PrintBinary("result", result);

        // We're done with that bit, rotate it off the right
        saveBits = saveBits >> 1;

        //Debug.WriteLine("");
    }

    return result;
}

void PrintTest(int nextNumber)
{
    var result = ReverseBits(nextNumber);

    Debug.WriteLine("---------------------------------------");
    PrintBinary("Original", nextNumber);
    PrintBinary("Reverse", result);
}

void Main()
{
    // Calculate the reverse for each number between 1 and 255
    for (int x = 250; x < 256; x++)
        PrintTest(x);
}

如何用0xFF对字节进行异或。

unsigned char reverse(unsigned char b) { b ^= 0xFF; return b; }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM