【问题标题】:How to implement modular exponentiation that requires at most double the bytesize of the number that is to be modularly exponentiated in C?如何实现模幂运算,它最多需要两倍于 C 中要模幂运算的数字的字节大小?
【发布时间】:2017-05-14 19:25:14
【问题描述】:

也就是说有这样的算法吗:

// powmod(m,e,n) = m^e % n
unsigned long long powmod(unsigned long m, unsigned long e, unsigned long long n)

不会溢出让我们说 m = 2^32 - 1, e = 3, n = 2^64 - 1 没有使用 gmp 或此类库?

【问题讨论】:

  • 有几十个带有关键字powmod的SO帖子。当然,其中之一会帮助你。 OTOH,发布您尝试过的内容会很高兴。
  • 如果n = 2^64 - 1,我认为没有代码需要n vs 的“双倍字节”大小。 “要模幂的数字”如果0 < n <= 2^32 - 1,那么是的。
  • @Nae 无论您将其发布到何处,答案都是相同的:对于 x 位的n 的给定 bitlen,您始终需要 2*x bitlen 操作。鉴于您的 unsigned long long bitlen 为 n,如果没有 unsigned long long long long 精度,您将无法做到这一点。
  • @Nae 查看 binary modular exponentiation 的伪代码 - 为什么您认为需要 Assert :: (modulus - 1) * (modulus - 1) does not overflow base 行?
  • 中心问题是binary modular exponentiation 需要在数学上比模数更宽result * base,在这种情况下是unsigned long long n,然后是mod 被采用。 C 未指定对大于 64 位的整数数学的支持。 wider_than_64_bit_math mod 64_bit_value 的结果是 64 位。

标签: c algorithm overflow exponentiation modular-arithmetic


【解决方案1】:

是的,您可以这样做。去下面的代码,因为有一个内置的Exp 可以测试;转换为 C 应该非常简单,因为唯一的库使用仅限于测试。

package main

import (
    "fmt"
    "math"
    "math/big"
    "math/rand"
)

// AddMod returns a + b (mod m).
func AddMod(a, b, m uint64) uint64 {
    a %= m
    b %= m
    if a >= m-b {
        return a - (m - b)
    }
    return a + b
}

// SubMod returns a - b (mod m).
func SubMod(a, b, m uint64) uint64 {
    a %= m
    b %= m
    if a < b {
        return a + (m - b)
    }
    return a - b
}

// Lshift32Mod returns 2^32 a (mod m).
func Lshift32Mod(a, m uint64) uint64 {
    a %= m
    // Let A = 2^32 a. The desired result is A - q* m, where q* = [A/m].
    // Approximate q* from below by q = [A/(m+err)] for the err in (0, 2^32] such
    // that 2^32|m+err. The discrepancy is
    //
    // q* - q < A (1/m - 1/(m+err)) + 1 = A err/(m (m+err)) + 1
    //
    // A - q m = A - q* m + (q* - q) m < m + A err/(m+err) + m < 2 m + 2^64.
    //
    // We conclude that a handful of loop iterations suffice.
    m0 := m & math.MaxUint32
    m1 := m >> 32
    q := a / (m1 + 1)
    q0 := q & math.MaxUint32
    q1 := q >> 32
    p := q0 * m0
    p0 := p & math.MaxUint32
    p1 := p >> 32
    a -= p1 + q0*m1 + q1*m0 + ((q1 * m1) << 32)
    for a > math.MaxUint32 {
        p0 += m0
        a -= m1
    }
    return SubMod(a<<32, p0, m)
}

// MulMod returns a b (mod m).
func MulMod(a, b, m uint64) uint64 {
    a0 := a & math.MaxUint32
    a1 := a >> 32
    b0 := b & math.MaxUint32
    b1 := b >> 32
    p0 := a0 * b0
    p1 := AddMod(a0*b1, a1*b0, m)
    p2 := a1 * b1
    return AddMod(p0, Lshift32Mod(AddMod(p1, Lshift32Mod(p2, m), m), m), m)
}

// PowMod returns a^b (mod m), where 0^0 = 1.
func PowMod(a, b, m uint64) uint64 {
    r := 1 % m
    for b != 0 {
        if (b & 1) != 0 {
            r = MulMod(r, a, m)
        }
        a = MulMod(a, a, m)
        b >>= 1
    }
    return r
}

func randUint64() uint64 {
    return uint64(rand.Uint32()) | (uint64(rand.Uint32()) << 32)
}

func main() {
    var biga, bigb, bigm, actual, bigmul, expected big.Int
    for i := 1; true; i++ {
        a := randUint64()
        b := randUint64()
        m := randUint64()
        biga.SetUint64(a)
        bigb.SetUint64(b)
        bigm.SetUint64(m)
        actual.SetUint64(MulMod(a, b, m))
        bigmul.Mul(&biga, &bigb)
        expected.Mod(&bigmul, &bigm)
        if actual.Cmp(&expected) != 0 {
            panic(fmt.Sprintf("MulMod(%d, %d, %d): expected %s; got %s", a, b, m, expected.String(), actual.String()))
        }
        if i%10 == 0 {
            actual.SetUint64(PowMod(a, b, m))
            expected.Exp(&biga, &bigb, &bigm)
            if actual.Cmp(&expected) != 0 {
                panic(fmt.Sprintf("PowMod(%d, %d, %d): expected %s; got %s", a, b, m, expected.String(), actual.String()))
            }
        }
        if i%100000 == 0 {
            println(i)
        }
    }
}

上述代码的C翻译,主函数中带有边缘测试值:

#include <stdio.h>
// AddMod returns a + b (mod m).
unsigned long long AddMod(unsigned long long a, unsigned long long b, unsigned long long m){
    a %= m;
    b %= m;
    if (a >= m-b) {
        return a - (m - b);
    }
    return a + b;
}

// SubMod returns a - b (mod m).
unsigned long long SubMod(unsigned long long a, unsigned long long b, unsigned long long m){
    a %= m;
    b %= m;
    if (a < b) {
        return a + (m - b);
    }
    return a - b;
}

// Lshift32Mod returns 2^32 a (mod m).
unsigned long long Lshift32Mod(unsigned long long a, unsigned long long m){
    a %= m;
    // Let A = 2^32 a. The desired result is A - q* m, where q* = [A/m].
    // Approximate q* from below by q = [A/(m+err)] for the err in (0, 2^32] such
    // that 2^32|m+err. The discrepancy is
    //
    // q* - q < A (1/m - 1/(m+err)) + 1 = A err/(m (m+err)) + 1
    //
    // A - q m = A - q* m + (q* - q) m < m + A err/(m+err) + m < 2 m + 2^64.
    //
    // We conclude that a handful of loop iterations suffice.
    unsigned long long m0 = m & 0xFFFFFFFF;
    unsigned long long m1 = m >> 32;
    unsigned long long q = a / (m1 + 1);
    unsigned long long q0 = q & 0xFFFFFFFF;
    unsigned long long q1 = q >> 32;
    unsigned long long p = q0 * m0;
    unsigned long long p0 = p & 0xFFFFFFFF;
    unsigned long long p1 = p >> 32;
    a -= p1 + q0*m1 + q1*m0 + ((q1 * m1) << 32);
    while (a > 0xFFFFFFFF) {
        p0 += m0;
        a -= m1;
    }
    return SubMod(a<<32, p0, m);
}

// MulMod returns a b (mod m).
unsigned long long MulMod(unsigned long long a, unsigned long long b, unsigned long long m){

    unsigned long long a0 = a & 0xFFFFFFFF;
    unsigned long long a1 = a >> 32;
    unsigned long long b0 = b & 0xFFFFFFFF;
    unsigned long long b1 = b >> 32;
    unsigned long long p0 = a0 * b0;
    unsigned long long p1 = AddMod(a0*b1, a1*b0, m);
    unsigned long long p2 = a1 * b1;

    return AddMod(p0, Lshift32Mod(AddMod(p1, Lshift32Mod(p2, m), m), m), m);
}

// PowMod returns a^b (mod m), where 0^0 = 1.
unsigned long long PowMod(unsigned long long a, unsigned long long b, unsigned long long m){
    unsigned long long r = 1 % m;
    while (b != 0) {
        if ((b & 1) != 0) {
            r = MulMod(r, a, m);
        }
        a = MulMod(a, a, m);
        b >>= 1;
    }
    return r;
}


int main(void){
    unsigned long long a = 4294967189;
    unsigned long long b = 4294967231;
    unsigned long long m = 18446743979220271189;
    unsigned long long c = 0;

    c = PowMod(a, b, m);
    printf("%llu %llu %llu %llu", a, b, m, c);
}

【讨论】:

  • @Nae 开始了。运算符与 C 中的相同,但 := 除外,它声明并分配了一个新变量。
  • 好的,我看到func MulMod(a, b, m uint32) uint32 通过将运算分解为四个 n/2 数学乘积来执行比 n 位更宽的乘法运算。希望 OP 可以翻译成 C 而不会产生错误。
  • @Nae 将操作数分成一半大小的块并使用长乘法/除法。
  • @DavidEisenstat Wee!翻译起来比我预期的要容易得多,而且效果很好,非常感谢!我会在一分钟内编辑你的 C 翻译帖子。
  • @DavidEisenstat 有什么办法可以引用这段代码吗?我需要它作为我本科论文的一部分。
猜你喜欢
  • 2017-07-28
  • 2018-07-22
  • 2017-04-03
  • 2017-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-21
  • 2011-01-13
相关资源
最近更新 更多