// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // IntFactorTable.cpp // Implementation of the prime factor table #include "math/core/mp/Int/IntFactorTable.hpp" #include "math/core/mp/Int/IntOps.hpp" #include "math/core/mp/Int/IntPrime.hpp" #include "math/core/mp/Int/IntGCD.hpp" #include #include #include namespace sangi { // Constructor IntFactorTable::IntFactorTable(int max_value) : max_value_(max_value) { if (max_value_ <= 0) { max_value_ = 0; return; } // Store odd numbers only (even numbers are fixed with prime factor 2) // The index of an odd number n is n/2 size_t table_size = (max_value_ + 1) / 2; table_.resize(table_size, 0); // Try to load from file if (max_value_ >= 100000) { // Use a file only for large tables if (loadFromFile()) { return; // Load succeeded } } // Load failed or file not used → build the table buildTable(); // Save to file (speeds up the next startup) if (max_value_ >= 100000) { saveToFile(); } } // Build the table with the sieve of Eratosthenes (largest prime factor version) void IntFactorTable::buildTable() { // Store the largest prime factor of an odd number n in table[n/2] // 0 means "prime" or "unprocessed" if (max_value_ < 1) return; // The prime factor of 1 is 1 (special case) if (max_value_ >= 1) { table_[oddIndex(1)] = 1; } // Sieve up to sqrt(max_value) int sqrt_max = static_cast(std::sqrt(static_cast(max_value_))); // For every odd prime p, write p into its multiples // Since smaller primes are processed first, the last value written is the largest prime factor for (int p = 3; p <= sqrt_max; p += 2) { // Check whether p is prime (prime if table[p] == 0) if (table_[oddIndex(p)] == 0) { // Record the largest prime factor p for the multiples of p // Start from the first odd composite multiple of p (skip p itself since it is prime, start from p*2) int start = 2 * p; // The first composite multiple of p (even) if ((start & 1) == 0) { start += p; // Make it odd (if p*2 is even, use p*3) } // Step through the multiples of p by 2*p (odd numbers only) for (int m = start; m <= max_value_; m += 2 * p) { // ★ Characteristic of LPF: overwrite unconditionally with no test ★ table_[oddIndex(m)] = static_cast(p); } } } // Process the multiples of primes p larger than sqrt_max // For these, p^2 > max_value, so only the small multiples of p fall within range for (int p = sqrt_max + (sqrt_max % 2 == 0 ? 1 : 2); p <= max_value_; p += 2) { // Check whether p is prime if (table_[oddIndex(p)] == 0) { // Start from the first odd composite multiple of p // Use int64_t: avoids overflow when 2*p exceeds INT_MAX int64_t start = 2LL * p; if ((start & 1) == 0) { start += p; // Make it odd } if (start > max_value_) continue; // Skip if out of range // Step through the multiples of p by 2*p (odd numbers only) for (int64_t m = start; m <= max_value_; m += 2LL * p) { table_[oddIndex(static_cast(m))] = static_cast(p); } } } } // Get the largest prime factor int IntFactorTable::operator[](int n) const { // Out of range if (n < 0 || n > max_value_) { return 0; } // Special cases if (n == 0 || n == 1) { return 0; // No prime factor } // 2 is prime if (n == 2) { return 0; } // Even (other than 2) if ((n & 1) == 0) { // For an even number, compare the largest prime factor of the odd part with 2 and return the larger int odd_part = n; while ((odd_part & 1) == 0) { odd_part >>= 1; } if (odd_part == 1) { return 2; // A power of 2 } // Get the largest prime factor of the odd part int odd_lpf = table_[oddIndex(odd_part)]; if (odd_lpf == 0) { // odd_part is prime // Compare the prime factor of the odd part with 2 return (odd_part > 2) ? odd_part : 2; } // Compare the largest prime factor recorded in the table with 2 return (odd_lpf > 2) ? odd_lpf : 2; } // Odd case int lpf = table_[oddIndex(n)]; if (lpf == 0) { // n is prime return 0; } return lpf; } // Prime factorization std::vector> IntFactorTable::factorize(const Int& n) const { std::vector> factors; // Check for special states if (n.isNaN() || n.isInfinite()) { return factors; // Empty } // Zero case if (n.isZero()) { factors.push_back({Int::Zero(), 1}); return factors; } // For a negative number, factorize the absolute value Int abs_n = abs(n); // One case if (abs_n.isOne()) { return factors; // Empty } Int remaining = abs_n; // Divide out as many factors of 2 as possible (number of trailing zero bits of LSB = exponent of 2) size_t exp_2 = remaining.countTrailingZeros(); if (exp_2 > 0) { factors.push_back({Int(2), static_cast(exp_2)}); remaining = remaining >> static_cast(exp_2); } // Stop once remaining becomes 1 if (remaining.isOne()) { return factors; } // Factorize using the table (while remaining is at most max_value) bool finished_in_table = false; while (remaining <= Int(max_value_) && !remaining.isOne()) { int n_int = remaining.toInt(); int lpf = (*this)[n_int]; if (lpf == 0) { // remaining is prime factors.push_back({remaining, 1}); finished_in_table = true; break; } // Divide out as many factors of lpf as possible Int p(lpf); int exp = 0; while (true) { Int quotient = remaining / p; Int remainder_div = remaining % p; if (!remainder_div.isZero()) { break; } remaining = quotient; exp++; } if (exp > 0) { factors.push_back({p, exp}); } } // If remaining > max_value, extract small prime factors via one-limb batch GCD if (!remaining.isOne() && !finished_in_table) { IntPrime::extractSmallPrimeFactors(remaining, factors); // If remaining > 1 even after dividing out small primes, it is a prime or a large composite if (!remaining.isOne()) { factors.push_back({remaining, 1}); } } // Sort the prime factors in ascending order std::sort(factors.begin(), factors.end(), [](const std::pair& a, const std::pair& b) { return a.first < b.first; }); return factors; } // Save to file bool IntFactorTable::saveToFile(const std::string& filename) const { std::ofstream ofs(filename, std::ios::binary); if (!ofs) { return false; } // Write max_value ofs.write(reinterpret_cast(&max_value_), sizeof(max_value_)); // Write the size of the table uint32_t table_size = static_cast(table_.size()); ofs.write(reinterpret_cast(&table_size), sizeof(table_size)); // Write the contents of the table ofs.write(reinterpret_cast(table_.data()), table_size * sizeof(uint32_t)); return ofs.good(); } // Load from file bool IntFactorTable::loadFromFile(const std::string& filename) { std::ifstream ifs(filename, std::ios::binary); if (!ifs) { return false; } // Read max_value int file_max_value; ifs.read(reinterpret_cast(&file_max_value), sizeof(file_max_value)); if (!ifs || file_max_value < max_value_) { return false; // The table in the file is too small } // Read the size of the table uint32_t file_table_size; ifs.read(reinterpret_cast(&file_table_size), sizeof(file_table_size)); if (!ifs || file_table_size < static_cast(table_.size())) { return false; // Insufficient size } // Read the contents of the table ifs.read(reinterpret_cast(table_.data()), table_.size() * sizeof(uint32_t)); if (!ifs) { return false; } return true; } } // namespace sangi