如何用原始类型进行模乘法

时间:2012-08-28 22:21:39

标签: c++ algorithm

有没有办法建立,例如没有BigInteger库的(853467 * 21660421200929) % 100000000000007(请注意,每个数字都适合64位整数,但乘法结果不适合)?

此解决方案似乎效率低下:

int64_t mulmod(int64_t a, int64_t b, int64_t m) {
    if (b < a)
        std::swap(a, b);
    int64_t res = 0;
    for (int64_t i = 0; i < a; i++) {
        res += b;
        res %= m;
    }
    return res;
}

8 个答案:

答案 0 :(得分:22)

您应该使用Russian Peasant multiplication。它使用重复加倍来计算所有值(b*2^i)%m,并在设置i的{​​{1}}时添加它们。

a

它会改进您的算法,因为它需要uint64_t mulmod(uint64_t a, uint64_t b, uint64_t m) { int64_t res = 0; while (a != 0) { if (a & 1) res = (res + b) % m; a >>= 1; b = (b << 1) % m; } return res; } 时间,而不是O(log(a))时间。

警告:无符号,仅当O(a)为63位或更少时才有效。

答案 1 :(得分:14)

Keith Randall's answer很好,但正如他所说的那样,需要注意的是,只有当m为63位或更少时才有效。

这是一个有两个优点的修改:

  1. 即使m为64位也可以。
  2. 它不需要使用模运算,这在某些处理器上可能很昂贵。
  3. (注意,res -= mtemp_b -= m行依赖于64位无符号整数溢出,以便给出预期的结果。这应该没问题,因为无符号整数溢出在C和C中定义良好C ++。因此它是important to use unsigned integer types。)

    uint64_t mulmod(uint64_t a, uint64_t b, uint64_t m) {
        uint64_t res = 0;
        uint64_t temp_b;
    
        /* Only needed if b may be >= m */
        if (b >= m) {
            if (m > UINT64_MAX / 2u)
                b -= m;
            else
                b %= m;
        }
    
        while (a != 0) {
            if (a & 1) {
                /* Add b to res, modulo m, without overflow */
                if (b >= m - res) /* Equiv to if (res + b >= m), without overflow */
                    res -= m;
                res += b;
            }
            a >>= 1;
    
            /* Double b, modulo m */
            temp_b = b;
            if (b >= m - b)       /* Equiv to if (2 * b >= m), without overflow */
                temp_b -= m;
            b += temp_b;
        }
        return res;
    }
    

答案 2 :(得分:4)

重复加倍算法的改进是检查一次可以计算多少位而没有溢出。可以对两个参数进行提前退出检查 - 加快N不是素数的(不太可能?)事件。

e.g。 100000000000007 == 0x00005af3107a4007,允许每次迭代计算16(或17)位。使用示例,实际迭代次数为3。

// just a conceptual routine
int get_leading_zeroes(uint64_t n)
{
   int a=0;
   while ((n & 0x8000000000000000) == 0) { a++; n<<=1; }
   return a;
}

uint64_t mulmod(uint64_t a, uint64_t b, uint64_t n)
{
     uint64_t result = 0;
     int N = get_leading_zeroes(n);
     uint64_t mask = (1<<N) - 1;
     a %= n;
     b %= n;  // Make sure all values are originally in the proper range?
     // n is not necessarily a prime -- so both a & b can end up being zero
     while (a>0 && b>0)
     {
         result = (result + (b & mask) * a) % n;  // no overflow
         b>>=N;
         a = (a << N) % n;
     }
     return result;
}

答案 3 :(得分:2)

你可以尝试一些能够将乘法分解为附加内容的东西:

// compute (a * b) % m:

unsigned int multmod(unsigned int a, unsigned int b, unsigned int m)
{
    unsigned int result = 0;

    a %= m;
    b %= m;

    while (b)
    {
        if (b % 2 != 0)
        {
            result = (result + a) % m;
        }

        a = (a * 2) % m;
        b /= 2;
    }

    return result;
}

答案 4 :(得分:2)

这两种方法对我都有用。第一个与你的相同,但是我将你的数字改为显性ULL。第二个使用汇编程序表示法,它应该更快。 密码学中也使用了算法(基本上我猜测基于RSA和RSA的密码学),就像已经提到的蒙哥马利减少一样,但我认为实施它们需要时间。

#include <algorithm>
#include <iostream>

__uint64_t mulmod1(__uint64_t a, __uint64_t b, __uint64_t m) {
  if (b < a)
    std::swap(a, b);
  __uint64_t res = 0;
  for (__uint64_t i = 0; i < a; i++) {
    res += b;
    res %= m;
  }
  return res;
}

__uint64_t mulmod2(__uint64_t a, __uint64_t b, __uint64_t m) {
  __uint64_t r;
  __asm__
  ( "mulq %2\n\t"
      "divq %3"
      : "=&d" (r), "+%a" (a)
      : "rm" (b), "rm" (m)
      : "cc"
  );
  return r;
}

int main() {
  using namespace std;
  __uint64_t a = 853467ULL;
  __uint64_t b = 21660421200929ULL;
  __uint64_t c = 100000000000007ULL;

  cout << mulmod1(a, b, c) << endl;
  cout << mulmod2(a, b, c) << endl;
  return 0;
}

答案 5 :(得分:1)

我可以建议你的算法有所改进。

您实际上通过每次添加a * b来迭代地计算b,在每次迭代后进行模数运算。最好每次b * x添加,而x确定为b * x不会溢出。

int64_t mulmod(int64_t a, int64_t b, int64_t m)
{
    a %= m;
    b %= m;

    int64_t x = 1;
    int64_t bx = b;

    while (x < a)
    {
        int64_t bb = bx * 2;
        if (bb <= bx)
            break; // overflow

        x *= 2;
        bx = bb;
    }

    int64_t ans = 0;

    for (; x < a; a -= x)
        ans = (ans + bx) % m;

    return (ans + a*b) % m;
}

答案 6 :(得分:0)

a * b % m等于a * b - (a * b / m) * m

使用浮点运算来近似a * b / m。近似值为正常的64位整数运算留下足够小的值,m最多为63位。

此方法受double的有效数限制,通常为52位。

uint64_t mod_mul_52(uint64_t a, uint64_t b, uint64_t m) {
    uint64_t c = (double)a * b / m - 1;
    uint64_t d = a * b - c * m;

    return d % m;
}

此方法受long double的有效数限制,通常为64位或更大。整数运算限制为63位。

uint64_t mod_mul_63(uint64_t a, uint64_t b, uint64_t m) {
    uint64_t c = (long double)a * b / m - 1;
    uint64_t d = a * b - c * m;

    return d % m;
}

这些方法要求ab小于m。要处理任意ab,请在计算c之前添加这些行。

a = a % m;
b = b % m;

在这两种方法中,最终的%操作都可以成为条件。

return d >= m ? d % m : d;

答案 7 :(得分:0)

O(log(b)) 解决方案,但实际上它会运行得更快,因为它每次都会检查 ab 是否适合 64 位整数。这个算法不会无限循环,因为a和b都适合64位整数,而且每次递归调用后,b /= 2,所以在最坏的情况下,b = 1,a * 1适合。使用 mult() 方法,我们可以计算大数的 a^b mod m,即使是 LLONG_MAX!请注意,在mult和addition方法中,a < mod, b < mod!

long exp(long a, long b, long mod){
    long x = a, y = 1, z = b;
    while(z > 0){
        if(z % 2 == 1)
            y = mult(y, x, mod);
        x = mult(x, x, mod);
        z /= 2;
    }
    return y;
}

long mult(long a, long b, long mod){
    if(a == 0)
        return 0;
    if(LLONG_MAX / a >= b)
        return (a * b) % mod;
    long rec = mult(a, b / 2, mod);
    long res = addition(rec, rec, mod);
    if(b % 2 == 1)
        res = addition(res, a, mod);
    return res;
}
 
long addition(long a, long b, long mod) {
    if(a > mod / 2)
        a = a - mod;
    if(b > mod / 2)
        b = b - mod;
    long ans = a + b;
    if(ans < 0)
        ans = mod + ans;
    return ans;
}