按位反转整数而不使用循环

时间:2014-02-02 14:10:25

标签: algorithm

我想编写一个反转整数位的程序 Ex 11000101至10100011
我知道如何使用循环来解决这个问题,但我遇到了使用字节移位来解决这个问题的解决方案:

num>>4|num<<4

我不明白这是如何运作的。有人可以解雇我吗?

5 个答案:

答案 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位:

<00> 0000 1100

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