Wu *_*eng 10 c integer-division
如何计算整数除法2 64 / n?假设:
unsigned long 是64位如果这样做18446744073709551616ul / n,我们将warning: integer constant is too large for its type在编译时到达。这是因为我们无法在64位CPU中表示2 64。另一种方法如下:
#define IS_POWER_OF_TWO(x) ((x & (x - 1)) == 0)
unsigned long q = 18446744073709551615ul / n;
if (IS_POWER_OF_TWO(n))
return q + 1;
else
return q;
Run Code Online (Sandbox Code Playgroud)
是否有更快的(CPU周期)或更干净的(编码)实现?
phuclv的使用想法-n很聪明,但可以简化很多。作为无符号长型,我们有-n = 2 64 -n,然后(-n)/ n = 2 64 / n-1,我们可以简单地加回1。
unsigned long foo(unsigned long n) {
return (-n)/n + 1;
}
Run Code Online (Sandbox Code Playgroud)
生成的代码正是您所期望的(通过godbolt在x86-64上的gcc 8.3 ):
mov rax, rdi
xor edx, edx
neg rax
div rdi
add rax, 1
ret
Run Code Online (Sandbox Code Playgroud)
我提出了另一个受此问题启发的解决方案。从那里我们知道
(a 1 + a 2 + a 3 + ... + a n )/n =
(a 1 /n + a 2 /n + a 3 /n + ... + a n /n) + (a 1 % n + a 2 % n + a 3 % n + ... + a n % n )/n
通过选择a 1 = a 2 = a 3 = ... = a n-1 = 1和a n = 2 64 - n我们将有
(a 1 + a 2 + a 3 + ... + a n )/n = (1 + 1 + 1 + ... + (2 64 - n))/n = 2 64 /n
= [(n - 1)*1/n + (2 64 - n)/n] + [(n - 1)*0 + (2 64 - n) % n]/n
= (2 64 - n)/n + ((2 64 - n) % n)/n
2 64 - n是n的 2 的补码,也就是-n,或者我们也可以写成~0 - n + 1。所以最终的解决方案是
uint64_t twoPow64div(uint64_t n)
{
return (-n)/n + (n + (-n) % n)/n + (n > 1ULL << 63);
}
Run Code Online (Sandbox Code Playgroud)
最后一部分是纠正结果,因为我们处理的是无符号整数,而不是像另一个问题中的有符号整数。在我的 PC 上检查了 32 位和 64 位版本,结果与您的解决方案匹配
然而,在 MSVC 上有一个128 位除法的内在函数,所以你可以像这样使用
uint64_t remainder;
return _udiv128(1, 0, n, &remainder);
Run Code Online (Sandbox Code Playgroud)
这导致最干净的输出
mov edx, 1
xor eax, eax
div rcx
ret 0
Run Code Online (Sandbox Code Playgroud)
这是演示
在大多数 x86 编译器上(一个值得注意的例外是 MSVC)long double也有 64 位精度,因此您可以使用其中任何一个
(uint64_t)(powl(2, 64)/n)
(uint64_t)(((long double)~0ULL)/n)
(uint64_t)(18446744073709551616.0L/n)
Run Code Online (Sandbox Code Playgroud)
虽然性能可能会更差。这也可以应用于任何long double具有超过 63 位有效数的实现,例如PowerPC及其双倍实现
有一个关于计算的相关问题((UINT_MAX + 1)/x)*x - 1: 整数算术:加 1 到 UINT_MAX 并除以 n 而不会溢出,也有聪明的解决方案。基于此,我们有
2 64 /n = (2 64 - n + n)/n = (2 64 - n)/n + 1 = (-n)/n + 1
这本质上只是获得Nate Eldredge 答案的另一种方式
这是Godbolt上其他编译器的一些演示
也可以看看:
| 归档时间: |
|
| 查看次数: |
450 次 |
| 最近记录: |