直截了当的方式,8 个操作(其他是对常量的操作):
M = (1<<(N-S)) - 1; // A mask with S lowest bits.
q = ( ((p & (M<<(2*N+S))) >> (3*S)) // Mask 'i', shift to new position.
+ ((p & (M<<( N+S))) >> (2*S)) // Likewise for 'j'.
+ ((p & (M<< S)) >> S)); // Likewise for 'k'.
看起来很复杂,但实际上并非如此,只是不容易(至少对我而言)让所有常量都正确。
为了创建具有较少操作的公式,我们观察到将数字U
向左移动一位与乘以 相同1<<U
。因此,由于乘法分布性,乘以等于((1<<U1) + (1<<U2) + ...)
左移U1
, U2
, ... 并将所有内容相加。
因此,我们可以尝试屏蔽和的所需部分i
,通过一次乘法将它们全部“移动”到相对于彼此的正确位置,然后将结果向右移动到最终目的地。这给了我们三个操作来计算。j
k
q
p
不幸的是,有一些限制,特别是对于我们试图同时获得所有三个的情况。当我们将数字相加时(间接地,通过将多个乘数相加),我们必须确保只能在一个数字中设置位,否则我们会得到错误的结果。如果我们尝试一次添加(间接)三个正确移位的数字,我们有:
iiiii...........jjjjj...........kkkkk.......
N-S S N-S S N-S
.....jjjjj...........kkkkk................
N-S N-S S N-S
..........kkkkk...............
N-S N-S N-S
请注意,第二个和第三个数字的左侧是 和 的位i
,j
但我们忽略它们。为此,我们假设乘法在 x86 上工作:将两种类型相乘T
得到多个 type T
,只有实际结果的最低位(如果没有溢出,则等于结果)。
因此,为了确保k
第三个数字中的位不与第一个数字中的位重叠j
,我们需要它,3*(N-S) <= N
即限制我们(移位后每个组件只有一个或两个位;不知道您是否曾经使用过精度低)。S >= 2*N/3
N = 8
S >= 6
但是,如果S >= 2*N/3
,我们只能使用 3 个操作:
// Constant multiplier to perform three shifts at once.
F = (1<<(32-3*N)) + (1<<(32-3*N+S)) + (1<<(32-3*N+2*S));
// Mask, shift/combine with multipler, right shift to destination.
q = (((p & ((M<<(2*N+S)) + (M<<(N+S)) + (M<<S))) * F)
>> (32-3*(N-S)));
如果 for 的约束S
太严格(可能是这样),我们可以结合第一个和第二个公式:用第二种方法计算i
和k
,然后j
从第一个公式添加。在这里,我们需要以下数字中的位不重叠:
iiiii...............kkkkk.......
N-S S N-S S N-S
..........kkkkk...............
N-S N-S N-S
即3*(N-S) <= 2*N
,它给出了S >= N / 3
,或者,N = 8
更不严格S >= 3
。公式如下:
// Constant multiplier to perform two shifts at once.
F = (1<<(32-3*N)) + (1<<(32-3*N+2*S));
// Mask, shift/combine with multipler, right shift to destination
// and then add 'j' from the straightforward formula.
q = ((((p & ((M<<(2*N+S)) + (M<<S))) * F) >> (32-3*(N-S)))
+ ((p & (M<<(N+S))) >> (2*S)));
此公式也适用于您的示例 where S = 4
。
这是否比直接方法更快取决于架构。另外,我不知道 C++ 是否保证假设的乘法溢出行为。最后,您需要确保值是无符号的并且正好是32 位,这样公式才能正常工作。