Merge pull request #38 from mlemanczyk/codex/fix-residue-method-iteration-issue

Fix residue iteration with filtered primes
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-09-20 05:50:00 +02:00
commit afcc8d87f9
2 files changed
+103 -9

No files matched your search

@@ -0,0 +1,77 @@
using System;
using System.Threading;
using EvenPerfectBitScanner;
using PerfectNumbers.Core;
using Xunit;
namespace EvenPerfectBitScanner.Tests;
public class ResidueCandidateEnumerationTests
{
[Fact]
public void ModResidueTracker_does_not_flag_small_primes_as_composite()
{
var tracker = new ModResidueTracker(ResidueModel.Identity, initialNumber: 13UL, initialized: true);
var primes = PrimesGenerator.SmallPrimes;
var primesPow2 = PrimesGenerator.SmallPrimesPow2;
ulong p = 13UL;
ulong remainder = p % 6UL;
for (int i = 0; i < 2000; i++)
{
tracker.BeginMerge(p);
bool composite = false;
for (int j = 0; j < primes.Length; j++)
{
if (primesPow2[j] > p)
{
break;
}
tracker.MergeOrAppend(p, primes[j], out bool divisible);
if (divisible)
{
composite = true;
break;
}
}
bool isPrime = PrimeTester.IsPrimeInternal(p, CancellationToken.None);
if (isPrime && composite)
{
throw new InvalidOperationException($"Prime {p} marked as composite.");
}
p = Program.TransformPAdd(p, ref remainder);
}
}
[Fact]
public void TransformPAdd_visits_both_prime_residue_classes()
{
ulong p = 13UL;
ulong remainder = p % 6UL;
bool sawOneModSix = false;
bool sawFiveModSix = false;
for (int i = 0; i < 256; i++)
{
ulong residue = p % 6UL;
if (residue == 1UL)
{
sawOneModSix = true;
}
else if (residue == 5UL)
{
sawFiveModSix = true;
}
p = Program.TransformPAdd(p, ref remainder);
}
Assert.True(sawOneModSix, "Sequence never produced numbers congruent to 1 mod 6.");
Assert.True(sawFiveModSix, "Sequence never produced numbers congruent to 5 mod 6.");
}
}
+26 -9
View File
@@ -554,6 +554,12 @@ internal static class Program
}
bool useFilter = !string.IsNullOrEmpty(filterFile);
if (useFilter && !useBitTransform)
{
// When replaying a filtered list of p values, iterate strictly along the prime sequence.
// This keeps candidate generation aligned with the recorded primes regardless of residue stepping.
_transformP = &TransformPAddPrimes;
}
HashSet<ulong> filter = [];
ulong maxP = 0UL;
if (useFilter)
@@ -950,9 +956,11 @@ internal static class Program
}
ulong next = (value << 1) | 1UL;
remainder = (remainder << 1) + 1UL;
remainder -= (remainder >= 6UL) ? 6UL : 0UL;
remainder -= (remainder >= 6UL) ? 6UL : 0UL;
remainder = (remainder << 1) + 1UL;
while (remainder >= 6UL)
{
remainder -= 6UL;
}
value = remainder switch
{
@@ -970,8 +978,11 @@ internal static class Program
return original;
}
remainder += value;
remainder -= (remainder >= 6UL) ? 6UL : 0UL;
remainder += value;
while (remainder >= 6UL)
{
remainder -= 6UL;
}
return next + value;
}
@@ -988,8 +999,11 @@ internal static class Program
return next;
}
remainder += diff;
remainder -= (remainder >= 6UL) ? 6UL : 0UL;
remainder += diff;
while (remainder >= 6UL)
{
remainder -= 6UL;
}
return next + diff;
}
@@ -1017,8 +1031,11 @@ internal static class Program
}
candidate += diff;
addRemainder += diff;
addRemainder -= (addRemainder >= 6UL) ? 6UL : 0UL;
addRemainder += diff;
while (addRemainder >= 6UL)
{
addRemainder -= 6UL;
}
}
if (advancePrime)