Support for q > 2^64 added in --mersenne=bydivisor mode

This commit is contained in:
mlemanczyk committed 2025-12-05 23:33:21 +01:00
1 parent cf5bd6bc1a
commit 86f20ed083
11 files changed
+412 -92

No files matched your search

@@ -1,4 +1,7 @@
using System;
using System.Buffers.Text; using System.Buffers.Text;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using PerfectNumbers.Core; using PerfectNumbers.Core;
@@ -93,19 +96,46 @@ internal static class CalculationResultHandler
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void HandleResult(ulong currentP, BigInteger divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
{
Span<char> divisorBuffer = stackalloc char[256];
if (!divisor.TryFormat(divisorBuffer, out int divisorChars, provider: CultureInfo.InvariantCulture))
{
string divisorString = divisor.ToString(CultureInfo.InvariantCulture);
HandleResultInternal(currentP, divisorString.AsSpan(), searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
return;
}
HandleResultInternal(currentP, divisorBuffer[..divisorChars], searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
}
internal static void HandleResult(ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite) internal static void HandleResult(ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
{
Span<char> divisorBuffer = stackalloc char[32];
if (!divisor.TryFormat(divisorBuffer, out int divisorChars, provider: CultureInfo.InvariantCulture))
{
string divisorString = divisor.ToString(CultureInfo.InvariantCulture);
HandleResultInternal(currentP, divisorString.AsSpan(), searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
return;
}
HandleResultInternal(currentP, divisorBuffer[..divisorChars], searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
}
private static void HandleResultInternal(ulong currentP, ReadOnlySpan<char> divisorText, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
{ {
// Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here. // Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
Span<byte> stackBuffer = stackalloc byte[128]; Span<byte> stackBuffer = stackalloc byte[256];
byte[]? rentedBuffer = null; byte[]? rentedBuffer = null;
Span<byte> utf8Span = stackBuffer; Span<byte> utf8Span = stackBuffer;
int byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests); int byteCount = TryFormatResultLine(utf8Span, currentP, divisorText, searchedMersenne, detailedCheck, passedAllTests);
if (byteCount < 0) if (byteCount < 0)
{ {
rentedBuffer = ThreadStaticPools.BytePool.Rent(256); int minimumSize = Math.Max((divisorText.Length + 5) * 4, 256);
rentedBuffer = ThreadStaticPools.BytePool.Rent(minimumSize);
utf8Span = rentedBuffer; utf8Span = rentedBuffer;
byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests); byteCount = TryFormatResultLine(utf8Span, currentP, divisorText, searchedMersenne, detailedCheck, passedAllTests);
if (byteCount < 0) if (byteCount < 0)
{ {
ThreadStaticPools.BytePool.Return(rentedBuffer); ThreadStaticPools.BytePool.Return(rentedBuffer);
@@ -114,7 +144,7 @@ internal static class CalculationResultHandler
} }
ReadOnlySpan<byte> utf8Line = utf8Span[..byteCount]; ReadOnlySpan<byte> utf8Line = utf8Span[..byteCount];
Span<char> charBuffer = stackalloc char[128]; Span<char> charBuffer = stackalloc char[256];
int charCount = Utf8Encoding.GetChars(utf8Line, charBuffer); int charCount = Utf8Encoding.GetChars(utf8Line, charBuffer);
ReadOnlySpan<char> recordSpan = charBuffer[..charCount]; ReadOnlySpan<char> recordSpan = charBuffer[..charCount];
@@ -229,7 +259,7 @@ internal static class CalculationResultHandler
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests) private static int TryFormatResultLine(Span<byte> destination, ulong currentP, ReadOnlySpan<char> divisorText, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
{ {
int offset = 0; int offset = 0;
@@ -246,16 +276,14 @@ internal static class CalculationResultHandler
destination[offset++] = (byte)','; destination[offset++] = (byte)',';
if (!Utf8Formatter.TryFormat(divisor, destination[offset..], out written)) int divisorByteCount = Utf8Encoding.GetByteCount(divisorText);
if (offset + divisorByteCount >= destination.Length)
{ {
return -1; return -1;
} }
offset += written; divisorByteCount = Utf8Encoding.GetBytes(divisorText, destination[offset..]);
if (offset >= destination.Length) offset += divisorByteCount;
{
return -1;
}
destination[offset++] = (byte)','; destination[offset++] = (byte)',';
+22 -4
View File
@@ -1,4 +1,5 @@
using System.Globalization; using System.Globalization;
using System.Numerics;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -50,7 +51,7 @@ internal readonly struct CliArguments
internal readonly double ZeroFractionHard; internal readonly double ZeroFractionHard;
internal readonly double ZeroFractionConjecture; internal readonly double ZeroFractionConjecture;
internal readonly int MaxZeroConjecture; internal readonly int MaxZeroConjecture;
internal readonly ulong MinK; internal readonly BigInteger MinK;
internal readonly int GpuRatio; internal readonly int GpuRatio;
private CliArguments( private CliArguments(
@@ -96,7 +97,7 @@ internal readonly struct CliArguments
double zeroFractionHard, double zeroFractionHard,
double zeroFractionConjecture, double zeroFractionConjecture,
int maxZeroConjecture, int maxZeroConjecture,
ulong minK, BigInteger minK,
int gpuRatio) int gpuRatio)
{ {
StartPrime = startPrime; StartPrime = startPrime;
@@ -191,7 +192,7 @@ internal readonly struct CliArguments
double zeroFractionHard = -1.0; double zeroFractionHard = -1.0;
double zeroFractionConjecture = -1.0; double zeroFractionConjecture = -1.0;
int maxZeroConjecture = -1; int maxZeroConjecture = -1;
ulong minK = 1UL; BigInteger minK = BigInteger.One;
int gpuRatio = PerfectNumberConstants.GpuRatio; int gpuRatio = PerfectNumberConstants.GpuRatio;
foreach (string argument in args) foreach (string argument in args)
@@ -314,15 +315,32 @@ internal readonly struct CliArguments
if (argument.StartsWith("--min-k=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--min-k=", StringComparison.OrdinalIgnoreCase))
{ {
if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--min-k=".Length), out ulong parsedMinK) && parsedMinK > 0UL) ReadOnlySpan<char> minKSpan = argument.AsSpan("--min-k=".Length);
if (Utf8CliParser.TryParseUInt64(minKSpan, out ulong parsedMinK) && parsedMinK > 0UL)
{ {
minK = parsedMinK; minK = parsedMinK;
} }
else else
{
try
{
BigInteger parsedMinKBig = BigInteger.Parse(minKSpan, CultureInfo.InvariantCulture);
if (parsedMinKBig > BigInteger.Zero)
{
minK = parsedMinKBig;
}
else
{ {
Console.WriteLine("Invalid value for --min-k."); Console.WriteLine("Invalid value for --min-k.");
return default; return default;
} }
}
catch
{
Console.WriteLine("Invalid value for --min-k.");
return default;
}
}
continue; continue;
} }
+6
View File
@@ -1,6 +1,7 @@
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using EvenPerfectBitScanner.Candidates; using EvenPerfectBitScanner.Candidates;
using EvenPerfectBitScanner.IO; using EvenPerfectBitScanner.IO;
@@ -241,6 +242,11 @@ internal static class Program
: new MersenneNumberDivisorByDivisorCpuTester(orderDevice); : new MersenneNumberDivisorByDivisorCpuTester(orderDevice);
_byDivisorTester.MinK = _cliArguments.MinK; _byDivisorTester.MinK = _cliArguments.MinK;
_byDivisorTester.BatchSize = scanBatchSize; _byDivisorTester.BatchSize = scanBatchSize;
if (_byDivisorTester is MersenneNumberDivisorByDivisorGpuTester && _cliArguments.MinK > ulong.MaxValue)
{
Console.WriteLine("--min-k values above UInt64 are only supported on the CPU path for --mersenne=bydivisor.");
return;
}
} }
// Load RLE blacklist (optional) // Load RLE blacklist (optional)
@@ -1,3 +1,4 @@
using System.Numerics;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit; using Xunit;
@@ -12,7 +13,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
{ {
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL }; var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
var tester = new FakeTester(); var tester = new FakeTester();
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed, ulong Divisor)>(); var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed, BigInteger Divisor)>();
int compositeMarks = 0; int compositeMarks = 0;
int clearedMarks = 0; int clearedMarks = 0;
@@ -31,12 +32,12 @@ public class MersenneNumberDivisorByDivisorTesterTests
compositeMarks.Should().Be(2); compositeMarks.Should().Be(2);
clearedMarks.Should().Be(2); clearedMarks.Should().Be(2);
results.Should().Equal( results.Should().Equal(
new[] new (ulong Prime, bool Searched, bool Detailed, bool Passed, BigInteger Divisor)[]
{ {
(5UL, true, true, true, 0UL), (5UL, true, true, true, BigInteger.Zero),
(7UL, true, true, false, 7UL), (7UL, true, true, false, new BigInteger(7)),
(11UL, true, true, false, 11UL), (11UL, true, true, false, new BigInteger(11)),
(13UL, true, true, true, 0UL), (13UL, true, true, true, BigInteger.Zero),
}); });
} }
@@ -46,7 +47,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
public int BatchSize { get; set; } public int BatchSize { get; set; }
public ulong MinK { get; set; } public BigInteger MinK { get; set; }
public string? StateFilePath { get; set; } public string? StateFilePath { get; set; }
@@ -54,7 +55,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
{ {
} }
public void ResumeFromState(ulong lastSavedK) public void ResumeFromState(BigInteger lastSavedK)
{ {
} }
@@ -70,7 +71,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
return DivisorLimit; return DivisorLimit;
} }
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor) public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out BigInteger divisor)
{ {
divisorsExhausted = true; divisorsExhausted = true;
if (prime % 7UL == 0UL) if (prime % 7UL == 0UL)
@@ -1,3 +1,4 @@
using System.Numerics;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.Gpu.Accelerators;
@@ -17,17 +18,17 @@ public class MersenneNumberDivisorCpuTesterTests
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try try
{ {
tester.IsPrime(gpu, 5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); tester.IsPrime(gpu, 5UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL); divisor.Should().Be(BigInteger.Zero);
tester.IsPrime(gpu, 7UL, out divisorsExhausted, out divisor).Should().BeTrue(); tester.IsPrime(gpu, 7UL, out divisorsExhausted, out divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL); divisor.Should().Be(BigInteger.Zero);
tester.IsPrime(gpu, 11UL, out divisorsExhausted, out divisor).Should().BeFalse(); tester.IsPrime(gpu, 11UL, out divisorsExhausted, out divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL); divisor.Should().BeGreaterThan(BigInteger.Zero);
} }
finally finally
{ {
@@ -1,3 +1,4 @@
using System.Numerics;
using System.Reflection; using System.Reflection;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
@@ -102,17 +103,17 @@ public class MersenneNumberDivisorGpuTesterTests
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try try
{ {
tester.IsPrime(gpu, 31UL, out bool exhausted, out ulong divisor).Should().BeTrue(); tester.IsPrime(gpu, 31UL, out bool exhausted, out BigInteger divisor).Should().BeTrue();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().Be(0UL); divisor.Should().Be(BigInteger.Zero);
tester.IsPrime(gpu, 37UL, out exhausted, out divisor).Should().BeFalse(); tester.IsPrime(gpu, 37UL, out exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL); divisor.Should().BeGreaterThan(BigInteger.Zero);
tester.IsPrime(gpu, 43UL, out exhausted, out divisor).Should().BeFalse(); tester.IsPrime(gpu, 43UL, out exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL); divisor.Should().BeGreaterThan(BigInteger.Zero);
} }
finally finally
{ {
@@ -145,15 +146,15 @@ public class MersenneNumberDivisorGpuTesterTests
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try try
{ {
tester.IsPrime(gpu, 31UL, out _, out ulong divisor).Should().BeTrue(); tester.IsPrime(gpu, 31UL, out _, out BigInteger divisor).Should().BeTrue();
divisor.Should().Be(0UL); divisor.Should().Be(BigInteger.Zero);
tester.IsPrime(gpu, 37UL, out bool exhausted, out divisor).Should().BeFalse(); tester.IsPrime(gpu, 37UL, out bool exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL); divisor.Should().BeGreaterThan(BigInteger.Zero);
tester.IsPrime(gpu, 41UL, out bool divisorsExhausted, out divisor).Should().BeFalse(); tester.IsPrime(gpu, 41UL, out bool divisorsExhausted, out divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL); divisor.Should().BeGreaterThan(BigInteger.Zero);
} }
finally finally
{ {
@@ -175,16 +176,16 @@ public class MersenneNumberDivisorGpuTesterTests
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try try
{ {
tester.IsPrime(gpu, exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime); tester.IsPrime(gpu, exponent, out bool exhausted, out BigInteger divisor).Should().Be(expectedPrime);
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
if (expectedPrime) if (expectedPrime)
{ {
divisor.Should().Be(0UL); divisor.Should().Be(BigInteger.Zero);
} }
else else
{ {
divisor.Should().BeGreaterThan(0UL); divisor.Should().BeGreaterThan(BigInteger.Zero);
} }
} }
finally finally
@@ -207,9 +208,9 @@ public class MersenneNumberDivisorGpuTesterTests
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try try
{ {
tester.IsPrime(gpu, 41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse(); tester.IsPrime(gpu, 41UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL); divisor.Should().BeGreaterThan(BigInteger.Zero);
} }
finally finally
{ {
@@ -397,9 +398,9 @@ public class MersenneNumberDivisorGpuTesterTests
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)! .GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(tester, 200UL); .SetValue(tester, 200UL);
tester.IsPrime(gpu, 19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); tester.IsPrime(gpu, 19UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL); divisor.Should().Be(BigInteger.Zero);
} }
finally finally
{ {
@@ -1,3 +1,4 @@
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@@ -8,15 +9,15 @@ namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
{ {
private ulong _divisorLimit; private BigInteger _divisorLimit;
private int _batchSize = 1_024; private int _batchSize = 1_024;
private ulong _minK = 1UL; private BigInteger _minK = BigInteger.One;
private readonly ComputationDevice _orderDevice; private readonly ComputationDevice _orderDevice;
private readonly TryCycleLengthDelegate _tryCalculateCycleLengthForExponent; private readonly TryCycleLengthDelegate _tryCalculateCycleLengthForExponent;
private readonly CalculateCycleLengthDelegate _calculateCycleLength; private readonly CalculateCycleLengthDelegate _calculateCycleLength;
private string? _stateFilePath; private string? _stateFilePath;
private int _stateCounter; private int _stateCounter;
private ulong _lastSavedK; private BigInteger _lastSavedK;
public ComputationDevice OrderDevice => _orderDevice; public ComputationDevice OrderDevice => _orderDevice;
private delegate bool TryCycleLengthDelegate( private delegate bool TryCycleLengthDelegate(
@@ -67,10 +68,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
set => _batchSize = Math.Max(1, value); set => _batchSize = Math.Max(1, value);
} }
public ulong MinK public BigInteger MinK
{ {
get => _minK; get => _minK;
set => _minK = value < 1UL ? 1UL : value; set => _minK = value < BigInteger.One ? BigInteger.One : value;
} }
public string? StateFilePath public string? StateFilePath
@@ -84,16 +85,16 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
_stateCounter = 0; _stateCounter = 0;
} }
public void ResumeFromState(ulong lastSavedK) public void ResumeFromState(BigInteger lastSavedK)
{ {
_lastSavedK = lastSavedK; _lastSavedK = lastSavedK;
_minK = lastSavedK + 1UL; _minK = lastSavedK + BigInteger.One;
_stateCounter = 0; _stateCounter = 0;
} }
public void ConfigureFromMaxPrime(ulong maxPrime) public void ConfigureFromMaxPrime(ulong maxPrime)
{ {
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); _divisorLimit = ComputeDivisorLimitFromMaxPrimeBig(maxPrime);
} }
public ulong DivisorLimit public ulong DivisorLimit
@@ -101,16 +102,21 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
return _divisorLimit; if (_divisorLimit.IsZero)
{
return ulong.MaxValue;
}
return _divisorLimit > ulong.MaxValue ? ulong.MaxValue : (ulong)_divisorLimit;
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit); public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor) public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out BigInteger divisor)
{ {
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); BigInteger allowedMax = ComputeAllowedMaxDivisorBig(prime, _divisorLimit);
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes. // The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
// if (allowedMax < 3UL) // if (allowedMax < 3UL)
@@ -138,13 +144,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
divisorsExhausted = processedAll || composite; divisorsExhausted = processedAll || composite;
divisor = 0UL; divisor = BigInteger.Zero;
return true; return true;
} }
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues) public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{ {
ulong divisorLimit = _divisorLimit; ulong divisorLimit = GetAllowedDivisorLimitForSpan(_divisorLimit);
int length = primes.Length; int length = primes.Length;
for (int index = 0; index < length; index++) for (int index = 0; index < length; index++)
{ {
@@ -166,6 +172,73 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
private bool CheckDivisors( private bool CheckDivisors(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
BigInteger allowedMax,
BigInteger minK,
out bool processedAll,
out BigInteger foundDivisor)
{
BigInteger normalizedMinK = minK < BigInteger.One ? BigInteger.One : minK;
BigInteger step = ((BigInteger)prime) << 1;
BigInteger firstDivisor = (step * normalizedMinK) + BigInteger.One;
bool unlimited = allowedMax.IsZero;
bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue;
bool limitFits64 = unlimited || allowedMax <= ulong.MaxValue;
if (fits64 && limitFits64)
{
ulong allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
bool composite64 = CheckDivisors64Bit(
gpu,
prime,
allowedMax64,
(ulong)normalizedMinK,
out processedAll,
out ulong foundDivisor64);
foundDivisor = foundDivisor64;
return composite64;
}
if (fits64 && allowedMax > ulong.MaxValue)
{
ulong allowedMax64 = ulong.MaxValue;
bool composite64 = CheckDivisors64Bit(
gpu,
prime,
allowedMax64,
(ulong)normalizedMinK,
out processedAll,
out ulong foundDivisor64);
if (composite64)
{
foundDivisor = foundDivisor64;
return true;
}
BigInteger iterations = ((BigInteger)(allowedMax64 - (ulong)firstDivisor) / (ulong)step) + BigInteger.One;
BigInteger nextK = normalizedMinK + iterations;
return CheckDivisorsLarge(
gpu,
prime,
allowedMax,
nextK,
step,
out processedAll,
out foundDivisor);
}
return CheckDivisorsLarge(
gpu,
prime,
allowedMax,
normalizedMinK,
step,
out processedAll,
out foundDivisor);
}
private bool CheckDivisors64Bit(
PrimeOrderCalculatorAccelerator gpu, PrimeOrderCalculatorAccelerator gpu,
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
@@ -410,6 +483,95 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return false; return false;
} }
private bool CheckDivisorsLarge(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
BigInteger allowedMax,
BigInteger minK,
BigInteger step,
out bool processedAll,
out BigInteger foundDivisor)
{
processedAll = true;
foundDivisor = BigInteger.Zero;
BigInteger currentK = minK < BigInteger.One ? BigInteger.One : minK;
BigInteger divisor = (step * currentK) + BigInteger.One;
_ = gpu;
if (allowedMax.IsZero)
{
processedAll = false;
return false;
}
if (divisor > allowedMax)
{
return false;
}
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
byte step10 = (byte)(step % 10);
byte step8 = (byte)(step % 8);
byte step3 = (byte)(step % 3);
byte step7 = (byte)(step % 7);
byte step11 = (byte)(step % 11);
byte step13 = (byte)(step % 13);
byte step17 = (byte)(step % 17);
byte step19 = (byte)(step % 19);
byte remainder10 = (byte)(divisor % 10);
byte remainder8 = (byte)(divisor % 8);
byte remainder3 = (byte)(divisor % 3);
byte remainder7 = (byte)(divisor % 7);
byte remainder11 = (byte)(divisor % 11);
byte remainder13 = (byte)(divisor % 13);
byte remainder17 = (byte)(divisor % 17);
byte remainder19 = (byte)(divisor % 19);
while (divisor <= allowedMax)
{
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
{
if (IsProbablePrimeBigInteger(divisor))
{
BigInteger powResult = BigInteger.ModPow(2, prime, divisor);
RecordState(currentK);
if (powResult.IsOne)
{
foundDivisor = divisor;
processedAll = true;
return true;
}
}
else
{
RecordState(currentK);
}
}
currentK += BigInteger.One;
divisor += step;
remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8);
remainder3 = AddMod3(remainder3, step3);
remainder7 = AddMod7(remainder7, step7);
remainder11 = AddMod11(remainder11, step11);
remainder13 = AddMod13(remainder13, step13);
remainder17 = AddMod17(remainder17, step17);
remainder19 = AddMod19(remainder19, step19);
if (divisor > allowedMax)
{
break;
}
}
processedAll = true;
foundDivisor = BigInteger.Zero;
return false;
}
private bool CheckDivisors64Range( private bool CheckDivisors64Range(
PrimeOrderCalculatorAccelerator gpu, PrimeOrderCalculatorAccelerator gpu,
ulong prime, ulong prime,
@@ -647,7 +809,7 @@ private bool CheckDivisors64(
return residue == 1UL ? (byte)1 : (byte)0; return residue == 1UL ? (byte)1 : (byte)0;
} }
private void RecordState(ulong k) private void RecordState(BigInteger k)
{ {
string? path = _stateFilePath; string? path = _stateFilePath;
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
@@ -679,6 +841,61 @@ private bool CheckDivisors64(
} }
} }
private static bool IsProbablePrimeBigInteger(BigInteger value)
{
if (value <= 3)
{
return value >= 2;
}
if (value.IsEven)
{
return false;
}
BigInteger d = value - 1;
int s = 0;
while ((d & 1) == 0)
{
d >>= 1;
s++;
}
ReadOnlySpan<int> bases = stackalloc int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
for (int i = 0; i < bases.Length; i++)
{
int baseValue = bases[i];
if ((BigInteger)baseValue >= value)
{
continue;
}
BigInteger x = BigInteger.ModPow(baseValue, d, value);
if (x == 1 || x == value - 1)
{
continue;
}
bool witnessFound = true;
for (int r = 1; r < s; r++)
{
x = BigInteger.ModPow(x, 2, value);
if (x == value - 1)
{
witnessFound = false;
break;
}
}
if (witnessFound)
{
return false;
}
}
return true;
}
private static bool TryCalculateCycleLengthForExponentCpu( private static bool TryCalculateCycleLengthForExponentCpu(
ulong divisor, ulong divisor,
PrimeOrderCalculatorAccelerator gpu, PrimeOrderCalculatorAccelerator gpu,
@@ -854,34 +1071,62 @@ private bool CheckDivisors64(
return (byte)((value + delta) & 7); return (byte)((value + delta) & 7);
} }
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong GetAllowedDivisorLimitForSpan(BigInteger divisorLimit)
{ {
// The by-divisor CPU configuration only supplies primes greater than 1, so the guard below never trips. if (divisorLimit.IsZero)
// if (maxPrime <= 1UL)
// {
// return 0UL;
// }
if (maxPrime - 1UL >= 64UL)
{ {
return ulong.MaxValue; return ulong.MaxValue;
} }
return (1UL << (int)(maxPrime - 1UL)) - 1UL; return divisorLimit > ulong.MaxValue ? ulong.MaxValue : (ulong)divisorLimit;
} }
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit) private static BigInteger ComputeDivisorLimitFromMaxPrimeBig(ulong maxPrime)
{ {
// Production by-divisor scans only handle primes, so inputs never fall below 2. if (maxPrime <= 1UL)
// if (prime <= 1UL) {
// { return BigInteger.Zero;
// return 0UL; }
// }
if (prime - 1UL >= 64UL) if (maxPrime - 1UL >= 64UL)
{
return (BigInteger.One << 256) - BigInteger.One;
}
return (BigInteger.One << (int)(maxPrime - 1UL)) - BigInteger.One;
}
private static BigInteger ComputeAllowedMaxDivisorBig(ulong prime, BigInteger divisorLimit)
{
if (prime <= 1UL)
{
return BigInteger.Zero;
}
if (divisorLimit.IsZero || prime - 1UL >= 64UL)
{ {
return divisorLimit; return divisorLimit;
} }
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit); BigInteger candidateLimit = (BigInteger.One << (int)(prime - 1UL)) - BigInteger.One;
if (divisorLimit.IsZero)
{
return candidateLimit;
}
return BigInteger.Min(candidateLimit, divisorLimit);
}
private static ulong ComputeAllowedMaxDivisor(ulong prime, BigInteger divisorLimit)
{
BigInteger allowed = ComputeAllowedMaxDivisorBig(prime, divisorLimit);
if (allowed.IsZero)
{
return ulong.MaxValue;
}
return allowed > ulong.MaxValue ? ulong.MaxValue : (ulong)allowed;
} }
@@ -1,4 +1,6 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using ILGPU; using ILGPU;
@@ -18,7 +20,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
// so the synchronization fields from the previous implementation remain commented out here. // so the synchronization fields from the previous implementation remain commented out here.
// private readonly object _sync = new(); // private readonly object _sync = new();
private ulong _divisorLimit; private ulong _divisorLimit;
private ulong _minK = 1UL; private BigInteger _minK = BigInteger.One;
// private bool _isConfigured; // private bool _isConfigured;
private readonly ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>())]; private readonly ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>())];
@@ -37,10 +39,10 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
set => GpuBatchSize = value; set => GpuBatchSize = value;
} }
public ulong MinK public BigInteger MinK
{ {
get => _minK; get => _minK;
set => _minK = value < 1UL ? 1UL : value; set => _minK = value < BigInteger.One ? BigInteger.One : value;
} }
public string? StateFilePath { get; set; } public string? StateFilePath { get; set; }
@@ -49,9 +51,14 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
{ {
} }
public void ResumeFromState(ulong lastSavedK) public void ResumeFromState(BigInteger lastSavedK)
{ {
_minK = lastSavedK + 1UL; if (lastSavedK > ulong.MaxValue)
{
throw new NotSupportedException("GPU by-divisor tester does not support MinK above UInt64.");
}
_minK = lastSavedK + BigInteger.One;
} }
public void ConfigureFromMaxPrime(ulong maxPrime) public void ConfigureFromMaxPrime(ulong maxPrime)
@@ -68,7 +75,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
// _isConfigured = true; // _isConfigured = true;
} }
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor) public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out BigInteger divisor)
{ {
ulong allowedMax; ulong allowedMax;
int batchCapacity; int batchCapacity;
@@ -113,7 +120,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
gpu, gpu,
prime, prime,
allowedMax, allowedMax,
_minK, GetMinKOrThrow(),
resources.CheckDivisorKernel, resources.CheckDivisorKernel,
resources.DivisorDataBuffer, resources.DivisorDataBuffer,
resources.OffsetBuffer, resources.OffsetBuffer,
@@ -144,10 +151,20 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
} }
divisorsExhausted = coveredRange; divisorsExhausted = coveredRange;
divisor = 0UL; divisor = BigInteger.Zero;
return true; return true;
} }
private ulong GetMinKOrThrow()
{
if (_minK > ulong.MaxValue)
{
throw new NotSupportedException("GPU by-divisor tester does not support MinK above UInt64.");
}
return (ulong)_minK;
}
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues) public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{ {
// The scanner always supplies matching spans, so the previous validation remains commented out. // The scanner always supplies matching spans, so the previous validation remains commented out.
@@ -1,3 +1,4 @@
using System.Numerics;
using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
@@ -6,7 +7,7 @@ public interface IMersenneNumberDivisorByDivisorTester
{ {
int BatchSize { get; set; } int BatchSize { get; set; }
ulong MinK { get; set; } BigInteger MinK { get; set; }
void ConfigureFromMaxPrime(ulong maxPrime); void ConfigureFromMaxPrime(ulong maxPrime);
@@ -14,7 +15,7 @@ public interface IMersenneNumberDivisorByDivisorTester
ulong GetAllowedMaxDivisor(ulong prime); ulong GetAllowedMaxDivisor(ulong prime);
bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor); bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out BigInteger divisor);
IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu); IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu);
@@ -22,7 +23,7 @@ public interface IMersenneNumberDivisorByDivisorTester
void ResetStateTracking(); void ResetStateTracking();
void ResumeFromState(ulong lastSavedK); void ResumeFromState(BigInteger lastSavedK);
void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues); void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
@@ -1,5 +1,6 @@
using System.Buffers; using System.Buffers;
using System.Globalization; using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.Gpu.Accelerators;
@@ -16,7 +17,7 @@ public static class MersenneNumberDivisorByDivisorTester
ulong startPrime, ulong startPrime,
Action markComposite, Action markComposite,
Action clearComposite, Action clearComposite,
Action<ulong, bool, bool, bool, ulong> printResult, Action<ulong, bool, bool, bool, BigInteger> printResult,
int threadCount, int threadCount,
int primesPerTask) int primesPerTask)
{ {
@@ -275,27 +276,27 @@ public static class MersenneNumberDivisorByDivisorTester
IMersenneNumberDivisorByDivisorTester testerForPrime = CreateTester(tester); IMersenneNumberDivisorByDivisorTester testerForPrime = CreateTester(tester);
string stateFile = Path.Combine(PerfectNumberConstants.ByDivisorStateDirectory, prime.ToString(CultureInfo.InvariantCulture) + ".bin"); string stateFile = Path.Combine(PerfectNumberConstants.ByDivisorStateDirectory, prime.ToString(CultureInfo.InvariantCulture) + ".bin");
ulong resumeK = tester.MinK; BigInteger resumeK = tester.MinK;
if (File.Exists(stateFile)) if (File.Exists(stateFile))
{ {
ulong lastK = File.ReadLines(stateFile) BigInteger lastK = File.ReadLines(stateFile)
.Where(static line => !string.IsNullOrWhiteSpace(line)) .Where(static line => !string.IsNullOrWhiteSpace(line))
.Select(static line => ulong.Parse(line, NumberStyles.None, CultureInfo.InvariantCulture)) .Select(static line => BigInteger.Parse(line, NumberStyles.None, CultureInfo.InvariantCulture))
.Max(); .Max();
resumeK = lastK + 1UL; resumeK = lastK + BigInteger.One;
testerForPrime.ResumeFromState(lastK); testerForPrime.ResumeFromState(lastK);
} }
else else
{ {
testerForPrime.ResumeFromState(0UL); testerForPrime.ResumeFromState(BigInteger.Zero);
} }
testerForPrime.MinK = resumeK; testerForPrime.MinK = resumeK;
testerForPrime.StateFilePath = stateFile; testerForPrime.StateFilePath = stateFile;
testerForPrime.ResetStateTracking(); testerForPrime.ResetStateTracking();
bool isPrime = testerForPrime.IsPrime(gpu, prime, out bool divisorsExhausted, out ulong divisor); bool isPrime = testerForPrime.IsPrime(gpu, prime, out bool divisorsExhausted, out BigInteger divisor);
if (!isPrime) if (!isPrime)
{ {
+1
View File
@@ -537,6 +537,7 @@ public static class UInt128Extensions
int i = 0, primesLength = smallPrimes.Length; int i = 0, primesLength = smallPrimes.Length;
UInt128 cycle = MersenneDivisorCycles.GetCycleCpu(q); UInt128 cycle = MersenneDivisorCycles.GetCycleCpu(q);
// TODO: If the cache lacks this cycle, immediately schedule the configured device // TODO: If the cache lacks this cycle, immediately schedule the configured device
// (GPU by default) to compute it on the fly and skip inserting it into the cache so // (GPU by default) to compute it on the fly and skip inserting it into the cache so
// wide-order factoring can still leverage cycle stepping without breaching the // wide-order factoring can still leverage cycle stepping without breaching the