Miller Rabin 定理是一种素性测试方法,其中利用到了素数的一些性质,还有费马小定理,以及二次探测定理。
素数的一些性质
我们首先看一下素数的 4 条有趣的性质:
-
不存在最大的素数 假设最大的素数为 ,小于它的所有素数为 ,那么我们可以构造出数 ,它不能被 中的任何一个数整除,故它是素数,又它显然大于 ,于是可以得出不存在最大的素数。
-
相邻素数之间的间隔任意大
考虑数 满足 ,那么 一定能被 整除,因此长度为 的数列 (注意没有 )都是合数,由于 可以任意大,所以相邻素数之间的间隔可以任意大。
-
所有大于 2 的素数都可以被唯一的表示为两个平方数之差
所有大于 2 的素数都是奇数,假设它是 ,那么它可以被表示为 。
下面证明这种表示法是唯一的。假设素数 可以被表示为 ,即 ,因为 是素数,所以只能有 ,得证。
-
当 为大于 2 的整数时, 和 两个数中,如果其中一个数是素数,那么另一个数一定是合数
显然 ,如果 ,那么 为合数;如果 ,那么 为合数。
Fermat 素性测试
回顾一下费马小定理:
若 为素数,且 ,那么有 。
根据费马小定理,我们很容易想到能不能利用它的逆定理来判定素数呢?很遗憾,费马小定理的逆定理是伪的。
人们用伪素数来称呼满足费马小定理的合数,如果是令 的情况下的伪素数,就叫它以 2 为底的伪素数,其他取值情况下的名字以此类推。
为了提高这个素数测试的正确率,一个很自然的想法就是多次选取 来判断,一旦不满足费马小定理就判定它为合数,显然正确率随 的选取次数的增加而增加。我们随机选取若干个小于 的数来作为 的取值进行测试,这个判定素数的方法就是所谓的 Fermat 素性测试。
然而不幸的是,存在 Carmichael 数,它们满足当 取遍所有小于 的数时都满足费马小定理,自身却是合数。
二次探测定理
要继续加强素数的判定方法,就轮到二次探测定理出场了。
当需要判定的数 时,显然 是偶数,如果小于 的数 满足 ,那么 ,若 为素数,则 或 ,我们可以利用这一性质来继续加强素数的判定。
二次探测定理就是指,如果 是奇素数,那么对于正整数 ,若 ,则 或 。
Miller Rabin 素性测试
Miller Rabin 素性测试综合以上方法,不断提取 的因子 2 ,将 表示为 的形式(其中 为奇数),首先得到 ,然后不断将其平方直到变为 ,每次平方后首先测试是否满足 ,看是否满足二次探测定理的条件,如果满足就立即判断是否满足它的结论,一旦不满足就判断 为合数。
Miller Rabin 素性测试也是不确定算法,我们可以把以 为底的满足 Miller Rabin 素性测试的合数称为以 为底的强伪素数。第一个以 2 为底的强伪素数是 2047 ,而第一个以 2 和 3 为底的强伪素数则大到了 1 373 653 。
对于大数的 Miller Rabin 测试,底数一般是随机选取,但是当待测数不太大时,底数的选取就有一些技巧了。如,当 时,选取 进行测试即可。随机选取 个底数进行测试的失误率大概是 。
另外还有第一个多项式的、确定的、无需其它条件的素性判断算法 AKS 算法,之后有空可以了解一下。
Miller Rabin 的一点小优化:当某一次平方后得到 ,那么之后所有的平方操作都会得到 1 ,可以直接通过此次测试。
bool MillerRabin(int n, int t = 8) { if (n < 3 || n % 2 == 0) return false; // n = 2 时虽然是素数,但是不满足 MillerRabin 素性测试的条件 int d = n - 1, r = 0; while (d % 2 == 0) d >>= 1, r++; while (t--) { // t 为测试次数,最好大于 8 int a = rand() % (n - 2) + 2, s; //为了不让 a = 1 ,所以最后是 +2 int u = qpow(a, d, n); if (u == 1) continue; //此时后面平方总会得到 1 ,必然满足测试,直接跳过 for (s = 0; s < r; ++s) { if (u == n - 1) break; //小优化 u = (long long)u * u % n; } if (s == r) return false; //如果存在一次平方后不满足条件,s 会增加到 r ,说明此次测试失败 } return true;}参考: