我想编写一个反转整数位的程序
Ex 11000101至10100011
我知道如何使用循环来解决这个问题,但我遇到了使用字节移位来解决这个问题的解决方案:
num>>4|num<<4
我不明白这是如何运作的。有人可以解雇我吗?
答案 0 :(得分:15)
这并没有反转这些位,它正在交换nybbles(4位单位)。换句话说,它将转向:
1100 0101 (abcd efgh)
成:
0101 1100 (efgh abcd)
并且只有当数据类型实际为8位时才会这样做(否则num << 4
会将最右边的8位放置一些。更安全的方法是确保在移位之前清除所有其他位:
((num & 0xf0) >> 4) | ((num & 0x0f) << 4)
有关按位运算符如何工作的精确度,请参阅this excellent answer。
完全位反转的等价表达式hgfe dcba
是相当可怕的:
((num & 0x01) << 7)
| ((num & 0x02) << 5)
| ((num & 0x04) << 3)
| ((num & 0x08) << 1)
| ((num & 0x10) >> 1)
| ((num & 0x20) >> 3)
| ((num & 0x40) >> 5)
| ((num & 0x80) >> 7)
提取并移位八位中的每一位。
还有一些优化可以在一次操作中处理非连续位组,例如:
num = ((num & 0xf0) >> 4) | ((num & 0x0f) << 4) // abcdefgh -> efghabcd
num = ((num & 0xcc) >> 2) | ((num & 0x33) << 2) // efghabcd -> ghefcdab
num = ((num & 0xaa) >> 1) | ((num & 0x55) << 1) // ghefcdab -> hgfedcba
这些工作通过抓取非连续位并向左或向右移动它们,掩码值显示哪些位受到影响:
0xf0, 0x0f -> 1111-0000, 0000-1111, shift by 4
0xcc, 0x33 -> 1100-1100, 0011-0011, shift by 2
0xaa, 0x55 -> 1010-1010, 0101-0101, shift by 1
每行中的第一个位掩码提取向右移位的位,第二个位抓取位向左移位。然后重新组合这两个结果。以第二个为例,假设您事先得到了abcdefgh
位,并且您评估了表达式((num & 0xcc) >> 2) | ((num & 0x33) << 2)
:
(num&0xcc)>>2 (num&0x33)<<2
------------- -------------
abcdefgh abcdefgh
11001100 00110011 'and' with mask
-------- --------
ab00ef00 00cd00gh
00ab00ef cd00gh00 shift right/left
\ /
00ab00ef
cd00gh00 'or' them together
--------
cdabghef
因此,您可以看到位提取,移位和重组的操作如何允许您反转值内部分的顺序:
ab cd ef gh
\ / \ /
X X
/ \ / \
cd ab gh ef
我建议您尝试使用第三个操作num = ((num & 0xaa) >> 1) | ((num & 0x55) << 1)
进行类似的实验,您会看到它也按预期运行,在每组中反转单个位。
答案 1 :(得分:2)
如前所述,它并没有扭转比特,只是半字节。但是你可以将一个真正的位反转分解为类似的东西,就像这样(未经测试):
// swap nibbles
x = x >> 4 | x << 4;
// swap groups of 2
x = (x >> 2) & 0x33 | (x & 0x33) << 2;
// swap groups of 1
x = (x >> 1) & 0x55 | (x & 0x55) << 1;
您当然可以扩展该模式以反转更多数字。每增加一步,它反转的数字的宽度加倍,这使得这种方法比将每一位逐个移动到其位置更具可扩展性。要反转一个64位的数字,这个算法只需要6“步”(除了1步以外的所有操作都是5次操作,所以大约30次操作),而逐位算法需要64步(每步3次操作,除了一步,所以191个操作)。
如果需要,您可以重新排序步骤。
答案 2 :(得分:1)
1100 0101
>> 4
表示我们将位向右移动4位:
<< 4
表示我们将位向左移动了4位:
0101 0000
|
是或操作:
0000 1100
↓↓↓↓ ↓↓↓↓ |
0101 0000
---------
0101 1100
如您所见,程序更改了4个位的两个块。
答案 3 :(得分:0)
如果您的整数在0..255
范围内,则可以将所有256
反转值制成表格。 (你可以将只是半字节列表,但这将是一个穷人的解决方案。)
对于较大的值,使用该表交换并同时反转字节。您可以通过蒙版和移位的组合进行交换,或者将四个字节映射到int。
答案 4 :(得分:0)
用于反转整数中位的顺序的递归方法 - 使用要反转的值和值的宽度调用。
procedure int REVERSEBITS( int VALUE; int WIDTH ) {
if WIDTH==1 then {
return VALUE;
} else {
// intermediate values may help make the algorithm more understandable
int HalfWidth = WIDTH >> 1; // number of bits to be swapped at this level
int HalfMask = HalfWidth-1; // mask for left or right half of the value
int RightHalfValue = VALUE & HalfMask; // extract right half from value
int LeftHalfValue = (VALUE >> HalfWidth) & HalfMask; // extract left half
// call reversing function on the two halves separately then swap the results
return (REVERSEBITS(RightHalfValue, HalfWidth) << HalfWidth)
| REVERSEBITS(LeftHalfValue, HalfWidth);
}
}
或者,通过将所有中间值替换为其定义来缩短函数(优化编译器通常会生成相同的代码)
procedure int REVERSEBITS( int VALUE; int WIDTH ) {
if WIDTH==1 then {
return VALUE;
} else {
return (REVERSEBITS((VALUE & ((WIDTH>>1)-1)), (WIDTH>>1)) << (WIDTH>>1))
| REVERSEBITS(((VALUE >> (WIDTH>>1)) & ((WIDTH>>1)-1)), (WIDTH>>1));
}
}
通过更改功能定义稍微简化功能代码,即调用要反转的值并将值宽度调整为HALF
procedure int REVERSEBITS( int VALUE; int WIDTH ) {
if WIDTH==0 then {
return VALUE;
} else {
return (REVERSEBITS((VALUE & (WIDTH-1)), (WIDTH >> 1)) << WIDTH)
| REVERSEBITS(((VALUE >> WIDTH) & (WIDTH-1)), (WIDTH >> 1));
}
}