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


110

尽管有多种方法可以反转字节中的位顺序,但我对开发人员实现“最简单”的方法感到好奇。通过反转,我的意思是:

1110 -> 0111
0010 -> 0100

这类似于但不重复 PHP问题。

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


使用内联汇编。最好将函数放在单独的翻译单元中。每个目标平台都有一个汇编语言模块。让构建过程选择模块。
Thomas Matthews 2010年

@Andreas最简单的实现
森2010年

Answers:


102

如果您正在谈论单个字节,那么最好进行表查找,除非出于某种原因您没有256个字节可用。


12
如果我们谈论的是易于实现而无需复制现成的解决方案的事情,则创建查找表仍然需要其他解决方案。(当然可以手动完成,但这容易出错并且很耗时……)
Arkku 2010年

7
如果忽略回文,可以将数组压缩为少于256个字节。
wilhelmtell

8
@wilhelmtell-您需要一张桌子才能知道哪些是回文。
Mark Ransom

6
@wilhelmtell:好吧,编写脚本需要另一种解决方案,这就是我的观点–查找表易于使用,但创建起来并不简单。(除非复制一个现成的查找表,但是复制一个解决方案也是一样。)例如,如果认为“最简单”的解决方案可以在考试或面试中写在纸上,那么我不会开始手动创建查找表并制作程序来完成它已经包括了另一种解决方案(与包含表和表的解决方案相比,单独解决方案要简单得多)。
Arkku

4
@Arkku的意思是编写一个脚本,该脚本输出前256个字节的表及其反向映射。是的,您将回到编写反向函数的位置,但是现在使用您喜欢的脚本语言编写它,并且它可以随您的需要而讨厌-一旦完成,您将立即将其丢弃,并运行一次。将脚本输出作为C代码,甚至:unsigned int rtable[] = {0x800, 0x4000, ...};。然后丢掉脚本,忘记您曾经拥有过的脚本。与等效的C ++代码相比,编写起来要快得多,并且它只会运行一次,因此您可以在C ++代码中获得O(1)运行时。
wilhelmtell

227

这应该工作:

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

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


26
合理地简短快速,但不简单。
Mark Ransom'4

3
这种方法还可以概括地执行字节交换以提高字节序。
Boojum 2010年

2
不是最简单的方法,但我喜欢+1。
森2010年

7
是的,很简单。这是一种分而治之的算法。优秀的!
keewic

它比下面@Arkku建议的方法快吗?
2013年

122

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

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

这相当容易编码和可视化验证。
最终,这甚至可能比完整表更快。二进制位便宜,并且该表很容易放在高速缓存行上。


10
这是降低表解决方案复杂性的绝佳方法。+1
e.James 2010年

3
很好,但是会给您带来缓存未命中的情况。
约翰·科特林斯基

7
@kotlinski:什么会导致缓存未命中?我认为小表版本可能比大表版本具有更高的缓存效率。在我的Core2上,缓存行为64字节宽,整个表将跨越多行,而较小的表很容易容纳一行。
deft_code 2011年

4
@kotlinski:对于缓存命中或替换策略而言,时间局部性比地址局部性更重要
CFI 2013年

6
@Harshdeep:考虑表条目的二进制编码索引。索引b0000(0)-> b0000(0x0)无聊; b0001(1) -> b1000(0x8)b0010(2) -> b0100(0x4)b1010(10) -> b0101(0x5)。看到图案了吗?它很简单,您可以在脑海中进行计算(如果您可以读取二进制文件,否则将需要纸来计算)。关于跨越一个8位整数的反转与将4位部分反转然后交换它们的相同之处;我要求经验和直觉(或魔法)。
deft_code 2014年

46

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

例如(在32位CPU上):

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

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


1
从您的URL:32位CPU:b =((b * 0x0802LU&0x22110LU)|(b * 0x8020LU&0x88440LU))* 0x10101LU >> 16;
约书亚

1
@约书亚:那也是我个人的最爱。注意事项(如链接页上所述)是需要将其分配或强制转换为uint8_t,否则高位将有垃圾。
Arkku

41

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

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
有用,谢谢。似乎我较慢的转换方法限制了嵌入式应用程序的性能。将表放在PIC上的ROM中(带有rom关键字)。
flend


25
template <typename T>
T reverse(T n, size_t b = sizeof(T) * CHAR_BIT)
{
    assert(b <= std::numeric_limits<T>::digits);

    T rv = 0;

    for (size_t i = 0; i < b; ++i, n >>= 1) {
        rv = (rv << 1) | (n & 0x01);
    }

    return rv;
}

编辑:

使用可选的位计数将其转换为模板


@nvl-已修复。我开始建设它作为一个模板,但一直到没有那么......太多的&GT做&LT差不多决定
andand

如需更多的脚踏功能,请替换sizeof(T)*8sizeof(T)*CHAR_BITS
Pillsy

6
@andand如需额外的吊坠,请替换sizeof(T)*CHAR_BITstd::numeric_limits<T>::digits(将近4年的修脚)。
Morwenn 2014年

1
应该是CHAR_BIT,不是CHAR_BITS
许妮

1
它应该是rv =(rv << 1)| (n&0x01);
Vignesh

16

两行:

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

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


14

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

该算法是:

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.

7
我认为这个答案与简单相反。非便携式,汇编和复杂的,足以用伪代码代替实际的汇编来编写。
deft_code 2010年

3
这很简单。之所以将其放入伪代码中,是因为汇编助记符特定于某种处理器,并且那里有很多品种。如果您愿意,我可以对其进行编辑以显示简单的汇编语言。
Thomas Matthews 2010年

可以看到编译器优化是否简化为合适的汇编指令。
闪亮的

12

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

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

美丽!到目前为止我最喜欢的。
Nick Rameau

这当然很简单,但是应该指出,执行时间是O(n)而不是O(log 2 n),其中n是位数(8、16、32、64等)。
Todd Lehman

10

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

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”
ljrk

CHAR_BIT假设char有8位,为什么要使用?
chqrlie

6

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

5

您可能对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扩展名(以初始化数组{...})。使用a bitset或a std::vector<bool>(或a boost::dynamic_bitset)的优点是您不限于字节或字,而是可以反转任意数量的位。

高温超导


比特集比这里的Pod简单吗?显示代码,否则不显示。
wilhelmtell

实际上,我认为代码将反转位集,然后将其反转回到其原始位置。改变ii!= size(); ii <size()/ 2; 并且会做得更好=)
Viktor Sehr 2010年

(@ viktor-sehr不,它不会,rev与bs不同)。无论如何,我自己都不喜欢这个答案:我认为这是二进制算术和移位运算符更适合的情况。它仍然是最容易理解的。
baol 2010年

怎么样std::vector<bool> b = { ... }; std::vector<bool> rb ( b.rbegin(), b.rend()); -直接使用反向迭代器?
MSalters

@MSalters我喜欢它的不变性。
baol 2017年

5

有多种方法可以反转位,具体取决于您所说的“最简单的方法”。


旋转反转

可能最合乎逻辑的是,在对第一位应用掩码的同时旋转字节(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)由于取消签名者char的长度为1个字节,等于8位,这意味着我们将扫描每个位 while (byte_len--)

2)我们首先用来检查b是否在最右边(b & 1)。如果是这样,我们将r的第|1位设置为,然后将r乘以2,将其向左移动1位(r << 1)

3)然后我们将无符号char b除以2,b >>=1以擦除位于变量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)乘法和与运算与AND运算一起复制原始字节中的位,因此它们每个仅出现在10位集中的一组中。与原始字节相反的位的位置与其在任何10位集中的相对位置一致。

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


分而治之

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的含义。

它没有利用GCC扩展名 '0b'的优势,自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;
}

自己尝试使用以上功能:

#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

3

查表或

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

编辑

看看这里为可能更好地为你工作的其他解决方案


3

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

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

这可以快速解决吗?

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 2 n),其中n是位数(8、16、32、64等)。在O(log²n)时间执行的答案请参见其他地方。
托德·雷曼

2

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

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


2

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

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

面罩应该未签名,对不起。
luci88filter '18

1

这一个是基于一个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以暗示您可能不想在那里有一个更复杂的(运行时)表达式。或使其成为内联函数。(但是总的来说,我很喜欢这样做,因为它很简单,可以很容易地从内存中完成,几乎没有出错的机会。)
Arkku,

1

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

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

在这里发现


1

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

extern uint8_t byte_mirror(uint8_t);

从C调用此函数

byteOutput= byte_mirror(byteInput);

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


尽管此代码可以回答问题,但最好包含一些上下文,解释其工作方式和使用时间。从长远来看,纯代码答案没有用。
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:

反向字节将在bl寄存器中


3
在其他情况下,这可能是一个合理的答案,但问题是有关C或C ++的问题,而不是关于asm ...
jadsq '17

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

作为风格上的注释,我发现对uint8_t1位字段的使用有点丑陋,因为似乎首先要说它将占用8位,但是在该行的末尾将其定义为仅一位。我会使用unsigned b0:1
。– 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;
}

请添加一些说明。
zcui93 '16

0

我觉得这很简单

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

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

我会加入解决方案,因为到目前为止我在答案中都找不到这样的东西。也许有些工程过度,但是它std::index_sequence在编译时使用C ++ 14生成了查找表。

#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

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

我知道这个问题已经过时,但是我仍然认为该主题对于某些目的而言是相关的,这是一个工作得很好并且易于阅读的版本。我不能说这是最快或最有效的,但它应该是最干净的一种。我还提供了一个帮助程序功能,可轻松显示位模式。该函数使用一些标准库函数,而不是编写自己的位操纵器。

#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

0

这帮助我处理了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
此功能实际上不起作用,0b11001111的反写应为0b11110011,但此功能失败。相同的测试方法适用于此处列出的许多其他功能。
丹,

是的,谢谢我更正了我的回答。感谢您让我知道我的错误:)
R1S8K
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.