快速版本是:取决于硬件、优化器、if it's division by a constant or not (pdf)、是否需要检查异常(例如,以 0 为模)、是否以及如何处理负数(this is a scary question for C++)等...
R 为无符号整数提供了一个简洁明了的答案,但除非您精通 C,否则很难理解。
R 所阐明的技术的关键是去除q 的倍数,直到不再有q 的倍数。我们可以用一个简单的循环天真地做到这一点:
while (p >= q) p -= q; // One liner, woohoo!
代码可能很短,但是对于较大的 p 值和较小的 q 值,这可能需要很长时间。
比一次剥离一个q 更好的是一次剥离多个q。请注意,我们实际上希望尽可能多地去除q —— 即,floor(p/q) 许多q ......事实上,这是一种有效的技术。对于无符号整数,人们会期望p % q == p - (p / q) * q。 (请注意,无符号整数除法会向下舍入。)
但这几乎感觉像是在作弊,因为除法和余数运算是如此密切相关。 (事实上,如果硬件本身支持除法,它通常支持除法计算余数运算,因为它们之间的关系非常密切。)
假设我们无法使用除法,我们如何找到大于 1 的 q 的倍数来去除?在硬件中,固定移位操作很便宜(如果实际上不是免费的),并且在概念上表示乘以 2 的非负幂。例如,将位串左移 3 相当于乘以 8(即 2^3),例如十进制的 5 相当于二进制的 '101'。通过在右边添加三个零(给出“101000”)将二进制“101”移位,结果是十进制的 50——五乘以八。
同样,班次操作与软件操作一样非常便宜,而且您很难快速找到不支持它们的控制器。 (一些架构,如 ARM 甚至可以将移位与其他指令结合起来,使它们在很多时候“空闲”。)
武装(无法抗拒)这些移位操作,我们可以进行如下操作:
- 找出我们可以将
q 乘以但仍小于p 的2 的最大幂。
- 从 2 的最大幂到最小幂,将
q 乘以 2 的每个幂,如果小于 p 的余数,则从 p 的余数中减去。
- 剩下的就是剩下的。
为什么会这样?因为最后你会发现所有的 2 的减幂实际上总和为floor(p / q)!不要相信我的话,类似的知识以very long time而闻名。
分解 R 的答案:
#define HI (-1U-(-1U/2))
这有效地为您提供了一个仅设置了最高值位的无符号整数。
unsigned i;
for (i=0; !(HI & (q<<i)); i++);
这一行实际上发现两个q 的最高幂可以在溢出一个无符号整数之前相乘。这不是绝对必要的,但除了增加所需的执行时间外,它不会改变结果。
如果您不熟悉这一行中的 C-isms:
-
(q<<i) 是 i 的左位移位。回想一下,这相当于乘以 2^i。
-
HI & (q<<i) 执行按位与。由于HI 仅填充了其最高位,因此只有当(q<<i) 大到足以导致最高位为非零时,才会导致非零值。再向左移动一次,就会出现整数溢出。
-
当
(HI & (q<<i)) 为零时,!(HI & (q<<i)) 为“真”,否则为“假”。
do { if (p >= (q<<i)) p -= (q<<i); } while (i--);
这是一个简单的递减循环do { .... } while (i--);。请注意,在i 上使用了后减法,因此循环执行,然后它检查i 是否不为零,然后从i 中减去1,然后如果其先前的检查导致true 它继续。当i 为0 时,它具有循环最后一次执行的属性。这很重要,因为我们可能需要剥离q 的未乘副本。
if (p >= (q<<i)) 检查 2^i * q 是否小于或等于 p。如果是,p -= (q<<i) 将其剥离。
剩下的。