为了提高这个素数测试的正确率,一个很自然的想法就是多次选取 a 来判断,一旦不满足费马小定理就判定它为合数,显然正确率随 a 的选取次数的增加而增加。我们随机选取若干个小于 n 的数来作为 a 的取值进行测试,这个判定素数的方法就是所谓的 Fermat 素性测试。
然而不幸的是,存在 Carmichael 数,它们满足当 a 取遍所有小于 n 的数时都满足费马小定理,自身却是合数。
二次探测定理
要继续加强素数的判定方法,就轮到二次探测定理出场了。
当需要判定的数 n>2 时,显然 n−1 是偶数,如果小于 n 的数 a 满足 an−1≡1(modn) ,那么 an−1−1=(a2n−1+1)(a2n−1−1)∣n ,若 n 为素数,则 a2n−1≡n−1(modn) 或 a2n−1≡n+1(modn) ,我们可以利用这一性质来继续加强素数的判定。
二次探测定理就是指,如果 p 是奇素数,那么对于正整数 x ,若 x2≡1(modp) ,则 x≡1(modp) 或 x≡p−1(modp) 。
Miller Rabin 素性测试
Miller Rabin 素性测试综合以上方法,不断提取 n−1 的因子 2 ,将 n−1 表示为 d×2r 的形式(其中 d 为奇数),首先得到 ad ,然后不断将其平方直到变为 an−1 ,每次平方后首先测试是否满足 (ad×2s)2≡1(modn) ,看是否满足二次探测定理的条件,如果满足就立即判断是否满足它的结论,一旦不满足就判断 n 为合数。
Miller Rabin 素性测试也是不确定算法,我们可以把以 a 为底的满足 Miller Rabin 素性测试的合数称为以 a 为底的强伪素数。第一个以 2 为底的强伪素数是 2047 ,而第一个以 2 和 3 为底的强伪素数则大到了 1 373 653 。
对于大数的 Miller Rabin 测试,底数一般是随机选取,但是当待测数不太大时,底数的选取就有一些技巧了。如,当 n<4,759,123,141 时,选取 2,7,61 进行测试即可。随机选取 k 个底数进行测试的失误率大概是 4−k 。
另外还有第一个多项式的、确定的、无需其它条件的素性判断算法 AKS 算法,之后有空可以了解一下。
Miller Rabin 的一点小优化:当某一次平方后得到 ad×2s≡n−1(modn) ,那么之后所有的平方操作都会得到 1 ,可以直接通过此次测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
boolMillerRabin(int n, int t = 8){ if (n < 3 || n % 2 == 0) returnfalse; // 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 = (longlong)u * u % n; } if (s == r) returnfalse; //如果存在一次平方后不满足条件,s 会增加到 r ,说明此次测试失败 } returntrue; }
Comments