using System.Numerics; using System.Runtime.CompilerServices; namespace PerfectNumbers.Core; public static class UInt128Extensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 BinaryGcd(this UInt128 u, UInt128 v) { UInt128 zero = UInt128.Zero; if (u == zero) { return v; } if (v == zero) { return u; } int shift = CountTrailingZeros(u | v); u >>= CountTrailingZeros(u); do { v >>= CountTrailingZeros(v); if (u > v) { (u, v) = (v, u); } v -= u; } while (v != zero); return u << shift; } public static ulong CalculateOrder(this UInt128 q) { if (q <= UInt128Numbers.Two) { return 0UL; } UInt128 one = UInt128.One; UInt128 phi = q - one; if (phi > ulong.MaxValue) { throw new NotImplementedException("Such big values are not yet supported"); } ulong order = (ulong)phi; uint[] smallPrimes = PrimesGenerator.SmallPrimes; ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2; int i = 0, primesLength = smallPrimes.Length; UInt128 cycle = MersenneDivisorCycles.GetCycle(q); ulong prime, temp; for (; i < primesLength; i++) { if (smallPrimesPow2[i] > order) { break; } prime = smallPrimes[i]; while (order % prime == 0UL) { temp = order / prime; if (temp.PowModWithCycle(q, cycle) == one) { order = temp; } else { break; } } } return order; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CountTrailingZeros(this UInt128 value) { ulong valuePart = (ulong)value; if (valuePart != 0UL) { return BitOperations.TrailingZeroCount(valuePart); } return 64 + BitOperations.TrailingZeroCount((ulong)(value >> 64)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPrimeCandidate(this UInt128 n) { UInt128 p; uint[] smallPrimes = PrimesGenerator.SmallPrimes; ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2; ulong i, smallPrimesCount = (ulong)smallPrimes.Length; for (i = 0UL; i < smallPrimesCount; i++) { p = smallPrimes[i]; if (smallPrimesPow2[i] > n) { break; } if (n % p == UInt128.Zero) { return n == p; } } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Mod10_8_5_3(this UInt128 value, out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3) { UInt128 temp = UInt128.Zero; uint byteSum = 0U; byte current = 0; uint accumulator = 0U; temp = value; do { current = (byte)temp; byteSum += current; temp >>= 8; } while (temp != UInt128.Zero); mod8 = (ulong)value & 7UL; accumulator = byteSum.Mod5(); mod5 = accumulator; mod10 = (mod8 & 1UL) == 0UL ? accumulator switch { 0U => 0UL, 1U => 6UL, 2U => 2UL, 3U => 8UL, _ => 4UL, } : accumulator switch { 0U => 5UL, 1U => 1UL, 2U => 7UL, 3U => 3UL, _ => 9UL, }; // Reusing accumulator to hold the mod 3 reduction now that the mod 5 value has been stored. accumulator = byteSum.Mod3(); mod3 = accumulator; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 Pow(this UInt128 value, ulong exponent) { UInt128 result = UInt128.One; UInt128 baseValue = value; while (exponent != 0UL) { if ((exponent & 1UL) != 0UL) { result *= baseValue; } baseValue *= baseValue; exponent >>= 1; } return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 ModPow(this UInt128 value, UInt128 exponent, UInt128 modulus) { UInt128 zero = UInt128.Zero; UInt128 result = UInt128.One; UInt128 baseValue = value % modulus; while (exponent != zero) { if ((exponent & UInt128.One) != zero) { result = MulMod(result, baseValue, modulus); } baseValue = MulMod(baseValue, baseValue, modulus); exponent >>= 1; } return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod10(this UInt128 value) { UInt128 zero = UInt128.Zero; if (value == zero) return 0UL; ulong result = (ulong)value; UInt128 high = value >> 64; while (high != zero) { // 2^64 ≡ 6 (mod 10) result = (result + (ulong)high * 6UL) % 10UL; high >>= 64; } return result % 10UL; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod8(this UInt128 value) => (ulong)value & 7UL; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod3(this UInt128 value) { ulong remainder = ((ulong)value % 3UL) + ((ulong)(value >> 64) % 3UL); return remainder >= 3UL ? remainder - 3UL : remainder; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod5(this UInt128 value) { ulong remainder = ((ulong)value % 5UL) + ((ulong)(value >> 64) % 5UL); return remainder >= 5UL ? remainder - 5UL : remainder; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod6(this UInt128 value) { return ((value.Mod3() << 1) | ((ulong)value & 1UL)) switch { 0UL => 0UL, 1UL => 3UL, 2UL => 4UL, 3UL => 1UL, 4UL => 2UL, _ => 5UL, }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod7(this UInt128 value) { ulong remainder = ((ulong)value % 7UL) + ((ulong)(value >> 64) % 7UL) * 2UL; while (remainder >= 7UL) { remainder -= 7UL; } return remainder; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod11(this UInt128 value) { ulong remainder = ((ulong)value % 11UL) + ((ulong)(value >> 64) % 11UL) * 5UL; while (remainder >= 11UL) { remainder -= 11UL; } return remainder; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod128(this UInt128 value) => (ulong)value & 127UL; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 Mul64(this UInt128 a, UInt128 b) { ulong aLow = (ulong)a; ulong bLow = (ulong)b; return ((UInt128)(aLow * (ulong)(b >> 64) + aLow.MulHigh(bLow)) << 64) | (aLow * bLow); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 MulMod(this UInt128 a, UInt128 b, UInt128 modulus) { UInt128 zero = UInt128.Zero; UInt128 result = UInt128.Zero; while (b != zero) { if ((b & UInt128.One) != zero) { result += a; if (result >= modulus) { result -= modulus; } } a <<= 1; if (a >= modulus) { a -= modulus; } b >>= 1; } return result; } }