Miller–Rabin 素数判定法
前提知識
を奇素数とする。フェルマーの小定理より、 と互いに素な正整数 について
である。
を で割れるだけ割って と表す。ただし は正整数、 は奇数である。上の式は
と表せる。
このとき、次の定理が成り立つ。
定理①
各変数を上のように定義したとき、
または
が成り立つ。
以下これを示す。
証明
の値について考える。
の平方根なので、 よりこれは または と合同である。
と合同なら が成立。
と合同なら平方根 を再びとり、同じように考える。
の間で と合同なものがあれば が成立する。
そういうものがない場合、 となるので が成立。
証明終わり
これの対偶を取ると、
定理②
を正整数、 を と互いに素な正整数として、
、ただし は正奇数、 は正整数としたとき、
かつ
ならば、 は合成数
がいえる。
判定法
定理②を用いて確率的な素数判定をするものが Miller–Rabin 素数判定法である。
具体的には、 n を素数判定したい正整数とすると、
- を の冪 と奇数 の積に分解する
- 以下の試行を何回か繰り返す
- 正整数 をランダムに選ぶ
- と が互いに素でないなら は合成数。終了
- 上の条件 をともに満たす場合、 は合成数。終了
- i. に戻る
- 試行に引っかからなかった場合、 は確率的素数である。終了
ここで、定理②の逆は成り立たない。つまり が合成数でも上の2つの条件 を満たさない場合がある。
しかし を様々に変えて試行すれば、いつかは を満たす が見つかる可能性が上がる。つまり を合成数と判定できる確率が上がる。
条件付き決定的判定
また、特定の底 について判定を行えば、ある範囲では素数であると決定的に判定できるような と範囲がいくつか発見されている。
例えば、 未満の数は、 の7つの底について判定を行えば、素数であると決定的に判定できることが分かっている。
実装例
TypeScript
export const millerRabin = (n: bigint) => {
if (n < 0n) throw Error('引数は正の整数でなければなりません');
// 2, 2の倍数 の場合の判定
if (n === 2n) return true;
if (n === 1n || n % 2n === 0n) return false;
const bit_num = n.toString(2).length;
const s = BigInt((n - 1n).toString(2).match(/0+$/g)?.[0].length ?? 0);
const d = (n - 1n) >> s;
if (n < 2n ** 64n) {
/** n が 2^64 未満の時、決定的に判定できる 参考: https://miller-rabin.appspot.com/#bases7 */
const bases_under_64 = [2n, 325n, 9375n, 28178n, 450775n, 9780504n, 1795265022n] as const;
challenge: for (const b_ of bases_under_64) {
const base = b_ >= n ? b_ % n : b_;
if (base === 0n) continue challenge;
if (exEuclidean(base, n).gcd != 1n) return false;
let y = modPow(base, d, n);
if (y === 1n) continue challenge;
for (let i = 0n; i < s; i++) {
if (y === n - 1n) continue challenge;
y = (y * y) % n;
}
return false;
}
return true;
} else {
/** 試行回数 */
const max_rot = 40;
challenge2: for (let i = 0; i < max_rot; i++) {
let b_ = 0n;
while (b_ < 2n || b_ >= n) {
b_ = getRandBIByBitLength(bit_num); // bit_num ビット以下の乱数を出力
}
if (exEuclidean(b_, n).gcd !== 1n) return false;
const base = b_;
let y = modPow(base, d, n);
if (y === 1n) continue challenge2;
for (let i = 0n; i < s; i++) {
if (y === n - 1n) continue challenge2;
y = (y * y) % n;
}
return false;
}
return true;
}
};