using System; using System.Collections.Generic; using System.Linq; using System.Numerics; namespace Algorithms.ModularArithmetic; /// /// Chinese Remainder Theorem: https://en.wikipedia.org/wiki/Chinese_remainder_theorem. /// public static class ChineseRemainderTheorem { /// /// The Chinese Remainder Theorem is used to compute x for given set of pairs of integers (a_i, n_i) with: /// /// x = a_0 mod n_0 /// x = a_1 mod n_1 /// ... /// x = a_k mod n_k /// /// for 0 <= i < k, for some positive integer k. Additional requirements are: /// /// n_i > 1 for 0 <= i < k /// n_i and n_j are coprime, for all 0 <= i < j < k /// 0 <= a_i < n_i, for all 0 <= i < k /// 0 <= x < n_0 * n_1 * ... * n_(k-1) /// /// /// An ordered list of a_0, a_1, ..., a_k. /// An ordered list of n_0, n_1, ..., n_k. /// The value x. /// If any of the requirements is not fulfilled. public static long Compute(List listOfAs, List listOfNs) { // Check the requirements for this algorithm: CheckRequirements(listOfAs, listOfNs); // For performance-reasons compute the product of all n_i as prodN, because we're going to need access to (prodN / n_i) for all i: var prodN = 1L; foreach (var n in listOfNs) { prodN *= n; } var result = 0L; for (var i = 0; i < listOfNs.Count; i++) { var a_i = listOfAs[i]; var n_i = listOfNs[i]; var modulus_i = prodN / n_i; var bezout_modulus_i = ExtendedEuclideanAlgorithm.Compute(n_i, modulus_i).BezoutB; result += a_i * bezout_modulus_i * modulus_i; } // Make sure, result is in [0, prodN). result %= prodN; if (result < 0) { result += prodN; } return result; } /// /// The Chinese Remainder Theorem is used to compute x for given set of pairs of integers (a_i, n_i) with: /// /// x = a_0 mod n_0 /// x = a_1 mod n_1 /// ... /// x = a_k mod n_k /// /// for 0 <= i < k, for some positive integer k. Additional requirements are: /// /// n_i > 1 for 0 <= i < k /// n_i and n_j are coprime, for all 0 <= i < j < k /// 0 <= a_i < n_i, for all 0 <= i < k /// 0 <= x < n_0 * n_1 * ... * n_(k-1) /// /// /// An ordered list of a_0, a_1, ..., a_k. /// An ordered list of n_0, n_1, ..., n_k. /// The value x. /// If any of the requirements is not fulfilled. public static BigInteger Compute(List listOfAs, List listOfNs) { // Check the requirements for this algorithm: CheckRequirements(listOfAs, listOfNs); // For performance-reasons compute the product of all n_i as prodN, because we're going to need access to (prodN / n_i) for all i: var prodN = BigInteger.One; foreach (var n in listOfNs) { prodN *= n; } var result = BigInteger.Zero; for (var i = 0; i < listOfNs.Count; i++) { var a_i = listOfAs[i]; var n_i = listOfNs[i]; var modulus_i = prodN / n_i; var bezout_modulus_i = ExtendedEuclideanAlgorithm.Compute(n_i, modulus_i).BezoutB; result += a_i * bezout_modulus_i * modulus_i; } // Make sure, result is in [0, prodN). result %= prodN; if (result < 0) { result += prodN; } return result; } /// /// Checks the requirements for the algorithm and throws an ArgumentException if they are not being met. /// /// An ordered list of a_0, a_1, ..., a_k. /// An ordered list of n_0, n_1, ..., n_k. /// If any of the requirements is not fulfilled. private static void CheckRequirements(List listOfAs, List listOfNs) { if (listOfAs == null || listOfNs == null || listOfAs.Count != listOfNs.Count) { throw new ArgumentException("The parameters 'listOfAs' and 'listOfNs' must not be null and have to be of equal length!"); } if (listOfNs.Any(x => x <= 1)) { throw new ArgumentException($"The value {listOfNs.First(x => x <= 1)} for some n_i is smaller than or equal to 1."); } if (listOfAs.Any(x => x < 0)) { throw new ArgumentException($"The value {listOfAs.First(x => x < 0)} for some a_i is smaller than 0."); } // Check if all pairs of (n_i, n_j) are coprime: for (var i = 0; i < listOfNs.Count; i++) { for (var j = i + 1; j < listOfNs.Count; j++) { long gcd; if ((gcd = ExtendedEuclideanAlgorithm.Compute(listOfNs[i], listOfNs[j]).Gcd) != 1L) { throw new ArgumentException($"The GCD of n_{i} = {listOfNs[i]} and n_{j} = {listOfNs[j]} equals {gcd} and thus these values aren't coprime."); } } } } /// /// Checks the requirements for the algorithm and throws an ArgumentException if they are not being met. /// /// An ordered list of a_0, a_1, ..., a_k. /// An ordered list of n_0, n_1, ..., n_k. /// If any of the requirements is not fulfilled. private static void CheckRequirements(List listOfAs, List listOfNs) { if (listOfAs == null || listOfNs == null || listOfAs.Count != listOfNs.Count) { throw new ArgumentException("The parameters 'listOfAs' and 'listOfNs' must not be null and have to be of equal length!"); } if (listOfNs.Any(x => x <= 1)) { throw new ArgumentException($"The value {listOfNs.First(x => x <= 1)} for some n_i is smaller than or equal to 1."); } if (listOfAs.Any(x => x < 0)) { throw new ArgumentException($"The value {listOfAs.First(x => x < 0)} for some a_i is smaller than 0."); } // Check if all pairs of (n_i, n_j) are coprime: for (var i = 0; i < listOfNs.Count; i++) { for (var j = i + 1; j < listOfNs.Count; j++) { BigInteger gcd; if ((gcd = ExtendedEuclideanAlgorithm.Compute(listOfNs[i], listOfNs[j]).Gcd) != BigInteger.One) { throw new ArgumentException($"The GCD of n_{i} = {listOfNs[i]} and n_{j} = {listOfNs[j]} equals {gcd} and thus these values aren't coprime."); } } } } }