如何在不使用太多计算器的情况下计算5 ^ 55 mod 221的模数?
我想在密码学的数论中有一些简单的原则可以用来计算这样的问题。
如何在不使用太多计算器的情况下计算5 ^ 55 mod 221的模数?
我想在密码学的数论中有一些简单的原则可以用来计算这样的问题。
好的,你想计算 a^b mod m
。 首先我们将采用朴素方法,然后再看如何改进。
首先,将 a mod m
进行约束。这意味着找到一个数 a1
,使得 0 <= a1 < m
并且 a = a1 mod m
。 然后,在循环中重复使用乘以 a1
并再次进行模运算 mod m
。 因此,伪代码如下:
a1 = a reduced mod m
p = 1
for(int i = 1; i <= b; i++) {
p *= a1
p = p reduced mod m
}
通过这种方法,我们避免了大于m^2
的数字,这是关键所在。我们之所以避免大于m^2
的数字,是因为在每一步中,0<=p且0<=a1。
例如,让我们计算5^55 mod 221
。首先,5
已经被减小到mod 221
。
因此,5^55 = 112 mod 221
。
现在,我们可以通过使用幂运算平方法来改进这个方法;这是一种著名的技巧,可以将指数幂运算降低到只需要log b
次乘法而不是b
次。请注意,使用我上面描述的算法和幂运算平方法进行改进,您最终会得到从右到左的二进制方法。a1 = a reduced mod m
p = 1
while (b > 0) {
if (b is odd) {
p *= a1
p = p reduced mod m
}
b /= 2
a1 = (a1 * a1) reduced mod m
}
因此,由于55在二进制中表示为110111
1 * (5^1 mod 221) = 5 mod 221
5 * (5^2 mod 221) = 125 mod 221
125 * (5^4 mod 221) = 112 mod 221
112 * (5^16 mod 221) = 112 mod 221
112 * (5^32 mod 221) = 112 mod 221
因此答案是5^55 = 112 mod 221
。这种方法的原理是:
55 = 1 + 2 + 4 + 16 + 32
以便于
5^55 = 5^(1 + 2 + 4 + 16 + 32) mod 221
= 5^1 * 5^2 * 5^4 * 5^16 * 5^32 mod 221
= 5 * 25 * 183 * 1 * 1 mod 221
= 22875 mod 221
= 112 mod 221
在计算5^1 mod 221
, 5^2 mod 221
等的步骤中,我们注意到5^(2^k)
= 5^(2^(k-1)) * 5^(2^(k-1))
,因为2^k = 2^(k-1) + 2^(k-1)
,所以我们可以先计算5^1
并减少mod 221
,然后将其平方并减少mod 221
以获得5^2 mod 221
等。
上述算法正式化了这个想法。
%
运算符是模运算符。因此,int p = 625 % 221
将把 183
赋值给 p
。您可以通过将 625
除以 221
并获得答案 2
来实现相同的功能作为整数除法。然后你取 625 - 2 * 221
来得到余数。在这种情况下,625 - 2 * 221 = 183
是答案。 - jason5^16 == 1 (mod 221)
。因此,5^k == 5^(k%16) (mod 221)
。 - Cascabel补充Jason的答案:
如果要处理非常大的指数,你可以利用指数的二进制展开来加快计算速度。首先通过重复平方计算5、5^2、5^4和5^8模221的余数:
5^1 = 5(mod 221)
5^2 = 5^2 (mod 221) = 25(mod 221)
5^4 = (5^2)^2 = 25^2(mod 221) = 625 (mod 221) = 183(mod221)
5^8 = (5^4)^2 = 183^2(mod 221) = 33489 (mod 221) = 118(mod 221)
5^16 = (5^8)^2 = 118^2(mod 221) = 13924 (mod 221) = 1(mod 221)
5^32 = (5^16)^2 = 1^2(mod 221) = 1(mod 221)
现在我们可以编写
55 = 1 + 2 + 4 + 16 + 32
so 5^55 = 5^1 * 5^2 * 5^4 * 5^16 * 5^32
= 5 * 25 * 625 * 1 * 1 (mod 221)
= 125 * 625 (mod 221)
= 125 * 183 (mod 183) - because 625 = 183 (mod 221)
= 22875 ( mod 221)
= 112 (mod 221)
你可以看到,对于非常大的指数,这将会更快(我相信它是对数而不是线性的b,但不确定)。
/* The algorithm is from the book "Discrete Mathematics and Its
Applications 5th Edition" by Kenneth H. Rosen.
(base^exp)%mod
*/
int modular(int base, unsigned int exp, unsigned int mod)
{
int x = 1;
int power = base % mod;
for (int i = 0; i < sizeof(int) * 8; i++) {
int least_sig_bit = 0x00000001 & (exp >> i);
if (least_sig_bit)
x = (x * power) % mod;
power = (power * power) % mod;
}
return x;
}
mod*mod > UINT_MAX + 1
时,x * power
和 power * power
可能会发生溢出。 - chux - Reinstate Monica5^55 mod221
= ( 5^10 * 5^10 * 5^10 * 5^10 * 5^10 * 5^5) mod221
= ( ( 5^10) mod221 * 5^10 * 5^10 * 5^10 * 5^10 * 5^5) mod221
= ( 77 * 5^10 * 5^10 * 5^10 * 5^10 * 5^5) mod221
= ( ( 77 * 5^10) mod221 * 5^10 * 5^10 * 5^10 * 5^5) mod221
= ( 183 * 5^10 * 5^10 * 5^10 * 5^5) mod221
= ( ( 183 * 5^10) mod221 * 5^10 * 5^10 * 5^5) mod221
= ( 168 * 5^10 * 5^10 * 5^5) mod221
= ( ( 168 * 5^10) mod 221 * 5^10 * 5^5) mod221
= ( 118 * 5^10 * 5^5) mod221
= ( ( 118 * 5^10) mod 221 * 5^5) mod221
= ( 25 * 5^5) mod221
= 112
在Java中,Jason的答案为(注意 i < exp
)。
private static void testModulus() {
int bse = 5, exp = 55, mod = 221;
int a1 = bse % mod;
int p = 1;
System.out.println("1. " + (p % mod) + " * " + bse + " = " + (p % mod) * bse + " mod " + mod);
for (int i = 1; i < exp; i++) {
p *= a1;
System.out.println((i + 1) + ". " + (p % mod) + " * " + bse + " = " + ((p % mod) * bse) % mod + " mod " + mod);
p = (p % mod);
}
}
这是我为IBAN验证编写的代码部分。欢迎使用。
static void Main(string[] args)
{
int modulo = 97;
string input = Reverse("100020778788920323232343433");
int result = 0;
int lastRowValue = 1;
for (int i = 0; i < input.Length; i++)
{
// Calculating the modulus of a large number Wikipedia http://en.wikipedia.org/wiki/International_Bank_Account_Number
if (i > 0)
{
lastRowValue = ModuloByDigits(lastRowValue, modulo);
}
result += lastRowValue * int.Parse(input[i].ToString());
}
result = result % modulo;
Console.WriteLine(string.Format("Result: {0}", result));
}
public static int ModuloByDigits(int previousValue, int modulo)
{
// Calculating the modulus of a large number Wikipedia http://en.wikipedia.org/wiki/International_Bank_Account_Number
return ((previousValue * 10) % modulo);
}
public static string Reverse(string input)
{
char[] arr = input.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
请提供一份基于C语言的Jason答案的另一个实现。
在与我的同学讨论之后,基于Jason的解释,如果你不太关心性能,我更喜欢递归版本:
例如:
#include<stdio.h>
int mypow( int base, int pow, int mod ){
if( pow == 0 ) return 1;
if( pow % 2 == 0 ){
int tmp = mypow( base, pow >> 1, mod );
return tmp * tmp % mod;
}
else{
return base * mypow( base, pow - 1, mod ) % mod;
}
}
int main(){
printf("%d", mypow(5,55,221));
return 0;
}
这被称为模幂运算(https://en.wikipedia.org/wiki/Modular_exponentiation)。
假设你有以下表达式:
19 ^ 3 mod 7
不要直接为19提供电源,你可以采取以下方法:
(((19 mod 7) * 19) mod 7) * 19) mod 7
但是这可能需要很长时间,因为有很多顺序乘法,所以你可以对平方值进行乘法:
x mod N -> x ^ 2 mod N -> x ^ 4 mod -> ... x ^ 2 |log y| mod N
x ^ y == (x ^ |y/2|) ^ 2 if y is even
x ^ y == x * ((x ^ |y/2|) ^ 2) if y is odd
因此,在Java中,递归模幂算法将如下所示:
/**
* Modular exponentiation algorithm
* @param x Assumption: x >= 0
* @param y Assumption: y >= 0
* @param N Assumption: N > 0
* @return x ^ y mod N
*/
public static long modExp(long x, long y, long N) {
if(y == 0)
return 1 % N;
long z = modExp(x, Math.abs(y/2), N);
if(y % 2 == 0)
return (long) ((Math.pow(z, 2)) % N);
return (long) ((x * Math.pow(z, 2)) % N);
}
特别感谢@chux发现了在y和0比较时返回值不正确的错误。