bool fermat(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 (quickPow(a, n - 1, n) != 1) return false; } return true;}
Python
def fermat(n): if n < 3: return n == 2 # test_time 为测试次数,建议设为不小于 8 # 的整数以保证正确率,但也不宜过大,否则会影响效率 for i in range(1, test_time + 1): a = random.randint(0, 32767) % (n - 2) + 2 if quickPow(a, n - 1, n) != 1: return False return True
如果 an−1≡1(modn) 但 n 不是素数,则称 n 为以 a 为底的 Fermat 伪素数。我们在实践中观察到,如果 an−1≡1(modn),那么 n 通常是素数。但其实存在反例:对于 n=341 且 a=2,虽然有 2340≡1(mod341),但是 341=11⋅31 是合数。事实上,对于任何固定的基底 a,这样的反例都有无穷多个 1。
既然对于单个基底,Fermat 素性测试无法保证正确性,一个自然的想法就是多检查几组基底。但是,即使检查了所有可能的与 n 互素的基底 a,依然无法保证 n 是素数。也就是说,费马小定理的逆命题并不成立:即使对于所有 a⊥n,都有 an−1≡1(modn),n 也不一定是素数。这样的数称为 Carmichael 数。它也有无穷多个。这迫使我们寻找更为严格的素性测试。
如果找出了一个非平凡平方根 au×2s≡n−1(modn),则之后的平方操作全都会得到 1。可以选择直接返回 false,也可以放到 t 次平方操作后再返回 false。
这样得到了较正确的 Miller Rabin:(来自 fjzzq2002)
参考实现
[list2tab]
C++
bool millerRabin(int n) { if (n < 3 || n % 2 == 0) return n == 2; if (n % 3 == 0) return n == 3; int u = n - 1, t = 0; while (u % 2 == 0) u /= 2, ++t; // test_time 为测试次数,建议设为不小于 8 // 的整数以保证正确率,但也不宜过大,否则会影响效率 for (int i = 0; i < test_time; ++i) { // 0, 1, n-1 可以直接通过测试, a 取值范围 [2, n-2] int a = rand() % (n - 3) + 2, v = quickPow(a, u, n); if (v == 1) continue; int s; for (s = 0; s < t; ++s) { if (v == n - 1) break; // 得到平凡平方根 n-1,通过此轮测试 v = (long long)v * v % n; } // 如果找到了非平凡平方根,则会由于无法提前 break; 而运行到 s == t // 如果 Fermat 素性测试无法通过,则一直运行到 s == t 前 v 都不会等于 -1 if (s == t) return 0; } return 1;}
Python
def millerRabin(n): if n < 3 or n % 2 == 0: return n == 2 if n % 3 == 0: return n == 3 u, t = n - 1, 0 while u % 2 == 0: u = u // 2 t = t + 1 # test_time 为测试次数,建议设为不小于 8 # 的整数以保证正确率,但也不宜过大,否则会影响效率 for i in range(test_time): # 0, 1, n-1 可以直接通过测试, a 取值范围 [2, n-2] a = random.randint(2, n - 2) v = pow(a, u, n) if v == 1: continue s = 0 while s < t: if v == n - 1: break v = v * v % n s = s + 1 # 如果找到了非平凡平方根,则会由于无法提前 break; 而运行到 s == t # 如果 Fermat 素性测试无法通过,则一直运行到 s == t 前 v 都不会等于 -1 if s == t: return False return True
可以证明 2,奇合数 n>9 通过随机选取的一个基底 a 的 Miller–Rabin 素性测试的概率至多为四分之一。因此,随机选取 k 个基底后,仍将合数误判为素数的概率不超过 1/4k。
证明
设 n−1=u2t,其中,u 是奇数且 t 是正整数。那么,整数 n 可以通过基底为 a 的 Miller–Rabin 素性测试说明
#include <iostream>unsigned long long p[16] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53}; // 根据数据范围可以确定使用的素数最大为53unsigned long long ans;unsigned long long n;// depth: 当前在枚举第几个素数// temp: 当前因子数量为 num的时候的数值// num: 当前因子数// up:上一个素数的幂,这次应该小于等于这个幂次嘛void dfs(unsigned long long depth, unsigned long long temp, unsigned long long num, unsigned long long up) { if (num > n || depth >= 16) return; // 边界条件 if (num == n && ans > temp) { // 取最小的ans ans = temp; return; } for (int i = 1; i <= up; i++) { if (temp * p[depth] > ans) break; // 剪枝:如果加一个这个乘数的结果比ans要大,则必不是最佳方案 dfs(depth + 1, temp = temp * p[depth], num * (i + 1), i); // 取一个该乘数,进行对下一个乘数的搜索 }}using std::cin;using std::cout;int main() { cin.tie(nullptr)->sync_with_stdio(false); cin >> n; ans = ~(unsigned long long)0; dfs(0, 1, 1, 64); cout << ans << '\n'; return 0;}
Pomerance, Carl, John L. Selfridge, and Samuel S. Wagstaff. “The pseudoprimes to 25⋅ 10⁹.” Mathematics of Computation 35, no. 151 (1980): 1003-1026. 的定理 1 说明了,对于固定的基底 a,能够通过更强的 Miller–Rabin 素性测试的合数也是无穷多的。 ↩
本结论及其证明参考了 Crandall, Richard, and Carl Pomerance. Prime numbers: a computational perspective. New York, NY: Springer New York, 2005. 的第 3.5 节。 ↩