diff --git a/EvenPerfectBitScanner.ResultsParser/Program.cs b/EvenPerfectBitScanner.ResultsParser/Program.cs index 7b39235..7079308 100644 --- a/EvenPerfectBitScanner.ResultsParser/Program.cs +++ b/EvenPerfectBitScanner.ResultsParser/Program.cs @@ -19,7 +19,7 @@ internal static class Program "/help" }; - private const string DefaultHeader = "p,searchedMersenne,detailedCheck,passedAllTests"; +private const string DefaultHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests"; private static int Main(string[] args) { diff --git a/EvenPerfectBitScanner/CalculationResultHandler.cs b/EvenPerfectBitScanner/CalculationResultHandler.cs index 47a8cf5..aacdf54 100644 --- a/EvenPerfectBitScanner/CalculationResultHandler.cs +++ b/EvenPerfectBitScanner/CalculationResultHandler.cs @@ -7,7 +7,7 @@ namespace EvenPerfectBitScanner; internal static class CalculationResultHandler { - private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests"; + private const string ResultsHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests"; private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)"; internal const int DefaultWriteBatchSize = 100; @@ -93,19 +93,19 @@ internal static class CalculationResultHandler } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite) + internal static void HandleResult(ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite) { // Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here. Span stackBuffer = stackalloc byte[128]; byte[]? rentedBuffer = null; Span utf8Span = stackBuffer; - int byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests); + int byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests); if (byteCount < 0) { rentedBuffer = ThreadStaticPools.BytePool.Rent(256); utf8Span = rentedBuffer; - byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests); + byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests); if (byteCount < 0) { ThreadStaticPools.BytePool.Return(rentedBuffer); @@ -229,7 +229,7 @@ internal static class CalculationResultHandler } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int TryFormatResultLine(Span destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests) + private static int TryFormatResultLine(Span destination, ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests) { int offset = 0; @@ -246,6 +246,19 @@ internal static class CalculationResultHandler destination[offset++] = (byte)','; + if (!Utf8Formatter.TryFormat(divisor, destination[offset..], out written)) + { + return -1; + } + + offset += written; + if (offset >= destination.Length) + { + return -1; + } + + destination[offset++] = (byte)','; + if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written)) { return -1; diff --git a/EvenPerfectBitScanner/IO/CalculationResultsFile.cs b/EvenPerfectBitScanner/IO/CalculationResultsFile.cs index a6a0b29..9b871dd 100644 --- a/EvenPerfectBitScanner/IO/CalculationResultsFile.cs +++ b/EvenPerfectBitScanner/IO/CalculationResultsFile.cs @@ -100,6 +100,7 @@ internal static class CalculationResultsFile int first = 0; int second = 0; int third = 0; + int fourth = 0; ulong parsedP = 0UL; ReadOnlySpan detailedSpan = default; ReadOnlySpan passedAllTestsSpan = default; @@ -139,8 +140,15 @@ internal static class CalculationResultsFile continue; } - detailedSpan = span[..third]; - passedAllTestsSpan = span[(third + 1)..]; + span = span[(third + 1)..]; + fourth = span.IndexOf(','); + if (fourth < 0) + { + continue; + } + + detailedSpan = span[..fourth]; + passedAllTestsSpan = span[(fourth + 1)..]; if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests)) { diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index c632736..a5610d1 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -422,7 +422,7 @@ internal static class Program _byDivisorStartPrime, static () => _lastCompositeP = true, static () => _lastCompositeP = false, - static (candidate, searchedMersenne, detailedCheck, passedAllTests) => CalculationResultHandler.HandleResult(candidate, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP), + static (candidate, searchedMersenne, detailedCheck, passedAllTests, divisor) => CalculationResultHandler.HandleResult(candidate, divisor, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP), threadCount, byDivisorPrimeRange); @@ -483,7 +483,7 @@ internal static class Program { p = buffer[j]; passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); - CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); + CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); } continue; @@ -506,7 +506,7 @@ internal static class Program } passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); - CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); + CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); if (p == maxP) { @@ -649,7 +649,7 @@ internal static class Program if (_cliArguments.UseByDivisor) { // Windowed pow2mod kernel handles by-divisor scans. - return _byDivisorTester!.IsPrime(p, out detailedCheck); + return _byDivisorTester!.IsPrime(p, out detailedCheck, out _); } detailedCheck = MersenneTesters.Value!.IsMersennePrime(p); diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs index ae8f317..b360c46 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs @@ -11,7 +11,7 @@ public class MersenneNumberDivisorByDivisorTesterTests { var candidates = new List { 5UL, 7UL, 11UL, 13UL }; var tester = new FakeTester(); - var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed)>(); + var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed, ulong Divisor)>(); int compositeMarks = 0; int clearedMarks = 0; @@ -22,7 +22,7 @@ public class MersenneNumberDivisorByDivisorTesterTests startPrime: 0UL, markComposite: () => compositeMarks++, clearComposite: () => clearedMarks++, - printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)), + printResult: (prime, searched, detailed, passed, divisor) => results.Add((prime, searched, detailed, passed, divisor)), threadCount: 1, primesPerTask: 2); @@ -32,10 +32,10 @@ public class MersenneNumberDivisorByDivisorTesterTests results.Should().Equal( new[] { - (5UL, true, true, true), - (7UL, true, true, false), - (11UL, true, true, false), - (13UL, true, true, true), + (5UL, true, true, true, 0UL), + (7UL, true, true, false, 7UL), + (11UL, true, true, false, 11UL), + (13UL, true, true, true, 0UL), }); } @@ -57,10 +57,23 @@ public class MersenneNumberDivisorByDivisorTesterTests return DivisorLimit; } - public bool IsPrime(ulong prime, out bool divisorsExhausted) + public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) { divisorsExhausted = true; - return prime % 7UL != 0UL && prime % 11UL != 0UL; + if (prime % 7UL == 0UL) + { + divisor = 7UL; + return false; + } + + if (prime % 11UL == 0UL) + { + divisor = 11UL; + return false; + } + + divisor = 0UL; + return true; } public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs index 0372959..87ff295 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs @@ -13,14 +13,17 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(11UL); - tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue(); + tester.IsPrime(5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); divisorsExhausted.Should().BeTrue(); + divisor.Should().Be(0UL); - tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue(); + tester.IsPrime(7UL, out divisorsExhausted, out divisor).Should().BeTrue(); divisorsExhausted.Should().BeTrue(); + divisor.Should().Be(0UL); - tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse(); + tester.IsPrime(11UL, out divisorsExhausted, out divisor).Should().BeFalse(); divisorsExhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); } [Fact] diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs index a3a62e2..707ec22 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs @@ -73,14 +73,17 @@ public class MersenneNumberDivisorGpuTesterTests var tester = new MersenneNumberDivisorByDivisorGpuTester(); tester.ConfigureFromMaxPrime(43UL); - tester.IsPrime(31UL, out bool exhausted).Should().BeTrue(); + tester.IsPrime(31UL, out bool exhausted, out ulong divisor).Should().BeTrue(); exhausted.Should().BeTrue(); + divisor.Should().Be(0UL); - tester.IsPrime(37UL, out exhausted).Should().BeFalse(); + tester.IsPrime(37UL, out exhausted, out divisor).Should().BeFalse(); exhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); - tester.IsPrime(43UL, out exhausted).Should().BeFalse(); + tester.IsPrime(43UL, out exhausted, out divisor).Should().BeFalse(); exhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); } [Fact] @@ -105,12 +108,15 @@ public class MersenneNumberDivisorGpuTesterTests var tester = new MersenneNumberDivisorByDivisorGpuTester(); tester.ConfigureFromMaxPrime(41UL); - tester.IsPrime(31UL, out _).Should().BeTrue(); - tester.IsPrime(37UL, out bool exhausted).Should().BeFalse(); + tester.IsPrime(31UL, out _, out ulong divisor).Should().BeTrue(); + divisor.Should().Be(0UL); + tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse(); exhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); - tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse(); + tester.IsPrime(41UL, out bool divisorsExhausted, out divisor).Should().BeFalse(); divisorsExhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); } @@ -124,8 +130,16 @@ public class MersenneNumberDivisorGpuTesterTests var tester = new MersenneNumberDivisorByDivisorGpuTester(); tester.ConfigureFromMaxPrime(exponent); - tester.IsPrime(exponent, out bool exhausted).Should().Be(expectedPrime); + tester.IsPrime(exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime); exhausted.Should().BeTrue(); + if (expectedPrime) + { + divisor.Should().Be(0UL); + } + else + { + divisor.Should().BeGreaterThan(0UL); + } } [Fact] [Trait("Category", "Fast")] @@ -138,8 +152,9 @@ public class MersenneNumberDivisorGpuTesterTests tester.ConfigureFromMaxPrime(43UL); - tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse(); + tester.IsPrime(41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse(); divisorsExhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); } [Fact] @@ -309,8 +324,9 @@ public class MersenneNumberDivisorGpuTesterTests .GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)! .SetValue(tester, 200UL); - tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue(); + tester.IsPrime(19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); divisorsExhausted.Should().BeTrue(); + divisor.Should().Be(0UL); } finally { diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index 483d373..49d90fb 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -42,7 +42,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit); - public bool IsPrime(ulong prime, out bool divisorsExhausted) + public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) { ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); @@ -60,7 +60,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv bool composite = CheckDivisors( prime, allowedMax, - out processedAll); + out processedAll, + out divisor); if (composite) { @@ -69,6 +70,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } divisorsExhausted = processedAll || composite; + divisor = 0UL; return true; } @@ -96,8 +98,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv private bool CheckDivisors( ulong prime, ulong allowedMax, - out bool processedAll) + out bool processedAll, + out ulong foundDivisor) { + foundDivisor = 0UL; // The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs. // Keeping the guard commented out documents the reasoning for benchmarks and tests. // if (allowedMax < 3UL) @@ -123,6 +127,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv if (divisor.CompareTo(limit) > 0) { processedAll = true; + foundDivisor = 0UL; return false; } @@ -182,7 +187,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv remainder3, remainder7, remainder11, - out processedAll); + out processedAll, + out foundDivisor); } ulong divisorHigh = divisor.High; @@ -240,6 +246,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { // A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides // the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p. + foundDivisor = candidate; processedAll = true; PrimeOrderCalculatorAccelerator.Return(gpu); return true; @@ -261,6 +268,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv PrimeOrderCalculatorAccelerator.Return(gpu); processedAll = true; + foundDivisor = 0UL; return false; } @@ -280,9 +288,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv byte remainder3, byte remainder7, byte remainder11, - out bool processedAll) + out bool processedAll, + out ulong foundDivisor) { processedAll = false; + foundDivisor = 0UL; bool canAdvance = step <= limit; var gpu = PrimeOrderCalculatorAccelerator.Rent(1); @@ -314,6 +324,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv if (divisorCycle == prime) { + foundDivisor = divisor; processedAll = true; PrimeOrderCalculatorAccelerator.Return(gpu); return true; @@ -327,12 +338,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } processedAll = true; + foundDivisor = 0UL; return false; } if (divisor > limit) { processedAll = true; + foundDivisor = 0UL; return false; } @@ -365,6 +378,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv if (divisorCycle == prime) { + foundDivisor = divisor; processedAll = true; PrimeOrderCalculatorAccelerator.Return(gpu); return true; @@ -380,6 +394,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv if (remainingIterations == 0UL) { processedAll = true; + foundDivisor = 0UL; return false; } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index fb01ae8..4e49e3c 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -70,7 +70,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; - public bool IsPrime(ulong prime, out bool divisorsExhausted) + public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) { ulong allowedMax; int batchCapacity; @@ -147,10 +147,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv if (composite) { divisorsExhausted = true; + divisor = lastProcessed; return false; } divisorsExhausted = coveredRange; + divisor = 0UL; return true; } @@ -933,4 +935,4 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv } IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); -} \ No newline at end of file +} diff --git a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs index e72255f..e93d1b5 100644 --- a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs @@ -10,7 +10,7 @@ public interface IMersenneNumberDivisorByDivisorTester ulong GetAllowedMaxDivisor(ulong prime); - bool IsPrime(ulong prime, out bool divisorsExhausted); + bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor); IDivisorScanSession CreateDivisorSession(); diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index c02f61d..728365d 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -14,7 +14,7 @@ public static class MersenneNumberDivisorByDivisorTester ulong startPrime, Action markComposite, Action clearComposite, - Action printResult, + Action printResult, int threadCount, int primesPerTask) { @@ -242,18 +242,18 @@ public static class MersenneNumberDivisorByDivisorTester void ProcessPrime(ulong prime) { Console.WriteLine($"Processing {prime}"); - bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted); + bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted, out ulong divisor); if (!isPrime) { markComposite(); - printResult(prime, true, true, false); + printResult(prime, true, true, false, divisor); Console.WriteLine($"Finished processing {prime}"); return; } clearComposite(); - printResult(prime, true, divisorsExhausted, true); + printResult(prime, true, divisorsExhausted, true, divisor); Console.WriteLine($"Finished processing {prime}"); }