【问题标题】:In C/C++ what's the simplest way to reverse the order of bits in a byte?在 C/C++ 中,反转字节中位顺序的最简单方法是什么?
【发布时间】:2010-04-08 19:32:07
【问题描述】:

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

1110 -> 0111
0010 -> 0100

这与this PHP 问题相似,但不是重复。

这类似于 this C 问题,但不是重复的。这个问题要求开发人员实现最简单的方法。 “最佳算法”关注内存和 CPU 性能。

【问题讨论】:

  • 使用内联汇编。更好的是,将该功能放入单独的翻译单元中。每个目标平台都有一个汇编语言模块。让构建过程选择模块。
  • @Andreas 最简单的实现

标签: c++ c bit-manipulation


【解决方案1】:

这应该可行:

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

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

【讨论】:

  • 合理的简短和快速,但并不简单。
  • 这种方法还干净地概括为执行字节交换以实现字节序。
  • 不是最简单的方法,但我喜欢 +1。
  • 是的,很简单。这是一种分而治之的算法。太棒了!
  • 聪明的解决方案,但您应该指出,这种方法仅适用于 CHAR_BIT 是 2 的精确幂的目标。
【解决方案2】:

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

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

这很容易编码和视觉验证。
最终,这甚至可能比一张完整的桌子还要快。位算法很便宜,而且表很容易放在缓存行上。

【讨论】:

  • 这是降低表格解决方案复杂性的绝佳方法。 +1
  • 不错,但会导致缓存未命中。
  • @kotlinski:什么会导致缓存未命中?我认为小表版本可能比大表版本缓存效率更高。在我的 Core2 上,缓存行的宽度为 64 字节,整个表将跨越多行,而较小的表很容易容纳一个单行。
  • @kotlinski:对于缓存命中或替换策略,临时位置比地址位置更重要
  • @Harshdeep:考虑表条目的二进制编码索引。索引 b0000(0) -> b0000(0x0) 无聊; b0001(1) -&gt; b1000(0x8)b0010(2) -&gt; b0100(0x4)b1010(10) -&gt; b0101(0x5)。看到图案了吗?它很简单,您可以在脑海中计算它(如果您可以阅读二进制文件,否则您需要纸来计算)。至于反转 8 位整数与反转 4 位部分然后交换它们的飞跃相同;我声称经验和直觉(或魔法)。
【解决方案3】:

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

【讨论】:

  • 如果我们谈论的是无需复制现成解决方案即可轻松实现的东西,创建查找表仍然需要另一种解决方案。 (当然也可以手动完成,但容易出错且耗时……)
  • 如果忽略回文数,您可以将数组压缩到 256 字节以下。
  • @wilhelmtell - 你需要一张表才能知道哪些是回文。
  • @wilhelmtell:好吧,要编写脚本,仍然需要另一种解决方案,这就是我的观点——查找表易于使用但创建起来并不简单。 (除了复制一个现成的查找表,但也可以复制任何解决方案。)例如,如果认为“最简单”的解决方案可以在考试或面试中写在纸上,我不会开始手工制作一个查找表并让程序执行它已经包含一个不同的解决方案(这比单独包含它和表的解决方案更简单)。
  • @Arkku 我的意思是编写一个脚本,输出前 256 个字节的表及其反向映射。是的,你又开始编写 reverse 函数了,但现在使用你最喜欢的脚本语言,它可以随心所欲——一旦完成并运行一次,你就会把它扔掉。将脚本输出为 C 代码,甚至:unsigned int rtable[] = {0x800, 0x4000, ...};。然后扔掉剧本,忘记你曾经拥有过它。它的编写速度比等效的 C++ 代码快得多,而且它只会运行一次,因此您的 C++ 代码的运行时间为 O(1)。
【解决方案4】:

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

例如(在 32 位 CPU 上):

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

如果“实现简单”是指在考试或面试中无需参考即可完成的操作,那么最安全的选择可能是将位以相反的顺序逐个复制到另一个变量中(已经显示在其他答案中)。

【讨论】:

  • 来自您的 URL:32 位 CPU:b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
  • @Joshua:这也是我个人的最爱。需要注意的是(如链接页面所述),它需要分配或转换为 uint8_t ,否则高位会有垃圾。
  • x86 指令 tzcntbzhi 可用于首先计算 floor(log_2(x + 2)) 并将从 tzcnt 计算的 MSB 上方的位分别归零,因此在一次性案例。在 AMD Ryzen Family 17h 上,tzcnt 和 bzhi 都是一个周期。它们在具有 2013 年 CPU 中发布的 x86 BMI 扩展的 AMD 和 Intel 平台上均受支持。
【解决方案5】:

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

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

【讨论】:

【解决方案6】:
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;
}

编辑:

将其转换为带有可选位数的模板

【讨论】:

  • @nvl - 已修复。我开始将其构建为模板,但中途决定不这样做...太多 &gt &lt
  • 对于额外的恋童癖,请将sizeof(T)*8 替换为sizeof(T)*CHAR_BITS
  • @andand 对于额外的额外吊坠,将sizeof(T)*CHAR_BIT 替换为std::numeric_limits&lt;T&gt;::digits(差不多4年后的迂腐)。
  • 应该是CHAR_BIT,而不是CHAR_BITS
  • 应该是 rv = (rv
【解决方案7】:

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


旋转反转

可能是最合乎逻辑的,包括旋转字节,同时在第一位 (n &amp; 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 &amp; 1)检查b是否在最右边; 如果是这样,我们使用| 在 r 上设置第 1 位,然后将其向左移动 1 位 将 r 乘以 2 与 (r &lt;&lt; 1)

  3. 然后我们用 b &gt;&gt;=1 将我们的 unsigned char b 除以 2 来擦除 位于变量 b 最右边的位。 提醒一下,b >>= 1;相当于 b /= 2;


在一行中反转

此解决方案归属于Rich Schroeppel in the Programming Hacks section

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

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

  3. 乘法和 AND 操作一起复制原始位 字节,因此它们每个都只出现在 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 extension,自 2014 年 12 月发布的 C++14 标准起就包含在内,因此在此答案可追溯到 2010 年 4 月之后一段时间

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

请检查下面的代码 sn-ps 以更好地记住和理解我们一半移动的解决方案:

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

注意:&gt;&gt; 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

您可以使用存储 reverse value of each byte(i * 0x0202020202ULL &amp; 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];
}

main.cpp

加入上述功能,自己试试吧:

#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 来测试下面的代码sn-p:

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

【讨论】:

  • Section [C++ Only] Reverse Any Unsigned (Template):请注意,如果类型模板参数 T 是有符号的,则掩码将表示负数,并且将
【解决方案8】:

两行:

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

"original" 是您要反转的字节。 "reversed" 是结果,初始化为 0。

【讨论】:

    【解决方案9】:

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

    算法是:

    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.
    

    【讨论】:

    • 我认为这个答案与简单相反。不可移植、汇编,并且足够复杂,可以用伪代码而不是实际的汇编来编写。
    • 这很简单。我将其放入伪代码中,因为汇编助记符特定于一种处理器,并且有很多品种。如果您愿意,我可以编辑它以显示简单的汇编语言。
    • 可以查看编译器优化是否简化为合适的汇编指令。
    【解决方案10】:

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

    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
    

    【讨论】:

    • 漂亮!到目前为止我最喜欢的。
    • 这当然很简单,但需要指出的是执行时间是O(n)而不是O(log₂ n),其中n是位数(8、16、32、 64 等)。
    【解决方案11】:

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

    unsigned char reverse(unsigned char c) {
       int shift;
       unsigned char result = 0;
       for (shift = 0; shift < CHAR_BIT; shift++) {
          if (c & (0x01 << shift))
             result |= (0x80 >> shift);
       }
       return result;
    }
    

    【讨论】:

    • 它是 CHAR_BIT,没有 's'
    • 假设char 有8 位,为什么还要使用CHAR_BIT
    【解决方案12】:

    对于非常有限的常量、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));
    

    【讨论】:

      【解决方案13】:

      您可能对std::vector&lt;bool&gt;(即位压缩)和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&lt;bool&gt;的解决方案

      #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&lt;bool&gt;(或boost::dynamic_bitset)的优势在于您不限于字节或字,而是可以反转任意数量的位。

      HTH

      【讨论】:

      • 这里的 bitset 比 pod 更简单吗?显示代码,否则不显示。
      • 实际上,我认为代码会反转bitset,然后将其反转回原来的样子。将 ii != size(); 更改为 ii 会做得更好 =)
      • (@viktor-sehr 不,不会,rev 与 bs 不同)。无论如何,我自己不喜欢这个答案:我认为这是二进制算术和移位运算符更适合的情况。它仍然是最容易理解的。
      • std::vector&lt;bool&gt; b = { ... }; std::vector&lt;bool&gt; rb ( b.rbegin(), b.rend()); 怎么样 - 直接使用反向迭代器?
      • @MSalters 我喜欢它的不变性。
      【解决方案14】:

      表查找或

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

      编辑

      查看here 寻找可能更适合您的其他解决方案

      【讨论】:

        【解决方案15】:

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

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

        【讨论】:

          【解决方案16】:

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

          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 循环的麻烦!但是请专家告诉我这是否有效且更快?

          【讨论】:

          • 执行时间为 O(n) 而不是 O(log₂ n),其中 n 是位数(8、16、32、64 等)。请参阅其他地方以获取在 O(log₂ n) 时间内执行的答案。
          【解决方案17】:

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

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

          【讨论】:

          • 值得注意的是,现代 ARM 和 AArch64 具有 rbit。 (是的,如果支持,它是高效的,而不是微编码的)。 x86 没有,尽管您可以使用 SSSE3 pshufb 作为半字节的查找表,并行位反转最多 16 个字节。我忘记了 RISC-V extension-b(位操作)是否有位反转。
          【解决方案18】:

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

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

          【讨论】:

          • 面具应该是未签名的对不起。
          【解决方案19】:

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

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

          Discovered here

          【讨论】:

            【解决方案20】:

            这个是基于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 ) 
            

            【讨论】:

            • 我会将b 放在括号中,以防它是一个比单个数字更复杂的表达式,并且可能还将宏重命名为REVERSE_BYTE 作为您可能不想要的提示在那里有一个更复杂的(运行时)表达式。或者使它成为一个内联函数。 (但总的来说,我喜欢这很简单,你可以很容易地从记忆中做到这一点,而且出错的可能性很小。)
            【解决方案21】:

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

            【讨论】:

              【解决方案22】:

              如果您使用小型微控制器并需要占用空间小的高速解决方案,这可能是解决方案。可以将它用于 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

              【讨论】:

              • 虽然这段代码可以回答这个问题,但最好包含一些上下文,解释它是如何工作的以及何时使用它。从长远来看,纯代码的答案没有用处。
              【解决方案23】:

              简单快捷:

              unsigned char reverse(unsigned char rv)
              {
              无符号字符 tmp=0;
              if( rv&0x01 ) tmp = 0x80;
              if( rv&0x02 ) tmp |= 0x40;
              if( rv&0x04 ) tmp |= 0x20;
              if( 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;
              }

              【讨论】:

              • 这比获得最多票数的答案更慢且不够优雅。
              • 真的很不幸。当您评论我编写的代码时,您的信息似乎很肤浅。如果您对代码执行时间和代码组装方式有一点经验,您就会知道我的代码更快。使用最快的代码是查找表 (LUT)。但问题是代码大小的增加。下一步是使用汇编语言(向右/向左移动 w/cry),但这取决于机器,编写起来会更难、更具体。另一方面使用循环会增加代码的执行时间,但是代码在内存中的大小会很小。
              • 如果你想参与讨论,先去研究一下汇编,看看执行这两​​个程序需要多少CPU时钟周期(你可以通过汇编编译)。我刚成为会员,看看 StackOverflow 网站的其他会员有多少信息/经验会很有趣。
              • 欢迎来到 SO。如果您认为是我否决了您的答案,那您就错了。只有当它已经在缓存中时,查找表才很快,最好是 L1。我提到的解决方案很容易扩展到超过一个字节的实体。执行时间取决于许多因素,包括目标架构、指令流水线的状态和缓存。在汇编指令的数量方面,我们去:godbolt.org/z/sadrhjn9a 另见aldeid.com/wiki/X86-assembly/Instructions/rol 和基准:quick-bench.com/q/Hs10lka2Xj1Y9SyQkgwSkeJ1M44
              • 先生。 zikoz 在您的链接中,当我的代码进行优化时有两倍的速度!! link先测试代码再评论不是更好吗?
              【解决方案24】:

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

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

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

              如果去掉cmets和空行,功能还是比较紧凑的,视觉上也很赏心悦目。

              您可以在 labstack here 上试用。

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

              【讨论】:

                【解决方案25】:

                我会加入我的解决方案,因为到目前为止我在答案中找不到类似的东西。它可能有点过度设计,但它在编译时使用 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

                【讨论】:

                  【解决方案26】:

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

                  #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
                  

                  【讨论】:

                    【解决方案27】:

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

                    【讨论】:

                    • 这个函数实际上不起作用,0b11001111的反面应该是0b11110011,但是这个函数失败了。相同的测试方法适用于此处列出的许多其他功能。
                    • 是的,谢谢我更正了我的答案。感谢您让我知道我的错误:)
                    【解决方案28】:

                    在各种在线资源的帮助下,我自己记下了这些(不确定它们是否 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,以帮助对其进行可视化。

                    事实上,甚至不必做一个双子串。查找字符串既可以用作查找所需的字母,也可以简单地将其用作索引查找。这就是我自己反映 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 库即可实现任意精度操作。为此,您必须对新的数字/字母进行子串化并进行字符串连接,而不是每次都简单地添加。

                    【讨论】:

                      【解决方案29】:
                      #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;
                      }
                      

                      【讨论】:

                        【解决方案30】:
                          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 寄存器中

                        【讨论】:

                        • 在其他情况下,这可能是一个公平的答案,但问题是关于 C 或 C++,而不是 asm ...
                        猜你喜欢
                        • 1970-01-01
                        • 2013-01-21
                        • 2012-06-01
                        • 2012-10-11
                        • 2010-09-14
                        • 2016-07-12
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多