From f2070a0f64f3e22086e7ce172801d0bfc98ec2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 22 Oct 2025 00:56:17 +0200 Subject: [PATCH] Document CPU sieve invariants and cache boolean guards --- .../PrimeTester/PrimeTesterIsPrimeTests.cs | 2 + PerfectNumbers.Core/PrimeTester.cs | 38 +++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs b/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs index 278eccf..51e76c4 100644 --- a/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs +++ b/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs @@ -8,6 +8,8 @@ public class PrimeTesterIsPrimeTests { [Theory] [Trait("Category", "Fast")] + // EvenPerfectBitScanner never routes these small values, but factoring helpers and residue checks still call into + // PrimeTester for sanity probes, so the regression suite keeps the tiny cases alongside large primes/composites. [InlineData(1UL, false)] [InlineData(2UL, true)] [InlineData(3UL, true)] diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 0abb6fc..15af30e 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -69,28 +69,24 @@ public sealed class PrimeTester(bool useInternal = false) internal static bool IsPrimeInternal(ulong n, CancellationToken ct) { - bool result = true; - if (n <= 3UL) + // EvenPerfectBitScanner streams monotonically increasing odd exponents that start at 136,279,841 and already exclude + // multiples of five. Factorization helpers (PrimeOrderCalculator, Pollard routines, and divisor-cycle warmups) reuse + // this path for arbitrary residues and cofactors, so keep the boolean guards even though the scanner never trips them. + bool isTwo = n == 2UL; + bool isOdd = (n & 1UL) != 0UL; + // TODO: Replace this modulo check with ULongExtensions.Mod5 so the CPU hot path reuses the benchmarked helper instead of `%`. + ulong mod5 = n % 5UL; + bool divisibleByFive = n > 5UL && mod5 == 0UL; + + bool result = n >= 2UL && (isTwo || isOdd) && !divisibleByFive; + bool requiresTrialDivision = result && n >= 7UL && !isTwo; + + if (requiresTrialDivision) { - result = n >= 2UL; - } - else if ((n & 1UL) == 0) - { - result = false; - } - else if (n > 5UL && (n % 5UL) == 0UL) - { - // TODO: Replace this modulo check with ULongExtensions.Mod5 so the CPU hot path - // reuses the benchmarked helper instead of `%` when sieving large prime ranges. - result = false; - } - else - { - if (n.Mod10() == 1UL && SharesFactorWithMaxExponent(n)) - { - result = false; - } - else + bool sharesMaxExponentFactor = n.Mod10() == 1UL && SharesFactorWithMaxExponent(n); + result &= !sharesMaxExponentFactor; + + if (result) { var smallPrimeDivisorsLength = PrimesGenerator.SmallPrimes.Length; uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes;