From e8d163f595e09ceaf02483e2ffb404944ee04ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Tue, 28 Oct 2025 23:22:16 +0100 Subject: [PATCH] Add three-A-two-B combined divisor schedule --- .../HeuristicCombinedPrimeTesterTests.cs | 51 +++++++++++++++++-- .../HeuristicCombinedPrimeTester.cs | 50 +++++++++++++++--- 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/PerfectNumbers.Core.Tests/HeuristicCombinedPrimeTesterTests.cs b/PerfectNumbers.Core.Tests/HeuristicCombinedPrimeTesterTests.cs index d6bdd28..a9e94a3 100644 --- a/PerfectNumbers.Core.Tests/HeuristicCombinedPrimeTesterTests.cs +++ b/PerfectNumbers.Core.Tests/HeuristicCombinedPrimeTesterTests.cs @@ -13,7 +13,7 @@ public class HeuristicCombinedPrimeTesterTests [InlineData((byte)1)] [InlineData((byte)7)] [InlineData((byte)9)] - public void Enumerator_uses_three_a_one_b_schedule_by_default(byte lastDigit) + public void Enumerator_uses_three_a_two_b_schedule_by_default(byte lastDigit) { Span groupBState = stackalloc HeuristicCombinedPrimeTester.HeuristicGroupBSequenceState[4]; @@ -49,10 +49,51 @@ public class HeuristicCombinedPrimeTesterTests groupACandidate.Group.Should().NotBe(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB); } - HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupBCandidate = results[index++]; - groupBCandidate.Value.Should().BeGreaterThan(13UL); - (groupBCandidate.Value % 10UL).Should().NotBe(3UL); - groupBCandidate.Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB); + for (int i = 0; i < 2; i++) + { + HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupBCandidate = results[index++]; + groupBCandidate.Value.Should().BeGreaterThan(13UL); + (groupBCandidate.Value % 10UL).Should().NotBe(3UL); + groupBCandidate.Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB); + } + } + } + + [Theory] + [Trait("Category", "Fast")] + [InlineData((byte)1)] + [InlineData((byte)7)] + [InlineData((byte)9)] + public void Three_a_two_b_pattern_interleaves_three_group_a_values_before_two_group_b_values(byte lastDigit) + { + ReadOnlySpan combined = HeuristicCombinedPrimeTester.GetCombinedDivisors( + lastDigit, + HeuristicCombinedPrimeTester.CombinedDivisorPattern.ThreeATwoB); + + combined.Length.Should().BeGreaterThan(28); + combined[0].Should().Be(3U); + combined[1].Should().Be(7U); + combined[2].Should().Be(11U); + combined[3].Should().Be(13U); + + int index = 4; + const int segmentsToCheck = 4; + + for (int segment = 0; segment < segmentsToCheck; segment++) + { + for (int i = 0; i < 3; i++) + { + uint groupAValue = combined[index++]; + groupAValue.Should().BeGreaterThan(13U); + (groupAValue % 10U).Should().Be(3U); + } + + for (int i = 0; i < 2; i++) + { + uint groupBValue = combined[index++]; + groupBValue.Should().BeGreaterThan(13U); + (groupBValue % 10U).Should().NotBe(3U); + } } } diff --git a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs index 4a26976..2ab9447 100644 --- a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs @@ -39,6 +39,11 @@ public sealed class HeuristicCombinedPrimeTester private static readonly Lazy CombinedDivisorsEnding7ThreeAOneB = new(() => BuildCombinedDivisors(7, CombinedDivisorPattern.ThreeAOneB), LazyThreadSafetyMode.ExecutionAndPublication); private static readonly Lazy CombinedDivisorsEnding9ThreeAOneB = new(() => BuildCombinedDivisors(9, CombinedDivisorPattern.ThreeAOneB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy CombinedDivisorsEnding1ThreeATwoB = new(() => BuildCombinedDivisors(1, CombinedDivisorPattern.ThreeATwoB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy CombinedDivisorsEnding3ThreeATwoB = new(() => BuildCombinedDivisors(3, CombinedDivisorPattern.ThreeATwoB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy CombinedDivisorsEnding7ThreeATwoB = new(() => BuildCombinedDivisors(7, CombinedDivisorPattern.ThreeATwoB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy CombinedDivisorsEnding9ThreeATwoB = new(() => BuildCombinedDivisors(9, CombinedDivisorPattern.ThreeATwoB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); internal enum CombinedDivisorPattern : byte @@ -46,6 +51,7 @@ public sealed class HeuristicCombinedPrimeTester TwoAOneB = 0, OneAOneB = 1, ThreeAOneB = 2, + ThreeATwoB = 3, } internal enum HeuristicDivisorGroup : byte @@ -404,7 +410,7 @@ Cleanup: private static ReadOnlySpan GetCombinedDivisors(byte nMod10) { - return GetCombinedDivisors(nMod10, CombinedDivisorPattern.ThreeAOneB); + return GetCombinedDivisors(nMod10, CombinedDivisorPattern.ThreeATwoB); } internal static ReadOnlySpan GetCombinedDivisors(byte nMod10, CombinedDivisorPattern pattern) @@ -435,6 +441,14 @@ Cleanup: 9 => CombinedDivisorsEnding9ThreeAOneB.Value, _ => ReadOnlySpan.Empty, }, + CombinedDivisorPattern.ThreeATwoB => nMod10 switch + { + 1 => CombinedDivisorsEnding1ThreeATwoB.Value, + 3 => CombinedDivisorsEnding3ThreeATwoB.Value, + 7 => CombinedDivisorsEnding7ThreeATwoB.Value, + 9 => CombinedDivisorsEnding9ThreeATwoB.Value, + _ => ReadOnlySpan.Empty, + }, _ => ReadOnlySpan.Empty, }; } @@ -454,13 +468,31 @@ Cleanup: } int groupBIndex = GetGroupBStartIndex(groupB); - int groupAInterleaveCount = pattern switch + int groupAInterleaveCount; + int groupBInterleaveCount; + switch (pattern) { - CombinedDivisorPattern.OneAOneB => 1, - CombinedDivisorPattern.TwoAOneB => 2, - CombinedDivisorPattern.ThreeAOneB => 3, - _ => 2, - }; + case CombinedDivisorPattern.OneAOneB: + groupAInterleaveCount = 1; + groupBInterleaveCount = 1; + break; + case CombinedDivisorPattern.TwoAOneB: + groupAInterleaveCount = 2; + groupBInterleaveCount = 1; + break; + case CombinedDivisorPattern.ThreeAOneB: + groupAInterleaveCount = 3; + groupBInterleaveCount = 1; + break; + case CombinedDivisorPattern.ThreeATwoB: + groupAInterleaveCount = 3; + groupBInterleaveCount = 2; + break; + default: + groupAInterleaveCount = 2; + groupBInterleaveCount = 1; + break; + } while (groupAIndex < groupA.Length || groupBIndex < groupB.Length) { @@ -472,10 +504,12 @@ Cleanup: addedA++; } - if (groupBIndex < groupB.Length) + int addedB = 0; + while (addedB < groupBInterleaveCount && groupBIndex < groupB.Length) { combined.Add(groupB[groupBIndex]); groupBIndex++; + addedB++; } }