// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // IntPrime.hpp // 素数判定(Miller-Rabin、Fermat)for Int #ifndef CALX_INT_PRIME_HPP #define CALX_INT_PRIME_HPP #include "IntBase.hpp" #include #include #include namespace calx { /** * @brief 素数判定アルゴリズム * * 確率的素数判定(Miller-Rabin)と決定的な小素数チェックを提供します。 * RSA暗号、楕円曲線暗号、素因数分解などで使用されます。 */ class IntPrime { public: /** * @brief 確率的素数判定(Miller-Rabin テスト) * * Miller-Rabin アルゴリズムによる確率的素数判定を行います。 * k 回のテストを行い、誤判定確率は最大 4^(-k) です。 * * アルゴリズム: * n-1 = 2^s × d (d は奇数) と分解 * k 回のラウンドで、底 a (2 <= a <= n-2) について: * - a^d mod n が 1 なら次のラウンドへ * - a^(2^r × d) mod n が -1 (r = 0..s-1) なら次のラウンドへ * - どちらも満たさなければ合成数と確定 * * @param n 判定する整数 * @param k テスト回数(デフォルト 20, 誤判定確率 ≤ 2^(-40)) * @return true: おそらく素数, false: 確実に合成数 * * 特殊ケース: * isMillerRabinPrime(n < 2) = false * isMillerRabinPrime(2) = true * isMillerRabinPrime(偶数) = false * isMillerRabinPrime(NaN) = false * isMillerRabinPrime(∞) = false * * 注意: * - k = 20 の場合、誤判定確率は 2^(-40) ≈ 10^(-12) * - 暗号用途では k = 40 以上を推奨(誤判定確率 ≤ 2^(-80)) * - 決定的判定ではないため、確実性が必要な場合は別のアルゴリズムを使用 */ static bool isMillerRabinPrime(const Int& n, int k = 20); /** * @brief フェルマーの小定理による確率的素数判定 * * フェルマーの小定理: p が素数なら a^(p-1) ≡ 1 (mod p) (gcd(a,p)=1) * この逆を利用して素数判定を行います。 * * 注意: * - Carmichael 数(擬素数)を素数と誤判定する可能性があります * - Miller-Rabin の方が信頼性が高いため、通常は Miller-Rabin を使用してください * - 教育目的や簡易チェック用 * * @param n 判定する整数 * @param k テスト回数(デフォルト 5) * @return true: おそらく素数, false: 確実に合成数 * * 特殊ケース: * isFermatPrime(n < 2) = false * isFermatPrime(2) = true * isFermatPrime(偶数) = false * isFermatPrime(NaN) = false */ static bool isFermatPrime(const Int& n, int k = 5); /** * @brief 汎用素数判定(推奨) * * Miller-Rabin テストを使用した確率的素数判定です。 * isMillerRabinPrime のエイリアスです。 * * @param n 判定する整数 * @param k テスト回数(デフォルト 20) * @return true: おそらく素数, false: 確実に合成数 */ static bool isProbablePrime(const Int& n, int k = 20) { return isMillerRabinPrime(n, k); } /** * @brief 決定的素数判定 (APRCL アルゴリズム) * * Adleman-Pomerance-Rumely-Cohen-Lenstra 法による決定的素数証明。 * Miller-Rabin と異なり、確率的でなく数学的に正確な結果を返す。 * * アルゴリズム: * 1. 円分環 Z[ζ_q]/(n) 上で Jacobi 和テストを実施 * 2. 全テスト通過 → n の素因数は限定された剰余類に属する * 3. 最終因数探索で非自明因数がなければ素数確定 * * @param n 判定する整数 * @return true: 素数 (確定), false: 合成数 (確定) * * 注意: * - Miller-Rabin より大幅に遅い (数秒〜数分) * - 暗号鍵生成などの高速性が必要な場面では isProbablePrime を使用 * - 数学的証明や検証が必要な場面で使用 */ static bool isProvablePrime(const Int& n); /** * @brief n より大きい最小の素数を返す * * n の次の素数を探索します。n+1, n+2, ... と順に素数判定を行います。 * * @param n 基準となる整数 * @param k Miller-Rabin テストの回数(デフォルト 20) * @return n より大きい最小の素数 * * 特殊ケース: * nextPrime(n < 2) = 2 * nextPrime(2) = 3 * nextPrime(NaN) = NaN * nextPrime(∞) = ∞ * * 注意: * - 大きい数では探索に時間がかかる可能性があります * - 素数定理により、n 付近の素数間隔は約 ln(n) です */ static Int nextPrime(const Int& n, int k = 20); /** * @brief n より小さい最大の素数を返す * * n の前の素数を探索します。n-1, n-2, ... と順に素数判定を行います。 * * @param n 基準となる整数 * @param k Miller-Rabin テストの回数(デフォルト 20) * @return n より小さい最大の素数 * * 特殊ケース: * prevPrime(n <= 2) = NaN(2 より小さい素数は存在しない) * prevPrime(3) = 2 * prevPrime(NaN) = NaN * prevPrime(∞) = NaN * * 注意: * - 大きい数では探索に時間がかかる可能性があります */ static Int prevPrime(const Int& n, int k = 20); /** * @brief Fermat 法による因数探索 * * n = a² - b² = (a+b)(a-b) の形を探す。 * n が 2 つの近い素数の積の場合に高速。 * * @param n 合成数(奇数、n > 1) * @param max_iterations 最大反復回数(デフォルト 10000) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_Fermat(const Int& n, uint64_t max_iterations = 10000); /** * @brief Pollard's rho 法による因数探索 * * 疑似乱数列 x_{k+1} = x_k² + c (mod n) を用いた因数探索。 * Brent の改良版(サイクル検出)を使用。 * 期待計算量: O(n^{1/4}) * * @param n 合成数(n > 1) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_PollardRho(const Int& n); /** * @brief Pollard p-1 法による因数探索 * * p-1 が B-smooth(全素因数 ≤ B)な素因数 p を見つける。 * 移植元: lib++20 Factor_P1 * * アルゴリズム: * M = lcm(2, 3, ..., B) を段階的に計算し、 * gcd(a^M - 1, n) を求める。p | n かつ (p-1) | M なら * a^M ≡ 1 (mod p) となり gcd > 1 で因数が見つかる。 * * @param n 合成数(n > 1) * @param B1 Stage 1 の smooth bound(デフォルト 10000) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_PollardP1(const Int& n, uint64_t B1 = 10000); /** * @brief ECM (楕円曲線法) による因数探索 * * Lenstra の楕円曲線素因数分解法。 * Montgomery 形式の楕円曲線 By² = x³ + Ax² + x を使用。 * 20〜60 桁程度の因数の探索に有効。 * 期待計算量: O(exp(√(2 ln p ln ln p))) (p は最小素因数) * * @param n 合成数(n > 1) * @param curves 試行する曲線の数(デフォルト 100) * @param B1 Stage 1 の smooth bound(デフォルト 10000) * @param B2 Stage 2 の bound(デフォルト 100*B1) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_ECM(const Int& n, int curves = 100, uint64_t B1 = 10000, uint64_t B2 = 0, const std::atomic* cancel = nullptr); /** * @brief MPQS (多重多項式二次篩) による因数探索 * * 二次篩法の多重多項式版。50〜100 桁の合成数の素因数分解に有効。 * Q(x) = (ax + b)² - n の値が factor base 上で smooth になる * x の組を集め、線形代数 (GF(2) 上のガウス消去) で因数を導出。 * * @param n 合成数(奇数, n > 1) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_MPQS(const Int& n, const std::atomic* cancel = nullptr); /** * @brief SIQS (Self-Initializing Quadratic Sieve) による因数分解 * * MPQS の強化版。主な改良点: * 1. Knuth-Schroeppel 乗数選択 * 2. sieve array による高速 smooth 判定 (log₂(p) 加算方式) * 3. SIQS 多項式: A = 複数 FB 素数の積, B を CRT で構成 * 4. Gray code による B 切り替え (self-initialization) * 5. 符号列 (-1) の正しい処理 * * @param n 合成数(奇数, n > 1) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_SIQS(const Int& n, const std::atomic* cancel = nullptr); /** * @brief GNFS (一般数体篩法) による因数探索 * * 大きな合成数(60桁以上)に対する準指数時間アルゴリズム。 * base-m 多項式選択、有理・代数因数基底、line sieve、 * GF(2) 線形代数、Hensel lifting による代数平方根を実装。 * * @param n 合成数(奇数, n > 1) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_GNFS(const Int& n); /** * @brief Williams p+1 法による因数探索 * * p+1 が B-smooth な素因数 p を見つける。p-1 法の双対。 * Lucas 列 V_k(a) mod n を用いて gcd(V_M - 2, n) を計算。 * * アルゴリズム: * 1. Lucas 列 V_k: V_0=2, V_1=a, V_{k+1}=a*V_k - V_{k-1} * 2. Binary ladder で V_M mod n を高速計算 * 3. g = gcd(V_M - 2, n) で因数検出 * * @param n 合成数(奇数, n > 1) * @param B1 smooth bound(デフォルト 10000) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_WilliamsP1(const Int& n, uint64_t B1 = 10000); /** * @brief SQUFOF (Square Form Factorization) による因数探索 * * Shanks の平方形式因数分解法。連分数展開に基づく。 * O(n^{1/4}) 時間、O(1) メモリ。小〜中規模 (〜18桁) で高速。 * * @param n 合成数(奇数, n > 1, 完全平方数でない) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_SQUFOF(const Int& n); /** * @brief Pollard rho 法(Montgomery 乗算版)による因数探索 * * Montgomery 剰余乗算を用いた高速な Pollard rho 法。 * 移植元: lib++20 Factor_Rho_Montgomery * * アルゴリズム: * R = 2^k > n となる R を取り、Montgomery 表現に変換。 * x_{k+1} = x_k² + 1 (mod n) を Montgomery 乗算で計算。 * Floyd のサイクル検出で gcd(x-y, n) > 1 となるまで反復。 * * @param n 合成数(奇数, n > 1) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_RhoMontgomery(const Int& n); /** * @brief Goldbach 予想に基づく因数探索 * * N = p * q (半素数) に対し、sum ≈ p + q を二分探索で推定し、 * 二次方程式 t² - sum*t + N = 0 を解いて因数を求める。 * 移植元: lib++20 Factor_Goldbach (Dr. Roger G. Doss, PhD) * * 注意: Factor_Fermat より一般に遅い。教育目的で提供。 * * @param n 合成数(n > 1, 半素数を想定) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_Goldbach(const Int& n); /** * @brief 試し割り法による因数探索 * * SMALL_PRIMES テーブルの素数で順に試し割りを行う。 * 移植元: lib++20 Factor_Try * * @param n 合成数(n > 1) * @return n の非自明な因数。見つからなければ n を返す */ static Int findFactor_TrialDivision(const Int& n); /** * @brief n 以下の素数の個数 (素数計数関数 π(n)) * * エラトステネスの篩を用いて n 以下の素数を数えます。 * * @param n 上限値(n >= 0) * @return n 以下の素数の個数 * * 特殊ケース: * primePi(0) = 0, primePi(1) = 0, primePi(2) = 1 */ static int64_t primePi(int64_t n); // 小素数で割り切れる因子をすべて抽出する(1リム・バッチ GCD 版) // remaining から小素数の因子を除去し、見つかった因子を factors に追加 // remaining は小素数で割り切った後の値に更新される // 注意: 素因数 2 は事前に countTrailingZeros() で除去済みの前提 static void extractSmallPrimeFactors( Int& remaining, std::vector>& factors); private: // 小素数リスト(3〜1987 の奇素数 299 個) // 素因数 2 はバッチ GCD ではなく LSB で処理するためここには含めない static constexpr int SMALL_PRIMES[] = { 3,5,7,11,13,17,19,23,29, 31,37,41,43,47,53,59,61,67,71, 73,79,83,89,97,101,103,107,109,113, 127,131,137,139,149,151,157,163,167,173, 179,181,191,193,197,199,211,223,227,229, 233,239,241,251,257,263,269,271,277,281, 283,293,307,311,313,317,331,337,347,349, 353,359,367,373,379,383,389,397,401,409, 419,421,431,433,439,443,449,457,461,463, 467,479,487,491,499,503,509,521,523,541, 547,557,563,569,571,577,587,593,599,601, 607,613,617,619,631,641,643,647,653,659, 661,673,677,683,691,701,709,719,727,733, 739,743,751,757,761,769,773,787,797,809, 811,821,823,827,829,839,853,857,859,863, 877,881,883,887,907,911,919,929,937,941, 947,953,967,971,977,983,991,997,1009,1013, 1019,1021,1031,1033,1039,1049,1051,1061,1063,1069, 1087,1091,1093,1097,1103,1109,1117,1123,1129,1151, 1153,1163,1171,1181,1187,1193,1201,1213,1217,1223, 1229,1231,1237,1249,1259,1277,1279,1283,1289,1291, 1297,1301,1303,1307,1319,1321,1327,1361,1367,1373, 1381,1399,1409,1423,1427,1429,1433,1439,1447,1451, 1453,1459,1471,1481,1483,1487,1489,1493,1499,1511, 1523,1531,1543,1549,1553,1559,1567,1571,1579,1583, 1597,1601,1607,1609,1613,1619,1621,1627,1637,1657, 1663,1667,1669,1693,1697,1699,1709,1721,1723,1733, 1741,1747,1753,1759,1777,1783,1787,1789,1801,1811, 1823,1831,1847,1861,1867,1871,1873,1877,1879,1889, 1901,1907,1913,1931,1933,1949,1951,1973,1979,1987 }; static constexpr int SMALL_PRIMES_COUNT = sizeof(SMALL_PRIMES) / sizeof(SMALL_PRIMES[0]); // 1リム・バッチ GCD 用テーブル // 連続する奇素数の積を 64bit に詰めたグループ struct PrimeBatch { uint64_t product; // バッチ内の素数の積 int start_index; // SMALL_PRIMES[] 内の開始インデックス int count; // バッチ内の素数の個数 }; // バッチテーブルを取得(初回呼び出し時に構築) static const std::vector& getBatches(); // 小素数での割り切れチェック(1リム・バッチ GCD 版) static bool isDivisibleBySmallPrime(const Int& n); }; } // namespace calx #endif // CALX_INT_PRIME_HPP