考虑以下功能:
inline unsigned int f(unsigned int n, unsigned int p)
{
return (n*2-1)%p;
}
现在假设n
(和p
)大于std::numeric_limits<int>::max()
。
例如f(4294967295U, 4294967291U)
。
数学结果为7
,但函数将返回2
,因为n*2
会溢出。
然后解决方案很简单:我们只需要使用64位整数。假设函数的声明必须保持不变:
inline unsigned int f(unsigned int n, unsigned int p)
{
return (static_cast<unsigned long long int>(n)*2-1)%p;
}
一切都很好。至少在原则上。问题是这个函数将在我的代码中被调用数百万次(我的意思是溢出版本),64位模数比32位版本慢(例如,参见here)。
问题如下:是否存在任何技巧(数学或算法)以避免执行64位版本的模数运算。什么是f
的新版本使用这个技巧? (保持相同的声明)。
n > 0
p > 2
n
可能低于p
:n=4294967289U
,p=4294967291U
答案 0 :(得分:3)
我们知道p
小于max
,然后n % p
小于最大值。它们都是未签名的,这意味着n % p
是正数,小于p
。无符号溢出是明确定义的,因此如果n % p * 2
超过p
,我们可以将其计算为n % p - p + n % p
,它不会溢出,因此它们将一起显示如下:
unsigned m = n % p;
unsigned r;
if (p - m < m) // m * 2 > p
r = m - p + m;
else // m * 2 <= p
r = m * 2;
// subtract 1, account for the fact that r can be 0
if (r == 0) r = p - 1;
else r = r - 1;
return r % p;
请注意,您可以避免使用上一个模数,因为我们知道r
不超过p * 2
(最多m * 2
,而m
不会超过p
),因此最后一行可以重写为
return r >= p ? r - p : r
使模数运算的数量为1。
答案 1 :(得分:1)
FWIW,这个版本似乎可以避免任何溢出:
std::uint32_t f(std::uint32_t n, std::uint32_t p)
{
auto m = n%p;
if (m <= p/2) {
return (m==0)*p+2*m-1;
}
return p-2*(p-m)-1;
}
Demo。我们的想法是,如果2*m-1
中出现溢出,我们可以使用p-2*(p-m)-1
,这可以通过将2
与模块化加法逆相乘来避免这种情况。
答案 2 :(得分:1)
即使我不喜欢处理AT&amp; T语法和GCC&#34;扩展asm约束&#34;,我认为这是有效的(它在我的,无可否认有限的测试中有效)
uint32_t f(uint32_t n, uint32_t p)
{
uint32_t res;
asm (
"xorl %%edx, %%edx\n\t"
"addl %%eax, %%eax\n\t"
"adcl %%edx, %%edx\n\t"
"subl $1, %%eax\n\t"
"sbbl $0, %%edx\n\t"
"divl %1"
: "=d"(res)
: "S"(p), "a"(n)
:
);
return res;
}
这些限制可能是不必要的严格或错误,我不知道。它似乎有效。
这里的想法是进行常规的32位分割,实际上需要64位的分红。它仅在商适用于32位(否则发出溢出信号)时有效,在这种情况下(p
至少为2,n
不为零),情况始终为真。除法之前的东西处理时间2(溢出到edx
,&#34;高一半&#34;),然后&#34;减去1&#34;潜在的借款。 "=d"
输出的东西使得剩余的结果。 "a"(n)
n
将eax
放入edx:eax
(让其选择其他注册表没有帮助,该部门将在"S"(p)
中进行输入)。 "r"(p)
可能是{{1}}(似乎有效),但我不确定是否相信它。