Compact by-divisor candidate filtering

This commit is contained in:
Marek Lemańczyk committed 2025-10-08 23:34:35 +02:00
1 parent 4ab70ec4a8
commit adac9ff5f9
3 files changed
+115 -25

No files matched your search

+6 -4
View File
@@ -322,7 +322,7 @@ public class MersenneDivisorCycles
factorCounts[2UL] = twoCount;
}
if (!AccumulateFactors(exponent, factorCache, factorCounts) || !AccumulateFactors(k, factorCache, factorCounts))
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
{
return false;
}
@@ -334,14 +334,15 @@ public class MersenneDivisorCycles
private static bool AccumulateFactors(
ulong value,
Dictionary<ulong, FactorCacheEntry>? cache,
Dictionary<ulong, int> counts)
Dictionary<ulong, int> counts,
bool cacheResult)
{
if (value <= 1UL)
{
return true;
}
if (!TryGetFactorization(value, cache, out FactorCacheEntry factorization))
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry factorization))
{
return false;
}
@@ -361,6 +362,7 @@ public class MersenneDivisorCycles
private static bool TryGetFactorization(
ulong value,
Dictionary<ulong, FactorCacheEntry>? cache,
bool cacheResult,
out FactorCacheEntry factorization)
{
if (cache is not null && cache.TryGetValue(value, out factorization))
@@ -389,7 +391,7 @@ public class MersenneDivisorCycles
Array.Sort(primes, exponents);
factorization = new FactorCacheEntry(primes, exponents, count);
if (cache is not null)
if (cache is not null && cacheResult)
{
cache[value] = factorization;
}
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Open.Numeric.Primes;
using System.Runtime.InteropServices;
namespace PerfectNumbers.Core;
@@ -27,29 +28,97 @@ public static class MersenneNumberDivisorByDivisorTester
_ = threadCount;
candidates.Sort();
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
int candidateCount = candidateSpan.Length;
bool applyStartPrime = startPrime > 0UL;
int skippedByPreviousResults = 0;
List<ulong> primesToTest = new(candidates.Count);
if (previousResults is not null && previousResults.Count > 0)
{
int recordedCount = previousResults.Count;
ulong[] recordedCandidates = ArrayPool<ulong>.Shared.Rent(recordedCount);
try
{
Span<ulong> recordedSpan = recordedCandidates.AsSpan(0, recordedCount);
int recordedIndex = 0;
foreach (ulong value in previousResults.Keys)
{
recordedSpan[recordedIndex++] = value;
}
recordedSpan.Sort();
int writeIndex = 0;
int skipIndex = 0;
for (int readIndex = 0; readIndex < candidateCount; readIndex++)
{
ulong candidate = candidateSpan[readIndex];
while (skipIndex < recordedCount && recordedSpan[skipIndex] < candidate)
{
skipIndex++;
}
if (skipIndex < recordedCount && recordedSpan[skipIndex] == candidate)
{
skippedByPreviousResults++;
skipIndex++;
continue;
}
candidateSpan[writeIndex++] = candidate;
}
if (writeIndex < candidateCount)
{
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
candidateSpan = CollectionsMarshal.AsSpan(candidates);
candidateCount = candidateSpan.Length;
}
}
finally
{
ArrayPool<ulong>.Shared.Return(recordedCandidates, clearArray: true);
}
}
if (skippedByPreviousResults > 0)
{
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
}
int startIndex = 0;
if (applyStartPrime)
{
startIndex = candidates.BinarySearch(startPrime);
if (startIndex < 0)
{
startIndex = ~startIndex;
}
else
{
while (startIndex > 0 && candidates[startIndex - 1] >= startPrime)
{
startIndex--;
}
}
}
List<ulong> primesToTest = candidates;
Span<ulong> primesSpan = CollectionsMarshal.AsSpan(primesToTest);
ulong maxPrime = 0UL;
int primeWriteIndex = 0;
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
bool hasPrime = primeEnumerator.MoveNext();
ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
for (int index = 0; index < candidates.Count; index++)
for (int index = startIndex; index < candidateCount; index++)
{
ulong candidate = candidates[index];
if (applyStartPrime && candidate < startPrime)
{
continue;
}
if (previousResults is not null && previousResults.ContainsKey(candidate))
{
skippedByPreviousResults++;
continue;
}
ulong candidate = primesSpan[index];
if (candidate <= 1UL)
{
@@ -76,17 +145,17 @@ public static class MersenneNumberDivisorByDivisorTester
continue;
}
primesSpan[primeWriteIndex++] = candidate;
if (candidate > maxPrime)
{
maxPrime = candidate;
}
primesToTest.Add(candidate);
}
if (skippedByPreviousResults > 0)
if (primeWriteIndex < primesToTest.Count)
{
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
primesToTest.RemoveRange(primeWriteIndex, primesToTest.Count - primeWriteIndex);
}
if (primesToTest.Count == 0)
+22 -3
View File
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -50,8 +50,27 @@ public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong
internal static class MontgomeryDivisorDataCache
{
private static readonly ConcurrentDictionary<ulong, MontgomeryDivisorData> Cache = new();
[ThreadStatic]
private static bool s_hasCachedValue;
[ThreadStatic]
private static ulong s_cachedModulus;
[ThreadStatic]
private static MontgomeryDivisorData s_cachedData;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MontgomeryDivisorData Get(ulong modulus) => Cache.GetOrAdd(modulus, static m => MontgomeryDivisorData.FromModulus(m));
public static MontgomeryDivisorData Get(ulong modulus)
{
if (s_hasCachedValue && s_cachedModulus == modulus)
{
return s_cachedData;
}
MontgomeryDivisorData computed = MontgomeryDivisorData.FromModulus(modulus);
s_cachedModulus = modulus;
s_cachedData = computed;
s_hasCachedValue = true;
return computed;
}
}