【问题标题】:Finding modulus with very large exponentiation and divisor in C在C中找到具有非常大的幂和除数的模数
【发布时间】:2019-05-01 05:36:46
【问题描述】:

我需要一个 C 中的函数来计算 a^n mod q,其中除数 q 被确定为非常大 (15,383,399,235,709,406,497),并且指数 n 可能也一样大。

基于模乘的性质,即(a * b) mod n = ((a mod n) * (b mod n)) mod n,我的尝试如下:

#include <stdio.h>

unsigned long long int modExp(unsigned long long int base, unsigned long long int expo, unsigned long long int mod)
{
    unsigned long long int out = 1;
    unsigned long long int cnt;
    for (cnt=expo; cnt>0; cnt--)
    {
        out = (out * base) % mod;
    }
    return out;
}

int main()
{
    printf("%llu", modExp(3, (189 + 50 * 223), 15383399235709406497));
    return 0;
}

如上面的 main 函数所示,我用底数 3、指数 (189 + 50 * 223) 和除数 15383399235709406497 测试了我的函数 modExp。它给出了输出 3915400295876975163 并带有一些警告

In function ‘findxA’:
findxA.c:17:32: warning: integer constant is so large that it is unsigned [enabled by default]
     unsigned long long int h = 12036625823877237123;
                                ^
findxA.c:17:5: warning: this decimal constant is unsigned only in ISO C90 [enabled by default]
     unsigned long long int h = 12036625823877237123;
     ^
findxA.c:18:32: warning: integer constant is so large that it is unsigned [enabled by default]
     unsigned long long int q = 15383399235709406497;
                                ^
findxA.c:18:5: warning: this decimal constant is unsigned only in ISO C90 [enabled by default]
     unsigned long long int q = 15383399235709406497;
     ^
findxA.c: In function ‘main’:
findxA.c:34:48: warning: integer constant is so large that it is unsigned [enabled by default]
     printf("%llu", modExp(3, (189 + 50 * 223), 15383399235709406497));
                                                ^
findxA.c:34:5: warning: this decimal constant is unsigned only in ISO C90 [enabled by default]
     printf("%llu", modExp(3, (189 + 50 * 223), 15383399235709406497));
     ^

为了验证这个答案,我将结果(由 C 函数给出)与通过评估用 Haskell 编写的表达式 3^(189 + 50 * 223) `mod` 15383399235709406497 给出的输出进行了比较。这个 Haskell 表达式计算为不同的小数,12349118475990906085。我认为是我的 C 函数错了,因为我相信 Haskell 在处理如此大的小数方面做得更好。

如何改进我的 C 函数 modExp?

编辑:我尝试了the first answer of this question. 的选项但是,当我尝试处理 unsigned long long int 的小数时,我已将每个输入和返回类型从 int 切换为 unsigned长长整数。这导致了分段错误。

Edit2:我错误地使用了上面链接中描述的功能。它不会给出分段错误;但它仍然没有输出正确的小数。

【问题讨论】:

  • 如果数字变大,这些警告可能是您最小的问题。您可以在数字中添加后缀ull 以将它们标记为unsigned long long

标签: c modulo unsigned-long-long-int


【解决方案1】:

这永远不会以这种方式工作!

这里单独乘法的结果

out = (out * base) % mod;

可能需要超过 64 位的基础数据类型。如果发生整数溢出(即最重要的位被截断),则 mod 操作会出错!

使用更大的数据类型,或使用不同的方法 :-)

如果您愿意,顺便说一句,请使用此测试代码查看您的输入实际上发生了两次整数溢出:

#include <stdio.h>

unsigned long long int modExp(unsigned long long int base, unsigned long long int expo, unsigned long long int mod)
{
    unsigned long long int out = 1;
    while(expo>0)
    {
        if(expo % 2 == 1)
            out = (out * base) % mod;
        expo = expo >> 1;
        if (base * base < base) printf("WARNING! INTEGER OVERFLOW!!!!\n");
        base = (base * base) % mod;
    }
    return out;
}

int main()
{
    printf("%llu", modExp(3, (189 + 50 * 223), 15383399235709406497));
    return 0;
}

【讨论】:

  • 为什么有人反对我的回答?整数溢出是 OP 的 powerMod 函数失败的唯一真正原因?
【解决方案2】:

为了减少溢出的机会,你可以依赖(x*y)%z == ((x%z) * (y%z)) % z

例如(未经测试):

unsigned long long int modExp(unsigned long long int base, unsigned long long int expo, unsigned long long int mod)
{
    unsigned long long int baseMod = base%mod;
    unsigned long long int out = 1;
    unsigned long long int cnt;
    for (cnt=expo; cnt>0; cnt--)
    {
        out = ( (out%mod) * baseMod) % mod;
    }
    return out;
}

另请注意,求幂可以作为“平方乘积”更有效地完成。例如,x ** 5 == 1*(x**4) * 0*(x**2) * 1*(x**1) 因为5 == 1*4 + 0*2 + 1*1(或5 == 101b)。

例如(未经测试):

unsigned long long int modExp(unsigned long long int base, unsigned long long int expo, unsigned long long int mod)
{
    unsigned long long int out = 1;
    unsigned long long int temp = base%mod;

    while(exp0 > 0) {
        if( (exp0 & 1) != 0) {
            out = (out * temp) % mod;
        }
        temp = (temp*temp) % mod;
        exp0 >>= 1;
    }
    return out;
}

对于大指数,这应该会对性能产生巨大影响(例如,如果指数是 123456,那么循环将有 17 次迭代,而不是 123456 次迭代)。

还有;如果这是为了某种密码学(并非不可能);那么您应该清楚地说明这一点,因为您可能想要“恒定时间”(以减少计时边信道的机会 - 例如,从 modExp() 执行的时间推断有关指数的信息)。

最后;即使进行了更改,最多 15,383,399,235,709,406,497 的数字对于unsigned long long 来说可能太大了(您需要确保mod * mod &lt;= ULLONG_MAX);这意味着您可能需要使用/实现“大整数”操作(例如,可能需要 typedef struct { uint32_t digit[4]; } MY_NUMBER_TYPE 来处理 128 位整数,具有乘法和取模函数)。

【讨论】:

  • 正如您所建议的,“平方求幂”策略成功了。但是,由于在 C 中处理非常大的整数总是很麻烦,所以我改用 Python,并使用了执行有效幂运算的内置函数 pow()。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-27
相关资源
最近更新 更多