「学习笔记」素性测试
给定一个正整数 \(n\),判断它是否是质数。
Fermat 素性测试
根据费马小定理,如果 \(n\) 是质数,则会满足 \(a^{n - 1} \equiv 1 \pmod n\)。
基本思想是不断地选取在 \(\left [2, n-1 \right ]\) 中的基 \(a\),并检验是否每次都有 \(a^{n-1} \equiv 1 \pmod n\)。
如果 \(a^{n−1} \bmod n = 1\) 但 \(n\) 不是素数,则 \(n\) 被称为以 \(a\) 为底的伪素数。我们在实践中观察到,如果 \(a^{n−1} \bmod n = 1\),那么 \(n\) 通常是素数。但这里也有个反例:如果 \(n = 341\) 且 \(a = 2\),即使 \(341 = 11 \cdot 31\) 是合数,有 \(2^{340}\equiv 1 {\pmod {341}}\)。事实上,\(341\) 是最小的伪素数基数。
因此很遗憾,费马小定理的逆定理并不成立,换言之,满足了 \(a^{n-1} \equiv 1 \pmod n\),\(n\) 也不一定是素数。
bool millerRabin(int n) {
if (n < 3) return n == 2;
// test_time 为测试次数,建议设为不小于 8 的整数以保证正确率,但也不宜过大,否则会影响效率
for (int i = 1; i <= test_time; ++i) {
int a = rand() % (n - 2) + 2;
if (qpow(a, n - 1, n) != 1) return 0;
}
return 1;
}
Miller-Rabin 素性测试
Miller-Rabin 素性测试(Miller–Rabin primality test)是进阶的素数判定方法。它是由 Miller 和 Rabin 二人根据费马小定理的逆定理(费马测试)优化得到的。
二次探测原理
如果 \(p\) 是奇素数,则 \(x^2 \equiv 1 \pmod p\) 的解为 \(x \equiv 1 \pmod p\) 或者 \(x \equiv p - 1 \pmod p\)。
证明:
又因为 \(p\) 是质数,所以只能有 \((x + 1) \equiv 0 \pmod p\) 或者 \((x - 1) \equiv 0 \pmod p\)。
所以 \(x \equiv p - 1 \pmod p\) 或者 \(x \equiv 1 \pmod p\)。
不妨将费马小定理和二次探测定理结合起来使用:
将 \(a^{n-1} \equiv 1 \pmod n\) 中的指数 \(n−1\) 分解为 \(n−1=u \times 2^t\),在每轮测试中对随机出来的 \(a\) 先求出 \(v = a^{u} \bmod n\),之后对这个值执行最多 \(t\) 次平方操作,若发现非平凡平方根时即可判断出其不是素数,否则再使用 Fermat 素性测试判断。
同余方程 \(x^2 \equiv 1 \pmod n\) 除 \(x \equiv \pm 1 \pmod n\) 的任何根称为“以 \(n\) 为模的 \(1\) 的非平凡平方根”。 若 \(n\) 存在以 \(n\) 为模的 \(1\) 的非平凡平方根,则 \(n\) 是合数。
\(x \equiv -1 \pmod n\) 可以看作是 \(x \equiv -1 + p \pmod n\),即 \(x \equiv p - 1 \pmod n\)
bool millerRabin(int n) {
if (n < 3 || n % 2 == 0) return n == 2;
int u = n - 1, t = 0;
while (u % 2 == 0) u /= 2, ++t;
// test_time 为测试次数,建议设为不小于 8 的整数以保证正确率,但也不宜过大,否则会影响效率
for (int i = 0; i < test_time; ++i) {
int a = rand() % (n - 2) + 2, v = qpow(a, u, n);
if (v == 1) continue;
int s;
for (s = 0; s < t; ++s) {
if (v == n - 1) break; // 得到平凡平方根 n-1,通过此轮测试
v = 1ll * v * v % n;
}
// 如果找到了非平凡平方根,则会由于无法提前 break; 而运行到 s == t
// 如果 Fermat 素性测试无法通过,则一直运行到 s == t 前 v 都不会等于 -1
if (s == t) return 0;
}
return 1;
}