直截了当,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 的所需部分,通过一次乘法将它们全部“移位”到相对于彼此的正确位置,然后将结果向右移位,最终的目的地。这为我们提供了三个操作来从 p 计算 q。
不幸的是,存在一些限制,尤其是在我们试图同时获得所有三个的情况下。当我们将数字相加时(间接地,通过将几个乘数相加),我们必须确保只能在一个数字中设置位,否则我们会得到错误的结果。如果我们尝试一次添加(间接)三个正确移位的数字,我们会得到:
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 相乘得到一个类型为 T 的数字,只有实际结果的最低位(如果没有溢出,则等于结果).
所以,为了确保第三个数字中的k 位不与第一个数字中的j 位重叠,我们需要3*(N-S) <= N,即S >= 2*N/3,对于N = 8,我们将其限制为@987654342 @(移位后每个组件只有一到两位;不知道您是否使用过那么低的精度)。
但是,如果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)));
如果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)));
这个公式也适用于S = 4 的示例。
这是否比直接方法更快取决于架构。另外,我不知道 C++ 是否保证假设的乘法溢出行为。最后,您需要确保值是无符号的并且完全为 32 位,这样公式才能正常工作。