【问题标题】:Avoiding overflow working modulo p避免溢出工作模 p
【发布时间】:2018-02-10 13:32:36
【问题描述】:

作为大学作业的一部分,我必须在椭圆曲线模 p = 2^255 - 19 上实现 C 标量乘法。由于所有计算都以 p 为模,因此使用原始类型(无符号长)。

但是,如果 a 和 b 是模 p 的两个整数,则存在计算 a*b 溢出的风险。我不知道如何避免这种情况。下面的代码正确吗?

long a = ...;
long b = ...;
long c = (a * b) % p;

或者我应该先投射 a 和 b 吗?

long a = ...;
long b = ...;
long long a1 = (long long) a;
long long b1 = (long long) b;
long c = (long) ((a1 * b1) % p);

我也一直在思考或与long long一起工作。

【问题讨论】:

  • 使用这种关系:(a * b) % m == (a%m * b%m) % m
  • 实际上并不能保证long long 大于long - 标准只规定了这些类型的最小尺寸
  • p 是 2^255 - 19,所以我认为在乘法之前取模是不够的
  • 您的 p 有 255 个二进制数字,因此所有本机类型都太窄了。
  • @Raf 基本类型:en.cppreference.com/w/cpp/language/types long 最小为 32

标签: c++ c integer-overflow


【解决方案1】:

在完成整个操作(乘法)时要牢记操作数的类型。你将两个long 变量相乘,结果如果大于long 变量可以容纳的值,就会溢出。

((a%p)*(b%p))%p 这提供了一种保护,它环绕在p 周围,但之前所说的仍然有效 - (a%p)*(b%p) 仍然可以溢出。 (考虑到a,b 的类型是long)。

如果您将long 的值存储在long long 中,则无需强制转换。但是是的,当乘法产生的值大于long long 可以容纳的值时,结果现在将溢出。

给你一个澄清:-

long a,b;
..
long long p = (a*b)%m;

这无济于事。完成后的乘法是long 算术。我们将最终结果存储在哪里并不重要。这取决于操作数的类型。

现在看看这个

long c = (long) ((a1 * b1) % p); 这里的结果将是两个long long 的乘法,并且会根据long long 可以保持的最大值溢出,但是当您将其分配给long 时仍然有溢出的机会。

如果 p255 字节,则使用 3264 位系统的内置类型 longlong long 类型将无法实现。当我们拥有512 位系统时,这肯定是可能的。还有一点需要注意的是,当p=2255-19 使用它进行模运算时几乎没有任何实用性。

如果sizeof long 等于sizeof long long,就像在 ILP64 和 LP64 中一样,那么使用 longlong long 将不会给你这样的结果。但如果sizeof long long 大于sizeof long,则将操作数保存在long long 中以防止乘法溢出很有用。

另一种方法是编写自己的大整数库(多精度整数库)或使用已经存在的大整数库(可能像this)。这个想法围绕着这样一个事实,即使用像char 这样简单的东西来实现更大的类型,然后对其进行操作。这是一个实施问题,围绕同一主题有很多实施。

【讨论】:

  • @coderredoc 这是为了感谢您的回答;)
【解决方案2】:

对于 255+ 位整数要求,标准操作和 C 库是不够的。

按照通用算法编写您自己的模乘法。

myint mod(myint a, myint m);
myint add(myint a, myint b);  // this may overflow
int   cmp(myint a, myint b);
int   isodd(myint a);
myint halve(myint a);

// (a+b)%mod
myint addmodmax(myint a, myint b, myint m) {
  myint sum = add(a,b);
  if (cmp(sum,a) < 0) {
    sum = add(mod(add(sum, 1),m), mod(myint_MAX,m)); // These additions do not overflow
  }
  return mod(sum, m);
}

// (a*b)%mod
myint mulmodmax(myint a, myint b, myint m) {
  myint prod = 0;
  while (cmp(b,0) > 0) {
    if (isodd(b)) {
      prod = addmodmax(prod, a, m);
    }
    b = halve(b);
    a = addmodmax(a, a, m);
  }
  return prod;
}

【讨论】:

    【解决方案3】:

    我最近遇到了同样的问题。

    首先我假设你的意思是 32-bit 整数(在阅读了你的 cmets 之后),但我认为这也适用于大整数(因为做一个简单的乘法意味着加倍字长,也会很慢)。

    选项 1

    我们使用以下属性:

    命题。 a*b mod m = (a - m)*(b - m) mod m
    证明。

    (a - m)*(b - m) mod m =  
    (a*b - (a+b)*m + m^2) mod m =  
    (a*b mod m - ((a+b) + m)*m mod m) mod m =  
    (a*b mod m) mod m = a*b mod m
    

    q.e.d.

    此外,如果 a,b 大约为 m,则 (a - m)*(b - m) mod m = (a - m)*(b - m)。您将需要解决 a,b > m 时的情况,但是我认为(m - a)*(m - b) mod m = a*b mod m 的有效性是上述命题的必然结果;当然,当差异很大(小模数,大 a 或 b;反之亦然)时不要这样做,否则会溢出。

    选项 2

    来自Wikipedia

    uint64_t mul_mod(uint64_t a, uint64_t b, uint64_t m)
    {
       uint64_t d = 0, mp2 = m >> 1;
       int i;
       if (a >= m) a %= m;
       if (b >= m) b %= m;
       for (i = 0; i < 64; ++i)
       {
           d = (d > mp2) ? (d << 1) - m : d << 1;
           if (a & 0x8000000000000000ULL)
               d += b;
           if (d >= m) d -= m;
           a <<= 1;
       }
       return d;
    }
    

    此外,假设 long double 和 32 位或 64 位整数(不是任意精度),您可以利用机器优先级来处理不同类型的最高有效位:

    在具有至少 64 位尾数的扩展精度格式可用的计算机体系结构上(例如大多数 x86 C 编译器的 long double 类型),以下例程通过采用以下技巧比任何算法解决方案都更快,通过硬件,浮点乘法会保留乘积的最高有效位,而整数乘法会保留最低有效位

    然后做:

    uint64_t mul_mod(uint64_t a, uint64_t b, uint64_t m)
    {
       long double x;
       uint64_t c;
       int64_t r;
       if (a >= m) a %= m;
       if (b >= m) b %= m;
       x = a;
       c = x * b / m;
       r = (int64_t)(a * b - c * m) % (int64_t)m;
       return r < 0 ? r + m : r;
    }
    

    这些保证不会溢出。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-07
      • 2020-03-08
      • 1970-01-01
      • 2010-11-30
      • 1970-01-01
      相关资源
      最近更新 更多