diff --git a/EvenPerfectBitScanner.Tests/ProgramTests.cs b/EvenPerfectBitScanner.Tests/ProgramTests.cs index 0e56a0c..ceef9af 100644 --- a/EvenPerfectBitScanner.Tests/ProgramTests.cs +++ b/EvenPerfectBitScanner.Tests/ProgramTests.cs @@ -351,6 +351,46 @@ public class ProgramTests } finally { + useDivisorField.SetValue(null, false); + mersenneField.SetValue(null, null); + primeField.SetValue(null, null); + residueField.SetValue(null, null); + forceCpuProp!.SetValue(null, false); + } + } + + [Fact] + public void Residue_mode_with_default_limits_accepts_large_known_primes() + { + var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; + var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; + var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; + var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); + + useDivisorField.SetValue(null, false); + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( + useIncremental: true, + useResidue: true, + maxK: 5_000_000UL, + residueDivisorSets: PerfectNumberConstants.ExtraDivisorCycleSearchLimit), trackAllValues: true)); + primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); + residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + forceCpuProp!.SetValue(null, false); + + try + { + Program.IsEvenPerfectCandidate(107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue(); + searched.Should().BeTrue(); + detailed.Should().BeTrue(); + + Program.IsEvenPerfectCandidate(127UL, 0UL, out searched, out detailed).Should().BeTrue(); + searched.Should().BeTrue(); + detailed.Should().BeTrue(); + } + finally + { + useDivisorField.SetValue(null, false); mersenneField.SetValue(null, null); primeField.SetValue(null, null); residueField.SetValue(null, null); diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs index 8017ced..06289b9 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs @@ -21,9 +21,57 @@ public class MersenneNumberResidueGpuTesterTests RunCase(tester, 29UL, 37UL, expectedPrime: false); RunCase(tester, 31UL, 1_000UL, expectedPrime: true); RunCase(tester, 89UL, 1_001UL, expectedPrime: true); + RunCase(tester, 107UL, 1_001UL, expectedPrime: true); RunCase(tester, 127UL, 1_001UL, expectedPrime: true); } + [Theory] + [InlineData(false)] + [InlineData(true)] + [Trait("Category", "Fast")] + public void Scan_handles_multiple_sets_without_false_positives(bool useGpuOrder) + { + var tester = new MersenneNumberResidueGpuTester(useGpuOrder); + + bool isPrime = true; + bool exhausted = false; + UInt128 perSetLimit = 1_666_667UL; + UInt128 setCount = 3UL; + UInt128 overallLimit = 5_000_000UL; + + tester.Scan( + 127UL, + (UInt128)127UL << 1, + LastDigitIsSeven(127UL), + perSetLimit, + setCount, + overallLimit, + ref isPrime, + ref exhausted); + + isPrime.Should().BeTrue(); + exhausted.Should().BeTrue(); + + isPrime = true; + exhausted = false; + perSetLimit = 20_000_000UL; + setCount = 5UL; + overallLimit = 100_000_000UL; + + tester.Scan( + 107UL, + (UInt128)107UL << 1, + LastDigitIsSeven(107UL), + perSetLimit, + setCount, + overallLimit, + ref isPrime, + ref exhausted); + + isPrime.Should().BeTrue(); + exhausted.Should().BeTrue(); + } + private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) { bool isPrime = true; diff --git a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs index b1b1378..9724244 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -195,12 +195,12 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One; // Small-cycles in-kernel early rejection from device table - if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1)) + if (q.High == 0UL && q.Low < (ulong)smallCycles.Length) { uint cyc = smallCycles[(int)q.Low]; - if (cyc != 0U && (exponent % cyc) == 0UL) + if (cyc != 0U && (exponent % cyc) != 0UL) { - orders[index] = exponent; + orders[index] = 0UL; return; } } @@ -340,12 +340,12 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne GpuUInt128 k = kStart + (GpuUInt128)idx; GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One; - if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1)) + if (q.High == 0UL && q.Low < (ulong)smallCycles.Length) { uint cyc = smallCycles[(int)q.Low]; - if (cyc != 0U && (exponent % cyc) == 0UL) + if (cyc != 0U && (exponent % cyc) != 0UL) { - orders[index] = exponent; + orders[index] = 0UL; return; } } @@ -468,10 +468,10 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One; // Small-cycles in-kernel early rejection from device table - if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1)) + if (q.High == 0UL && q.Low < (ulong)smallCycles.Length) { uint cyc = smallCycles[(int)q.Low]; - if (cyc != 0U && (exponent % (ulong)cyc) == 0UL) + if (cyc != 0U && (exponent % (ulong)cyc) != 0UL) { return; } @@ -547,10 +547,10 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne GpuUInt128 k = kStart + (GpuUInt128)idx; GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One; // Small-cycles in-kernel early rejection from device table - if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1)) + if (q.High == 0UL && q.Low < (ulong)smallCycles.Length) { uint cyc = smallCycles[(int)q.Low]; - if (cyc != 0U && (exponent % (ulong)cyc) == 0UL) + if (cyc != 0U && (exponent % (ulong)cyc) != 0UL) { return; }