From a83ae56caf3fc4dbbf6ca8c10b3069d11d3200cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 26 Oct 2025 16:04:05 +0100 Subject: [PATCH] Use LastDigit enum across Mersenne scanners --- ...MersenneNumberIncrementalCpuTesterTests.cs | 5 ++- ...MersenneNumberIncrementalGpuTesterTests.cs | 5 ++- .../MersenneNumberOrderCpuTesterTests.cs | 5 ++- .../MersenneNumberOrderGpuTesterTests.cs | 7 ++-- .../MersenneNumberResidueCpuTesterTests.cs | 5 ++- .../MersenneNumberResidueGpuTesterTests.cs | 5 ++- .../Cpu/MersenneNumberIncrementalCpuTester.cs | 2 +- .../Cpu/MersenneNumberOrderCpuTester.cs | 4 +- .../Cpu/MersenneNumberResidueCpuTester.cs | 8 ++-- .../Gpu/MersenneNumberIncrementalGpuTester.cs | 4 +- .../Gpu/MersenneNumberOrderGpuTester.cs | 4 +- .../Gpu/MersenneNumberResidueGpuTester.cs | 4 +- PerfectNumbers.Core/LastDigit.cs | 10 +++++ PerfectNumbers.Core/MersenneNumberTester.cs | 26 ++++++------- PerfectNumbers.Core/PrimesGenerator.cs | 38 ++++++++++++++----- 15 files changed, 83 insertions(+), 49 deletions(-) create mode 100644 PerfectNumbers.Core/LastDigit.cs diff --git a/PerfectNumbers.Core.Tests/MersenneNumberIncrementalCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberIncrementalCpuTesterTests.cs index 9edcee3..dd7307d 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberIncrementalCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberIncrementalCpuTesterTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core; using PerfectNumbers.Core.Cpu; using Xunit; using UInt128 = System.UInt128; @@ -7,7 +8,7 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberIncrementalCpuTesterTests { - private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; [Theory] [InlineData(GpuKernelType.Incremental)] @@ -30,7 +31,7 @@ public class MersenneNumberIncrementalCpuTesterTests private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) { bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); + tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); isPrime.Should().Be(expectedPrime); } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberIncrementalGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberIncrementalGpuTesterTests.cs index e17d04f..b990ece 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberIncrementalGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberIncrementalGpuTesterTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core; using PerfectNumbers.Core.Gpu; using Xunit; using UInt128 = System.UInt128; @@ -7,7 +8,7 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberIncrementalGpuTesterTests { - private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; [Theory(Skip = "Disabled until the GPU incremental cycle heuristics are ported.")] [InlineData(GpuKernelType.Pow2Mod, false)] @@ -32,7 +33,7 @@ public class MersenneNumberIncrementalGpuTesterTests private static void RunCase(MersenneNumberIncrementalGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) { bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); + tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); isPrime.Should().Be(expectedPrime); } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberOrderCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberOrderCpuTesterTests.cs index 3348ceb..2d2756d 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberOrderCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberOrderCpuTesterTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core; using PerfectNumbers.Core.Cpu; using Xunit; using UInt128 = System.UInt128; @@ -7,7 +8,7 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberOrderCpuTesterTests { - private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; [Theory] [InlineData(GpuKernelType.Incremental)] @@ -30,7 +31,7 @@ public class MersenneNumberOrderCpuTesterTests private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) { bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); + tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); isPrime.Should().Be(expectedPrime); } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberOrderGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberOrderGpuTesterTests.cs index 5f9a69b..2063482 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberOrderGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberOrderGpuTesterTests.cs @@ -1,5 +1,6 @@ using System.Reflection; using FluentAssertions; +using PerfectNumbers.Core; using PerfectNumbers.Core.Gpu; using Xunit; using UInt128 = System.UInt128; @@ -8,7 +9,7 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberOrderGpuTesterTests { - private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; [Theory] [InlineData(GpuKernelType.Pow2Mod, false)] @@ -64,7 +65,7 @@ public class MersenneNumberOrderGpuTesterTests tester.Scan( exponent, (UInt128)exponent << 1, - LastDigitIsSeven(exponent), + GetLastDigit(exponent), (UInt128)10UL, ref isPrime); @@ -86,7 +87,7 @@ public class MersenneNumberOrderGpuTesterTests private static void RunCase(MersenneNumberOrderGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) { bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); + tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); isPrime.Should().Be(expectedPrime); } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs index 6c43e0d..aad010d 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core; using PerfectNumbers.Core.Cpu; using Xunit; using UInt128 = System.UInt128; @@ -7,7 +8,7 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberResidueCpuTesterTests { - private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; [Fact] [Trait("Category", "Fast")] @@ -24,7 +25,7 @@ public class MersenneNumberResidueCpuTesterTests private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) { bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); + tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); isPrime.Should().Be(expectedPrime); } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs index 13ffde9..6063de1 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core; using PerfectNumbers.Core.Gpu; using Xunit; using UInt128 = System.UInt128; @@ -7,7 +8,7 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberResidueGpuTesterTests { - private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; [Theory] [InlineData(false)] @@ -27,7 +28,7 @@ public class MersenneNumberResidueGpuTesterTests private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) { bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); + tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); isPrime.Should().Be(expectedPrime); } } diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberIncrementalCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberIncrementalCpuTester.cs index 7506cc1..c794e07 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberIncrementalCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberIncrementalCpuTester.cs @@ -5,7 +5,7 @@ public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType) private static readonly MersenneDivisorCycles _cycles = MersenneDivisorCycles.Shared; private readonly GpuKernelType _kernelType = kernelType; - public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { UInt128 k = UInt128.One; UInt128 one = k; diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberOrderCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberOrderCpuTester.cs index cc1996f..14c3638 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberOrderCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberOrderCpuTester.cs @@ -4,7 +4,7 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType) { private readonly GpuKernelType _kernelType = kernelType; - public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { UInt128 k = 1UL; var auto = new MersenneResidueAutomaton(exponent); @@ -17,7 +17,7 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType) // TODO: When this lookup misses the snapshot, invoke the configured device to compute the // single required cycle on demand without persisting it so the CPU order path retains the // cycle-stepping speedups while honoring the no-extra-cache constraint for large divisors. - int allowMask2 = lastIsSeven ? CpuConstants.LastSevenMask10 : CpuConstants.LastOneMask10; + int allowMask2 = lastDigit == LastDigit.Seven ? CpuConstants.LastSevenMask10 : CpuConstants.LastOneMask10; bool shouldCheck = ((allowMask2 >> (int)auto.Mod10R) & 1) != 0; if (shouldCheck) { diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs index 8ee835d..fd19c49 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs @@ -5,7 +5,7 @@ public class MersenneNumberResidueCpuTester private ModResidueTracker? _mersenneResidueTracker; // CPU residue variant using tracker + unrolled residue updates (no method calls in the loop). - public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { // Initialize/update Mersenne residue tracker and start a merge walk for ascending q divisors @@ -29,9 +29,9 @@ public class MersenneNumberResidueCpuTester step10 = step10.Mod10(); // Allowed last-digit sets for q depending on last digit of M_p - // lastIsSeven == true => allow {7,9} - // lastIsSeven == false => allow {1,3,7,9} - int allowMask = lastIsSeven ? CpuConstants.LastSevenMask10 : CpuConstants.LastOneMask10; + // LastDigit.Seven => allow {7,9} + // otherwise => allow {1,3,7,9} + int allowMask = lastDigit == LastDigit.Seven ? CpuConstants.LastSevenMask10 : CpuConstants.LastOneMask10; // Predeclare temps to avoid redeclaration overhead in the loop UInt128 remaining; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs index 871c201..ccc1bd9 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs @@ -12,7 +12,7 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u private readonly GpuKernelType _kernelType = kernelType; private readonly bool _useGpuOrder = useGpuOrder; - public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder})."); @@ -23,7 +23,7 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy UInt128 kStart = 1UL; ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; - byte last = lastIsSeven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters + byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters var pow2Kernel = gpuLease.Pow2ModKernel; var incKernel = gpuLease.IncrementalKernel; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs index d52970f..a4d0a60 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs @@ -7,7 +7,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO private readonly GpuKernelType _kernelType = kernelType; private readonly bool _useGpuOrder = useGpuOrder; - public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); var execution = gpuLease.EnterExecutionScope(); @@ -16,7 +16,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR UInt128 kStart = UInt128.One; ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; - byte last = lastIsSeven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters + byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters var kernel = _kernelType switch { diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index 91a6b48..dac5e5a 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -24,7 +24,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) // requested by the caller, skip queuing additional block generation, and keep the snapshot cache untouched. // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. - public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { var limiter = GpuPrimeWorkLimiter.Acquire(); var gpuLease = RentAccelerator(); @@ -46,7 +46,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) UInt128 kStart = 1UL; UInt128 limit = maxK + UInt128.One; - byte last = lastIsSeven ? (byte)1 : (byte)0; + byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; twoP.Mod10_8_5_3(out ulong step10, out ulong step8, out ulong step5, out ulong step3); step10 = step10.Mod10(); diff --git a/PerfectNumbers.Core/LastDigit.cs b/PerfectNumbers.Core/LastDigit.cs new file mode 100644 index 0000000..c53093f --- /dev/null +++ b/PerfectNumbers.Core/LastDigit.cs @@ -0,0 +1,10 @@ +namespace PerfectNumbers.Core; + +public enum LastDigit : byte +{ + One, + Three, + Five, + Seven, + Nine, +} diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index 0a5f763..cce3324 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -58,7 +58,7 @@ public sealed class MersenneNumberTester( } UInt128 twoP = (UInt128)exponent << 1; // 2 * p - bool lastIsSeven = (exponent & 3UL) == 3UL; + LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; // CPU path: compute orders directly and cache if (!_useGpuOrder) { @@ -71,7 +71,7 @@ public sealed class MersenneNumberTester( remainder = q.Mod10(); // TODO: Replace these repeated Mod10/Mod8/Mod3/Mod5 calls with a residue automaton walk so // order warmups reuse the optimized cycle stepping validated in the residue benchmarks. - bool shouldCheck = lastIsSeven + bool shouldCheck = lastDigit == LastDigit.Seven ? remainder == 7UL || remainder == 9UL || remainder == 3UL : remainder == 1UL || remainder == 3UL || remainder == 9UL; @@ -112,7 +112,7 @@ public sealed class MersenneNumberTester( remainder2 = q2.Mod10(); // TODO: Reuse the same residue-automaton fast path here so the GPU warmup staging avoids `%` and // branches the benchmarks showed slower than the tracked stepping helper. - bool shouldCheck2 = lastIsSeven + bool shouldCheck2 = lastDigit == LastDigit.Seven ? remainder2 == 7UL || remainder2 == 9UL || remainder2 == 3UL : remainder2 == 1UL || remainder2 == 3UL || remainder2 == 9UL; @@ -230,7 +230,7 @@ public sealed class MersenneNumberTester( UInt128 twoP = (UInt128)exponent << 1; // 2 * p // UInt128 maxDivisor = new(ulong.MaxValue, ulong.MaxValue); UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1; - bool lastIsSeven = (exponent & 3UL) == 3UL; + LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; // In residue mode the residue scan is the final primality check. if (_useResidue) @@ -242,11 +242,11 @@ public sealed class MersenneNumberTester( if (_useGpuScan) { - _residueGpuTester!.Scan(exponent, twoP, lastIsSeven, maxK, ref prePrime); + _residueGpuTester!.Scan(exponent, twoP, lastDigit, maxK, ref prePrime); } else { - _residueCpuTester!.Scan(exponent, twoP, lastIsSeven, maxK, ref prePrime); + _residueCpuTester!.Scan(exponent, twoP, lastDigit, maxK, ref prePrime); } return prePrime; @@ -258,14 +258,14 @@ public sealed class MersenneNumberTester( // Scan a limited range of k to quickly reject obvious cases. UInt128 twoPPre = (UInt128)exponent << 1; UInt128 maxKPre = (UInt128)PreLucasPreScanK; - bool lastIsSevenPre = (exponent & 3UL) == 3UL; + LastDigit lastDigitPre = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; if (_useGpuScan) { - _incrementalGpuTester.Scan(exponent, twoPPre, lastIsSevenPre, maxKPre, ref prePrime); + _incrementalGpuTester.Scan(exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); } else { - _incrementalCpuTester.Scan(exponent, twoPPre, lastIsSevenPre, maxKPre, ref prePrime); + _incrementalCpuTester.Scan(exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); } if (!prePrime) @@ -284,22 +284,22 @@ public sealed class MersenneNumberTester( { if (_useGpuOrder) { - _orderGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); + _orderGpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); } else { - _orderCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); + _orderCpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); } } else { if (_useGpuScan) { - _incrementalGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); + _incrementalGpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); } else { - _incrementalCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); + _incrementalCpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); } } diff --git a/PerfectNumbers.Core/PrimesGenerator.cs b/PerfectNumbers.Core/PrimesGenerator.cs index 754d302..da1f050 100644 --- a/PerfectNumbers.Core/PrimesGenerator.cs +++ b/PerfectNumbers.Core/PrimesGenerator.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.CompilerServices; namespace PerfectNumbers.Core; @@ -85,14 +86,16 @@ public static class PrimesGenerator if (candidate != 2U) { - if (lastOneCount < targetInt && IsAllowedForLastOne(candidate)) + LastDigit lastDigit = GetLastDigit(candidate); + + if (lastOneCount < targetInt && IsAllowedForLastOne(candidate, lastDigit)) { lastOne[lastOneCount] = candidate; lastOnePow2[lastOneCount] = checked((ulong)candidate * candidate); lastOneCount++; } - if (lastSevenCount < targetInt && IsAllowedForLastSeven(candidate)) + if (lastSevenCount < targetInt && IsAllowedForLastSeven(candidate, lastDigit)) { lastSeven[lastSevenCount] = candidate; lastSevenPow2[lastSevenCount] = checked((ulong)candidate * candidate); @@ -108,25 +111,40 @@ public static class PrimesGenerator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsAllowedForLastOne(uint prime) + private static LastDigit GetLastDigit(uint prime) { - // TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path - // reuses the benchmarked residue helper instead of repeated divisions. return (prime % 10U) switch { - 1U or 3U or 9U => true, - _ => prime == 7U || prime == 11U, + 1U => LastDigit.One, + 3U => LastDigit.Three, + 5U => LastDigit.Five, + 7U => LastDigit.Seven, + 9U => LastDigit.Nine, + _ => throw new ArgumentOutOfRangeException(nameof(prime)), }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsAllowedForLastSeven(uint prime) + private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit) + { + // TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path + // reuses the benchmarked residue helper instead of repeated divisions. + return lastDigit switch + { + LastDigit.One or LastDigit.Three or LastDigit.Nine => true, + LastDigit.Seven => prime == 7U, + _ => prime == 11U, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit) { // TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster // residue helper used elsewhere in the scanner. - return (prime % 10U) switch + return lastDigit switch { - 3U or 7U or 9U => true, + LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true, _ => prime == 11U, }; }