// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // IntFactorization.cpp // Implementation of the complete prime factorization dispatcher #include "math/core/mp/Int/IntFactorization.hpp" #include "math/core/mp/Int/IntOps.hpp" #include "math/core/mp/Int/IntSqrt.hpp" #include "math/core/mp/ThreadPool.hpp" #include #include #include #include #include #include namespace sangi { // ============================================================================ // uint64-specific prime factorization — fast path that never uses Int objects // ============================================================================ namespace { // Smallest prime factor table (SPF) — odd numbers only, uint16 (SPF of a composite ≤ √TABLE_LIMIT) // TABLE_LIMIT = 1,000,000 → √ ≈ 1000 → uint16 is sufficient // Memory: 500,000 × 2 bytes = 1 MB static constexpr int TABLE_LIMIT = 1000000; struct SpfTable { uint16_t data[(TABLE_LIMIT + 1) / 2]; // data[n/2] = SPF of odd n (0 = prime) SpfTable() { // Initialize all entries to 0 (prime) std::memset(data, 0, sizeof(data)); data[0] = 1; // n=1 is special int sqrt_limit = static_cast(std::sqrt(static_cast(TABLE_LIMIT))); for (int p = 3; p <= sqrt_limit; p += 2) { if (data[p >> 1] == 0) { // p is prime // Record the SPF for odd multiples of p (only if unset → smallest prime factor) for (int64_t m = static_cast(p) * p; m <= TABLE_LIMIT; m += 2 * p) { if (data[m >> 1] == 0) { data[m >> 1] = static_cast(p); } } } } } }; const SpfTable& getSpfTable() { static const SpfTable table; return table; } // Build the table at process startup (avoids the latency on the first factorize() call) static const auto& spf_init_ = getSpfTable(); // Forward declaration of pollardRho64 (already defined in IntPrime.cpp's anonymous namespace, but cannot be linked) uint64_t mulmod64(uint64_t a, uint64_t b, uint64_t m) { #ifdef _MSC_VER uint64_t hi; uint64_t lo = _umul128(a, b, &hi); uint64_t rem; _udiv128(hi, lo, m, &rem); return rem; #else return static_cast( (static_cast(a) * b) % m); #endif } uint64_t gcd64(uint64_t a, uint64_t b) { if (a == 0) return b; if (b == 0) return a; #ifdef _MSC_VER unsigned long shift; _BitScanForward64(&shift, a | b); unsigned long sa; _BitScanForward64(&sa, a); a >>= sa; unsigned long sb; _BitScanForward64(&sb, b); b >>= sb; #else int shift = __builtin_ctzll(a | b); a >>= __builtin_ctzll(a); b >>= __builtin_ctzll(b); #endif while (true) { if (a > b) { uint64_t t = a; a = b; b = t; } b -= a; if (b == 0) return a << shift; #ifdef _MSC_VER unsigned long ctz; _BitScanForward64(&ctz, b); b >>= ctz; #else b >>= __builtin_ctzll(b); #endif } } uint64_t pollardRho64_local(uint64_t n) { if (n % 2 == 0) return 2; if (n < 4) return n; for (uint64_t c = 1; c <= 20; ++c) { uint64_t x = 2, y = 2, d = 1; auto f = [&](uint64_t v) { return (mulmod64(v, v, n) + c) % n; }; uint64_t r = 1, q = 1; while (d == 1) { x = y; for (uint64_t i = 0; i < r; ++i) y = f(y); uint64_t k = 0; while (k < r && d == 1) { uint64_t ys = y; uint64_t m = (r - k < 128) ? (r - k) : 128; for (uint64_t i = 0; i < m; ++i) { y = f(y); uint64_t diff = (x > y) ? (x - y) : (y - x); q = mulmod64(q, diff, n); } d = gcd64(q, n); k += m; if (d == n) { // Backtrack y = ys; d = 1; while (d == 1) { y = f(y); uint64_t diff = (x > y) ? (x - y) : (y - x); d = gcd64(diff, n); } } } r <<= 1; q = 1; if (r > 1000000) break; } if (d != 1 && d != n) return d; } return n; // Not found } // Deterministic Miller-Rabin (uint64 range) // Deterministic below 2^64 with bases {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37} bool isPrime64(uint64_t n) { if (n < 2) return false; if (n < 4) return true; if (n % 2 == 0) return false; if (n % 3 == 0) return n == 3; if (n % 5 == 0) return n == 5; if (n % 7 == 0) return n == 7; // n-1 = d * 2^r uint64_t d = n - 1; int r = 0; while ((d & 1) == 0) { d >>= 1; ++r; } // Modular exponentiation (binary method) auto powmod = [](uint64_t base, uint64_t exp, uint64_t mod) -> uint64_t { uint64_t result = 1; base %= mod; while (exp > 0) { if (exp & 1) result = mulmod64(result, base, mod); exp >>= 1; base = mulmod64(base, base, mod); } return result; }; // For n < 3,215,031,751, bases {2, 3, 5, 7} are sufficient // For n < 2^64, {2,3,5,7,11,13,17,19,23,29,31,37} are deterministic static const uint64_t witnesses[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}; int num_witnesses = (n < 3215031751ULL) ? 4 : 12; for (int i = 0; i < num_witnesses; ++i) { uint64_t a = witnesses[i]; if (a >= n) continue; uint64_t x = powmod(a, d, n); if (x == 1 || x == n - 1) continue; bool composite = true; for (int j = 0; j < r - 1; ++j) { x = mulmod64(x, x, n); if (x == n - 1) { composite = false; break; } } if (composite) return false; } return true; } // Helper to add a prime factor to factors void addFactor64(std::vector>& factors, uint64_t p, int exp) { Int pi(static_cast(p)); for (auto& [fp, fe] : factors) { if (fp == pi) { fe += exp; return; } } factors.push_back({std::move(pi), exp}); } // Factorize using the SPF table (n ≤ TABLE_LIMIT, n is odd) void factorize_spf(uint64_t n, std::vector>& factors) { const auto& spf = getSpfTable(); while (n > 1) { if ((n & 1) == 0) { int exp = 0; while ((n & 1) == 0) { n >>= 1; ++exp; } addFactor64(factors, 2, exp); continue; } uint16_t s = spf.data[n >> 1]; if (s == 0) { // n is prime addFactor64(factors, n, 1); return; } uint64_t p = s; int exp = 0; while (n % p == 0) { n /= p; ++exp; } addFactor64(factors, p, exp); } } // uint64 Fermat method — instantly finds balanced semiprimes uint64_t fermat64(uint64_t n, int max_iter = 1000) { // ceil(sqrt(n)) — the double approximation is sufficient (error ≤ 1 in the 64-bit range) uint64_t a = static_cast(std::ceil(std::sqrt(static_cast(n)))); // Correct for the precision shortfall of double while (a * a < n) ++a; for (int i = 0; i < max_iter; ++i) { uint64_t a2 = a * a; uint64_t b2 = a2 - n; uint64_t b = static_cast(std::sqrt(static_cast(b2))); // Correct the double approximation while (b * b < b2) ++b; if (b * b == b2) { uint64_t f = a - b; if (f > 1 && f < n) return f; } ++a; } return n; } // uint64-specific prime factorization — fast path that never uses Int objects void factorize_u64(uint64_t n, std::vector>& factors) { if (n <= 1) return; // Handle even numbers if ((n & 1) == 0) { int exp = 0; while ((n & 1) == 0) { n >>= 1; ++exp; } addFactor64(factors, 2, exp); if (n <= 1) return; } // Within the table range: factorize instantly via an SPF table lookup if (n <= TABLE_LIMIT) { factorize_spf(n, factors); return; } // Outside the table range (n > 10^6): // 1. The SPF table sieve already covers factors below 1000 → only try 1000 and above // However, since n itself came from outside the table, small factors must also be tried // → the SPF table's prime list cannot be used, so run trial division over 3~997 // Trial division by primes 3~997 (the range sieved below 1000 when building the SPF table) // Primes can be obtained from the SPF table: prime if data[p/2] == 0 { const auto& spf = getSpfTable(); for (uint64_t p = 3; p < 1000 && p * p <= n; p += 2) { if (spf.data[p >> 1] != 0) continue; // Skip composites if (n % p == 0) { int exp = 0; while (n % p == 0) { n /= p; ++exp; } addFactor64(factors, p, exp); } } if (n <= 1) return; // If n now fits within the table range if (n <= TABLE_LIMIT) { factorize_spf(n, factors); return; } } // Trial division over 1009~1999 (beyond the SPF table's √TABLE_LIMIT ≈ 1000) for (uint64_t p = 1009; p < 2000 && p * p <= n; p += 2) { if (n % p == 0) { int exp = 0; while (n % p == 0) { n /= p; ++exp; } addFactor64(factors, p, exp); } } if (n <= 1) return; if (n <= TABLE_LIMIT) { factorize_spf(n, factors); return; } // If what remains is prime, add it if (n < static_cast(1987) * 1987) { // 1987² ≈ less than 3.95 million → below the square of the trial-division range, so definitely prime addFactor64(factors, n, 1); return; } if (isPrime64(n)) { addFactor64(factors, n, 1); return; } // Composite: split with Fermat + pollardRho64 and recurse std::vector composites; composites.push_back(n); while (!composites.empty()) { uint64_t c = composites.back(); composites.pop_back(); if (c <= 1) continue; // Factorize via SPF if within the table range if (c <= TABLE_LIMIT) { factorize_spf(c, factors); continue; } if (isPrime64(c)) { addFactor64(factors, c, 1); continue; } // Fermat method (instantly finds balanced semiprimes) uint64_t f = fermat64(c, 1000); if (f > 1 && f < c) { composites.push_back(f); composites.push_back(c / f); continue; } // Pollard rho f = pollardRho64_local(c); if (f > 1 && f < c) { composites.push_back(f); composites.push_back(c / f); continue; } // Not found (theoretically unreachable) addFactor64(factors, c, 1); } } } // anonymous namespace // ============================================================================ // findFactor — single factor search // Staged application of multiple algorithms // ============================================================================ Int IntFactorization::findFactor(const Int& n, int millerRabinK) { // Even if (n.isEven()) return Int(2); // 1 or less if (n <= 1) return n; // Primality check — a deterministic test for small n (few rounds suffice) int bits = static_cast(n.bitLength()); { // n < 2^64: k=2 suffices (SPRP-2,3 is deterministic below 2^64) // n < 2^128: k=5 suffices (a practically sufficient probability) int k = (bits <= 64) ? 2 : (bits <= 128) ? 5 : millerRabinK; if (IntPrime::isProbablePrime(n, k)) return n; } // ================================================================ // Stage 1: Fermat method — a light initial attempt (< 1us) // Instantly finds balanced semiprimes with p ≈ q. Always tried first since the cost is extremely low. // 1000 iterations cover the range |p-q| ≤ ~2000·√2 ≈ 2828. // ================================================================ { Int f = IntPrime::findFactor_Fermat(n, 1000); if (f > Int(1) && f < n) return f; } // ================================================================ // Stage 2: Pollard rho / Brent (a few us~a few ms) // The most general-purpose method. O(p^{1/2}), so faster for smaller factors. // Accelerated with dedicated uint64 (≤63 bits) / 128-bit Montgomery (≤127 bits) versions. // ================================================================ { Int f = IntPrime::findFactor_PollardRho(n); if (f > Int(1) && f < n) return f; } // ================================================================ // Stage 3: special methods — selected by digit count // ================================================================ // 3a: SQUFOF (≤60 bits only, O(n^{1/4}), minimal memory) // A small-to-medium-scale fallback when rho did not find a factor. if (bits <= 60) { Int f = IntPrime::findFactor_SQUFOF(n); if (f > Int(1) && f < n) return f; } // 3b: Pollard p-1 — run only when bits >= 50 // For small numbers rho is sufficient. B1 is tuned according to bits. if (bits >= 50) { uint64_t B1 = (bits <= 80) ? 5000 : 50000; Int f = IntPrime::findFactor_PollardP1(n, B1); if (f > Int(1) && f < n) return f; } // 3c: Williams p+1 — run only when bits >= 50 if (bits >= 50) { uint64_t B1 = (bits <= 80) ? 5000 : 50000; Int f = IntPrime::findFactor_WilliamsP1(n, B1); if (f > Int(1) && f < n) return f; } // ================================================================ // Stage 4: heavy factorization // ================================================================ // ECM factorization (sequential) { int curves = (bits <= 80) ? 50 : (bits <= 120) ? 100 : 200; Int f = IntPrime::findFactor_ECM(n, curves, 0); if (f > Int(1) && f < n) return f; } return n; // No factor was found } // ============================================================================ // factorize — complete prime factorization // ============================================================================ std::vector> IntFactorization::factorize(const Int& n, int millerRabinK) { // Special cases if (n.isNaN() || n.isInfinite()) return {}; if (n.isZero()) return {{Int(0), 1}}; Int remaining = n; if (remaining.isNegative()) remaining = -remaining; if (remaining.isOne()) return {}; std::vector> factors; // Stage 1: remove the prime factor 2 (fast via bit shifts) if (remaining.isEven()) { int exp2 = 0; while (remaining.isEven()) { remaining >>= 1; ++exp2; } factors.push_back({Int(2), exp2}); } if (remaining.isOne()) { std::sort(factors.begin(), factors.end()); return factors; } // ================================================================ // Stage 1.5: uint64 fast path — for 1 limb, factorize without ever using Int // Completed with the SPF table (≤ 10^6) + trial division + pollardRho64. // Every number of 19 digits or fewer is handled by this path. // ================================================================ if (remaining.bitLength() <= 63) { uint64_t n64 = remaining.toUInt64(); factorize_u64(n64, factors); std::sort(factors.begin(), factors.end()); return factors; } // Stage 2: remove factors in 3~1987 via small-prime batch GCD IntPrime::extractSmallPrimeFactors(remaining, factors); // Done if remaining == 1 if (remaining.isOne()) { std::sort(factors.begin(), factors.end()); return factors; } // Stage 3: if the remainder is prime, add it and finish if (IntPrime::isProbablePrime(remaining, millerRabinK)) { factors.push_back({remaining, 1}); std::sort(factors.begin(), factors.end()); return factors; } // Stage 4: recursively factorize the remainder // Manage composites with a stack std::vector composites; composites.push_back(remaining); while (!composites.empty()) { Int c = composites.back(); composites.pop_back(); if (c.isOne()) continue; if (IntPrime::isProbablePrime(c, millerRabinK)) { // Prime: add to the factor list bool found = false; for (auto& [p, e] : factors) { if (p == c) { ++e; found = true; break; } } if (!found) factors.push_back({c, 1}); continue; } // Composite: find one factor and split Int f = findFactor(c, millerRabinK); if (f == c || f <= 1) { // No factor was found (theoretically unreachable, but a safety measure) // Add the remainder itself as a factor bool found = false; for (auto& [p, e] : factors) { if (p == c) { ++e; found = true; break; } } if (!found) factors.push_back({c, 1}); continue; } Int cofactor = c / f; // Push f and cofactor onto the stack and process recursively composites.push_back(f); composites.push_back(cofactor); } // Sort in ascending order std::sort(factors.begin(), factors.end()); return factors; } // ============================================================================ // toString — string representation of the factorization result // ============================================================================ std::string IntFactorization::toString(const std::vector>& factors) { if (factors.empty()) return "1"; std::ostringstream oss; bool first = true; for (auto& [p, e] : factors) { if (!first) oss << " * "; first = false; oss << p.toString(); if (e > 1) oss << "^" << e; } return oss.str(); } } // namespace sangi