diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 3a71d82..75c252b 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Text; @@ -73,12 +74,13 @@ internal static class Program bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device bool orderOnGpu = true; // controls order computations device int scanBatchSize = 2_097_152, sliceSize = 32; - ulong residueKMax = 5_000_000UL; + UInt128 residueKMax = 5_000_000UL; string filterFile = string.Empty; string cyclesPath = DefaultCyclesPath; int cyclesBatchSize = 512; bool continueCyclesGeneration = false; - ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; + UInt128 divisorCyclesSearchLimit128 = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; + ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; // NTT backend selection (GPU): reference vs staged for (int i = 0; i < args.Length; i++) @@ -140,19 +142,23 @@ internal static class Program int eq = arg.IndexOf('='); divisor = UInt128.Parse(arg.AsSpan(eq + 1)); } - else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) - { - int eq = arg.IndexOf('='); - divisorCyclesSearchLimit = ulong.Parse(arg.AsSpan(eq + 1)); - } - else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase)) - { - int eq = arg.IndexOf('='); - if (ulong.TryParse(arg.AsSpan(eq + 1), out var kmax)) - { - residueKMax = kmax; - } - } + else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) + { + int eq = arg.IndexOf('='); + if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var limit)) + { + divisorCyclesSearchLimit128 = limit; + divisorCyclesSearchLimit = limit > (UInt128)ulong.MaxValue ? ulong.MaxValue : (ulong)limit; + } + } + else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase)) + { + int eq = arg.IndexOf('='); + if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var kmax)) + { + residueKMax = kmax; + } + } // Replaces --lucas=cpu|gpu; controls device for Lucas and for // incremental/pow2mod scanning else if (arg.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase)) @@ -448,8 +454,8 @@ internal static class Program useGpuScan: mersenneOnGpu, useGpuOrder: orderOnGpu, useResidue: useResidue, - maxK: residueKMax, - residueDivisorSets: divisorCyclesSearchLimit); + maxK: residueKMax, + residueDivisorSets: divisorCyclesSearchLimit128); if (!useLucas) { tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL); @@ -741,7 +747,7 @@ internal static class Program Console.WriteLine(" --divisor-cycles-device=cpu|gpu device for cycles generation (default gpu)"); Console.WriteLine(" --divisor-cycles-batch-size= batch size for cycles generation (default 512)"); Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation"); - Console.WriteLine(" --divisor-cycles-limit= cycle search iterations when --mersenne=divisor or residue"); + Console.WriteLine(" --divisor-cycles-limit= cycle search iterations when --mersenne=divisor or residue (UInt128 supported; residue path honors full range)"); Console.WriteLine(" --use-order test primality via q order"); Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU"); diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs index 8885e0c..a5e960a 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs @@ -30,7 +30,7 @@ public class MersenneNumberResidueCpuTesterTests (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, - 1UL, + UInt128.One, (UInt128)maxK, ref isPrime, ref exhausted); diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs index 5e44cb6..8017ced 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs @@ -33,7 +33,7 @@ public class MersenneNumberResidueGpuTesterTests (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, - 1UL, + UInt128.One, (UInt128)maxK, ref isPrime, ref exhausted); diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs index 6e37b3b..6bc8966 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs @@ -10,12 +10,12 @@ public class MersenneNumberResidueCpuTester UInt128 twoP, bool lastIsSeven, UInt128 perSetLimit, - ulong setCount, + UInt128 setCount, UInt128 overallLimit, ref bool isPrime, ref bool divisorsExhausted) { - if (setCount == 0UL || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero) + if (setCount == UInt128.Zero || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero) { return; } @@ -52,9 +52,9 @@ public class MersenneNumberResidueCpuTester ModResidueTracker tracker = _mersenneResidueTracker!; UInt128 limitInclusive = overallLimit + UInt128.One; - for (ulong setIndex = 0; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++) + for (UInt128 setIndex = UInt128.Zero; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++) { - UInt128 setOffset = perSetLimit * (UInt128)setIndex; + UInt128 setOffset = perSetLimit * setIndex; UInt128 k = setOffset + UInt128.One; if (k >= limitInclusive) { diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index 32f0ee4..1e8f0af 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -12,20 +12,20 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) // via GpuKernelPool to avoid host round-trips. // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. - public void Scan( - ulong exponent, - UInt128 twoP, - bool lastIsSeven, - UInt128 perSetLimit, - ulong setCount, - UInt128 overallLimit, - ref bool isPrime, - ref bool divisorsExhausted) - { - if (setCount == 0UL || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero) - { - return; - } + public void Scan( + ulong exponent, + UInt128 twoP, + bool lastIsSeven, + UInt128 perSetLimit, + UInt128 setCount, + UInt128 overallLimit, + ref bool isPrime, + ref bool divisorsExhausted) + { + if (setCount == UInt128.Zero || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero) + { + return; + } var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); var accelerator = gpuLease.Accelerator; @@ -48,9 +48,9 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) Span orders = orderArray.AsSpan(0, batchSize); try { - for (ulong setIndex = 0; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++) - { - UInt128 setOffset = perSetLimit * (UInt128)setIndex; + for (UInt128 setIndex = UInt128.Zero; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++) + { + UInt128 setOffset = perSetLimit * setIndex; UInt128 setStart = setOffset + UInt128.One; if (setStart >= limitInclusive) { diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index fefc38f..c70e596 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -25,13 +25,13 @@ public sealed class MersenneNumberTester( bool useGpuScan = true, bool useGpuOrder = true, bool useResidue = true, - ulong maxK = 5_000_000UL, - ulong residueDivisorSets = PerfectNumberConstants.ExtraDivisorCycleSearchLimit) + UInt128? maxK = null, + UInt128? residueDivisorSets = null) { private readonly bool _useResidue = useResidue; private readonly bool _useIncremental = useIncremental && !useResidue; - private readonly ulong _maxK = maxK; - private readonly ulong _residueDivisorSetCount = residueDivisorSets; + private readonly UInt128 _maxK = maxK ?? (UInt128)5_000_000UL; + private readonly UInt128 _residueDivisorSetCount = residueDivisorSets ?? (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit; private readonly GpuKernelType _kernelType = kernelType; private readonly bool _useModuloWorkaround = useModuloWorkaround; private readonly bool _useGpuLucas = useGpuLucas; @@ -44,7 +44,7 @@ public sealed class MersenneNumberTester( private readonly MersenneNumberOrderCpuTester _orderCpuTester = new(kernelType); private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerGpuTester = new(); private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerCpuTester = new(); - private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null; + private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null; private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null; // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags. @@ -246,17 +246,17 @@ public sealed class MersenneNumberTester( // In residue mode the residue scan is the final primality check. if (_useResidue) { - if ((UInt128)_maxK < maxK) + if (_maxK < maxK) { maxK = _maxK; } - if (_maxK == 0UL || _residueDivisorSetCount == 0UL) + if (_maxK == UInt128.Zero || _residueDivisorSetCount == UInt128.Zero) { return prePrime; } - UInt128 totalLimit = (UInt128)_maxK; + UInt128 totalLimit = _maxK; if (totalLimit > maxK) { totalLimit = maxK; @@ -267,23 +267,23 @@ public sealed class MersenneNumberTester( return true; } - ulong requestedSets = _residueDivisorSetCount; - if (requestedSets == 0UL) + UInt128 requestedSets = _residueDivisorSetCount; + if (requestedSets == UInt128.Zero) { - requestedSets = 1UL; + requestedSets = UInt128.One; } - UInt128 perSetLimit = DivideRoundUp(totalLimit, (UInt128)requestedSets); + UInt128 perSetLimit = DivideRoundUp(totalLimit, requestedSets); if (perSetLimit == UInt128.Zero) { perSetLimit = UInt128.One; } UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit); - ulong effectiveSets = requestedSets; - if (setsNeeded < (UInt128)effectiveSets) + UInt128 effectiveSets = requestedSets; + if (setsNeeded < effectiveSets) { - effectiveSets = (ulong)setsNeeded; + effectiveSets = setsNeeded; } bool scanCompleted = false;