模逆可以如下计算(来自Rosetta Code):
#include <stdio.h>
int mul_inv(int a, int b)
{
int b0 = b, t, q;
int x0 = 0, x1 = 1;
if (b == 1) return 1;
while (a > 1) {
q = a / b;
t = b, b = a % b, a = t;
t = x0, x0 = x1 - q * x0, x1 = t;
}
if (x1 < 0) x1 += b0;
return x1;
}
但是,您可以看到输入为ints
。上面的代码也可以用于无符号整数(例如uint64_t
)吗?我的意思是,用int
替换所有uint64_t
可以吗?我可以尝试输入很少的内容,但尝试使用所有64位组合都是不可行的。
我对两个方面特别感兴趣:
对于a
和b
的值[0,2 ^ 64),所有计算都不会上溢/下溢(或无损害的上溢)吗?
(x1 < 0)
在未签名的情况下会是什么样?
答案 0 :(得分:2)
首先该算法如何工作?它基于Extended Euclidean algorithm用于计算GCD。简而言之,想法是:如果我们可以找到一些整数系数m
和n
使得
a*m + b*n = 1
然后m
将是模逆问题的答案。很容易看到,因为
a*m + b*n = a*m (mod b)
幸运的是,扩展欧几里得算法确实做到了这一点:如果a
和b
是互质的,它将找到这样的m
和n
。它的工作方式如下:对于每次迭代,跟踪两个三元组(ai, xai, yai)
和(bi, xbi, ybi)
,以便在每个步骤中
ai = a0*xai + b0*yai
bi = a0*xbi + b0*ybi
因此,当算法最终在ai = 0
和bi = GCD(a0,b0)
的状态下停止时,则
1 = GCD(a0,b0) = a0*xbi + b0*ybi
使用更明确的方式来计算模数:if
q = a / b
r = a % b
然后
r = a - q * b
另一个重要的事情是,可以证明在每一步a
和b
上,对于肯定的|xai|,|xbi| <= b
和|yai|,|ybi| <= a
。这意味着在计算这些系数时不会有溢出。此外,不幸的是,在每个方程式中第一个步骤之后,每个步骤都有可能出现负值,一个为正,另一个为负。
您问题中的代码所执行的是同一算法的简化版本:由于我们感兴趣的只是x[a/b]
系数,因此仅跟踪它们,而忽略y[a/b]
。使该代码适用于uint64_t
的最简单方法是在这样的单独字段中明确跟踪符号:
typedef struct tag_uint64AndSign {
uint64_t value;
bool isNegative;
} uint64AndSign;
uint64_t mul_inv(uint64_t a, uint64_t b)
{
if (b <= 1)
return 0;
uint64_t b0 = b;
uint64AndSign x0 = { 0, false }; // b = 1*b + 0*a
uint64AndSign x1 = { 1, false }; // a = 0*b + 1*a
while (a > 1)
{
if (b == 0) // means original A and B were not co-prime so there is no answer
return 0;
uint64_t q = a / b;
// (b, a) := (a % b, b)
// which is the same as
// (b, a) := (a - q * b, b)
uint64_t t = b; b = a % b; a = t;
// (x0, x1) := (x1 - q * x0, x0)
uint64AndSign t2 = x0;
uint64_t qx0 = q * x0.value;
if (x0.isNegative != x1.isNegative)
{
x0.value = x1.value + qx0;
x0.isNegative = x1.isNegative;
}
else
{
x0.value = (x1.value > qx0) ? x1.value - qx0 : qx0 - x1.value;
x0.isNegative = (x1.value > qx0) ? x1.isNegative : !x0.isNegative;
}
x1 = t2;
}
return x1.isNegative ? (b0 - x1.value) : x1.value;
}
请注意,如果a
和b
不是互质的,或者b
为0或1时,此问题将无法解决。在所有这些情况下,我的代码都返回0
,这对于任何实际的解决方案都是不可能的值。
还要注意,尽管计算出的值实际上是模逆,但由于在uint64_t
上的乘法运算会溢出,因此简单乘法不会总是产生1。例如,对于a = 688231346938900684
和b = 2499104367272547425
,结果为inv = 1080632715106266389
a * inv = 688231346938900684 * 1080632715106266389 =
= 743725309063827045302080239318310076 =
= 2499104367272547425 * 297596738576991899 + 1 =
= b * 297596738576991899 + 1
但是,如果对类型a
的{{1}}和inv
进行幼稚乘法,则会得到uint64_t
,因此4042520075082636476
将是{{ 1}},而不是预期的(a*inv)%b
。