首先,对原始问题的观察,以及您提到的后续扩展:
您描述的“移动一点”操作实际上是连续位范围的旋转。在您的示例中,您将 1-5 位(含)向左旋转一位:
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+
| 0 | 1 | 0<--1<--1<--0<--1 | 0 | -> | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
+---+---+-|-+---+---+---+-^-+---+ +---+---+---+---+---+---+---+---+
| |
+---------------+
如果您认为此操作的更一般形式是使用三个参数“将某个范围内的位旋转一定量”:
- 要包含在轮换中的最低有效位
- 要包含在轮换中的最高有效位
- 要循环的位数
然后它变成一个单一的基本原语,可以执行所有你想做的事情:
- 您显然可以移动任何位(选择适当的最低/最高有效位参数);
- 您可以向左或向右旋转,因为如果您要旋转 n 位范围,则向右旋转 k 位与向左旋转相同按 n - k 位;
- 它可以简单地推广到任何位宽;
- 根据定义,我们一次可以旋转不止一位。
所以现在,所需要的就是构建这个原语......
首先,我们几乎肯定需要一个位掩码来处理我们关心的位。
我们可以通过将 1 向左移动 n + 1 位,然后减去 1 来形成位 0 - n 的掩码。例如位 0-5 的掩码将是(二进制):
00111111
...可以通过取1来形成:
00000001
...向左移动 5+1 = 6 位:
01000000
...减去 1 得到:
00111111
在 C 中,这将是 (1 << (bit + 1)) - 1。但是这里有一个微妙之处,至少对于 C 而言(当您将其标记为与语言无关时,我为题外话道歉,但这很重要,并且在其他语言中也可能存在类似问题):您的类型(或更多)的宽度会导致未定义的行为。因此,如果我们尝试为 8 位类型的 0-7 位构建掩码,则计算结果将是 (1 << 8) - 1,这将是未定义的。 (它可能适用于某些系统和某些编译器,但不能移植。)在您最终会转移到符号位的情况下,有符号类型也存在未定义的行为问题。
幸运的是,在 C 语言中,我们可以通过使用 unsigned 类型并将表达式编写为 (1 << bit) + (1 << bit) - 1 来避免这些问题。具有无符号 n 位值的算术由标准定义为模 2n 减少,并且所有单独的操作都是明确定义的,因此我们保证得到正确的答案。
(题外话结束。)
好的,所以现在我们有了位 0 - msb 的掩码。我们想为位 lsb - msb 做一个掩码,我们可以通过减去位 0 - (lsb-1) 的掩码来实现,即(1 << lsb) - 1。例如
00111111 mask for bits 0-5: (1 << 5) + (1 << 5) - 1
- 00000001 mask for bits 0-0: (1 << 1) - 1
-------- -------------------------------
00111110 mask for bits 1-5: (1 << 5) + (1 << 5) - (1 << 1)
所以掩码的最终表达式是:
mask = (1 << msb) + (1 << msb) - (1 << lsb);
可以通过按位与掩码选择要旋转的位:
to_rotate = value & mask;
...可以通过带有反转掩码的 AND 选择将保持不变的位:
untouched = value & ~mask;
旋转本身可以很容易地分两部分进行:首先,我们可以通过简单地向左旋转to_rotate并丢弃任何落在掩码之外的位来获得旋转部分的最左边位:
left = (to_rotate << shift) & mask;
要获得最右边的位,请将to_rotate right 旋转 (n - shift) 位,其中 n em> 是我们正在旋转的位数(这个 n 可以计算为msb + 1 - lsb):
right = (to_rotate >> (msb + 1 - lsb - shift)) & mask;
将untouched、left和right中的所有位组合起来即可得到最终结果:
result = untouched | left | right;
您的原始示例将像这样工作(msb 是 5,lsb 是 1,shift 是 1):
value = 01011010
mask = 00111110 from (1 << 5) + (1 << 5) - (1 << 1)
01011010 value
& 00111110 mask
----------
to_rotate = 00011010
01011010 value
& 11000001 ~mask (i.e. inverted mask)
----------
untouched = 01000000
00110100 to_rotate << 1
& 00111110 mask
----------
left = 00110100
00000001 to_rotate >> 4 (5 + 1 - 1 - 1 = 4)
& 00111110 mask
----------
right = 00000000
01000000 untouched
00110100 left
| 00000000 right
----------
result = 01110100
下面是一个不同的示例,它具有 16 位输入值,msb = 15、lsb = 4 和 shift = 4(旋转 4 位十六进制值的前 3 个十六进制数字):
value = 0101011001111000 (0x5678)
mask = 1111111111110000 from (1 << 15) + (1 << 15) - (1 << 4)
0101011001111000 value
& 1111111111110000 mask
------------------
to_rotate = 0101011001110000
0101011001111000 value
& 0000000000001111 ~mask
------------------
untouched = 0000000000001000
0110011100000000 to_rotate << 4
& 1111111111110000 mask
------------------
left = 0110011100000000
0000000001010110 to_rotate >> 8 (15 + 1 - 4 - 4 = 8)
& 1111111111110000 mask
------------------
right = 0000000001010000
0000000000001000 untouched
0110011100000000 left
| 0000000001010000 right
------------------
result = 0110011101011000 = 0x6758