Miller-Rabin素数测试算法
title: Miller-Rabin素数测试算法
date: 2022-03-15 18:42:01
categories: 算法
tags: [素数,算法]
mathjax: true
学习RSA过程中用到了大素数,记录学习过程。
目前没有算法能够直接生成一个素数(据本人了解),只能通过不断生成随机数直到产生素数的方法来生成。如果$n$较小,可以直接对$2$到$\sqrt{n}$ 的所有整数取模验证,但当$n$特别大时,$O(\sqrt{n})$ 复杂度耗费的大量时间是不能被接受的。此时如果牺牲部分准确度来换取时间的优化,即Miller-Rabin算法的思想。
根据百度百科,使用快速幂运算,这个算法的时间复杂度是 $O(𝑘\log3𝑛)$ ,判错概率是$4{-k}$,$k$为判断次数。下面为该算法的实现原理。
引理
1. 费马小定理
如果$p$是素数,且 $gcd(a, p) = 1$ ,则有 $a^{p-1}\equiv 1 \pmod {p}$ 。
它的逆命题并不能推出$p$为素数,但如果不满足 $a^{p-1} \equiv 1 \pmod {p}$ 的$p$一定是合数。
2. 有限域上的平方根定理
如果$p$是一个奇质数,且 $e \geq 1$ ,则方程
$$
x2 \equiv 1 \pmod {pe}
$$
仅有两个根 $x=1$ 或者 $x=-1$ ,$\pm1$也称为1的平凡平方根。
在 $e=1$ 即模$p$的情况下,$x=-1$等价于$x=p-1$ 。
把 $x = 1$ 和 $x = p-1$ 称为“$x$对模$p$来说1的平凡平方根”不太通顺,Miller-Rabin索性测试用到这个方程:$x2 \equiv 1 \pmod {n}$ 。如果一个数$x$满足方程$x2 \equiv 1 \pmod {𝑛}$,但$x$不等于平凡平方根1或$n-1$ ,那么称$x$是对模n来说1的“非平凡”平方根。例如,$x=6$ , $n=35$ ,6是对模35来说1的非平凡平方根。
推论:如果对模n存在1的非平凡平方根,则n是合数。
Miller-Rabin算法
判断一个大数n是否为素数时,可以利用上面的费马小定理 $a^{n-1} \equiv 1 \pmod {n}$ ,如果不满足该式,则可断定n为合数。但在该判断前可以用引理2的推理进一步增强准确性。
对于偶数$n-1$ ,一定可以拆分为 $n-1 = 2 s * d$ :
$$
a {n-1} = a {2 {s} * d} = (((ad)2)\cdots)2
$$
从$x=ad$开始,依次平方s次,每次平方的时候模n。由有限域上的平方根定理可知,如果模n时结果为1,那么$x$一定是1或者$n-1$,如果不满足则n为合数。如果$x$为1的平凡平方根或模n的结果不为1,$x = x^2$继续下一次平方,然后模n判断。
当平方到了s次,此时$x = a ^ {n-1}$ ,判断$x$模n的结果是否为1。如果不为1判定n为合数,如果为1则可认为n大概率为素数。
算法的代码实现
在进行$a{d} \pmod n$ 和 $x2 \pmod n$时如果直接运算,大概率会溢出,所以需要使用快速幂和快速乘。
#include <stdio.h>
#include <stdlib.h>
typedef long long ll;
ll qMul(ll a, ll b, ll mod);
ll qPow(ll base, ll power, ll mod);
int Miller_Rabin(ll n, int repeat);
int main() {
ll n;
while (printf("Input a n to judge:\n") && scanf("%lld", &n) != EOF) {
printf(Miller_Rabin(n, 10) ? "Yes\n\n" : "No\n\n");
}
return 0;
}
//quick multiply
ll qMul(ll a, ll b, ll mod) {
ll ret = 0;
while (b) {
if (b & 1) ret = (ret + a) % mod;
a = (a + a) % mod;
b >>= 1;
}
return ret;
}
//quick power
ll qPow(ll base, ll power, ll mod) {
ll ret = 1;
while (power) {
if (power & 1) ret = qMul(ret, base, mod);
base = qMul(base, base, mod);
power >>= 1;
}
return ret % mod;
}
int Miller_Rabin(ll n, int repeat) {
if (n == 2 || n == 3) return 1;
if (n % 2 == 0 || n == 1) return 0;
ll d = n - 1;
int s = 0;
while (!(d & 1)) ++s, d >>= 1;
for (int i = 0; i < repeat; i++) {
ll a = rand() % (n - 3) + 2;
ll x = qPow(a, d, n);
ll y = 0;
for (int j = 0; j < s; j++) {
y = qMul(x, x, n);
if (y == 1 && x != 1 && x != (n - 1)) return 0;
x = y;
}
if (y != 1) return 0;
}
return 1;
}