From 01277832e6a877d2389aca67a4e77e9e1d35037d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 10 Sep 2025 01:51:37 +0200 Subject: [PATCH] Implement UInt128 exponentiation and apply in Mersenne scan --- .../UInt128ExtensionsTests.cs | 34 ++++++++++++++++++ PerfectNumbers.Core/MersenneNumberTester.cs | 2 +- PerfectNumbers.Core/UInt128Extensions.cs | 36 ++++++++++++++----- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs b/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs index d735285..875a568 100644 --- a/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs +++ b/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs @@ -112,6 +112,40 @@ public class UInt128ExtensionsTests mod3.Should().Be(expected3); } + [Theory] + [Trait("Category", "Fast")] + [InlineData(2UL, 0UL)] + [InlineData(2UL, 1UL)] + [InlineData(2UL, 63UL)] + [InlineData(2UL, 127UL)] + [InlineData(3UL, 5UL)] + public void Pow_matches_BigInteger(ulong value, ulong exponent) + { + UInt128 v = value; + UInt128 expected = (UInt128)BigPow(value, exponent); + + v.Pow(exponent).Should().Be(expected); + } + + private static BigInteger BigPow(ulong value, ulong exponent) + { + BigInteger result = BigInteger.One; + BigInteger baseValue = value; + + while (exponent != 0UL) + { + if ((exponent & 1UL) != 0UL) + { + result *= baseValue; + } + + baseValue *= baseValue; + exponent >>= 1; + } + + return result; + } + [Theory] [Trait("Category", "Fast")] [InlineData(2UL, 10UL, 17UL)] diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index e47c9b4..765cb69 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -214,7 +214,7 @@ public sealed class MersenneNumberTester( bool prePrime = true; UInt128 twoP = (UInt128)exponent << 1; // 2 * p // UInt128 maxDivisor = new(ulong.MaxValue, ulong.MaxValue); - UInt128 maxK = (UInt128Numbers.Two ^ exponent) / 2 + 1; + UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1; bool lastIsSeven = (exponent & 3UL) == 3UL; // This is how maxK was for residue test before changes UInt128 maxK = maxKOverride ?? ResidueMaxK; diff --git a/PerfectNumbers.Core/UInt128Extensions.cs b/PerfectNumbers.Core/UInt128Extensions.cs index d8c93d1..78bca9f 100644 --- a/PerfectNumbers.Core/UInt128Extensions.cs +++ b/PerfectNumbers.Core/UInt128Extensions.cs @@ -150,15 +150,35 @@ public static class UInt128Extensions high >>= 64; } - mod10 = result % 10UL; - } + mod10 = result % 10UL; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 ModPow(this UInt128 value, UInt128 exponent, UInt128 modulus) - { - UInt128 result, one = result = UInt128.One, - zero = UInt128.Zero, - baseValue = value % modulus; + [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 result, one = result = UInt128.One, + zero = UInt128.Zero, + baseValue = value % modulus; while (exponent != zero) {