(n * 2-1)%p:当n和p为32位时,避免使用64位

时间:2015-04-20 21:36:13

标签: c++ algorithm c++11 math modulus

考虑以下功能:

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的新版本使用这个技巧? (保持相同的声明)。

  • 注1:n > 0
  • 注2:p > 2
  • 注3:n可能低于pn=4294967289Up=4294967291U
  • 注4:使用的模数操作次数越少越好(3 32位模数太大,2有趣,1肯定会超越)
  • 注5:当然结果将取决于处理器。假设在最后的至强可用的超级计算机上使用。

3 个答案:

答案 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) neax放入edx:eax(让其选择其他注册表没有帮助,该部门将在"S"(p)中进行输入)。 "r"(p)可能是{{1}}(似乎有效),但我不确定是否相信它。