// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // CRT.hpp — Chinese Remainder Theorem // // Provides the extended_gcd and chinese_remainder_theorem functions and the // CRT class. The CRT class operates efficiently when repeatedly transforming // over the same set of moduli by precomputing the inverses. #ifndef SANGI_CRT_HPP #define SANGI_CRT_HPP #include #include #include #include #include namespace sangi { // ============================================================================ // Extended Euclidean algorithm // ============================================================================ /** * @brief Extended Euclidean algorithm * * Finds s, t, and gcd(a, b) satisfying a*s + b*t = gcd(a, b). * * @param a the first integer * @param b the second integer * @param s output: s in a*s + b*t = gcd(a, b) * @param t output: t in a*s + b*t = gcd(a, b) * @return the greatest common divisor of a and b */ inline int64_t extended_gcd(int64_t a, int64_t b, int64_t& s, int64_t& t) { s = 1, t = 0; int64_t u = 0, v = 1; while (b != 0) { int64_t q = a / b; int64_t b_old = b; b = a - q * b; a = b_old; int64_t u_old = u; u = s - q * u; s = u_old; int64_t v_old = v; v = t - q * v; t = v_old; } return a; // greatest common divisor } // ============================================================================ // Chinese Remainder Theorem (function version) // ============================================================================ /** * @brief Conversion across multiple moduli using the Chinese Remainder Theorem * @param remainders array of residue values for each modulus * @param moduli array of moduli * @return the smallest non-negative integer satisfying all congruences */ template T chinese_remainder_theorem(const std::vector& remainders, const std::vector& moduli) { if (remainders.size() != moduli.size() || remainders.empty()) { throw std::invalid_argument("Remainders and moduli arrays must have the same non-zero size"); } T result = 0; T M = 1; // Compute the product of all moduli for (int mod : moduli) { M *= mod; } // Compute for each residue for (size_t i = 0; i < moduli.size(); i++) { T m_i = moduli[i]; T M_i = M / m_i; // Compute the inverse using the extended Euclidean algorithm int64_t s, t; int64_t g = extended_gcd(static_cast(M_i), static_cast(m_i), s, t); // If the moduli are not pairwise coprime if (g != 1) { throw MathError("Moduli must be pairwise coprime in Chinese remainder theorem"); } // Accumulate into the result result = (result + remainders[i] * M_i * s) % M; } // Normalize to a non-negative value if (result < 0) result += M; return result; } // ============================================================================ // CRT class (precomputed version) // ============================================================================ /** * @brief Chinese Remainder Theorem class (CRT) * * Efficient when CRT is applied repeatedly to the same set of moduli. * The constructor precomputes the inverses and performs forward/inverse * conversion in O(n). * * * @tparam T integer type (int64_t, etc.) */ template class CRT { size_t m_count; T m_modulus; // Product of all moduli std::vector m_moduli; // Each modulus std::vector m_inv; // Precomputed weights (M_i * M_i^{-1} mod m_i) public: /** * @brief Constructor — precomputes inverses * @param moduli array of moduli (must be pairwise coprime) */ explicit CRT(const std::vector& moduli) : m_count(moduli.size()) , m_moduli(moduli.begin(), moduli.end()) , m_inv(moduli.size()) { if (moduli.empty()) { throw std::invalid_argument("CRT requires at least one modulus"); } // Product of all moduli m_modulus = T(1); for (auto m : moduli) { m_modulus *= T(m); } // Precompute weights: m_inv[i] = M_i * (M_i^{-1} mod m_i) // where M_i = m_modulus / m_i for (size_t i = 0; i < m_count; ++i) { T M_i = m_modulus / T(moduli[i]); // Compute the inverse of (M_i mod m_i) via the extended GCD int64_t M_i_mod = static_cast(M_i % T(moduli[i])); int64_t s, t; int64_t g = extended_gcd(M_i_mod, static_cast(moduli[i]), s, t); if (g != 1) { throw MathError("Moduli must be pairwise coprime for CRT"); } m_inv[i] = M_i * T(s); } } /** @brief Return the product of all moduli */ const T& modulus() const { return m_modulus; } /** @brief Return the number of moduli */ size_t count() const { return m_count; } /** * @brief Convert an integer to its residues (forward conversion) * @param x integer to convert * @return residue values for each modulus */ std::vector toResidues(const T& x) const { std::vector residues(m_count); for (size_t i = 0; i < m_count; ++i) { residues[i] = static_cast(x % m_moduli[i]); } return residues; } /** * @brief Recover an integer from its residues (inverse conversion) * @param residues residue values for each modulus * @return the recovered integer (0 <= result < modulus()) */ T fromResidues(const std::vector& residues) const { if (residues.size() != m_count) { throw std::invalid_argument("Residues size must match moduli count"); } T result = T(0); for (size_t i = 0; i < m_count; ++i) { result = (result + T(residues[i]) * m_inv[i]) % m_modulus; } // Normalize to a non-negative value if (result < T(0)) result += m_modulus; return result; } }; } // namespace sangi #endif // SANGI_CRT_HPP