From 95a0b030f6e73db80aa48becbd7055de100d6611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 21 Sep 2025 10:14:26 +0200 Subject: [PATCH] Add optimized modulo helpers for unsigned integers --- .../UInt128ExtensionsTests.cs | 24 +++ .../UIntExtensionsTests.cs | 85 ++++++++++ .../ULongExtensionsTests.cs | 80 +++++++++ .../Gpu/MersenneNumberResidueGpuTester.cs | 5 +- PerfectNumbers.Core/UInt128Extensions.cs | 95 +++++++---- PerfectNumbers.Core/UIntExtensions.cs | 61 +++++++ PerfectNumbers.Core/ULongExtensions.cs | 159 +++++++++++++----- 7 files changed, 425 insertions(+), 84 deletions(-) create mode 100644 PerfectNumbers.Core.Tests/UIntExtensionsTests.cs create mode 100644 PerfectNumbers.Core/UIntExtensions.cs diff --git a/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs b/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs index 875a568..22beea2 100644 --- a/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs +++ b/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs @@ -209,6 +209,30 @@ public class UInt128ExtensionsTests value.Mod5().Should().Be((ulong)(value % 5)); } + [Fact] + [Trait("Category", "Fast")] + public void Mod7_matches_operator() + { + UInt128 value = (UInt128.One << 100) + 9876543210987654321UL; + value.Mod7().Should().Be((ulong)(value % 7)); + } + + [Fact] + [Trait("Category", "Fast")] + public void Mod11_matches_operator() + { + UInt128 value = (UInt128.One << 120) + 9876543210987654321UL; + value.Mod11().Should().Be((ulong)(value % 11)); + } + + [Fact] + [Trait("Category", "Fast")] + public void Mod128_matches_operator() + { + UInt128 value = (UInt128.One << 120) + 9876543210987654321UL; + value.Mod128().Should().Be((ulong)(value % 128)); + } + [Fact] [Trait("Category", "Fast")] public void Mul64_matches_expected_product() diff --git a/PerfectNumbers.Core.Tests/UIntExtensionsTests.cs b/PerfectNumbers.Core.Tests/UIntExtensionsTests.cs new file mode 100644 index 0000000..48d6ae1 --- /dev/null +++ b/PerfectNumbers.Core.Tests/UIntExtensionsTests.cs @@ -0,0 +1,85 @@ +using FluentAssertions; +using Xunit; + +namespace PerfectNumbers.Core.Tests; + +public class UIntExtensionsTests +{ + [Theory] + [Trait("Category", "Fast")] + [InlineData(0U)] + [InlineData(1U)] + [InlineData(1234567890U)] + [InlineData(uint.MaxValue)] + public void Mod3_matches_operator(uint value) + { + value.Mod3().Should().Be(value % 3U); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0U)] + [InlineData(1U)] + [InlineData(1234567890U)] + [InlineData(uint.MaxValue)] + public void Mod5_matches_operator(uint value) + { + value.Mod5().Should().Be(value % 5U); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0U)] + [InlineData(1U)] + [InlineData(1234567890U)] + [InlineData(uint.MaxValue)] + public void Mod7_matches_operator(uint value) + { + value.Mod7().Should().Be(value % 7U); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0U)] + [InlineData(1U)] + [InlineData(1234567890U)] + [InlineData(uint.MaxValue)] + public void Mod8_matches_operator(uint value) + { + value.Mod8().Should().Be(value % 8U); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0U)] + [InlineData(1U)] + [InlineData(1234567890U)] + [InlineData(uint.MaxValue)] + public void Mod10_matches_operator(uint value) + { + value.Mod10().Should().Be(value % 10U); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0U)] + [InlineData(1U)] + [InlineData(1234567890U)] + [InlineData(uint.MaxValue)] + public void Mod11_matches_operator(uint value) + { + value.Mod11().Should().Be(value % 11U); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0U)] + [InlineData(1U)] + [InlineData(1234567890U)] + [InlineData(uint.MaxValue)] + public void Mod128_matches_operator(uint value) + { + value.Mod128().Should().Be(value % 128U); + } +} + diff --git a/PerfectNumbers.Core.Tests/ULongExtensionsTests.cs b/PerfectNumbers.Core.Tests/ULongExtensionsTests.cs index 68df302..bd67e46 100644 --- a/PerfectNumbers.Core.Tests/ULongExtensionsTests.cs +++ b/PerfectNumbers.Core.Tests/ULongExtensionsTests.cs @@ -21,6 +21,86 @@ public class ULongExtensionsTests ULongExtensions.Mod10(value).Should().Be(expected); } + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(ulong.MaxValue)] + [InlineData(1234567890123456789UL)] + public void Mod3_matches_operator(ulong value) + { + value.Mod3().Should().Be(value % 3UL); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(ulong.MaxValue)] + [InlineData(1234567890123456789UL)] + public void Mod5_matches_operator(ulong value) + { + value.Mod5().Should().Be(value % 5UL); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(ulong.MaxValue)] + [InlineData(1234567890123456789UL)] + public void Mod7_matches_operator(ulong value) + { + value.Mod7().Should().Be(value % 7UL); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(ulong.MaxValue)] + [InlineData(1234567890123456789UL)] + public void Mod8_matches_operator(ulong value) + { + value.Mod8().Should().Be(value % 8UL); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(ulong.MaxValue)] + [InlineData(1234567890123456789UL)] + public void Mod11_matches_operator(ulong value) + { + value.Mod11().Should().Be(value % 11UL); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(ulong.MaxValue)] + [InlineData(1234567890123456789UL)] + public void Mod128_matches_operator(ulong value) + { + value.Mod128().Should().Be(value % 128UL); + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData(1UL)] + [InlineData(123456789UL)] + [InlineData(ulong.MaxValue)] + public void Mod10_8_5_3Steps_matches_manual_formula(ulong value) + { + value.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); + + ulong expected10 = ((value % 10UL) << 1) % 10UL; + ulong expected8 = ((value & 7UL) << 1) & 7UL; + ulong expected5 = ((value % 5UL) << 1) % 5UL; + ulong expected3 = ((value % 3UL) << 1) % 3UL; + + step10.Should().Be(expected10); + step8.Should().Be(expected8); + step5.Should().Be(expected5); + step3.Should().Be(expected3); + } + [Theory] [Trait("Category", "Fast")] [InlineData(2UL, true)] diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index d44cc73..a5fb8ae 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -24,10 +24,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) UInt128 kStart = 1UL; byte last = lastIsSeven ? (byte)1 : (byte)0; var kernel = gpuLease.Pow2ModKernel; - ulong step10 = (exponent.Mod10() << 1).Mod10(); - ulong step8 = ((exponent & 7UL) << 1) & 7UL; - ulong step3 = ((exponent % 3UL) << 1) % 3UL; - ulong step5 = ((exponent % 5UL) << 1) % 5UL; + exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); GpuUInt128 twoPGpu = (GpuUInt128)twoP; var orderBuffer = accelerator.Allocate1D(batchSize); diff --git a/PerfectNumbers.Core/UInt128Extensions.cs b/PerfectNumbers.Core/UInt128Extensions.cs index 2b31094..bcb93c1 100644 --- a/PerfectNumbers.Core/UInt128Extensions.cs +++ b/PerfectNumbers.Core/UInt128Extensions.cs @@ -203,48 +203,73 @@ public static class UInt128Extensions } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod10(this UInt128 value) - { - UInt128 zero = UInt128.Zero; - if (value == zero) - return 0UL; + public static ulong Mod10(this UInt128 value) + { + UInt128 zero = UInt128.Zero; + if (value == zero) + return 0UL; - ulong result = (ulong)value; - UInt128 high = value >> 64; + ulong result = (ulong)value; + UInt128 high = value >> 64; - while (high != zero) - { - // 2^64 ≡ 6 (mod 10) - result = (result + (ulong)high * 6UL).Mod10(); - high >>= 64; - } + while (high != zero) + { + // 2^64 ≡ 6 (mod 10) + result = (result + (ulong)high * 6UL).Mod10(); + high >>= 64; + } - return result.Mod10(); - } + return result.Mod10(); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod8(this UInt128 value) => (ulong)value & 7UL; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod3(this UInt128 value) - { - // 2^64 ≡ 1 (mod 3) - ulong rem = ((ulong)value % 3UL) + ((ulong)(value >> 64) % 3UL); - return rem >= 3UL ? rem - 3UL : rem; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod8(this UInt128 value) => (ulong)value & 7UL; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod5(this UInt128 value) - { - // 2^64 ≡ 1 (mod 5) - ulong rem = ((ulong)value % 5UL) + ((ulong)(value >> 64) % 5UL); - return rem >= 5UL ? rem - 5UL : rem; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod3(this UInt128 value) + { + ulong remainder = ((ulong)value).Mod3() + ((ulong)(value >> 64)).Mod3(); + return remainder >= 3UL ? remainder - 3UL : remainder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod5(this UInt128 value) + { + ulong remainder = ((ulong)value).Mod5() + ((ulong)(value >> 64)).Mod5(); + return remainder >= 5UL ? remainder - 5UL : remainder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod7(this UInt128 value) + { + ulong remainder = ((ulong)value).Mod7() + ((ulong)(value >> 64)).Mod7() * 2UL; + while (remainder >= 7UL) + { + remainder -= 7UL; + } + + return remainder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod11(this UInt128 value) + { + ulong remainder = ((ulong)value).Mod11() + ((ulong)(value >> 64)).Mod11() * 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) - { + [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); diff --git a/PerfectNumbers.Core/UIntExtensions.cs b/PerfectNumbers.Core/UIntExtensions.cs new file mode 100644 index 0000000..fe9b85e --- /dev/null +++ b/PerfectNumbers.Core/UIntExtensions.cs @@ -0,0 +1,61 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core; + +public static class UIntExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Mod3(this uint value) => PerfectNumbersMath.FastRemainder3(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Mod5(this uint value) => PerfectNumbersMath.FastRemainder5(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Mod7(this uint value) + { + uint remainder = 0U; + uint temp = value; + + while (temp != 0U) + { + remainder += temp & 7U; + temp >>= 3; + if (remainder >= 7U) + { + remainder -= 7U; + } + } + + return remainder >= 7U ? remainder - 7U : remainder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Mod8(this uint value) => value & 7U; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Mod10(this uint value) + { + uint quotient = (uint)(((ulong)value * 0xCCCCCCCDUL) >> 35); + return value - quotient * 10U; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Mod11(this uint value) + { + uint remainder = 0U; + uint temp = value; + + while (temp != 0U) + { + remainder += temp & 1023U; + temp >>= 10; + remainder -= 11U * (remainder / 11U); + } + + return remainder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Mod128(this uint value) => value & 127U; +} + diff --git a/PerfectNumbers.Core/ULongExtensions.cs b/PerfectNumbers.Core/ULongExtensions.cs index 25b4ae6..77f4241 100644 --- a/PerfectNumbers.Core/ULongExtensions.cs +++ b/PerfectNumbers.Core/ULongExtensions.cs @@ -58,51 +58,62 @@ public static class ULongExtensions } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Mod10_8_5_3(this ulong value, out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3) - { - ulong temp = value; - uint byteSum = 0U; + public static void Mod10_8_5_3(this ulong value, out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3) + { + ulong temp = value; + uint byteSum = 0U; - do - { - byteSum += (byte)temp; - temp >>= 8; - } - while (temp != 0UL); + do + { + byteSum += (byte)temp; + temp >>= 8; + } + while (temp != 0UL); - mod8 = value & 7UL; + mod8 = value & 7UL; - uint mod5Value = PerfectNumbersMath.FastRemainder5(byteSum); - uint mod3Value = PerfectNumbersMath.FastRemainder3(byteSum); + uint mod5Value = PerfectNumbersMath.FastRemainder5(byteSum); + uint mod3Value = PerfectNumbersMath.FastRemainder3(byteSum); - ulong parity = mod8 & 1UL; - mod10 = parity == 0UL - ? mod5Value switch - { - 0U => 0UL, - 1U => 6UL, - 2U => 2UL, - 3U => 8UL, - _ => 4UL, - } - : mod5Value switch - { - 0U => 5UL, - 1U => 1UL, - 2U => 7UL, - 3U => 3UL, - _ => 9UL, - }; + ulong parity = mod8 & 1UL; + mod10 = parity == 0UL + ? mod5Value switch + { + 0U => 0UL, + 1U => 6UL, + 2U => 2UL, + 3U => 8UL, + _ => 4UL, + } + : mod5Value switch + { + 0U => 5UL, + 1U => 1UL, + 2U => 7UL, + 3U => 3UL, + _ => 9UL, + }; - mod5 = mod5Value; - mod3 = mod3Value; - } + mod5 = mod5Value; + mod3 = mod3Value; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPrimeCandidate(this ulong n) - { - int i = 0; - ulong p; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Mod10_8_5_3Steps(this ulong value, out ulong step10, out ulong step8, out ulong step5, out ulong step3) + { + value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3); + + step10 = (mod10 << 1).Mod10(); + step8 = ((mod8 << 1) & 7UL); + step5 = (mod5 << 1).Mod5(); + step3 = (mod3 << 1).Mod3(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPrimeCandidate(this ulong n) + { + int i = 0; + ulong p; uint[] smallPrimes = PrimesGenerator.SmallPrimes; ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2; int len = smallPrimes.Length; @@ -123,12 +134,70 @@ public static class ULongExtensions return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod10(this ulong value) - { - ulong quotient = (ulong)(((UInt128)value * 0xCCCCCCCCCCCCCCCDUL) >> 67); - return value - quotient * 10UL; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod10(this ulong value) + { + ulong quotient = (ulong)(((UInt128)value * 0xCCCCCCCCCCCCCCCDUL) >> 67); + return value - quotient * 10UL; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod8(this ulong value) => value & 7UL; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod3(this ulong value) + { + uint sum = (uint)(value & 0xFFFFUL); + sum += (uint)((value >> 16) & 0xFFFFUL); + sum += (uint)((value >> 32) & 0xFFFFUL); + sum += (uint)((value >> 48) & 0xFFFFUL); + + return PerfectNumbersMath.FastRemainder3(sum); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod5(this ulong value) + { + uint sum = (uint)(value & 0xFFFFUL); + sum += (uint)((value >> 16) & 0xFFFFUL); + sum += (uint)((value >> 32) & 0xFFFFUL); + sum += (uint)((value >> 48) & 0xFFFFUL); + + return PerfectNumbersMath.FastRemainder5(sum); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod7(this ulong value) + { + uint low = (uint)value; + uint high = (uint)(value >> 32); + + ulong remainder = low.Mod7() + (ulong)high.Mod7() * 4UL; + while (remainder >= 7UL) + { + remainder -= 7UL; + } + + return remainder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod11(this ulong value) + { + uint low = (uint)value; + uint high = (uint)(value >> 32); + + ulong remainder = low.Mod11() + (ulong)high.Mod11() * 4UL; + while (remainder >= 11UL) + { + remainder -= 11UL; + } + + return remainder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod128(this ulong value) => value & 127UL; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 Mul64(this ulong a, ulong b) => ((UInt128)a.MulHigh(b) << 64) | (UInt128)(a * b);