使用基元类型进行模乘法的方法

Ways to do modulo multiplication with primitive types

本文关键字:方法 类型      更新时间:2023-10-16

>有没有办法构建例如 (853467 * 21660421200929) % 100000000000007没有 BigInteger 库(请注意,每个数字都适合 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;
}

你应该使用俄罗斯农民乘法。 它使用重复加倍来计算(b*2^i)%m的所有值,如果设置了a的第i位,则将它们相加。

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)时间。

注意事项:无符号,仅当m为 63 位或更少时才有效。

Keith Randall 的回答很好,但正如他所说,需要注意的是,它只有在 63 位或更少时才能m工作。

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

  1. 即使m是 64 位,它也可以工作。
  2. 它不需要使用模运算,这在某些处理器上可能很昂贵。

(请注意,res -= m 行和temp_b -= m行依赖于 64 位无符号整数溢出才能给出预期的结果。这应该没问题,因为无符号整数溢出在 C 和 C++ 中定义得很好。因此,使用无符号整数类型非常重要。

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;
}

这两种方法都对我有用。第一个和你的一样,但我把你的号码改成了 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 %2nt"
      "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;
}

重复加倍算法的一个改进是检查一次可以计算多少位而不会溢出。可以对这两个参数进行早期退出检查 - 加速N不是素数的(不太可能?(事件。

例如 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;
}

你可以尝试一些将乘法分解为加法的方法:

// 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;
}
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;

我可以建议改进您的算法。

您实际上通过每次添加b来迭代计算a * 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;
}