4919 lines
121 KiB
C#
4919 lines
121 KiB
C#
// #define DivisorSet_BitContradiction
|
|
// #define DivisorSet_BitTree
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Numerics;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Globalization;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Concurrent;
|
|
using System.Buffers;
|
|
using PerfectNumbers.Core.Gpu;
|
|
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
using PerfectNumbers.Core.ByDivisor;
|
|
using PerfectNumbers.Core.Persistence;
|
|
|
|
namespace PerfectNumbers.Core.Cpu;
|
|
|
|
public enum DivisorSet
|
|
{
|
|
Sequential,
|
|
Pow2Groups,
|
|
Predictive,
|
|
Percentile,
|
|
Additive,
|
|
TopDown,
|
|
BitContradiction,
|
|
BitTree,
|
|
}
|
|
|
|
[EnumDependentTemplate(typeof(DivisorSet))]
|
|
[DeviceDependentTemplate(typeof(ComputationDevice))]
|
|
[NameSuffix("Order")]
|
|
public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneNumberDivisorByDivisorTester
|
|
{
|
|
public BigInteger DivisorLimit;
|
|
public BigInteger MinK = EnvironmentConfiguration.MinK;
|
|
|
|
#if DEVICE_GPU || DEVICE_HYBRID
|
|
public readonly PrimeOrderCalculatorAccelerator Accelerator = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
#endif
|
|
|
|
#pragma warning disable CS8618 // StateFilePath is always set on EvenPerfectBitScanner execution path when ResumeFromState is called.
|
|
private string StateFilePath;
|
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
|
|
|
private int _stateCounter;
|
|
private BigInteger _lastSavedK;
|
|
private bool _pow2MinusOneChecked;
|
|
private Pow2Minus1StateRepository? _pow2MinusOneRepository;
|
|
private KStateRepository _kRepository = null!;
|
|
private KStateRepository? _cheapRepository;
|
|
private BigInteger _cheapLastSavedK;
|
|
private BigInteger _cheapResumeK;
|
|
|
|
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
|
private static readonly ConcurrentDictionary<ulong, byte> _checkedPow2MinusOne = new();
|
|
private ulong _currentPrime;
|
|
[ThreadStatic]
|
|
private static Dictionary<ulong, ulong>? _pow2DivisorPowModCache;
|
|
#if DETAILED_LOG
|
|
[ThreadStatic]
|
|
private static long _pow2CacheHits;
|
|
[ThreadStatic]
|
|
private static long _pow2CacheMisses;
|
|
[ThreadStatic]
|
|
private static long _pow2CacheLookups;
|
|
[ThreadStatic]
|
|
private static long _pow2CacheEligible;
|
|
#endif
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static void ResetPow2Cache()
|
|
{
|
|
_pow2DivisorPowModCache?.Clear();
|
|
#if DETAILED_LOG
|
|
_pow2CacheHits = 0;
|
|
_pow2CacheMisses = 0;
|
|
_pow2CacheLookups = 0;
|
|
_pow2CacheEligible = 0;
|
|
#endif
|
|
}
|
|
|
|
#if DivisorSet_BitContradiction
|
|
private static Persistence.BitContradictionStateRepository? _bitContradictionStateRepository;
|
|
#endif
|
|
#if DivisorSet_BitTree
|
|
private static Persistence.BitContradictionStateRepository? _bitTreeStateRepository;
|
|
#endif
|
|
#if DivisorSet_TopDown
|
|
private BigInteger _topDownCursor;
|
|
#endif
|
|
|
|
#if DivisorSet_Pow2Groups
|
|
private const int PercentScale = DivisorByDivisorConfig.PercentScale;
|
|
private static readonly int[] SpecialPercentPromille = DivisorByDivisorConfig.SpecialPercentPromille;
|
|
private static readonly (int Start, int End)[] PercentGroupsPromille = DivisorByDivisorConfig.PercentGroupsPromille;
|
|
private static long _specialHits;
|
|
private static long _groupHits;
|
|
|
|
private enum Pow2Phase
|
|
{
|
|
None,
|
|
Special,
|
|
Groups,
|
|
}
|
|
|
|
private Pow2Phase _currentPow2Phase;
|
|
private string _specialStateFilePath = string.Empty;
|
|
private string _groupsStateFilePath = string.Empty;
|
|
private int _specialStateCounter;
|
|
private int _groupsStateCounter;
|
|
private BigInteger _specialLastSavedK;
|
|
private BigInteger _groupsLastSavedK;
|
|
private BigInteger _specialResumeK;
|
|
private BigInteger _groupsResumeK;
|
|
private bool _preparedSpecialsInitialized;
|
|
private int _preparedSpecialRangePromille;
|
|
private int[] _preparedSpecialSingles = Array.Empty<int>();
|
|
private (int Start, int End)[] _preparedSpecialRanges = Array.Empty<(int Start, int End)>();
|
|
private KStateRepository? _specialRepository;
|
|
private KStateRepository? _groupsRepository;
|
|
#endif
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool IsMersenneValue(ulong prime, in BigInteger divisor)
|
|
{
|
|
if (divisor <= BigInteger.One || prime == 0UL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger plusOne = divisor + BigInteger.One;
|
|
if (plusOne <= BigInteger.One || !IsPowerOfTwoBig(plusOne))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int exponentBits = GetBitLengthPortable(plusOne) - 1;
|
|
if (exponentBits <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return prime == (ulong)exponentBits;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static int GetBitLengthPortable(in BigInteger value)
|
|
{
|
|
// if (value.IsZero)
|
|
// {
|
|
// return 0;
|
|
// }
|
|
|
|
// byte[] bytes = value.ToByteArray(isUnsigned: true, isBigEndian: false);
|
|
// byte msb = bytes[^1];
|
|
// return ((bytes.Length - 1) * 8) + (BitOperations.Log2(msb) + 1);
|
|
return (int)(value.GetBitLength());
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool IsPowerOfTwoBig(in BigInteger value)
|
|
{
|
|
return value > BigInteger.One && (value & (value - BigInteger.One)) == BigInteger.Zero;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool TryGetMersenneStopK(ulong prime, in BigInteger step, out BigInteger stopK, out BigInteger tailStartK)
|
|
{
|
|
stopK = BigInteger.Zero;
|
|
tailStartK = BigInteger.Zero;
|
|
|
|
if (prime == 0UL || prime > int.MaxValue)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int bits = checked((int)prime);
|
|
BigInteger mersenne = (BigInteger.One << bits) - BigInteger.One;
|
|
BigInteger denominator = step;
|
|
if (denominator <= BigInteger.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
stopK = (mersenne - BigInteger.One) / denominator;
|
|
if (stopK < BigInteger.One)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int tailCount = PerfectNumberConstants.ByDivisorMersenneTailCount;
|
|
if (tailCount < 0)
|
|
{
|
|
tailCount = 0;
|
|
}
|
|
|
|
BigInteger tail = stopK - tailCount;
|
|
tailStartK = tail > BigInteger.One ? tail : BigInteger.One;
|
|
return true;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool TryClampMaxKForMersenneTail(ulong prime, in BigInteger step, ref BigInteger maxKAllowed)
|
|
{
|
|
if (maxKAllowed <= BigInteger.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!TryGetMersenneStopK(prime, step, out BigInteger stopK, out BigInteger tailStartK))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (maxKAllowed < tailStartK)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger cap = stopK - BigInteger.One;
|
|
if (cap < BigInteger.One)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (maxKAllowed > cap)
|
|
{
|
|
maxKAllowed = cap;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private bool TryBuildCheapRange(in BigInteger minK, in BigInteger chunkSize, in BigInteger maxKAllowed, out BigInteger start, out BigInteger end)
|
|
{
|
|
start = minK > BigInteger.One ? minK : BigInteger.One;
|
|
if (_cheapResumeK > start)
|
|
{
|
|
start = _cheapResumeK;
|
|
}
|
|
|
|
if (start > maxKAllowed)
|
|
{
|
|
end = BigInteger.Zero;
|
|
return false;
|
|
}
|
|
|
|
BigInteger span = chunkSize > BigInteger.One ? chunkSize - BigInteger.One : BigInteger.Zero;
|
|
end = start + span;
|
|
if (end > maxKAllowed)
|
|
{
|
|
end = maxKAllowed;
|
|
}
|
|
|
|
return end >= start;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private void RecordCheapState(in BigInteger k)
|
|
{
|
|
if (_currentPrime == 0UL || _cheapRepository is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (k <= _cheapLastSavedK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_cheapRepository.Upsert(_currentPrime, k);
|
|
_cheapLastSavedK = k;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
public void ResetStateTracking()
|
|
{
|
|
ResetPow2Cache();
|
|
_stateCounter = 0;
|
|
_pow2MinusOneChecked = false;
|
|
_currentPrime = 0UL;
|
|
_cheapLastSavedK = BigInteger.Zero;
|
|
_cheapResumeK = BigInteger.One;
|
|
#if DivisorSet_TopDown
|
|
_topDownCursor = BigInteger.Zero;
|
|
#endif
|
|
#if DivisorSet_Pow2Groups
|
|
_specialStateCounter = 0;
|
|
_groupsStateCounter = 0;
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
_preparedSpecialsInitialized = false;
|
|
#endif
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
|
|
{
|
|
ResetPow2Cache();
|
|
StateFilePath = stateFile;
|
|
_lastSavedK = lastSavedK;
|
|
MinK = minK;
|
|
_stateCounter = 0;
|
|
_kRepository = EnvironmentConfiguration.ByDivisorKStateRepository ?? throw new InvalidOperationException("By-divisor k-state repository not initialized.");
|
|
_pow2MinusOneRepository = EnvironmentConfiguration.ByDivisorPow2Minus1Repository;
|
|
_cheapRepository = EnvironmentConfiguration.ByDivisorCheapKStateRepository;
|
|
_cheapLastSavedK = BigInteger.Zero;
|
|
_cheapResumeK = MinK;
|
|
|
|
string stateName = Path.GetFileNameWithoutExtension(stateFile);
|
|
if (ulong.TryParse(stateName, NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime))
|
|
{
|
|
_currentPrime = parsedPrime;
|
|
if (_kRepository.TryGet(parsedPrime, out BigInteger storedK) && storedK > BigInteger.Zero)
|
|
{
|
|
_lastSavedK = storedK;
|
|
}
|
|
|
|
_pow2MinusOneChecked = _pow2MinusOneRepository?.IsChecked(parsedPrime) ?? false;
|
|
if (_cheapRepository is not null && _cheapRepository.TryGet(parsedPrime, out BigInteger cheapStored) && cheapStored > BigInteger.Zero)
|
|
{
|
|
_cheapLastSavedK = cheapStored;
|
|
_cheapResumeK = cheapStored + BigInteger.One;
|
|
}
|
|
}
|
|
#if DivisorSet_TopDown
|
|
_topDownCursor = _lastSavedK;
|
|
#endif
|
|
#if DivisorSet_Pow2Groups
|
|
_specialRepository = EnvironmentConfiguration.ByDivisorSpecialStateRepository;
|
|
_groupsRepository = EnvironmentConfiguration.ByDivisorGroupsStateRepository;
|
|
_specialStateCounter = 0;
|
|
_groupsStateCounter = 0;
|
|
_specialLastSavedK = lastSavedK;
|
|
_groupsLastSavedK = lastSavedK;
|
|
if (_currentPrime != 0UL)
|
|
{
|
|
if (_specialRepository is not null && _specialRepository.TryGet(_currentPrime, out BigInteger storedSpecial) && storedSpecial > BigInteger.Zero)
|
|
{
|
|
_specialLastSavedK = storedSpecial;
|
|
}
|
|
|
|
if (_groupsRepository is not null && _groupsRepository.TryGet(_currentPrime, out BigInteger storedGroups) && storedGroups > BigInteger.Zero)
|
|
{
|
|
_groupsLastSavedK = storedGroups;
|
|
}
|
|
}
|
|
|
|
_specialResumeK = _specialLastSavedK + BigInteger.One;
|
|
_groupsResumeK = _groupsLastSavedK + BigInteger.One;
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
_preparedSpecialsInitialized = false;
|
|
#endif
|
|
#if DivisorSet_BitContradiction
|
|
_bitContradictionStateRepository = EnvironmentConfiguration.BitContradictionStateRepository;
|
|
#endif
|
|
#if DivisorSet_BitTree
|
|
_bitTreeStateRepository = EnvironmentConfiguration.BitContradictionStateRepository;
|
|
#endif
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
|
{
|
|
_currentPrime = prime;
|
|
ResetPow2Cache();
|
|
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
|
|
|
|
// if (TryFindPowerOfTwoMinusOneDivisor(prime, allowedMax, out divisor))
|
|
// {
|
|
// divisorsExhausted = true;
|
|
// return false;
|
|
// }
|
|
|
|
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
|
|
// if (allowedMax < 3UL)
|
|
// {
|
|
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
|
|
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
|
|
// divisorsExhausted = true;
|
|
// return true;
|
|
// }
|
|
|
|
|
|
bool composite = CheckDivisors(
|
|
prime,
|
|
allowedMax,
|
|
MinK,
|
|
out divisorsExhausted,
|
|
out divisor);
|
|
|
|
if (composite)
|
|
{
|
|
divisorsExhausted = true;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
|
{
|
|
BigInteger divisorLimitBigInteger = MersenneNumberDivisorByDivisorTester.ComputeDivisorLimitFromMaxPrimeBig(maxPrime);
|
|
DivisorLimit = divisorLimitBigInteger;
|
|
ulong divisorLimit = MersenneNumberDivisorByDivisorTester.GetAllowedDivisorLimitForSpan(divisorLimitBigInteger);
|
|
int length = primes.Length;
|
|
for (int index = 0; index < length; index++)
|
|
{
|
|
allowedMaxValues[index] = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
public readonly IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
|
|
{
|
|
#if DEVICE_GPU
|
|
var session = new MersenneCpuDivisorScanSessionWithGpuOrder();
|
|
session.Configure(gpu);
|
|
return session;
|
|
#elif DEVICE_HYBRID
|
|
var session = new MersenneCpuDivisorScanSessionWithHybridOrder();
|
|
session.Configure(gpu);
|
|
return session;
|
|
#else
|
|
return new MersenneCpuDivisorScanSessionWithCpuOrder();
|
|
#endif
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisors(
|
|
ulong prime,
|
|
BigInteger allowedMax,
|
|
BigInteger minK,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
// TODO: Is minK ever < 1? Can this be optimized - normalizedMinK is always >= 1 after this
|
|
BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One;
|
|
BigInteger step = ((BigInteger)prime) << 1;
|
|
BigInteger firstDivisor = (step * normalizedMinK) + BigInteger.One;
|
|
|
|
bool primeLastIsSeven = (prime & 3UL) == 3UL;
|
|
ushort primeDecimalMask = DivisorGenerator.GetDecimalMask(primeLastIsSeven);
|
|
bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue,
|
|
unlimited = allowedMax.IsZero,
|
|
// This checks if the limit fits 64-bit
|
|
composite64 = unlimited || allowedMax <= ulong.MaxValue;
|
|
|
|
ulong allowedMax64,
|
|
foundDivisor64;
|
|
|
|
#if DivisorSet_Pow2Groups
|
|
bool compositePow2 = CheckDivisorsPow2Groups(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
normalizedMinK,
|
|
step,
|
|
out processedAll,
|
|
out foundDivisor);
|
|
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={(prime & 1023UL)} using pow2-groups plan; model skipped.");
|
|
return compositePow2;
|
|
#else
|
|
#if DivisorSet_BitContradiction
|
|
BitContradictionSolverWithAMultiplier.Initialize();
|
|
bool compositeBitContradiction = CheckDivisorsBitContradiction(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
normalizedMinK,
|
|
step,
|
|
out processedAll,
|
|
out foundDivisor);
|
|
return compositeBitContradiction;
|
|
#endif
|
|
#if DivisorSet_BitTree
|
|
bool compositeBitTree = CheckDivisorsBitTree(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
normalizedMinK,
|
|
step,
|
|
out processedAll,
|
|
out foundDivisor);
|
|
return compositeBitTree;
|
|
#endif
|
|
#if DivisorSet_TopDown
|
|
bool compositeTopDown = CheckDivisorsTopDown(
|
|
prime,
|
|
allowedMax,
|
|
normalizedMinK,
|
|
out processedAll,
|
|
out foundDivisor);
|
|
return compositeTopDown;
|
|
#endif
|
|
#if DivisorSet_Predictive || DivisorSet_Percentile || DivisorSet_Additive
|
|
ByDivisorClassModel? classifier = EnvironmentConfiguration.ByDivisorClassModel;
|
|
if (classifier is not null)
|
|
{
|
|
bool compositePlanned = TryExecutePlannedScan(
|
|
classifier,
|
|
prime,
|
|
allowedMax,
|
|
normalizedMinK,
|
|
out processedAll,
|
|
out foundDivisor);
|
|
if (compositePlanned)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (processedAll)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"[by-divisor-plan] p={prime} suffix={(prime & 1023UL)} model unavailable, falling back to sequential scan.");
|
|
}
|
|
#endif
|
|
|
|
if (fits64 && composite64)
|
|
{
|
|
allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
|
|
// composite64 = CheckDivisors64Bit(
|
|
// prime,
|
|
// primeDecimalMask,
|
|
// allowedMax64,
|
|
// (ulong)normalizedMinK,
|
|
// out processedAll,
|
|
// out foundDivisor64);
|
|
composite64 = CheckDivisorsSequentialPow2Cache64(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax64,
|
|
(ulong)normalizedMinK,
|
|
out processedAll,
|
|
out foundDivisor64);
|
|
foundDivisor = foundDivisor64;
|
|
return composite64;
|
|
}
|
|
|
|
if (fits64 && allowedMax > ulong.MaxValue)
|
|
{
|
|
allowedMax64 = ulong.MaxValue;
|
|
// composite64 = CheckDivisors64Bit(
|
|
// prime,
|
|
// primeDecimalMask,
|
|
// allowedMax64,
|
|
// (ulong)normalizedMinK,
|
|
// out processedAll,
|
|
// out foundDivisor64);
|
|
composite64 = CheckDivisorsSequentialPow2Cache64(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax64,
|
|
(ulong)normalizedMinK,
|
|
out processedAll,
|
|
out foundDivisor64);
|
|
if (composite64)
|
|
{
|
|
foundDivisor = foundDivisor64;
|
|
return true;
|
|
}
|
|
|
|
BigInteger iterations = ((BigInteger)(allowedMax64 - (ulong)firstDivisor) / (ulong)step) + BigInteger.One;
|
|
BigInteger nextK = normalizedMinK + iterations;
|
|
return CheckDivisorsLarge(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
nextK,
|
|
step,
|
|
out processedAll,
|
|
out foundDivisor);
|
|
}
|
|
#endif
|
|
|
|
// return CheckDivisorsLarge(
|
|
// prime,
|
|
// primeDecimalMask,
|
|
// allowedMax,
|
|
// normalizedMinK,
|
|
// step,
|
|
// out processedAll,
|
|
// out foundDivisor);
|
|
return CheckDivisorsSequentialPow2CacheBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
normalizedMinK,
|
|
step,
|
|
out processedAll,
|
|
out foundDivisor);
|
|
}
|
|
|
|
#if DivisorSet_Pow2Groups
|
|
private static readonly (int, int)[] _emptyPow2Groups = Array.Empty<(int, int)>();
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsPow2Groups(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
BigInteger allowedMax,
|
|
BigInteger minK,
|
|
BigInteger step,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
foundDivisor = BigInteger.Zero;
|
|
processedAll = true;
|
|
|
|
if (allowedMax.IsZero)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One;
|
|
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
|
|
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
|
|
if (maxKAllowed < normalizedMinK)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ReadOnlySpan<int> specialPercents = SpecialPercentPromille;
|
|
ReadOnlySpan<(int Start, int End)> groups = PercentGroupsPromille;
|
|
|
|
// Segment 1: ulong range
|
|
if (normalizedMinK <= BigIntegerNumbers.UlongMaxValue)
|
|
{
|
|
ulong startK64 = (ulong)normalizedMinK;
|
|
ulong maxK64 = maxKAllowed > BigIntegerNumbers.UlongMaxValue ? ulong.MaxValue : (ulong)maxKAllowed;
|
|
ulong allowedMax64 = allowedMax > BigIntegerNumbers.UlongMaxValue ? ulong.MaxValue : (ulong)allowedMax;
|
|
|
|
bool specialFound = ProcessPow2Phase(
|
|
prime,
|
|
allowedMax64,
|
|
prime << 1,
|
|
_specialResumeK > BigInteger.Zero ? _specialResumeK : startK64,
|
|
startK64,
|
|
Pow2Phase.Special,
|
|
_emptyPow2Groups,
|
|
specialPercents,
|
|
ref processedAll,
|
|
out ulong foundDiv64);
|
|
if (specialFound)
|
|
{
|
|
foundDivisor = foundDiv64;
|
|
return true;
|
|
}
|
|
|
|
bool groupFound = ProcessPow2Phase(
|
|
prime,
|
|
allowedMax64,
|
|
prime << 1,
|
|
_groupsResumeK > BigInteger.Zero ? _groupsResumeK : startK64,
|
|
startK64,
|
|
Pow2Phase.Groups,
|
|
groups,
|
|
ReadOnlySpan<int>.Empty,
|
|
ref processedAll,
|
|
out foundDiv64);
|
|
if (groupFound)
|
|
{
|
|
foundDivisor = foundDiv64;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Segment 2: UInt128 range
|
|
BigInteger start128 = BigInteger.Max(normalizedMinK, BigIntegerNumbers.UlongMaxValuePlusOne);
|
|
if (start128 <= maxKAllowed && start128 <= BigIntegerNumbers.UInt128MaxValue)
|
|
{
|
|
BigInteger end128 = BigInteger.Min(maxKAllowed, BigIntegerNumbers.UInt128MaxValue);
|
|
bool specialFound128 = ProcessPow2PhaseBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
step,
|
|
_specialResumeK > start128 ? _specialResumeK : start128,
|
|
normalizedMinK,
|
|
start128,
|
|
end128,
|
|
Pow2Phase.Special,
|
|
_emptyPow2Groups,
|
|
specialPercents,
|
|
ref processedAll,
|
|
out BigInteger foundBig);
|
|
if (specialFound128)
|
|
{
|
|
foundDivisor = foundBig;
|
|
return true;
|
|
}
|
|
|
|
bool groupFound128 = ProcessPow2PhaseBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
step,
|
|
_groupsResumeK > start128 ? _groupsResumeK : start128,
|
|
normalizedMinK,
|
|
start128,
|
|
end128,
|
|
Pow2Phase.Groups,
|
|
groups,
|
|
ReadOnlySpan<int>.Empty,
|
|
ref processedAll,
|
|
out foundBig);
|
|
if (groupFound128)
|
|
{
|
|
foundDivisor = foundBig;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Segment 3: BigInteger range
|
|
BigInteger startBig = BigInteger.Max(normalizedMinK, BigIntegerNumbers.UInt128MaxValuePlusOne);
|
|
if (startBig <= maxKAllowed)
|
|
{
|
|
bool specialFoundBig = ProcessPow2PhaseBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
step,
|
|
_specialResumeK > startBig ? _specialResumeK : startBig,
|
|
normalizedMinK,
|
|
startBig,
|
|
maxKAllowed,
|
|
Pow2Phase.Special,
|
|
_emptyPow2Groups,
|
|
specialPercents,
|
|
ref processedAll,
|
|
out BigInteger foundBig);
|
|
if (specialFoundBig)
|
|
{
|
|
foundDivisor = foundBig;
|
|
return true;
|
|
}
|
|
|
|
bool groupFoundBig = ProcessPow2PhaseBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
allowedMax,
|
|
step,
|
|
_groupsResumeK > startBig ? _groupsResumeK : startBig,
|
|
normalizedMinK,
|
|
startBig,
|
|
maxKAllowed,
|
|
Pow2Phase.Groups,
|
|
groups,
|
|
ReadOnlySpan<int>.Empty,
|
|
ref processedAll,
|
|
out foundBig);
|
|
if (groupFoundBig)
|
|
{
|
|
foundDivisor = foundBig;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
#if DivisorSet_BitTree
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsBitTree(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
BigInteger allowedMax,
|
|
BigInteger minK,
|
|
BigInteger step,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = BigInteger.Zero;
|
|
|
|
// Default: frontier/windowed scan over (q,a) space.
|
|
MersenneCombinedDivisorScannerFullStreamExtended.QNibbleGenerator16.SelfTest(138000001);
|
|
// MersenneBitTreeDivisorScannerWithAMultiplier.ScanResult scanResult = MersenneBitTreeDivisorScannerWithAMultiplier.TryFindDivisorByStreamingScan(prime, 128, false, out BigInteger branchDivisor);
|
|
// MersenneCombinedDivisorScannerFullStreamExtended.ScanResult scanResult = MersenneCombinedDivisorScannerFullStreamExtended.TryFindDivisorByStreamingScan(prime, 128, false, out BigInteger branchDivisor);
|
|
// MersenneResidueNibbleAutomatonScanner.ScanResult scanResult = MersenneResidueNibbleAutomatonScanner.TryFindDivisorByResidueAutomaton(prime, 4096, false, out BigInteger branchDivisor);
|
|
MersenneShiftedPJumpResidueScanner64.ScanResult scanResult = MersenneShiftedPJumpResidueScanner64.TryFindDivisorBySparseShiftU(prime, 4096, 10, out BigInteger branchDivisor);
|
|
|
|
// if (TryExactBitTreeCheck(prime, allowedMax, minK, step, out BigInteger branchDivisor))
|
|
// if (scanResult == MersenneCombinedDivisorScannerFullStreamExtended.ScanResult.FoundDivisor)
|
|
// if (scanResult == MersenneResidueNibbleAutomatonScanner.ScanResult.FoundDivisor)
|
|
if (scanResult == MersenneShiftedPJumpResidueScanner64.ScanResult.FoundDivisor)
|
|
{
|
|
foundDivisor = branchDivisor;
|
|
return true;
|
|
}
|
|
else if (scanResult == MersenneShiftedPJumpResidueScanner64.ScanResult.RuledOut)
|
|
// else if (scanResult == MersenneResidueNibbleAutomatonScanner.ScanResult.Candidate)
|
|
// else if (scanResult == MersenneCombinedDivisorScannerFullStreamExtended.ScanResult.Candidate)
|
|
{
|
|
processedAll = true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool TryBitTreeBranchScan(ulong prime, BigInteger allowedMax, out BigInteger divisor)
|
|
{
|
|
divisor = BigInteger.Zero;
|
|
|
|
if (prime == 0UL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int pBits = checked((int)prime);
|
|
var stack = new Stack<(int Index, int Carry, BigInteger Q, BigInteger A)>();
|
|
// q0 = 1, a0 = 1 (to make r0 = 1)
|
|
stack.Push((Index: 1, Carry: 0, Q: BigInteger.One, A: BigInteger.One));
|
|
|
|
while (stack.Count > 0)
|
|
{
|
|
var state = stack.Pop();
|
|
int j = state.Index;
|
|
int carry = state.Carry;
|
|
BigInteger qVal = state.Q;
|
|
BigInteger aVal = state.A;
|
|
|
|
if (j >= pBits)
|
|
{
|
|
if (carry == 0)
|
|
{
|
|
if (allowedMax.IsZero || qVal <= allowedMax)
|
|
{
|
|
divisor = qVal;
|
|
return true;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for (int qBit = 0; qBit <= 1; qBit++)
|
|
{
|
|
BigInteger qCandidate = qVal;
|
|
if (qBit == 1)
|
|
{
|
|
qCandidate |= (BigInteger.One << j);
|
|
}
|
|
|
|
// Sum over assigned bits up to column j (a_i and q_{j-i} known up to j).
|
|
int sum = carry;
|
|
for (int i = 0; i <= j; i++)
|
|
{
|
|
bool aBit = ((aVal >> i) & BigInteger.One) == BigInteger.One;
|
|
bool qBitVal = ((qCandidate >> (j - i)) & BigInteger.One) == BigInteger.One;
|
|
if (aBit && qBitVal)
|
|
{
|
|
sum += 1;
|
|
}
|
|
}
|
|
|
|
// Required result bit = 1 for columns < p
|
|
if ((sum & 1) != 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Set a_j to satisfy parity (already implied by sum&1==1)
|
|
BigInteger aNext = aVal;
|
|
if (((aVal >> j) & BigInteger.One) == BigInteger.Zero)
|
|
{
|
|
// keep as 0; sum already odd
|
|
}
|
|
|
|
int carryNext = (sum - 1) >> 1;
|
|
|
|
if (!allowedMax.IsZero && qCandidate > allowedMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
stack.Push((j + 1, carryNext, qCandidate, aNext));
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private static bool TryBitTreeFrontierScan(ulong prime, in BigInteger allowedMax, out BigInteger divisor)
|
|
{
|
|
divisor = BigInteger.Zero;
|
|
|
|
const int WindowSize = 200_000_000;
|
|
const int MemoryFrontierLimit = 1_000_000;
|
|
const int DiskBatchMax = 100_000;
|
|
|
|
int pBits;
|
|
try
|
|
{
|
|
pBits = checked((int)prime);
|
|
}
|
|
catch (OverflowException)
|
|
{
|
|
Console.WriteLine($"[bittree-frontier] exponent too large to check p={prime}");
|
|
return false;
|
|
}
|
|
|
|
int maxQBits = allowedMax.IsZero ? pBits : GetBitLengthPortable(allowedMax);
|
|
if (maxQBits <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int hardLimit = pBits + maxQBits + 2;
|
|
Span<int> initialOnes = stackalloc int[1];
|
|
initialOnes[0] = 0;
|
|
|
|
var frontier = new Queue<BitTreeFrontierState>(capacity: MemoryFrontierLimit);
|
|
BigInteger initialQ = BigInteger.One;
|
|
BigInteger initialA = BigInteger.One;
|
|
frontier.Enqueue(new BitTreeFrontierState(
|
|
column: 1,
|
|
carry: 0,
|
|
baseIndex: 0,
|
|
qSuffix: initialQ,
|
|
aSuffix: initialA,
|
|
maxQIndex: 0,
|
|
lastNonZeroA: 0,
|
|
qOnes: initialOnes.ToArray()));
|
|
using BitTreeFrontierRepository frontierRepo = BitTreeFrontierRepository.Open(Path.Combine("Checks"), "bittree.frontier.faster");
|
|
|
|
Console.WriteLine($"[bittree-frontier] p={prime} active_paths=1 column=1");
|
|
|
|
long prunedPaths = 0;
|
|
long pruneLogThreshold = 1_000_000;
|
|
const int ColumnLogStep = 1_000_000;
|
|
int lastLoggedColumn = 1;
|
|
void TrackPrune(int column)
|
|
{
|
|
prunedPaths++;
|
|
if (prunedPaths >= pruneLogThreshold)
|
|
{
|
|
Console.WriteLine($"[bittree-frontier] p={prime} pruned_paths={prunedPaths} column={column}");
|
|
pruneLogThreshold <<= 1;
|
|
}
|
|
if (column - lastLoggedColumn >= ColumnLogStep)
|
|
{
|
|
lastLoggedColumn = column;
|
|
Console.WriteLine($"[bittree-frontier] p={prime} exploring_column={column}");
|
|
}
|
|
}
|
|
|
|
int maxFrontier = 1;
|
|
int printCount = 0;
|
|
|
|
static byte[] SerializeState(in BitTreeFrontierState state)
|
|
{
|
|
using var ms = new MemoryStream();
|
|
using var bw = new BinaryWriter(ms, Encoding.UTF8, leaveOpen: true);
|
|
bw.Write(state.Column);
|
|
bw.Write(state.Carry);
|
|
bw.Write(state.BaseIndex);
|
|
bw.Write(state.MaxQIndex);
|
|
bw.Write(state.LastNonZeroA);
|
|
bw.Write(state.QOnes.Length);
|
|
for (int i = 0; i < state.QOnes.Length; i++)
|
|
{
|
|
bw.Write(state.QOnes[i]);
|
|
}
|
|
byte[] qBytes = state.QSuffix.ToByteArray(isUnsigned: true, isBigEndian: false);
|
|
bw.Write(qBytes.Length);
|
|
bw.Write(qBytes);
|
|
byte[] aBytes = state.ASuffix.ToByteArray(isUnsigned: true, isBigEndian: false);
|
|
bw.Write(aBytes.Length);
|
|
bw.Write(aBytes);
|
|
bw.Flush();
|
|
return ms.ToArray();
|
|
}
|
|
|
|
static BitTreeFrontierState DeserializeState(BinaryReader br)
|
|
{
|
|
int column = br.ReadInt32();
|
|
int carry = br.ReadInt32();
|
|
int baseIndex = br.ReadInt32();
|
|
int maxQIndex = br.ReadInt32();
|
|
int lastNonZeroA = br.ReadInt32();
|
|
int onesLength = br.ReadInt32();
|
|
int[] qOnes = new int[onesLength];
|
|
for (int i = 0; i < onesLength; i++)
|
|
{
|
|
qOnes[i] = br.ReadInt32();
|
|
}
|
|
int qLen = br.ReadInt32();
|
|
byte[] qBytes = br.ReadBytes(qLen);
|
|
int aLen = br.ReadInt32();
|
|
byte[] aBytes = br.ReadBytes(aLen);
|
|
BigInteger qSuffix = new BigInteger(qBytes, isUnsigned: true, isBigEndian: false);
|
|
BigInteger aSuffix = new BigInteger(aBytes, isUnsigned: true, isBigEndian: false);
|
|
return new BitTreeFrontierState(column, carry, baseIndex, qSuffix, aSuffix, maxQIndex, lastNonZeroA, qOnes);
|
|
}
|
|
|
|
static byte[] SerializeBatch(IEnumerable<BitTreeFrontierState> states)
|
|
{
|
|
using var ms = new MemoryStream();
|
|
using var bw = new BinaryWriter(ms, Encoding.UTF8, leaveOpen: true);
|
|
foreach (var s in states)
|
|
{
|
|
byte[] payload = SerializeState(in s);
|
|
bw.Write(payload.Length);
|
|
bw.Write(payload);
|
|
}
|
|
bw.Flush();
|
|
return ms.ToArray();
|
|
}
|
|
|
|
static List<BitTreeFrontierState> DeserializeBatch(byte[] batch)
|
|
{
|
|
var list = new List<BitTreeFrontierState>();
|
|
using var ms = new MemoryStream(batch);
|
|
using var br = new BinaryReader(ms, Encoding.UTF8, leaveOpen: true);
|
|
while (ms.Position < ms.Length)
|
|
{
|
|
int len = br.ReadInt32();
|
|
byte[] payload = br.ReadBytes(len);
|
|
using var itemStream = new MemoryStream(payload);
|
|
using var itemReader = new BinaryReader(itemStream, Encoding.UTF8, leaveOpen: true);
|
|
list.Add(DeserializeState(itemReader));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
void SpillFrontierIfNeeded()
|
|
{
|
|
if (frontier.Count <= MemoryFrontierLimit)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int toSpill = frontier.Count - MemoryFrontierLimit;
|
|
if (toSpill <= 0 || toSpill < DiskBatchMax)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var batchStates = new List<BitTreeFrontierState>(toSpill);
|
|
for (int i = 0; i < toSpill && frontier.Count > 0; i++)
|
|
{
|
|
batchStates.Add(frontier.Dequeue());
|
|
}
|
|
|
|
byte[] batchBytes = SerializeBatch(batchStates);
|
|
frontierRepo.AppendBatch(batchBytes);
|
|
// Console.WriteLine($"[bittree-frontier] spilled {batchStates.Count} states to disk; in-memory={frontier.Count}");
|
|
}
|
|
|
|
bool ReloadFrontierFromRepo()
|
|
{
|
|
if (frontier.Count > 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (frontierRepo.TryDequeueBatch(1, out List<byte[]> batches) && batches.Count > 0)
|
|
{
|
|
foreach (byte[] b in batches)
|
|
{
|
|
foreach (var s in DeserializeBatch(b))
|
|
{
|
|
frontier.Enqueue(s);
|
|
}
|
|
}
|
|
Console.WriteLine($"[bittree-frontier] reloaded {frontier.Count} states from disk.");
|
|
return frontier.Count > 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
while (frontier.Count > 0)
|
|
{
|
|
BitTreeFrontierState state = frontier.Dequeue();
|
|
if (state.Column > hardLimit)
|
|
{
|
|
TrackPrune(state.Column);
|
|
continue;
|
|
}
|
|
|
|
state = SlideWindow(state, WindowSize);
|
|
|
|
for (int qBitChoice = 0; qBitChoice <= 1; qBitChoice++)
|
|
{
|
|
BigInteger qCandidate = state.QSuffix;
|
|
int maxQIndex = state.MaxQIndex;
|
|
int[] qOnes = state.QOnes;
|
|
|
|
if (qBitChoice == 1)
|
|
{
|
|
int bitIndex = state.Column - state.BaseIndex;
|
|
qCandidate |= BigInteger.One << bitIndex;
|
|
maxQIndex = Math.Max(maxQIndex, state.Column);
|
|
|
|
int[] extended = new int[qOnes.Length + 1];
|
|
Array.Copy(qOnes, extended, qOnes.Length);
|
|
extended[qOnes.Length] = state.Column;
|
|
qOnes = extended;
|
|
}
|
|
|
|
BigInteger absoluteQ = qCandidate << state.BaseIndex;
|
|
if (!allowedMax.IsZero && absoluteQ > allowedMax)
|
|
{
|
|
TrackPrune(state.Column);
|
|
continue;
|
|
}
|
|
|
|
int qBitLength = maxQIndex + 1;
|
|
if (qBitLength > maxQBits)
|
|
{
|
|
TrackPrune(state.Column);
|
|
continue;
|
|
}
|
|
|
|
int maxAIndexAllowed = pBits - qBitLength;
|
|
if (maxAIndexAllowed < 0)
|
|
{
|
|
TrackPrune(state.Column);
|
|
continue;
|
|
}
|
|
|
|
int sum = state.Carry;
|
|
for (int i = 0; i < qOnes.Length; i++)
|
|
{
|
|
int offset = qOnes[i];
|
|
if (offset == 0 || offset > state.Column)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int aIndex = state.Column - offset;
|
|
int aBitIndex = aIndex - state.BaseIndex;
|
|
if (aBitIndex >= 0 && aBitIndex < WindowSize && ((state.ASuffix >> aBitIndex) & BigInteger.One) == BigInteger.One)
|
|
{
|
|
sum += 1;
|
|
}
|
|
}
|
|
|
|
int requiredParity = state.Column < pBits ? 1 : 0;
|
|
int parity = sum & 1;
|
|
int aBit = parity == requiredParity ? 0 : 1;
|
|
|
|
BigInteger aCandidate = state.ASuffix;
|
|
int aBitIndexCurrent = state.Column - state.BaseIndex;
|
|
if (aBit == 1)
|
|
{
|
|
if (aBitIndexCurrent >= WindowSize)
|
|
{
|
|
TrackPrune(state.Column);
|
|
continue;
|
|
}
|
|
aCandidate |= BigInteger.One << aBitIndexCurrent;
|
|
}
|
|
|
|
int lastNonZeroA = aBit == 1 ? state.Column : state.LastNonZeroA;
|
|
if (lastNonZeroA > maxAIndexAllowed)
|
|
{
|
|
TrackPrune(state.Column);
|
|
continue;
|
|
}
|
|
|
|
int columnTotal = sum + aBit;
|
|
int carry = columnTotal >> 1;
|
|
|
|
bool doneWithOnes = lastNonZeroA < 0 || (state.Column + 1 - lastNonZeroA) > maxQIndex;
|
|
if (state.Column + 1 >= pBits && carry == 0 && doneWithOnes)
|
|
{
|
|
divisor = absoluteQ;
|
|
return true;
|
|
}
|
|
|
|
if (state.Column + 1 > hardLimit)
|
|
{
|
|
TrackPrune(state.Column);
|
|
continue;
|
|
}
|
|
|
|
int[] nextOnes = qOnes;
|
|
if (qBitChoice == 1)
|
|
{
|
|
nextOnes = new int[qOnes.Length + 1];
|
|
Array.Copy(qOnes, nextOnes, qOnes.Length);
|
|
nextOnes[^1] = state.Column;
|
|
}
|
|
|
|
frontier.Enqueue(new BitTreeFrontierState(
|
|
column: state.Column + 1,
|
|
carry: carry,
|
|
baseIndex: state.BaseIndex,
|
|
qSuffix: qCandidate,
|
|
aSuffix: aCandidate,
|
|
maxQIndex: maxQIndex,
|
|
lastNonZeroA: lastNonZeroA,
|
|
qOnes: nextOnes));
|
|
|
|
SpillFrontierIfNeeded();
|
|
|
|
if (frontier.Count > maxFrontier)
|
|
{
|
|
maxFrontier = frontier.Count;
|
|
printCount++;
|
|
if (printCount == 10_000_000)
|
|
{
|
|
printCount = 0;
|
|
Console.WriteLine($"[bittree-frontier] p={prime} active_paths={maxFrontier} column={state.Column + 1}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (frontier.Count == 0 && !ReloadFrontierFromRepo())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private readonly struct BitTreeFrontierState
|
|
{
|
|
public BitTreeFrontierState(
|
|
int column,
|
|
int carry,
|
|
int baseIndex,
|
|
BigInteger qSuffix,
|
|
BigInteger aSuffix,
|
|
int maxQIndex,
|
|
int lastNonZeroA,
|
|
int[] qOnes)
|
|
{
|
|
Column = column;
|
|
Carry = carry;
|
|
BaseIndex = baseIndex;
|
|
QSuffix = qSuffix;
|
|
ASuffix = aSuffix;
|
|
MaxQIndex = maxQIndex;
|
|
LastNonZeroA = lastNonZeroA;
|
|
QOnes = qOnes;
|
|
}
|
|
|
|
public readonly int Column;
|
|
public readonly int Carry;
|
|
public readonly int BaseIndex;
|
|
public readonly BigInteger QSuffix;
|
|
public readonly BigInteger ASuffix;
|
|
public readonly int MaxQIndex;
|
|
public readonly int LastNonZeroA;
|
|
public readonly int[] QOnes;
|
|
}
|
|
|
|
private readonly struct BitTreeBacktrackFrame
|
|
{
|
|
public BitTreeBacktrackFrame(BitTreeFrontierState state, int nextChoice)
|
|
{
|
|
State = state;
|
|
NextChoice = nextChoice;
|
|
}
|
|
|
|
public BitTreeFrontierState State { get; }
|
|
public int NextChoice { get; }
|
|
|
|
public BitTreeBacktrackFrame WithNextChoice(int nextChoice)
|
|
{
|
|
return new BitTreeBacktrackFrame(State, nextChoice);
|
|
}
|
|
}
|
|
|
|
|
|
private static BitTreeFrontierState SlideWindow(in BitTreeFrontierState state, int windowSize)
|
|
{
|
|
int span = state.Column - state.BaseIndex + 1;
|
|
if (span < windowSize)
|
|
{
|
|
return state;
|
|
}
|
|
|
|
int shift = span - (windowSize - 1);
|
|
int newBase = state.BaseIndex + shift;
|
|
|
|
BigInteger qSuffix = state.QSuffix >> shift;
|
|
BigInteger aSuffix = state.ASuffix >> shift;
|
|
|
|
List<int> filtered = null;
|
|
for (int i = 0; i < state.QOnes.Length; i++)
|
|
{
|
|
int pos = state.QOnes[i];
|
|
if (pos >= newBase)
|
|
{
|
|
filtered ??= new List<int>(state.QOnes.Length - i);
|
|
filtered.Add(pos);
|
|
}
|
|
}
|
|
|
|
int[] qOnes = filtered == null ? Array.Empty<int>() : filtered.ToArray();
|
|
|
|
int lastNonZeroA = state.LastNonZeroA;
|
|
if (lastNonZeroA < newBase)
|
|
{
|
|
lastNonZeroA = -1;
|
|
}
|
|
|
|
return new BitTreeFrontierState(
|
|
column: state.Column,
|
|
carry: state.Carry,
|
|
baseIndex: newBase,
|
|
qSuffix: qSuffix,
|
|
aSuffix: aSuffix,
|
|
maxQIndex: state.MaxQIndex,
|
|
lastNonZeroA: lastNonZeroA,
|
|
qOnes: qOnes);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private bool TryExactBitTreeCheck(
|
|
ulong prime,
|
|
BigInteger allowedMax,
|
|
BigInteger currentK,
|
|
BigInteger step,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
foundDivisor = BigInteger.Zero;
|
|
|
|
bool hasLimit = !allowedMax.IsZero;
|
|
BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero;
|
|
ulong computedCycle;
|
|
|
|
bool decided;
|
|
bool divides;
|
|
BigInteger divisor = (step * currentK) + BigInteger.One;
|
|
while (true)
|
|
{
|
|
// if (divisor <= ulong.MaxValue)
|
|
// {
|
|
// ulong divisor64 = (ulong)divisor;
|
|
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64);
|
|
// // We're reusing divides variable to limit registry pressure as primeOrderFailed flag.
|
|
// #if DEVICE_GPU
|
|
// decided = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
|
|
// divisor64,
|
|
// Accelerator,
|
|
// prime,
|
|
// divisorData,
|
|
// out computedCycle,
|
|
// out divides);
|
|
// #elif DEVICE_HYBRID
|
|
// decided = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
|
|
// divisor64,
|
|
// Accelerator,
|
|
// prime,
|
|
// divisorData,
|
|
// out computedCycle,
|
|
// out divides);
|
|
// #else
|
|
// decided = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
|
|
// divisor64,
|
|
// prime,
|
|
// divisorData,
|
|
// out computedCycle,
|
|
// out divides);
|
|
// #endif
|
|
|
|
// if (!decided)
|
|
// {
|
|
// #if DEVICE_GPU
|
|
// computedCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
|
|
// divisor64,
|
|
// Accelerator,
|
|
// divisorData,
|
|
// skipPrimeOrderHeuristic: divides);
|
|
// #elif DEVICE_HYBRID
|
|
// computedCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
|
|
// divisor64,
|
|
// Accelerator,
|
|
// divisorData,
|
|
// skipPrimeOrderHeuristic: divides);
|
|
// #else
|
|
// computedCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
|
|
// divisor64,
|
|
// divisorData,
|
|
// skipPrimeOrderHeuristic: divides);
|
|
// #endif
|
|
// }
|
|
|
|
// if (computedCycle > 0UL)
|
|
// {
|
|
// if ((prime % computedCycle) != 0UL)
|
|
// {
|
|
// goto MOVE_NEXT;
|
|
// }
|
|
// }
|
|
// else
|
|
// {
|
|
// computedCycle = prime;
|
|
// }
|
|
// }
|
|
// else
|
|
// {
|
|
computedCycle = prime;
|
|
// }
|
|
|
|
decided = TryExactBitTreeCheck(computedCycle, divisor, out divides);
|
|
if (!decided)
|
|
{
|
|
throw new InvalidOperationException($"BitTree check did not decide for p={prime}, divisor={divisor} (k={currentK}).");
|
|
}
|
|
|
|
if (divides)
|
|
{
|
|
foundDivisor = divisor;
|
|
return true;
|
|
}
|
|
|
|
RecordState(currentK);
|
|
|
|
MOVE_NEXT:
|
|
if (hasLimit && currentK >= maxK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
currentK += BigInteger.One;
|
|
divisor += step;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private bool TryExactBitTreeCheck(ulong exponent, BigInteger divisor, out bool divides)
|
|
{
|
|
#if DETAILED_LOG
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
#endif
|
|
int qBitLength = GetBitLengthPortable(divisor);
|
|
int aBitLength = GetBitLengthPortable(exponent);
|
|
|
|
ArrayPool<int> intPool = ThreadStaticPools.IntPool;
|
|
int pBits = checked((int)exponent);
|
|
|
|
int[] oneOffsetsArr = intPool.Rent(qBitLength);
|
|
int oneCount = 0;
|
|
int i;
|
|
for (i = 0; i < qBitLength; i++)
|
|
{
|
|
if (((divisor >> i) & BigInteger.One) == BigInteger.One)
|
|
{
|
|
oneOffsetsArr[oneCount++] = i;
|
|
}
|
|
}
|
|
|
|
// This condition will never trigger on EvenPerfectBitScanner execution paths. We always provide valid q != 0.
|
|
// if (oneCount == 0)
|
|
// {
|
|
// divides = false;
|
|
// intPool.Return(oneOffsetsArr);
|
|
// return true;
|
|
// }
|
|
|
|
// i's in oneOffsetsArr are built in ascending order by i. We don't need to sort it.
|
|
// Array.Sort(oneOffsetsArr, 0, oneCount);
|
|
ReadOnlySpan<int> oneOffsets = oneOffsetsArr.AsSpan(0, oneCount);
|
|
int maxOffset = oneOffsets[^1];
|
|
|
|
int windowSize = qBitLength;
|
|
byte[] aWindowArray = ArrayPool<byte>.Shared.Rent(windowSize);
|
|
Span<byte> aWindow = aWindowArray.AsSpan(0, windowSize);
|
|
aWindow.Clear();
|
|
|
|
long lastNonZeroA = -1;
|
|
int carry = 0;
|
|
int column = 0;
|
|
|
|
while (true)
|
|
{
|
|
bool targetOne = column < pBits;
|
|
int sum = carry;
|
|
|
|
for (i = 0; i < oneCount; i++)
|
|
{
|
|
int offset = oneOffsets[i];
|
|
if (offset == 0)
|
|
{
|
|
continue; // a_bit for current column, handled below
|
|
}
|
|
|
|
if (offset > column)
|
|
{
|
|
break;
|
|
}
|
|
|
|
int aIndex = column - offset;
|
|
byte aBit = aWindow[aIndex % windowSize];
|
|
sum += aBit;
|
|
}
|
|
|
|
int requiredParity = targetOne ? 1 : 0;
|
|
int parity = sum & 1;
|
|
byte aCurrent = (byte)(parity == requiredParity ? 0 : 1);
|
|
int columnTotal = sum + aCurrent;
|
|
|
|
if ((columnTotal & 1) != requiredParity)
|
|
{
|
|
ArrayPool<byte>.Shared.Return(aWindowArray);
|
|
|
|
divides = false;
|
|
intPool.Return(oneOffsetsArr);
|
|
return true;
|
|
}
|
|
|
|
aWindow[column % windowSize] = aCurrent;
|
|
if (aCurrent == 1)
|
|
{
|
|
lastNonZeroA = column;
|
|
}
|
|
|
|
carry = columnTotal >> 1;
|
|
column++;
|
|
|
|
bool doneWithOnes = lastNonZeroA < 0 || column - lastNonZeroA > maxOffset;
|
|
if (column >= pBits && carry == 0 && doneWithOnes)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (column > pBits + maxOffset + 2 && carry == 0 && doneWithOnes)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (column > pBits + maxOffset + aBitLength + 2)
|
|
{
|
|
ArrayPool<byte>.Shared.Return(aWindowArray);
|
|
|
|
divides = false;
|
|
intPool.Return(oneOffsetsArr);
|
|
return true;
|
|
}
|
|
}
|
|
#if DETAILED_LOG
|
|
sw.Stop();
|
|
#endif
|
|
|
|
ArrayPool<byte>.Shared.Return(aWindowArray);
|
|
|
|
divides = carry == 0;
|
|
#if DETAILED_LOG
|
|
var elapsed = sw.Elapsed;
|
|
Console.WriteLine($"[bittree ({elapsed})] Prime={exponent}, Divisor={divisor} Decided=True Divides={divides}");
|
|
#endif
|
|
if (!divides)
|
|
{
|
|
string message = $"[bittree] p={exponent} ruled out divisor={divisor} (parity/carry exhaustion)";
|
|
_bitTreeStateRepository?.Upsert(exponent, message);
|
|
}
|
|
|
|
intPool.Return(oneOffsetsArr);
|
|
return true;
|
|
}
|
|
|
|
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
// private bool TryExactBitTreeCheck(ulong prime, BigInteger divisor, ulong effectiveExponent, out bool divides)
|
|
// {
|
|
// divides = false;
|
|
|
|
// // These conditions below will never be met on EvenPerfectBitScanner execution paths.
|
|
// // if (prime == 0 || divisor <= BigInteger.One)
|
|
// // {
|
|
// // Console.WriteLine($"[bittree] invalid inputs {prime} / {divisor}");
|
|
// // return false;
|
|
// // }
|
|
|
|
// // if ((divisor & BigInteger.One) == BigInteger.Zero)
|
|
// // {
|
|
// // divides = false;
|
|
// // return true;
|
|
// // }
|
|
|
|
// int qBitLength = GetBitLengthPortable(divisor);
|
|
// if (prime <= (ulong)qBitLength)
|
|
// {
|
|
// divides = false;
|
|
// return true;
|
|
// }
|
|
|
|
// ulong exponent64 = effectiveExponent > 0UL ? effectiveExponent : prime;
|
|
// int pBits;
|
|
// try
|
|
// {
|
|
// pBits = checked((int)exponent);
|
|
// }
|
|
// catch (OverflowException)
|
|
// {
|
|
// Console.WriteLine($"[bittree] exponent too large to check p={exponent}");
|
|
// return false;
|
|
// }
|
|
|
|
// int[] oneOffsetsArr = new int[qBitLength];
|
|
// int oneCount = 0;
|
|
// for (int i = 0; i < qBitLength; i++)
|
|
// {
|
|
// if (((divisor >> i) & BigInteger.One) == BigInteger.One)
|
|
// {
|
|
// oneOffsetsArr[oneCount++] = i;
|
|
// }
|
|
// }
|
|
|
|
// if (oneCount == 0)
|
|
// {
|
|
// divides = false;
|
|
// return true;
|
|
// }
|
|
|
|
// Array.Sort(oneOffsetsArr, 0, oneCount);
|
|
// ReadOnlySpan<int> oneOffsets = oneOffsetsArr.AsSpan(0, oneCount);
|
|
// int maxOffset = oneOffsets[^1];
|
|
|
|
// int windowSize = qBitLength;
|
|
// bool rented = windowSize <= 1024;
|
|
// byte[] aWindowArray = rented ? ArrayPool<byte>.Shared.Rent(1024) : new byte[windowSize];
|
|
// Span<byte> aWindow = aWindowArray.AsSpan(0, windowSize);
|
|
// aWindow.Clear();
|
|
|
|
// long lastNonZeroA = -1;
|
|
// int carry = 0;
|
|
// int column = 0;
|
|
|
|
// while (true)
|
|
// {
|
|
// bool targetOne = column < pBits;
|
|
// int sum = carry;
|
|
|
|
// for (int idx = 0; idx < oneCount; idx++)
|
|
// {
|
|
// int offset = oneOffsets[idx];
|
|
// if (offset == 0)
|
|
// {
|
|
// continue; // a_bit for current column, handled below
|
|
// }
|
|
|
|
// if (offset > column)
|
|
// {
|
|
// break;
|
|
// }
|
|
|
|
// int aIndex = column - offset;
|
|
// byte aBit = aWindow[aIndex % windowSize];
|
|
// sum += aBit;
|
|
// }
|
|
|
|
// int requiredParity = targetOne ? 1 : 0;
|
|
// int parity = sum & 1;
|
|
// byte aCurrent = (byte)(parity == requiredParity ? 0 : 1);
|
|
// int columnTotal = sum + aCurrent;
|
|
|
|
// if ((columnTotal & 1) != requiredParity)
|
|
// {
|
|
// if (rented)
|
|
// {
|
|
// ArrayPool<byte>.Shared.Return(aWindowArray);
|
|
// }
|
|
// divides = false;
|
|
// return true;
|
|
// }
|
|
|
|
// aWindow[column % windowSize] = aCurrent;
|
|
// if (aCurrent == 1)
|
|
// {
|
|
// lastNonZeroA = column;
|
|
// }
|
|
|
|
// carry = columnTotal >> 1;
|
|
// column++;
|
|
|
|
// bool doneWithOnes = lastNonZeroA < 0 || column - lastNonZeroA > maxOffset;
|
|
// if (column >= pBits && carry == 0 && doneWithOnes)
|
|
// {
|
|
// break;
|
|
// }
|
|
|
|
// if (column > pBits + maxOffset + 2 && carry == 0 && doneWithOnes)
|
|
// {
|
|
// break;
|
|
// }
|
|
|
|
// if (column > pBits + maxOffset + windowSize + 2)
|
|
// {
|
|
// if (rented)
|
|
// {
|
|
// ArrayPool<byte>.Shared.Return(aWindowArray);
|
|
// }
|
|
// divides = false;
|
|
// return true;
|
|
// }
|
|
// }
|
|
|
|
// if (rented)
|
|
// {
|
|
// ArrayPool<byte>.Shared.Return(aWindowArray);
|
|
// }
|
|
// divides = carry == 0;
|
|
// Console.WriteLine($"[bittree] Prime={prime}, Divisor={divisor} Decided=True Divides={divides}");
|
|
// if (!divides)
|
|
// {
|
|
// string message = $"[bittree] p={prime} ruled out divisor={divisor} (parity/carry exhaustion)";
|
|
// _bitTreeStateRepository?.Upsert(prime, message);
|
|
// }
|
|
|
|
// return true;
|
|
// }
|
|
#endif
|
|
|
|
#if !DivisorSet_Pow2Groups && (DivisorSet_Predictive || DivisorSet_Percentile || DivisorSet_Additive)
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool TryExecutePlannedScan(
|
|
ByDivisorClassModel model,
|
|
ulong prime,
|
|
BigInteger allowedMax,
|
|
BigInteger minK,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
processedAll = false;
|
|
foundDivisor = BigInteger.Zero;
|
|
|
|
if (minK > ulong.MaxValue)
|
|
{
|
|
// Console.WriteLine($"[by-divisor-plan] p={prime} using BigInteger plan (minK={minK}).");
|
|
return TryExecutePlannedScanBig(model, prime, allowedMax, minK, out processedAll, out foundDivisor);
|
|
}
|
|
|
|
ulong allowedMax64;
|
|
if (allowedMax.IsZero)
|
|
{
|
|
allowedMax64 = ulong.MaxValue;
|
|
}
|
|
else if (allowedMax > ulong.MaxValue)
|
|
{
|
|
// Console.WriteLine($"[by-divisor-plan] p={prime} using BigInteger plan (allowedMax={allowedMax}).");
|
|
return TryExecutePlannedScanBig(model, prime, allowedMax, minK, out processedAll, out foundDivisor);
|
|
}
|
|
else
|
|
{
|
|
allowedMax64 = (ulong)allowedMax;
|
|
}
|
|
bool composite = TryExecutePlannedScan64(
|
|
model,
|
|
prime,
|
|
allowedMax64,
|
|
(ulong)minK,
|
|
out processedAll,
|
|
out ulong foundDivisor64);
|
|
if (composite)
|
|
{
|
|
foundDivisor = foundDivisor64;
|
|
return true;
|
|
}
|
|
|
|
if (processedAll)
|
|
{
|
|
foundDivisor = BigInteger.Zero;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool TryExecutePlannedScanBig(
|
|
ByDivisorClassModel model,
|
|
ulong prime,
|
|
in BigInteger allowedMax,
|
|
in BigInteger minK,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = BigInteger.Zero;
|
|
|
|
if (allowedMax.IsZero)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
BigInteger step = ((BigInteger)prime) << 1;
|
|
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
|
|
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
|
|
if (maxKAllowed <= BigInteger.Zero || minK > maxKAllowed)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
ByDivisorScanTuning tuning = EnvironmentConfiguration.ByDivisorScanTuning;
|
|
ByDivisorScanPlan plan = model.BuildPlan(prime, allowedMax, tuning);
|
|
int suffix = (int)(prime & 1023UL);
|
|
_ = suffix;
|
|
// ByDivisorClassEntry entry = model.Entries[suffix];
|
|
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={suffix} k50={entry.K50:F2} k75={entry.K75:F2} gap={entry.GapProbability:F3} cheap<= {plan.CheapLimit:F0} start={plan.Start:F0} target={plan.Target:F0} rho1={plan.Rho1:F2} rho2={plan.Rho2:F2} maxK={maxKAllowed}");
|
|
|
|
BigInteger cheapChunk = BigInteger.Min((BigInteger)plan.CheapLimit, maxKAllowed);
|
|
BigInteger cheapStart = BigInteger.Zero;
|
|
BigInteger cheapEnd = BigInteger.Zero;
|
|
if (TryBuildCheapRange(minK, cheapChunk, maxKAllowed, out cheapStart, out cheapEnd))
|
|
{
|
|
BigInteger currentK = cheapStart;
|
|
bool compositeCheap = CheckDivisorsBigPlanRange(
|
|
prime,
|
|
step,
|
|
allowedMax,
|
|
cheapStart,
|
|
cheapEnd,
|
|
out bool processedRange,
|
|
out BigInteger foundRange,
|
|
ref currentK);
|
|
if (compositeCheap)
|
|
{
|
|
RecordCheapState(currentK);
|
|
foundDivisor = foundRange;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
RecordCheapState(cheapEnd);
|
|
}
|
|
|
|
BigInteger startPlan = plan.Start <= 0d ? minK : new BigInteger(Math.Ceiling(plan.Start));
|
|
if (startPlan < minK)
|
|
{
|
|
startPlan = minK;
|
|
}
|
|
|
|
BigInteger target = plan.Target <= 0d ? startPlan + BigInteger.One : new BigInteger(Math.Ceiling(plan.Target));
|
|
if (target < startPlan)
|
|
{
|
|
target = startPlan + BigInteger.One;
|
|
}
|
|
|
|
BigInteger current = (cheapEnd > BigInteger.Zero ? cheapEnd + BigInteger.One : minK);
|
|
if (current < startPlan)
|
|
{
|
|
current = startPlan;
|
|
}
|
|
|
|
#if DivisorSet_Predictive
|
|
bool loggedProgressive = false;
|
|
while (current <= maxKAllowed)
|
|
{
|
|
if (!loggedProgressive)
|
|
{
|
|
// Console.WriteLine($"[by-divisor-progressive] p={prime} startK={current} target={target} rho={(current < target ? plan.Rho1 : plan.Rho2):F2} maxK={maxKAllowed}");
|
|
loggedProgressive = true;
|
|
}
|
|
|
|
BigInteger startRange = current;
|
|
BigInteger endRange = current;
|
|
bool composite = CheckDivisorsBigPlanRange(
|
|
prime,
|
|
step,
|
|
allowedMax,
|
|
startRange,
|
|
endRange,
|
|
out bool processedRange,
|
|
out BigInteger foundRange,
|
|
ref startRange);
|
|
if (composite)
|
|
{
|
|
foundDivisor = foundRange;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
double multiplier = current < target ? plan.Rho1 : plan.Rho2;
|
|
BigInteger advanced = new BigInteger(Math.Ceiling((double)current * multiplier));
|
|
if (advanced <= current)
|
|
{
|
|
advanced = current + BigInteger.One;
|
|
}
|
|
|
|
if (advanced > maxKAllowed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
current = advanced;
|
|
}
|
|
#elif DivisorSet_Percentile
|
|
BigInteger k10 = plan.K10 > 0d ? new BigInteger(Math.Ceiling(plan.K10)) : current;
|
|
BigInteger k25 = plan.K25 > 0d ? new BigInteger(Math.Ceiling(plan.K25)) : k10 + BigInteger.One;
|
|
BigInteger k50 = plan.K50 > 0d ? new BigInteger(Math.Ceiling(plan.K50)) : k25 + BigInteger.One;
|
|
BigInteger k75 = plan.K75 > 0d ? new BigInteger(Math.Ceiling(plan.K75)) : k50 + BigInteger.One;
|
|
|
|
BigInteger[] stops = new BigInteger[]
|
|
{
|
|
k25 - BigInteger.One,
|
|
k50 - BigInteger.One,
|
|
k75 - BigInteger.One,
|
|
};
|
|
|
|
BigInteger startRangeK = current > k10 ? current : k10;
|
|
for (int i = 0; i < stops.Length; i++)
|
|
{
|
|
BigInteger endRange = stops[i];
|
|
if (endRange < startRangeK)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool composite = CheckDivisorsBigPlanRange(
|
|
prime,
|
|
step,
|
|
allowedMax,
|
|
startRangeK,
|
|
endRange,
|
|
out bool processedRange,
|
|
out BigInteger foundRange,
|
|
ref startRangeK);
|
|
if (composite)
|
|
{
|
|
foundDivisor = foundRange;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
startRangeK = endRange + BigInteger.One;
|
|
if (startRangeK > maxKAllowed)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// After percentile sweeps, escalate geometrically.
|
|
BigInteger startGeo = startRangeK;
|
|
if (startGeo < k75)
|
|
{
|
|
startGeo = k75;
|
|
}
|
|
|
|
bool loggedPercentile = false;
|
|
while (startGeo <= maxKAllowed)
|
|
{
|
|
if (!loggedPercentile)
|
|
{
|
|
loggedPercentile = true;
|
|
}
|
|
|
|
BigInteger scanStart = startGeo;
|
|
BigInteger scanEnd = scanStart;
|
|
bool composite = CheckDivisorsBigPlanRange(
|
|
prime,
|
|
step,
|
|
allowedMax,
|
|
scanStart,
|
|
scanEnd,
|
|
out bool processedRange,
|
|
out BigInteger foundRange,
|
|
ref scanStart);
|
|
if (composite)
|
|
{
|
|
foundDivisor = foundRange;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
BigInteger advanced = new BigInteger(Math.Ceiling((double)startGeo * plan.Rho2));
|
|
if (advanced <= startGeo)
|
|
{
|
|
advanced = startGeo + BigInteger.One;
|
|
}
|
|
|
|
if (advanced > maxKAllowed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
startGeo = advanced;
|
|
}
|
|
#else
|
|
BigInteger delta = plan.DeltaK > 0d ? new BigInteger(Math.Ceiling(plan.DeltaK)) : BigInteger.One;
|
|
if (delta < BigInteger.One)
|
|
{
|
|
delta = BigInteger.One;
|
|
}
|
|
|
|
if (delta > maxKAllowed)
|
|
{
|
|
delta = maxKAllowed;
|
|
}
|
|
|
|
bool loggedAdditive = false;
|
|
while (current <= maxKAllowed)
|
|
{
|
|
if (!loggedAdditive)
|
|
{
|
|
loggedAdditive = true;
|
|
}
|
|
|
|
BigInteger startRange = current;
|
|
bool composite = CheckDivisorsBigPlanRange(
|
|
prime,
|
|
step,
|
|
allowedMax,
|
|
startRange,
|
|
startRange,
|
|
out bool processedRange,
|
|
out BigInteger foundRange,
|
|
ref startRange);
|
|
if (composite)
|
|
{
|
|
foundDivisor = foundRange;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
BigInteger advanced = current + delta;
|
|
if (advanced <= current)
|
|
{
|
|
advanced = current + BigInteger.One;
|
|
}
|
|
|
|
if (advanced > maxKAllowed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
current = advanced;
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsBigPlanRange(
|
|
ulong prime,
|
|
in BigInteger step,
|
|
in BigInteger allowedMax,
|
|
in BigInteger startK,
|
|
in BigInteger endK,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor,
|
|
ref BigInteger currentK)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = BigInteger.Zero;
|
|
|
|
if (startK < BigInteger.One || endK < startK)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
|
|
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
|
|
if (startK > maxKAllowed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger effectiveEnd = endK > maxKAllowed ? maxKAllowed : endK;
|
|
ushort primeDecimalMask = DivisorGenerator.GetDecimalMask((prime & 3UL) == 3UL);
|
|
BigInteger divisor = (step * startK) + BigInteger.One;
|
|
if (divisor > allowedMax)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger iterations = (effectiveEnd - startK) + BigInteger.One;
|
|
currentK = startK;
|
|
|
|
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
|
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
|
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
|
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
|
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
|
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
|
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
|
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
|
|
|
byte remainder10 = rem10.Initialize(divisor);
|
|
BigInteger primeBig = (BigInteger)prime;
|
|
|
|
while (iterations.Sign > 0)
|
|
{
|
|
if (remainder10 == 1 || remainder10 == 7)
|
|
{
|
|
if (rem3.NextIsNotDivisible(divisor) &&
|
|
rem7.NextIsNotDivisible(divisor) &&
|
|
rem11.NextIsNotDivisible(divisor) &&
|
|
rem13.NextIsNotDivisible(divisor) &&
|
|
rem17.NextIsNotDivisible(divisor) &&
|
|
rem19.NextIsNotDivisible(divisor))
|
|
{
|
|
byte remainder8 = rem8.ComputeNext(divisor);
|
|
if (remainder8 == 1 || remainder8 == 7)
|
|
{
|
|
if (((primeDecimalMask >> remainder10) & 1) != 0)
|
|
{
|
|
RecordState(currentK);
|
|
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
|
|
// if (powResult.IsOne && !IsMersenneValue(prime, divisor))
|
|
if (powResult.IsOne)
|
|
{
|
|
foundDivisor = divisor;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
iterations--;
|
|
if (iterations.IsZero)
|
|
{
|
|
break;
|
|
}
|
|
|
|
currentK++;
|
|
divisor += step;
|
|
remainder10 = rem10.ComputeNext(divisor);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool TryExecutePlannedScan64(
|
|
ByDivisorClassModel model,
|
|
ulong prime,
|
|
ulong allowedMax,
|
|
ulong minK,
|
|
out bool processedAll,
|
|
out ulong foundDivisor)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
|
|
ulong step64 = prime << 1;
|
|
if (step64 == 0UL)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
ByDivisorScanTuning tuning = EnvironmentConfiguration.ByDivisorScanTuning;
|
|
ByDivisorScanPlan plan = model.BuildPlan(prime, allowedMax == ulong.MaxValue ? BigInteger.Zero : new BigInteger(allowedMax), tuning);
|
|
int suffix = (int)(prime & 1023UL);
|
|
_ = suffix;
|
|
// ByDivisorClassEntry entry = model.Entries[suffix];
|
|
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={suffix} k50={entry.K50:F2} k75={entry.K75:F2} gap={entry.GapProbability:F3} cheap<= {plan.CheapLimit:F0} start={plan.Start:F0} target={plan.Target:F0} rho1={plan.Rho1:F2} rho2={plan.Rho2:F2}");
|
|
|
|
ulong effectiveAllowed = allowedMax == 0UL ? ulong.MaxValue : allowedMax;
|
|
ulong maxKAllowed = effectiveAllowed == ulong.MaxValue ? ulong.MaxValue : ((effectiveAllowed - 1UL) / step64);
|
|
if (maxKAllowed == 0UL || maxKAllowed < minK)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
BigInteger cheapChunk = new BigInteger(Math.Min(plan.CheapLimit, (double)maxKAllowed));
|
|
BigInteger cheapStart = BigInteger.Zero;
|
|
BigInteger cheapEnd = BigInteger.Zero;
|
|
if (TryBuildCheapRange(new BigInteger(minK), cheapChunk, new BigInteger(maxKAllowed), out cheapStart, out cheapEnd))
|
|
{
|
|
if (cheapStart <= ulong.MaxValue && cheapEnd <= ulong.MaxValue)
|
|
{
|
|
ulong cheapStart64 = (ulong)cheapStart;
|
|
ulong cheapEnd64 = (ulong)cheapEnd;
|
|
ulong currentK = cheapStart64;
|
|
bool composite = CheckDivisors64Range(
|
|
prime,
|
|
step64,
|
|
effectiveAllowed,
|
|
cheapStart64,
|
|
cheapEnd64,
|
|
ref currentK,
|
|
out bool processedRange,
|
|
out ulong foundRange);
|
|
if (composite)
|
|
{
|
|
RecordCheapState(currentK);
|
|
foundDivisor = foundRange;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
RecordCheapState(cheapEnd);
|
|
}
|
|
}
|
|
|
|
double nextK = Math.Max(plan.Start, (cheapEnd > BigInteger.Zero ? (double)(cheapEnd + BigInteger.One) : minK));
|
|
if (nextK < minK)
|
|
{
|
|
nextK = minK;
|
|
}
|
|
|
|
double target = plan.Target <= 0d ? nextK : plan.Target;
|
|
if (target < nextK)
|
|
{
|
|
target = nextK + 1d;
|
|
}
|
|
|
|
ulong current = (ulong)Math.Ceiling(nextK);
|
|
#if DivisorSet_Predictive
|
|
bool loggedProgressive = false;
|
|
while (current <= maxKAllowed)
|
|
{
|
|
if (!loggedProgressive)
|
|
{
|
|
Console.WriteLine($"[by-divisor-progressive] p={prime} startK={current} target={target:F0} rho={(current < target ? plan.Rho1 : plan.Rho2):F2} maxK={maxKAllowed}");
|
|
loggedProgressive = true;
|
|
}
|
|
|
|
ulong currentK = current;
|
|
bool compositeSingle = CheckDivisors64Range(
|
|
prime,
|
|
step64,
|
|
effectiveAllowed,
|
|
currentK,
|
|
currentK,
|
|
ref currentK,
|
|
out bool processedSingle,
|
|
out ulong foundSingle);
|
|
if (compositeSingle)
|
|
{
|
|
foundDivisor = foundSingle;
|
|
return true;
|
|
}
|
|
|
|
if (!processedSingle)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
double multiplier = current < target ? plan.Rho1 : plan.Rho2;
|
|
double advanced = Math.Ceiling(current * multiplier);
|
|
if (advanced <= current)
|
|
{
|
|
advanced = current + 1d;
|
|
}
|
|
|
|
if (advanced > maxKAllowed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
current = (ulong)advanced;
|
|
}
|
|
#elif DivisorSet_Percentile
|
|
ulong k10 = plan.K10 > 0d ? (ulong)Math.Ceiling(plan.K10) : current;
|
|
ulong k25 = plan.K25 > 0d ? (ulong)Math.Ceiling(plan.K25) : k10 + 1UL;
|
|
ulong k50 = plan.K50 > 0d ? (ulong)Math.Ceiling(plan.K50) : k25 + 1UL;
|
|
ulong k75 = plan.K75 > 0d ? (ulong)Math.Ceiling(plan.K75) : k50 + 1UL;
|
|
|
|
ReadOnlySpan<ulong> stops = stackalloc ulong[3]
|
|
{
|
|
k25 > 0UL ? k25 - 1UL : 0UL,
|
|
k50 > 0UL ? k50 - 1UL : 0UL,
|
|
k75 > 0UL ? k75 - 1UL : 0UL,
|
|
};
|
|
|
|
ulong rangeStart = current > k10 ? current : k10;
|
|
for (int i = 0; i < stops.Length; i++)
|
|
{
|
|
ulong stop = stops[i];
|
|
if (stop < rangeStart)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ulong currentK = rangeStart;
|
|
bool composite = CheckDivisors64Range(
|
|
prime,
|
|
step64,
|
|
effectiveAllowed,
|
|
rangeStart,
|
|
stop,
|
|
ref currentK,
|
|
out bool processedRange,
|
|
out ulong foundRange);
|
|
if (composite)
|
|
{
|
|
foundDivisor = foundRange;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
rangeStart = stop + 1UL;
|
|
if (rangeStart > maxKAllowed)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ulong geoStart = rangeStart < k75 ? k75 : rangeStart;
|
|
while (geoStart <= maxKAllowed)
|
|
{
|
|
ulong currentK = geoStart;
|
|
bool composite = CheckDivisors64Range(
|
|
prime,
|
|
step64,
|
|
effectiveAllowed,
|
|
currentK,
|
|
currentK,
|
|
ref currentK,
|
|
out bool processedSingle,
|
|
out ulong foundSingle);
|
|
if (composite)
|
|
{
|
|
foundDivisor = foundSingle;
|
|
return true;
|
|
}
|
|
|
|
if (!processedSingle)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
double advanced = Math.Ceiling(geoStart * plan.Rho2);
|
|
if (advanced <= geoStart)
|
|
{
|
|
advanced = geoStart + 1d;
|
|
}
|
|
|
|
if (advanced > maxKAllowed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
geoStart = (ulong)advanced;
|
|
}
|
|
#else
|
|
ulong delta = plan.DeltaK > 0d ? (ulong)Math.Ceiling(plan.DeltaK) : 1UL;
|
|
if (delta == 0UL)
|
|
{
|
|
delta = 1UL;
|
|
}
|
|
|
|
if (delta > maxKAllowed)
|
|
{
|
|
delta = maxKAllowed;
|
|
}
|
|
|
|
while (current <= maxKAllowed)
|
|
{
|
|
ulong currentK = current;
|
|
bool composite = CheckDivisors64Range(
|
|
prime,
|
|
step64,
|
|
effectiveAllowed,
|
|
currentK,
|
|
currentK,
|
|
ref currentK,
|
|
out bool processedSingle,
|
|
out ulong foundSingle);
|
|
if (composite)
|
|
{
|
|
foundDivisor = foundSingle;
|
|
return true;
|
|
}
|
|
|
|
if (!processedSingle)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
ulong advanced = current + delta;
|
|
if (advanced <= current || advanced == 0UL)
|
|
{
|
|
advanced = current + 1UL;
|
|
}
|
|
|
|
if (advanced > maxKAllowed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
current = advanced;
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisors64Bit(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
ulong allowedMax,
|
|
ulong minK,
|
|
out bool processedAll,
|
|
out ulong foundDivisor)
|
|
{
|
|
foundDivisor = 0UL;
|
|
|
|
#if DivisorSet_Pow2Groups
|
|
ulong step64 = prime << 1;
|
|
if (step64 == 0UL)
|
|
{
|
|
processedAll = true;
|
|
return false;
|
|
}
|
|
|
|
processedAll = true;
|
|
BigInteger stepBig = step64;
|
|
BigInteger maxKAllowed = allowedMax == 0UL ? BigInteger.Zero : (((BigInteger)allowedMax - BigInteger.One) / stepBig);
|
|
TryClampMaxKForMersenneTail(prime, stepBig, ref maxKAllowed);
|
|
|
|
ReadOnlySpan<int> specialPercents = SpecialPercentPromille;
|
|
ReadOnlySpan<(int Start, int End)> groups = PercentGroupsPromille;
|
|
|
|
// Segment 1: ulong range
|
|
ulong max64K = maxKAllowed <= ulong.MaxValue ? (ulong)maxKAllowed : ulong.MaxValue;
|
|
if (minK <= max64K)
|
|
{
|
|
ulong max64 = allowedMax == 0UL ? ulong.MaxValue : allowedMax;
|
|
bool specialFound = ProcessPow2Phase(
|
|
prime,
|
|
max64,
|
|
step64,
|
|
_specialResumeK > BigInteger.Zero ? _specialResumeK : minK,
|
|
minK,
|
|
Pow2Phase.Special,
|
|
_emptyPow2Groups,
|
|
specialPercents,
|
|
ref processedAll,
|
|
out foundDivisor);
|
|
if (specialFound)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool groupFound = ProcessPow2Phase(
|
|
prime,
|
|
max64,
|
|
step64,
|
|
_groupsResumeK > BigInteger.Zero ? _groupsResumeK : minK,
|
|
minK,
|
|
Pow2Phase.Groups,
|
|
groups,
|
|
ReadOnlySpan<int>.Empty,
|
|
ref processedAll,
|
|
out foundDivisor);
|
|
if (groupFound)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Segment 2: UInt128 range
|
|
BigInteger start128 = BigInteger.Max(minK, BigIntegerNumbers.UlongMaxValuePlusOne);
|
|
if (maxKAllowed > BigIntegerNumbers.UlongMaxValue && start128 <= maxKAllowed)
|
|
{
|
|
BigInteger end128 = BigInteger.Min(maxKAllowed, BigIntegerNumbers.UInt128MaxValue);
|
|
bool specialFound128 = ProcessPow2PhaseBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
(BigInteger)allowedMax == BigInteger.Zero ? BigInteger.Zero : (BigInteger)allowedMax,
|
|
stepBig,
|
|
_specialResumeK > start128 ? _specialResumeK : start128,
|
|
minK,
|
|
start128,
|
|
end128,
|
|
Pow2Phase.Special,
|
|
_emptyPow2Groups,
|
|
specialPercents,
|
|
ref processedAll,
|
|
out BigInteger foundBig);
|
|
if (specialFound128)
|
|
{
|
|
foundDivisor = foundBig > ulong.MaxValue ? ulong.MaxValue : (ulong)foundBig;
|
|
return true;
|
|
}
|
|
|
|
bool groupFound128 = ProcessPow2PhaseBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
(BigInteger)allowedMax == BigInteger.Zero ? BigInteger.Zero : (BigInteger)allowedMax,
|
|
stepBig,
|
|
_groupsResumeK > start128 ? _groupsResumeK : start128,
|
|
minK,
|
|
start128,
|
|
end128,
|
|
Pow2Phase.Groups,
|
|
groups,
|
|
ReadOnlySpan<int>.Empty,
|
|
ref processedAll,
|
|
out foundBig);
|
|
if (groupFound128)
|
|
{
|
|
foundDivisor = foundBig > ulong.MaxValue ? ulong.MaxValue : (ulong)foundBig;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Segment 3: BigInteger range beyond UInt128
|
|
BigInteger startBig = BigInteger.Max(minK, BigIntegerNumbers.UInt128MaxValuePlusOne);
|
|
if (maxKAllowed > BigIntegerNumbers.UInt128MaxValue && startBig <= maxKAllowed)
|
|
{
|
|
bool specialFoundBig = ProcessPow2PhaseBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
(BigInteger)allowedMax == BigInteger.Zero ? BigInteger.Zero : (BigInteger)allowedMax,
|
|
stepBig,
|
|
_specialResumeK > startBig ? _specialResumeK : startBig,
|
|
minK,
|
|
startBig,
|
|
maxKAllowed,
|
|
Pow2Phase.Special,
|
|
_emptyPow2Groups,
|
|
specialPercents,
|
|
ref processedAll,
|
|
out BigInteger foundBig);
|
|
if (specialFoundBig)
|
|
{
|
|
foundDivisor = foundBig > ulong.MaxValue ? ulong.MaxValue : (ulong)foundBig;
|
|
return true;
|
|
}
|
|
|
|
bool groupFoundBig = ProcessPow2PhaseBig(
|
|
prime,
|
|
primeDecimalMask,
|
|
(BigInteger)allowedMax == BigInteger.Zero ? BigInteger.Zero : (BigInteger)allowedMax,
|
|
stepBig,
|
|
_groupsResumeK > startBig ? _groupsResumeK : startBig,
|
|
minK,
|
|
startBig,
|
|
maxKAllowed,
|
|
Pow2Phase.Groups,
|
|
groups,
|
|
ReadOnlySpan<int>.Empty,
|
|
ref processedAll,
|
|
out foundBig);
|
|
if (groupFoundBig)
|
|
{
|
|
foundDivisor = foundBig > ulong.MaxValue ? ulong.MaxValue : (ulong)foundBig;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
#else
|
|
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
|
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
|
// if (allowedMax < 3UL)
|
|
// {
|
|
// return false;
|
|
// }
|
|
|
|
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
|
|
|
|
ref GpuUInt128 step = ref workSet.Step;
|
|
step.High = 0UL;
|
|
step.Low = prime;
|
|
step.ShiftLeft(1);
|
|
|
|
ref GpuUInt128 limit = ref workSet.Limit;
|
|
limit.High = 0UL;
|
|
limit.Low = allowedMax;
|
|
|
|
ref GpuUInt128 divisor = ref workSet.Divisor;
|
|
divisor.High = step.High;
|
|
divisor.Low = step.Low;
|
|
divisor.Add(1UL);
|
|
if (minK > 1UL)
|
|
{
|
|
GpuUInt128 offset = new(minK - 1UL);
|
|
divisor = step * offset;
|
|
divisor.Add(1UL);
|
|
}
|
|
|
|
if (divisor.CompareTo(limit) > 0)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
|
|
// Intentionally recomputes factorizations without a per-thread cache.
|
|
// The previous factor cache recorded virtually no hits and only slowed down the scan.
|
|
|
|
ulong currentK = minK,
|
|
// We're reusing candidate variable as stepLow to limit registry pressure
|
|
candidate = step.Low,
|
|
// We're reusing computedCycle variable as lowerEnd to limit registry pressure
|
|
computedCycle,
|
|
// We're reusing divisorCycle variable as maxK to limit registry pressure
|
|
divisorCycle;
|
|
|
|
// We're reusing computed variable as processedTop to limit registry pressure
|
|
bool computed,
|
|
// We're reusing primeOrderFailed variable as processedBottom to limit registry pressure
|
|
primeOrderFailed,
|
|
composite;
|
|
|
|
if (step.High == 0UL && limit.High == 0UL)
|
|
{
|
|
divisorCycle = allowedMax > 0UL ? (allowedMax - 1UL) / candidate : 0UL;
|
|
if (divisorCycle == 0UL)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
|
|
computed = true;
|
|
primeOrderFailed = true;
|
|
composite = false;
|
|
|
|
if (minK <= divisorCycle)
|
|
{
|
|
composite = CheckDivisors64Range(
|
|
prime,
|
|
candidate,
|
|
allowedMax,
|
|
minK,
|
|
divisorCycle,
|
|
ref currentK,
|
|
out computed,
|
|
out foundDivisor);
|
|
if (composite)
|
|
{
|
|
processedAll = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
computedCycle = minK > 1UL ? minK - 1UL : 0UL;
|
|
if (computedCycle >= 1UL && minK == 1UL)
|
|
{
|
|
computedCycle = Math.Min(computedCycle, divisorCycle);
|
|
currentK = 1UL;
|
|
composite = CheckDivisors64Range(
|
|
prime,
|
|
candidate,
|
|
allowedMax,
|
|
1UL,
|
|
computedCycle,
|
|
ref currentK,
|
|
out primeOrderFailed,
|
|
out foundDivisor);
|
|
}
|
|
|
|
processedAll = computed && primeOrderFailed;
|
|
return composite;
|
|
}
|
|
|
|
#if DEVICE_GPU || DEVICE_HYBRID
|
|
PrimeOrderCalculatorAccelerator gpu = Accelerator;
|
|
#endif
|
|
var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor);
|
|
|
|
while (divisor.CompareTo(limit) <= 0)
|
|
{
|
|
if (residueStepper.IsAdmissible())
|
|
{
|
|
candidate = divisor.Low;
|
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
|
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
|
|
#if DEVICE_GPU
|
|
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
|
|
candidate,
|
|
gpu,
|
|
prime,
|
|
divisorData,
|
|
out computedCycle,
|
|
out primeOrderFailed);
|
|
#elif DEVICE_HYBRID
|
|
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
|
|
candidate,
|
|
gpu,
|
|
prime,
|
|
divisorData,
|
|
out computedCycle,
|
|
out primeOrderFailed);
|
|
#else
|
|
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
|
|
candidate,
|
|
prime,
|
|
divisorData,
|
|
out computedCycle,
|
|
out primeOrderFailed);
|
|
#endif
|
|
if (computed && computedCycle != 0UL)
|
|
{
|
|
divisorCycle = computedCycle;
|
|
}
|
|
else
|
|
{
|
|
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
|
|
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
|
|
#if DEVICE_GPU
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
|
|
candidate,
|
|
gpu,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#elif DEVICE_HYBRID
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
|
|
candidate,
|
|
gpu,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#else
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
|
|
candidate,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#endif
|
|
}
|
|
|
|
RecordState(currentK);
|
|
// if (divisorCycle == prime && !IsMersenneValue(prime, candidate))
|
|
if (divisorCycle == prime)
|
|
{
|
|
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
|
|
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
|
|
foundDivisor = candidate;
|
|
processedAll = true;
|
|
return true;
|
|
}
|
|
|
|
if (divisorCycle == 0UL)
|
|
{
|
|
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
|
}
|
|
}
|
|
|
|
divisor.Add(step);
|
|
currentK++;
|
|
residueStepper.Advance();
|
|
}
|
|
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool IsPowerOfTwo(ulong value) => value != 0 && (value & (value - 1)) == 0;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static ulong PowModUlong(ulong exponent, ulong modulus)
|
|
{
|
|
ulong result = 1 % modulus;
|
|
ulong baseVal = 2 % modulus;
|
|
ulong exp = exponent;
|
|
while (exp > 0)
|
|
{
|
|
if ((exp & 1UL) != 0)
|
|
{
|
|
result = (ulong)(((UInt128)result * baseVal) % modulus);
|
|
}
|
|
|
|
baseVal = (ulong)(((UInt128)baseVal * baseVal) % modulus);
|
|
exp >>= 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool TryGetPow2PowModCached(ulong divisor, out ulong powMod)
|
|
{
|
|
#if DETAILED_LOG
|
|
_pow2CacheLookups++;
|
|
#endif
|
|
_pow2DivisorPowModCache ??= new Dictionary<ulong, ulong>(64);
|
|
if (_pow2DivisorPowModCache.TryGetValue(divisor, out powMod))
|
|
{
|
|
#if DETAILED_LOG
|
|
_pow2CacheHits++;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
#if DETAILED_LOG
|
|
_pow2CacheMisses++;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static void StorePow2PowModCached(ulong divisor, ulong powMod)
|
|
{
|
|
// EvenPerfectBitScanner's logic forces the divisor to be initialized here. We first get, then store.
|
|
_pow2DivisorPowModCache![divisor] = powMod;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsSequentialPow2Cache64(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
ulong allowedMax,
|
|
ulong minK,
|
|
out bool processedAll,
|
|
out ulong foundDivisor)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
|
|
ulong step = prime << 1;
|
|
if (step == 0UL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ulong effectiveMax = allowedMax == 0UL ? ulong.MaxValue : allowedMax;
|
|
ulong maxKAllowed = (effectiveMax - 1UL) / step;
|
|
if (maxKAllowed < 1UL || minK > maxKAllowed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UInt128 step128 = step;
|
|
UInt128 divisor128 = (step128 * minK) + UInt128.One;
|
|
if (divisor128 > effectiveMax)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ulong divisor = (ulong)divisor128;
|
|
ulong currentK = minK;
|
|
var residueStepper = new MersenneDivisorResidueStepper(prime, new GpuUInt128(step), new GpuUInt128(divisor));
|
|
|
|
while (divisor <= effectiveMax)
|
|
{
|
|
if (residueStepper.IsAdmissible())
|
|
{
|
|
// Stopwatch sw = Stopwatch.StartNew();
|
|
RecordState(currentK);
|
|
ulong powMod;
|
|
bool isPow2 = IsPowerOfTwo(currentK);
|
|
#if DETAILED_LOG
|
|
if (isPow2)
|
|
{
|
|
_pow2CacheEligible++;
|
|
}
|
|
#endif
|
|
if (!isPow2 || !TryGetPow2PowModCached(divisor, out powMod))
|
|
{
|
|
powMod = PowModUlong(prime, divisor);
|
|
if (isPow2)
|
|
{
|
|
StorePow2PowModCached(divisor, powMod);
|
|
}
|
|
}
|
|
|
|
if (powMod == 1UL)
|
|
{
|
|
// sw.Stop();
|
|
foundDivisor = divisor;
|
|
return true;
|
|
}
|
|
|
|
// sw.Stop();
|
|
// Console.WriteLine($"[sequential-pow2] p={prime} divisor={divisor} powmod={powMod} isPow2={isPow2} pow2Eligible={_pow2CacheEligible} lookups={_pow2CacheLookups} cacheHits={_pow2CacheHits} cacheMisses={_pow2CacheMisses} elapsed={sw.Elapsed}");
|
|
}
|
|
|
|
if (currentK == maxKAllowed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
divisor += step;
|
|
currentK++;
|
|
residueStepper.Advance();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsSequentialPow2CacheBig(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
BigInteger allowedMax,
|
|
BigInteger currentK,
|
|
BigInteger step,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
foundDivisor = BigInteger.Zero;
|
|
if (allowedMax.IsZero)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
processedAll = true;
|
|
BigInteger divisor = (step * currentK) + BigInteger.One;
|
|
if (divisor > allowedMax)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
|
|
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
|
|
if (currentK > maxKAllowed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger remainingIterationsBig = maxKAllowed - currentK + BigInteger.One;
|
|
|
|
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
|
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
|
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
|
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
|
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
|
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
|
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
|
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
|
|
|
byte remainder10 = rem10.Initialize(divisor);
|
|
BigInteger primeBig = (BigInteger)prime;
|
|
while (remainingIterationsBig.Sign > 0)
|
|
{
|
|
bool passesSmallModuli = remainder10 == 1 || remainder10 == 7;
|
|
if (passesSmallModuli && ((primeDecimalMask >> remainder10) & 1) != 0)
|
|
{
|
|
if (rem3.NextIsNotDivisible(divisor) &&
|
|
rem7.NextIsNotDivisible(divisor) &&
|
|
rem11.NextIsNotDivisible(divisor) &&
|
|
rem13.NextIsNotDivisible(divisor) &&
|
|
rem17.NextIsNotDivisible(divisor) &&
|
|
rem19.NextIsNotDivisible(divisor))
|
|
{
|
|
byte remainder8 = rem8.ComputeNext(divisor);
|
|
if (remainder8 == 1 || remainder8 == 7)
|
|
{
|
|
RecordState(currentK);
|
|
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
|
|
if (powResult.IsOne)
|
|
{
|
|
foundDivisor = divisor;
|
|
processedAll = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
currentK++;
|
|
divisor += step;
|
|
remainder10 = rem10.ComputeNext(divisor);
|
|
|
|
remainingIterationsBig--;
|
|
if (remainingIterationsBig.IsZero)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
processedAll = true;
|
|
foundDivisor = BigInteger.Zero;
|
|
return false;
|
|
}
|
|
|
|
#if DivisorSet_Pow2Groups
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool ProcessPow2Phase(
|
|
ulong prime,
|
|
ulong max64,
|
|
ulong step64,
|
|
BigInteger resumeK,
|
|
ulong minK,
|
|
Pow2Phase phase,
|
|
ReadOnlySpan<(int Start, int End)> ranges,
|
|
ReadOnlySpan<int> singles,
|
|
ref bool processedAll,
|
|
out ulong foundDivisor)
|
|
{
|
|
foundDivisor = 0UL;
|
|
int specialRangePromille = EnvironmentConfiguration.ByDivisorSpecialRange;
|
|
if (specialRangePromille < 0)
|
|
{
|
|
specialRangePromille = 0;
|
|
}
|
|
|
|
specialRangePromille *= 100; // convert percent to promille-of-percentScale (10_000 = 100%)
|
|
EnsurePreparedSpecials(specialRangePromille);
|
|
if (resumeK > ulong.MaxValue)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
BigInteger effectiveResume = resumeK < minK ? minK : resumeK;
|
|
BigInteger firstDivisorBig = (((BigInteger)prime) << 1) * effectiveResume + BigInteger.One;
|
|
if (firstDivisorBig > max64)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
ulong firstDivisor = (ulong)firstDivisorBig;
|
|
int startBit = BitOperations.Log2(firstDivisor);
|
|
int maxBit = BitOperations.Log2(max64);
|
|
ulong currentK = (ulong)effectiveResume;
|
|
_currentPow2Phase = phase;
|
|
FlushPow2State(effectiveResume, phase);
|
|
|
|
ReadOnlySpan<int> effectiveSingles = singles;
|
|
ReadOnlySpan<(int Start, int End)> effectiveRanges = ranges;
|
|
if (phase == Pow2Phase.Special)
|
|
{
|
|
effectiveSingles = _preparedSpecialSingles;
|
|
effectiveRanges = _preparedSpecialRanges;
|
|
}
|
|
|
|
try
|
|
{
|
|
for (int bit = startBit; bit <= maxBit; bit++)
|
|
{
|
|
ulong lower = 1UL << bit;
|
|
ulong upperExclusive = bit == 63 ? ulong.MaxValue : lower << 1;
|
|
|
|
if (lower > max64)
|
|
{
|
|
break;
|
|
}
|
|
|
|
BigInteger lowerBig = lower;
|
|
|
|
for (int i = 0; i < effectiveSingles.Length; i++)
|
|
{
|
|
int startProm = effectiveSingles[i];
|
|
int endProm = effectiveSingles[i];
|
|
if (specialRangePromille > 0)
|
|
{
|
|
startProm = Math.Max(0, effectiveSingles[i] - specialRangePromille);
|
|
endProm = Math.Min(PercentScale, effectiveSingles[i] + specialRangePromille);
|
|
}
|
|
|
|
if (specialRangePromille == 0)
|
|
{
|
|
int adjustedEndProm = Math.Min(PercentScale, startProm + 1);
|
|
BigInteger scaledStart = (PercentScale + startProm) * lowerBig;
|
|
BigInteger scaledEnd = (PercentScale + adjustedEndProm) * lowerBig;
|
|
|
|
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
|
|
BigInteger qMax = scaledEnd / PercentScale;
|
|
|
|
if ((qMin & 1) == 0)
|
|
{
|
|
qMin += 1;
|
|
}
|
|
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
|
|
if (qMin < 3 || qMin > qMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMin < lowerBig)
|
|
{
|
|
qMin = lowerBig | 1;
|
|
}
|
|
|
|
if (qMax >= upperExclusive)
|
|
{
|
|
qMax = upperExclusive - 1;
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
}
|
|
|
|
if (qMin > qMax || qMin > max64)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMax > max64)
|
|
{
|
|
qMax = max64;
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
}
|
|
|
|
BigInteger stepBig = step64;
|
|
BigInteger kMinBig = (qMin - 1 + (stepBig - 1)) / stepBig;
|
|
BigInteger kMaxBig = (qMax - 1) / stepBig;
|
|
|
|
if (kMinBig < minK || kMinBig < effectiveResume)
|
|
{
|
|
kMinBig = BigInteger.Max(BigInteger.Max(minK, effectiveResume), kMinBig);
|
|
}
|
|
|
|
if (kMinBig > kMaxBig)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (kMaxBig > ulong.MaxValue)
|
|
{
|
|
kMaxBig = ulong.MaxValue;
|
|
}
|
|
|
|
ulong kStart = (ulong)kMinBig;
|
|
ulong kEnd = (ulong)kMaxBig;
|
|
|
|
bool composite = CheckDivisors64Range(
|
|
prime,
|
|
step64,
|
|
max64,
|
|
kStart,
|
|
kEnd,
|
|
ref currentK,
|
|
out bool processedRange,
|
|
out foundDivisor);
|
|
if (composite)
|
|
{
|
|
RecordPow2Hit(phase);
|
|
FlushPow2State(currentK, phase);
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BigInteger scaledStart = (PercentScale + startProm) * lowerBig;
|
|
BigInteger scaledEnd = (PercentScale + endProm) * lowerBig;
|
|
|
|
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
|
|
BigInteger qMax = scaledEnd / PercentScale;
|
|
|
|
if ((qMin & 1) == 0)
|
|
{
|
|
qMin += 1;
|
|
}
|
|
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
|
|
if (qMin < 3 || qMin > qMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMin < lowerBig)
|
|
{
|
|
qMin = lowerBig | 1;
|
|
}
|
|
|
|
if (qMax >= upperExclusive)
|
|
{
|
|
qMax = upperExclusive - 1;
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
}
|
|
|
|
if (qMin > qMax || qMin > max64)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMax > max64)
|
|
{
|
|
qMax = max64;
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
}
|
|
|
|
BigInteger stepBig = step64;
|
|
BigInteger kMinBig = (qMin - 1 + (stepBig - 1)) / stepBig;
|
|
BigInteger kMaxBig = (qMax - 1) / stepBig;
|
|
|
|
if (kMinBig < minK || kMinBig < effectiveResume)
|
|
{
|
|
kMinBig = BigInteger.Max(minK, effectiveResume);
|
|
}
|
|
|
|
if (kMinBig > kMaxBig)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (kMaxBig > ulong.MaxValue)
|
|
{
|
|
kMaxBig = ulong.MaxValue;
|
|
}
|
|
|
|
ulong kStart = (ulong)kMinBig;
|
|
ulong kEnd = (ulong)kMaxBig;
|
|
|
|
bool composite = CheckDivisors64Range(
|
|
prime,
|
|
step64,
|
|
max64,
|
|
kStart,
|
|
kEnd,
|
|
ref currentK,
|
|
out bool processedRange,
|
|
out foundDivisor);
|
|
if (composite)
|
|
{
|
|
RecordPow2Hit(phase);
|
|
FlushPow2State(currentK, phase);
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < effectiveRanges.Length; i++)
|
|
{
|
|
int start = effectiveRanges[i].Start;
|
|
int end = effectiveRanges[i].End;
|
|
|
|
BigInteger scaledStart = (PercentScale + start) * lowerBig;
|
|
BigInteger scaledEnd = (PercentScale + end) * lowerBig;
|
|
|
|
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
|
|
BigInteger qMax = scaledEnd / PercentScale;
|
|
|
|
if ((qMin & 1) == 0)
|
|
{
|
|
qMin += 1;
|
|
}
|
|
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
|
|
if (qMin < 3 || qMin > qMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMin < lowerBig)
|
|
{
|
|
qMin = lowerBig | 1;
|
|
}
|
|
|
|
if (qMax >= upperExclusive)
|
|
{
|
|
qMax = upperExclusive - 1;
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
}
|
|
|
|
if (qMin > qMax || qMin > max64)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMax > max64)
|
|
{
|
|
qMax = max64;
|
|
if ((qMax & 1) == 0)
|
|
{
|
|
qMax -= 1;
|
|
}
|
|
}
|
|
|
|
BigInteger stepBig = step64;
|
|
BigInteger kMinBig = (qMin - 1 + (stepBig - 1)) / stepBig;
|
|
BigInteger kMaxBig = (qMax - 1) / stepBig;
|
|
|
|
BigInteger kStartBig = kMinBig - BigInteger.One;
|
|
if (kStartBig < BigInteger.One)
|
|
{
|
|
kStartBig = kMinBig;
|
|
}
|
|
|
|
if (kStartBig < minK || kStartBig < effectiveResume)
|
|
{
|
|
kStartBig = BigInteger.Max(BigInteger.Max(minK, effectiveResume), kMinBig);
|
|
}
|
|
|
|
if (kStartBig > kMaxBig)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (kMaxBig > ulong.MaxValue)
|
|
{
|
|
kMaxBig = ulong.MaxValue;
|
|
}
|
|
|
|
ulong kStart = (ulong)kStartBig;
|
|
ulong kEnd = (ulong)kMaxBig;
|
|
|
|
bool composite = CheckDivisors64Range(
|
|
prime,
|
|
step64,
|
|
max64,
|
|
kStart,
|
|
kEnd,
|
|
ref currentK,
|
|
out bool processedRange,
|
|
out foundDivisor);
|
|
if (composite)
|
|
{
|
|
RecordPow2Hit(phase);
|
|
FlushPow2State(currentK, phase);
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentK >= (ulong)effectiveResume)
|
|
{
|
|
FlushPow2State(currentK, phase);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
if (currentK >= (ulong)effectiveResume)
|
|
{
|
|
FlushPow2State(currentK, phase);
|
|
}
|
|
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void EnsurePreparedSpecials(int specialRangePromille)
|
|
{
|
|
if (_preparedSpecialsInitialized && specialRangePromille == _preparedSpecialRangePromille)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_preparedSpecialRangePromille = specialRangePromille;
|
|
if (specialRangePromille == 0)
|
|
{
|
|
_preparedSpecialSingles = SpecialPercentPromille;
|
|
_preparedSpecialRanges = Array.Empty<(int Start, int End)>();
|
|
_preparedSpecialsInitialized = true;
|
|
return;
|
|
}
|
|
|
|
int[] specials = SpecialPercentPromille;
|
|
if (specials.Length == 0)
|
|
{
|
|
_preparedSpecialSingles = Array.Empty<int>();
|
|
_preparedSpecialRanges = Array.Empty<(int Start, int End)>();
|
|
_preparedSpecialsInitialized = true;
|
|
return;
|
|
}
|
|
|
|
int[] sorted = new int[specials.Length];
|
|
Array.Copy(specials, sorted, specials.Length);
|
|
Array.Sort(sorted);
|
|
|
|
int range = specialRangePromille;
|
|
List<(int Start, int End)> merged = new(sorted.Length);
|
|
int currentStart = Math.Max(0, sorted[0] - range);
|
|
int currentEnd = Math.Min(PercentScale, sorted[0] + range);
|
|
|
|
for (int i = 1; i < sorted.Length; i++)
|
|
{
|
|
int start = Math.Max(0, sorted[i] - range);
|
|
int end = Math.Min(PercentScale, sorted[i] + range);
|
|
if (start <= currentEnd)
|
|
{
|
|
if (end > currentEnd)
|
|
{
|
|
currentEnd = end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
merged.Add((currentStart, currentEnd));
|
|
currentStart = start;
|
|
currentEnd = end;
|
|
}
|
|
}
|
|
|
|
merged.Add((currentStart, currentEnd));
|
|
_preparedSpecialRanges = merged.ToArray();
|
|
_preparedSpecialSingles = Array.Empty<int>();
|
|
_preparedSpecialsInitialized = true;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private void FlushPow2State(BigInteger k, Pow2Phase phase)
|
|
{
|
|
if (phase == Pow2Phase.None || k < MinK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string targetPath = phase == Pow2Phase.Special ? _specialStateFilePath : _groupsStateFilePath;
|
|
ref int counter = ref (phase == Pow2Phase.Special ? ref _specialStateCounter : ref _groupsStateCounter);
|
|
ref BigInteger last = ref (phase == Pow2Phase.Special ? ref _specialLastSavedK : ref _groupsLastSavedK);
|
|
|
|
if (k <= last)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(targetPath))
|
|
{
|
|
string? directory = Path.GetDirectoryName(targetPath);
|
|
if (!string.IsNullOrEmpty(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
File.AppendAllText(targetPath, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
|
|
}
|
|
|
|
counter = 0;
|
|
last = k;
|
|
}
|
|
#endif
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisors128Range(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
UInt128 step,
|
|
BigInteger allowedMax,
|
|
UInt128 startK,
|
|
UInt128 endK,
|
|
out BigInteger foundDivisor,
|
|
out bool processedAll)
|
|
{
|
|
foundDivisor = BigInteger.Zero;
|
|
processedAll = true;
|
|
|
|
if (startK > endK)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UInt128 allowedMax128 = allowedMax <= BigIntegerNumbers.UInt128MaxValue ? (UInt128)allowedMax : UInt128.MaxValue;
|
|
UInt128 maxKAllowed = (allowedMax128 - UInt128.One) / step;
|
|
if (endK > maxKAllowed)
|
|
{
|
|
endK = maxKAllowed;
|
|
if (startK > endK)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
UInt128 divisor = (step * startK) + UInt128.One;
|
|
processedAll = true;
|
|
|
|
for (UInt128 k = startK; k <= endK; k++)
|
|
{
|
|
ushort rem3 = (ushort)(divisor % 3);
|
|
ushort rem5 = (ushort)(divisor % 5);
|
|
ushort rem7 = (ushort)(divisor % 7);
|
|
ushort rem11 = (ushort)(divisor % 11);
|
|
ushort rem13 = (ushort)(divisor % 13);
|
|
ushort rem17 = (ushort)(divisor % 17);
|
|
ushort rem19 = (ushort)(divisor % 19);
|
|
|
|
if (rem3 != 0 && rem5 != 0 && rem7 != 0 && rem11 != 0 && rem13 != 0 && rem17 != 0 && rem19 != 0)
|
|
{
|
|
int rem10 = (int)(divisor % 10);
|
|
ushort rem8 = (ushort)(divisor % 8);
|
|
if ((rem8 == 1 || rem8 == 7) && ((primeDecimalMask >> rem10) & 1) != 0)
|
|
{
|
|
BigInteger divisorBig = (BigInteger)divisor;
|
|
RecordState((BigInteger)k);
|
|
BigInteger powResult = BigInteger.ModPow(2, prime, divisorBig);
|
|
// if (powResult.IsOne && !IsMersenneValue(prime, divisorBig))
|
|
if (powResult.IsOne)
|
|
{
|
|
foundDivisor = divisorBig;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (k == endK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
divisor += step;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsLarge(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
BigInteger allowedMax,
|
|
BigInteger currentK,
|
|
BigInteger step,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
foundDivisor = BigInteger.Zero;
|
|
if (allowedMax.IsZero)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
processedAll = true;
|
|
BigInteger divisor = (step * currentK) + BigInteger.One;
|
|
if (divisor > allowedMax)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
|
|
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
|
|
if (currentK > maxKAllowed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger remainingIterationsBig = maxKAllowed - currentK + BigInteger.One;
|
|
|
|
if (step <= ulong.MaxValue)
|
|
{
|
|
UInt128 step128 = (UInt128)(ulong)step;
|
|
UInt128 maxKByStep = UInt128Numbers.MaxValueMinusOne / step128;
|
|
bool allowedFits128 = allowedMax <= BigIntegerNumbers.UInt128MaxValue;
|
|
if (allowedFits128)
|
|
{
|
|
UInt128 limitK = (((UInt128)allowedMax) - UInt128.One) / step128;
|
|
if (limitK < maxKByStep)
|
|
{
|
|
maxKByStep = limitK;
|
|
}
|
|
}
|
|
|
|
BigInteger maxKByStepBig = (BigInteger)maxKByStep;
|
|
if (maxKByStepBig >= currentK)
|
|
{
|
|
if (currentK > BigIntegerNumbers.UInt128MaxValue)
|
|
{
|
|
currentK = maxKByStepBig + BigInteger.One;
|
|
}
|
|
else
|
|
{
|
|
UInt128 startK = (UInt128)currentK;
|
|
bool composite128 = CheckDivisors128Range(
|
|
prime,
|
|
primeDecimalMask,
|
|
step128,
|
|
allowedMax,
|
|
startK,
|
|
maxKByStep,
|
|
out BigInteger found128,
|
|
out bool processed128);
|
|
if (composite128)
|
|
{
|
|
foundDivisor = found128;
|
|
return true;
|
|
}
|
|
|
|
if (!processed128)
|
|
{
|
|
processedAll = false;
|
|
}
|
|
|
|
currentK = maxKByStepBig + BigInteger.One;
|
|
divisor = (step * currentK) + BigInteger.One;
|
|
if (divisor > allowedMax)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
|
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
|
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
|
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
|
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
|
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
|
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
|
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
|
|
|
byte remainder10 = rem10.Initialize(divisor);
|
|
BigInteger primeBig = (BigInteger)prime;
|
|
while (remainingIterationsBig.Sign > 0)
|
|
{
|
|
bool passesSmallModuli = remainder10 == 1 || remainder10 == 7;
|
|
if (passesSmallModuli && ((primeDecimalMask >> remainder10) & 1) != 0)
|
|
{
|
|
if (rem3.NextIsNotDivisible(divisor) &&
|
|
rem7.NextIsNotDivisible(divisor) &&
|
|
rem11.NextIsNotDivisible(divisor) &&
|
|
rem13.NextIsNotDivisible(divisor) &&
|
|
rem17.NextIsNotDivisible(divisor) &&
|
|
rem19.NextIsNotDivisible(divisor))
|
|
{
|
|
byte remainder8 = rem8.ComputeNext(divisor);
|
|
if (remainder8 == 1 || remainder8 == 7)
|
|
{
|
|
RecordState(currentK);
|
|
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
|
|
// if (powResult.IsOne && !IsMersenneValue(prime, divisor))
|
|
if (powResult.IsOne)
|
|
{
|
|
foundDivisor = divisor;
|
|
processedAll = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
currentK++;
|
|
divisor += step;
|
|
remainder10 = rem10.ComputeNext(divisor);
|
|
|
|
remainingIterationsBig--;
|
|
if (remainingIterationsBig.IsZero)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
processedAll = true;
|
|
foundDivisor = BigInteger.Zero;
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisors64Range(
|
|
ulong prime,
|
|
ulong step,
|
|
ulong allowedMax,
|
|
ulong startK,
|
|
ulong endK,
|
|
ref ulong currentK,
|
|
out bool processedAll,
|
|
out ulong foundDivisor)
|
|
{
|
|
if (startK < 1UL || endK < startK)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
|
|
ulong maxKAllowed = (allowedMax - 1UL) / step;
|
|
{
|
|
BigInteger maxKBig = maxKAllowed;
|
|
if (TryClampMaxKForMersenneTail(prime, (BigInteger)step, ref maxKBig))
|
|
{
|
|
maxKAllowed = maxKBig > ulong.MaxValue ? ulong.MaxValue : (ulong)maxKBig;
|
|
}
|
|
}
|
|
if (endK > maxKAllowed)
|
|
{
|
|
endK = maxKAllowed;
|
|
if (startK > endK)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
}
|
|
currentK = startK;
|
|
|
|
UInt128 step128 = step;
|
|
UInt128 allowedMax128 = allowedMax;
|
|
UInt128 startDivisor128 = (step128 * startK) + UInt128.One;
|
|
if (startDivisor128 > allowedMax128)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
|
|
UInt128 rangeLimit128 = (step128 * endK) + UInt128.One;
|
|
if (rangeLimit128 > allowedMax128)
|
|
{
|
|
rangeLimit128 = allowedMax128;
|
|
}
|
|
|
|
ulong startDivisor = (ulong)startDivisor128;
|
|
ulong rangeLimit = (ulong)rangeLimit128;
|
|
|
|
var residueStepper = new MersenneDivisorResidueStepper(prime, (GpuUInt128)step128, (GpuUInt128)startDivisor128);
|
|
|
|
return CheckDivisors64(
|
|
prime,
|
|
step,
|
|
rangeLimit,
|
|
startDivisor,
|
|
ref residueStepper,
|
|
ref currentK,
|
|
out processedAll,
|
|
out foundDivisor);
|
|
}
|
|
|
|
#if DivisorSet_TopDown
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsTopDown(
|
|
ulong prime,
|
|
BigInteger allowedMax,
|
|
BigInteger minK,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
foundDivisor = BigInteger.Zero;
|
|
processedAll = false;
|
|
|
|
BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One;
|
|
ulong step = prime << 1;
|
|
ulong allowedMax64 = allowedMax.IsZero ? ulong.MaxValue : (allowedMax > ulong.MaxValue ? ulong.MaxValue : (ulong)allowedMax);
|
|
if (allowedMax64 <= 1UL)
|
|
{
|
|
processedAll = true;
|
|
return false;
|
|
}
|
|
|
|
ulong maxKAllowed = (allowedMax64 - 1UL) / step;
|
|
{
|
|
BigInteger maxKBig = maxKAllowed;
|
|
if (TryClampMaxKForMersenneTail(prime, (BigInteger)step, ref maxKBig))
|
|
{
|
|
maxKAllowed = maxKBig > ulong.MaxValue ? ulong.MaxValue : (ulong)maxKBig;
|
|
}
|
|
}
|
|
|
|
if (maxKAllowed < 1UL)
|
|
{
|
|
processedAll = true;
|
|
return false;
|
|
}
|
|
|
|
if (normalizedMinK > maxKAllowed)
|
|
{
|
|
processedAll = true;
|
|
return false;
|
|
}
|
|
|
|
ulong minK64 = normalizedMinK > ulong.MaxValue ? ulong.MaxValue : (ulong)normalizedMinK;
|
|
if (minK64 < 1UL)
|
|
{
|
|
minK64 = 1UL;
|
|
}
|
|
|
|
ulong cursor = _topDownCursor > ulong.MaxValue ? ulong.MaxValue : (ulong)_topDownCursor;
|
|
ulong currentK = maxKAllowed;
|
|
if (cursor > 0 && cursor <= maxKAllowed)
|
|
{
|
|
currentK = maxKAllowed - cursor;
|
|
}
|
|
|
|
if (currentK < minK64)
|
|
{
|
|
processedAll = true;
|
|
return false;
|
|
}
|
|
|
|
UInt128 step128 = step;
|
|
UInt128 divisor = (step128 * currentK) + UInt128.One;
|
|
var residueStepper = new MersenneDivisorResidueStepperDescending(prime, new GpuUInt128(step), new GpuUInt128(divisor));
|
|
while (currentK >= minK64)
|
|
{
|
|
if (divisor > allowedMax64)
|
|
{
|
|
processedAll = true;
|
|
return false;
|
|
}
|
|
|
|
if (residueStepper.IsAdmissible())
|
|
{
|
|
ulong divisor64 = (ulong)divisor;
|
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64);
|
|
#if DEVICE_GPU
|
|
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
|
|
divisor64,
|
|
Accelerator,
|
|
prime,
|
|
divisorData,
|
|
out ulong computedCycle,
|
|
out bool primeOrderFailed);
|
|
#elif DEVICE_HYBRID
|
|
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
|
|
divisor64,
|
|
Accelerator,
|
|
prime,
|
|
divisorData,
|
|
out ulong computedCycle,
|
|
out bool primeOrderFailed);
|
|
#else
|
|
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
|
|
divisor64,
|
|
prime,
|
|
divisorData,
|
|
out ulong computedCycle,
|
|
out bool primeOrderFailed);
|
|
#endif
|
|
ulong divisorCycle;
|
|
if (!computed || computedCycle == 0UL)
|
|
{
|
|
#if DEVICE_GPU
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
|
|
divisor64,
|
|
Accelerator,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#elif DEVICE_HYBRID
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
|
|
divisor64,
|
|
Accelerator,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#else
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
|
|
divisor64,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
divisorCycle = computedCycle;
|
|
}
|
|
|
|
RecordState(cursor);
|
|
if (divisorCycle == prime && !IsMersenneValue(prime, divisor64))
|
|
{
|
|
foundDivisor = divisor64;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
cursor++;
|
|
if (cursor > maxKAllowed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (currentK == minK64)
|
|
{
|
|
break;
|
|
}
|
|
|
|
currentK--;
|
|
divisor -= step128;
|
|
residueStepper.Retreat();
|
|
}
|
|
|
|
processedAll = true;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisors64(
|
|
ulong prime,
|
|
ulong step,
|
|
ulong limit,
|
|
ulong divisor,
|
|
ref MersenneDivisorResidueStepper residueStepper,
|
|
ref ulong currentK,
|
|
out bool processedAll,
|
|
out ulong foundDivisor)
|
|
{
|
|
#if DEVICE_GPU || DEVICE_HYBRID
|
|
PrimeOrderCalculatorAccelerator gpu = Accelerator;
|
|
#endif
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
ulong computedCycle;
|
|
bool primeOrderFailed;
|
|
ulong divisorCycle;
|
|
bool computed;
|
|
if (step > limit)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
|
|
if (divisor > limit)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
|
|
ulong maxKAllowed = ((limit - 1UL) / step);
|
|
ulong targetK = ((divisor - 1UL) / step);
|
|
if (targetK > maxKAllowed)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
|
|
ulong remainingIterations = (maxKAllowed - targetK) + 1UL;
|
|
while (remainingIterations > 0UL)
|
|
{
|
|
if (residueStepper.IsAdmissible())
|
|
{
|
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
|
#if DEVICE_GPU
|
|
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
|
|
divisor,
|
|
gpu,
|
|
prime,
|
|
divisorData,
|
|
out computedCycle,
|
|
out primeOrderFailed);
|
|
#elif DEVICE_HYBRID
|
|
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
|
|
divisor,
|
|
gpu,
|
|
prime,
|
|
divisorData,
|
|
out computedCycle,
|
|
out primeOrderFailed);
|
|
#else
|
|
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
|
|
divisor,
|
|
prime,
|
|
divisorData,
|
|
out computedCycle,
|
|
out primeOrderFailed);
|
|
#endif
|
|
if (!computed || computedCycle == 0UL)
|
|
{
|
|
#if DEVICE_GPU
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
|
|
divisor,
|
|
gpu,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#elif DEVICE_HYBRID
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
|
|
divisor,
|
|
gpu,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#else
|
|
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
|
|
divisor,
|
|
divisorData,
|
|
skipPrimeOrderHeuristic: primeOrderFailed);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
divisorCycle = computedCycle;
|
|
}
|
|
|
|
RecordState(currentK);
|
|
if (divisorCycle == prime && !IsMersenneValue(prime, divisor))
|
|
{
|
|
foundDivisor = divisor;
|
|
return true;
|
|
}
|
|
|
|
if (divisorCycle == 0UL)
|
|
{
|
|
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
|
}
|
|
}
|
|
|
|
remainingIterations--;
|
|
if (remainingIterations == 0UL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
divisor += step;
|
|
currentK++;
|
|
residueStepper.Advance();
|
|
}
|
|
|
|
processedAll = true;
|
|
foundDivisor = 0UL;
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private void RecordState(BigInteger k)
|
|
{
|
|
#if DivisorSet_TopDown
|
|
if (_currentPrime != 0UL)
|
|
{
|
|
if (k > _lastSavedK)
|
|
{
|
|
int next = _stateCounter + 1;
|
|
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
|
|
{
|
|
_kRepository.Upsert(_currentPrime, k);
|
|
_stateCounter = 0;
|
|
_lastSavedK = k;
|
|
_topDownCursor = k;
|
|
}
|
|
else
|
|
{
|
|
_stateCounter = next;
|
|
}
|
|
_topDownCursor = k;
|
|
}
|
|
}
|
|
return;
|
|
#endif
|
|
#if DivisorSet_Pow2Groups
|
|
if (_currentPow2Phase == Pow2Phase.Special)
|
|
{
|
|
RecordStatePow2(k, _specialRepository, ref _specialStateCounter, ref _specialLastSavedK);
|
|
return;
|
|
}
|
|
|
|
if (_currentPow2Phase == Pow2Phase.Groups)
|
|
{
|
|
RecordStatePow2(k, _groupsRepository, ref _groupsStateCounter, ref _groupsLastSavedK);
|
|
return;
|
|
}
|
|
#endif
|
|
if (_currentPrime != 0UL)
|
|
{
|
|
if (k > _lastSavedK)
|
|
{
|
|
int next = _stateCounter + 1;
|
|
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
|
|
{
|
|
_kRepository.Upsert(_currentPrime, k);
|
|
_lastSavedK = k;
|
|
_stateCounter = 0;
|
|
}
|
|
else
|
|
{
|
|
_stateCounter = next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if DivisorSet_Pow2Groups
|
|
private void RecordState(BigInteger k, Pow2Phase phase)
|
|
{
|
|
switch (phase)
|
|
{
|
|
case Pow2Phase.Special:
|
|
RecordStatePow2(k, _specialRepository, ref _specialStateCounter, ref _specialLastSavedK);
|
|
break;
|
|
case Pow2Phase.Groups:
|
|
RecordStatePow2(k, _groupsRepository, ref _groupsStateCounter, ref _groupsLastSavedK);
|
|
break;
|
|
default:
|
|
RecordState(k);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void RecordStatePow2(BigInteger k, KStateRepository? repo, ref int counter, ref BigInteger lastSavedK)
|
|
{
|
|
if (_currentPrime == 0UL || repo is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (k <= lastSavedK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int next = counter + 1;
|
|
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
|
|
{
|
|
repo.Upsert(_currentPrime, k);
|
|
counter = 0;
|
|
lastSavedK = k;
|
|
}
|
|
else
|
|
{
|
|
counter = next;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private static void RecordPow2Hit(Pow2Phase phase)
|
|
{
|
|
long special = Interlocked.Read(ref _specialHits);
|
|
long groups = Interlocked.Read(ref _groupHits);
|
|
if (phase == Pow2Phase.Special)
|
|
{
|
|
special = Interlocked.Increment(ref _specialHits);
|
|
}
|
|
else if (phase == Pow2Phase.Groups)
|
|
{
|
|
groups = Interlocked.Increment(ref _groupHits);
|
|
}
|
|
|
|
Console.WriteLine($"By-divisor pow2groups hits: specials={special}, groups={groups}");
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private static int GetBitLength(BigInteger value)
|
|
{
|
|
if (value.IsZero)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
byte[] bytes = value.ToByteArray();
|
|
int lastIndex = bytes.Length - 1;
|
|
return (lastIndex * 8) + BitOperations.Log2(bytes[lastIndex]) + 1;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool ProcessPow2PhaseBig(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
BigInteger allowedMax,
|
|
BigInteger step,
|
|
BigInteger resumeK,
|
|
BigInteger minK,
|
|
BigInteger segmentStartK,
|
|
BigInteger segmentEndK,
|
|
Pow2Phase phase,
|
|
ReadOnlySpan<(int Start, int End)> ranges,
|
|
ReadOnlySpan<int> singles,
|
|
ref bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
foundDivisor = BigInteger.Zero;
|
|
if (allowedMax.IsZero || segmentStartK > segmentEndK)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int specialRangePromille = EnvironmentConfiguration.ByDivisorSpecialRange;
|
|
if (specialRangePromille < 0)
|
|
{
|
|
specialRangePromille = 0;
|
|
}
|
|
specialRangePromille *= 100; // convert percent to promille-of-percentScale (10_000 = 100%)
|
|
EnsurePreparedSpecials(specialRangePromille);
|
|
|
|
BigInteger effectiveStart = segmentStartK;
|
|
if (resumeK > effectiveStart)
|
|
{
|
|
effectiveStart = resumeK;
|
|
}
|
|
|
|
if (effectiveStart < minK)
|
|
{
|
|
effectiveStart = minK;
|
|
}
|
|
|
|
if (effectiveStart > segmentEndK)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BigInteger firstDivisor = (step * effectiveStart) + BigInteger.One;
|
|
if (firstDivisor > allowedMax)
|
|
{
|
|
processedAll = false;
|
|
return false;
|
|
}
|
|
|
|
int startBit = GetBitLength(firstDivisor) - 1;
|
|
int maxBit = GetBitLength(allowedMax) - 1;
|
|
_currentPow2Phase = phase;
|
|
FlushPow2State(effectiveStart, phase);
|
|
|
|
ReadOnlySpan<int> effectiveSingles = singles;
|
|
ReadOnlySpan<(int Start, int End)> effectiveRanges = ranges;
|
|
if (phase == Pow2Phase.Special)
|
|
{
|
|
effectiveSingles = _preparedSpecialSingles;
|
|
effectiveRanges = _preparedSpecialRanges;
|
|
}
|
|
|
|
try
|
|
{
|
|
for (int bit = startBit; bit <= maxBit; bit++)
|
|
{
|
|
BigInteger lower = BigInteger.One << bit;
|
|
BigInteger upperExclusive = BigInteger.One << (bit + 1);
|
|
|
|
if (lower > allowedMax)
|
|
{
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < effectiveSingles.Length; i++)
|
|
{
|
|
int startProm = effectiveSingles[i];
|
|
int endProm = effectiveSingles[i];
|
|
if (specialRangePromille > 0)
|
|
{
|
|
startProm = Math.Max(0, effectiveSingles[i] - specialRangePromille);
|
|
endProm = Math.Min(PercentScale, effectiveSingles[i] + specialRangePromille);
|
|
}
|
|
|
|
BigInteger scaledStart = (PercentScale + startProm) * lower;
|
|
BigInteger scaledEnd = (PercentScale + (specialRangePromille == 0 ? Math.Min(PercentScale, startProm + 1) : endProm)) * lower;
|
|
|
|
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
|
|
BigInteger qMax = scaledEnd / PercentScale;
|
|
|
|
if (qMin.IsEven)
|
|
{
|
|
qMin += BigInteger.One;
|
|
}
|
|
|
|
if (qMax.IsEven)
|
|
{
|
|
qMax -= BigInteger.One;
|
|
}
|
|
|
|
if (qMin < 3 || qMin > qMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMin < lower)
|
|
{
|
|
qMin = lower | BigInteger.One;
|
|
}
|
|
|
|
if (qMax >= upperExclusive)
|
|
{
|
|
qMax = upperExclusive - BigInteger.One;
|
|
if ((qMax & BigInteger.One) == BigInteger.Zero)
|
|
{
|
|
qMax -= BigInteger.One;
|
|
}
|
|
}
|
|
|
|
if (qMin > qMax || qMin > allowedMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMax > allowedMax)
|
|
{
|
|
qMax = allowedMax;
|
|
if ((qMax & BigInteger.One) == BigInteger.Zero)
|
|
{
|
|
qMax -= BigInteger.One;
|
|
}
|
|
}
|
|
|
|
BigInteger kMin = (qMin - BigInteger.One + (step - BigInteger.One)) / step;
|
|
BigInteger kMax = (qMax - BigInteger.One) / step;
|
|
|
|
if (kMin < minK || kMin < effectiveStart)
|
|
{
|
|
kMin = BigInteger.Max(BigInteger.Max(minK, effectiveStart), kMin);
|
|
}
|
|
|
|
if (kMax > segmentEndK)
|
|
{
|
|
kMax = segmentEndK;
|
|
}
|
|
|
|
if (kMin > kMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
BigInteger currentK = kMin;
|
|
bool composite = CheckDivisorsBigRangePow2(
|
|
prime,
|
|
primeDecimalMask,
|
|
step,
|
|
allowedMax,
|
|
kMin,
|
|
kMax,
|
|
phase,
|
|
ref currentK,
|
|
out bool processedRange,
|
|
out BigInteger foundBig);
|
|
if (composite)
|
|
{
|
|
RecordPow2Hit(phase);
|
|
FlushPow2State(currentK, phase);
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
foundDivisor = foundBig;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < effectiveRanges.Length; i++)
|
|
{
|
|
int startProm = effectiveRanges[i].Start;
|
|
int endProm = effectiveRanges[i].End;
|
|
if (specialRangePromille > 0 && phase == Pow2Phase.Special)
|
|
{
|
|
startProm = Math.Max(0, startProm - specialRangePromille);
|
|
endProm = Math.Min(PercentScale, endProm + specialRangePromille);
|
|
}
|
|
|
|
BigInteger scaledStart = (PercentScale + startProm) * lower;
|
|
BigInteger scaledEnd = (PercentScale + endProm) * lower;
|
|
|
|
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
|
|
BigInteger qMax = scaledEnd / PercentScale;
|
|
|
|
if ((qMin & BigInteger.One) == BigInteger.Zero)
|
|
{
|
|
qMin += BigInteger.One;
|
|
}
|
|
|
|
if ((qMax & BigInteger.One) == BigInteger.Zero)
|
|
{
|
|
qMax -= BigInteger.One;
|
|
}
|
|
|
|
if (qMin < 3 || qMin > qMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMin < lower)
|
|
{
|
|
qMin = lower | BigInteger.One;
|
|
}
|
|
|
|
if (qMax >= upperExclusive)
|
|
{
|
|
qMax = upperExclusive - BigInteger.One;
|
|
if ((qMax & BigInteger.One) == BigInteger.Zero)
|
|
{
|
|
qMax -= BigInteger.One;
|
|
}
|
|
}
|
|
|
|
if (qMin > qMax || qMin > allowedMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (qMax > allowedMax)
|
|
{
|
|
qMax = allowedMax;
|
|
if ((qMax & BigInteger.One) == BigInteger.Zero)
|
|
{
|
|
qMax -= BigInteger.One;
|
|
}
|
|
}
|
|
|
|
BigInteger kMin = (qMin - BigInteger.One + (step - BigInteger.One)) / step;
|
|
BigInteger kMax = (qMax - BigInteger.One) / step;
|
|
|
|
if (kMin < minK || kMin < effectiveStart)
|
|
{
|
|
kMin = BigInteger.Max(BigInteger.Max(minK, effectiveStart), kMin);
|
|
}
|
|
|
|
if (kMax > segmentEndK)
|
|
{
|
|
kMax = segmentEndK;
|
|
}
|
|
|
|
if (kMin > kMax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
BigInteger currentK = kMin;
|
|
bool composite = CheckDivisorsBigRangePow2(
|
|
prime,
|
|
primeDecimalMask,
|
|
step,
|
|
allowedMax,
|
|
kMin,
|
|
kMax,
|
|
phase,
|
|
ref currentK,
|
|
out bool processedRange,
|
|
out BigInteger foundBig);
|
|
if (composite)
|
|
{
|
|
RecordPow2Hit(phase);
|
|
FlushPow2State(currentK, phase);
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
foundDivisor = foundBig;
|
|
return true;
|
|
}
|
|
|
|
if (!processedRange)
|
|
{
|
|
processedAll = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_currentPow2Phase = Pow2Phase.None;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsBigRangePow2(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
BigInteger step,
|
|
BigInteger allowedMax,
|
|
BigInteger startK,
|
|
BigInteger endK,
|
|
Pow2Phase phase,
|
|
ref BigInteger currentK,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
if (startK < BigInteger.One || endK < startK)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = BigInteger.Zero;
|
|
return false;
|
|
}
|
|
|
|
currentK = startK;
|
|
BigInteger divisor = (step * currentK) + BigInteger.One;
|
|
if (divisor > allowedMax && !allowedMax.IsZero)
|
|
{
|
|
processedAll = false;
|
|
foundDivisor = BigInteger.Zero;
|
|
return false;
|
|
}
|
|
|
|
BigInteger iterations = (endK - currentK) + BigInteger.One;
|
|
|
|
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
|
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
|
var rem5 = new BigIntegerCycleRemainderStepper(5);
|
|
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
|
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
|
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
|
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
|
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
|
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
|
|
|
byte remainder10 = (byte)rem10.Initialize(divisor);
|
|
byte remainder8 = (byte)rem8.Initialize(divisor);
|
|
byte remainder5 = (byte)rem5.Initialize(divisor);
|
|
byte remainder3 = (byte)rem3.Initialize(divisor);
|
|
byte remainder7 = (byte)rem7.Initialize(divisor);
|
|
byte remainder11 = (byte)rem11.Initialize(divisor);
|
|
byte remainder13 = (byte)rem13.Initialize(divisor);
|
|
byte remainder17 = (byte)rem17.Initialize(divisor);
|
|
byte remainder19 = (byte)rem19.Initialize(divisor);
|
|
|
|
while (iterations > BigInteger.Zero)
|
|
{
|
|
bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
|
|
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((primeDecimalMask >> remainder10) & 1) != 0)
|
|
{
|
|
RecordState(currentK, phase);
|
|
BigInteger powResult = BigInteger.ModPow(2, prime, divisor);
|
|
if (powResult.IsOne && !IsMersenneValue(prime, divisor))
|
|
{
|
|
foundDivisor = divisor;
|
|
processedAll = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
currentK += BigInteger.One;
|
|
divisor += step;
|
|
remainder10 = (byte)rem10.ComputeNext(divisor);
|
|
remainder8 = (byte)rem8.ComputeNext(divisor);
|
|
remainder5 = (byte)rem5.ComputeNext(divisor);
|
|
remainder3 = (byte)rem3.ComputeNext(divisor);
|
|
remainder7 = (byte)rem7.ComputeNext(divisor);
|
|
remainder11 = (byte)rem11.ComputeNext(divisor);
|
|
remainder13 = (byte)rem13.ComputeNext(divisor);
|
|
remainder17 = (byte)rem17.ComputeNext(divisor);
|
|
remainder19 = (byte)rem19.ComputeNext(divisor);
|
|
|
|
iterations--;
|
|
}
|
|
|
|
processedAll = true;
|
|
foundDivisor = BigInteger.Zero;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if DivisorSet_BitContradiction
|
|
private static readonly BigInteger Five = (BigInteger)5;
|
|
private static int _consolePrintOut;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool CheckDivisorsBitContradiction(
|
|
ulong prime,
|
|
ushort primeDecimalMask,
|
|
BigInteger allowedMax,
|
|
BigInteger minK,
|
|
BigInteger step,
|
|
out bool processedAll,
|
|
out BigInteger foundDivisor)
|
|
{
|
|
processedAll = true;
|
|
foundDivisor = BigInteger.Zero;
|
|
|
|
BigInteger currentK = minK;
|
|
bool hasLimit = !allowedMax.IsZero;
|
|
BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero;
|
|
int stepMod8 = (int)(step % 8);
|
|
int kRem8 = (int)(currentK % 8);
|
|
|
|
BigInteger divisor = (step * currentK) + BigInteger.One;
|
|
_qBits = new int[(int)prime];
|
|
Stopwatch sw = new Stopwatch();
|
|
// sw.Start();
|
|
while (true)
|
|
{
|
|
int remainder8 = (stepMod8 * kRem8 + 1) & 7;
|
|
if (remainder8 != 1 && remainder8 != 7)
|
|
{
|
|
goto MOVE_NEXT;
|
|
}
|
|
|
|
// if (
|
|
// divisor <= ulong.MaxValue &&
|
|
// !PrimeTesterByLastDigit.IsPrimeCpu((ulong)divisor)
|
|
// )
|
|
// {
|
|
// goto MOVE_NEXT;
|
|
// }
|
|
|
|
bool divides;
|
|
// divides = BigInteger.ModPow(BigIntegerNumbers.Two, prime, divisor) == 1;
|
|
|
|
sw.Reset();
|
|
sw.Start();
|
|
bool decided = TryExactBitContradictionCheck(prime, divisor, out divides);
|
|
sw.Stop();
|
|
if (!decided)
|
|
{
|
|
throw new InvalidOperationException($"BitContradiction check did not decide for p={prime}, divisor={divisor} (k={currentK}).");
|
|
}
|
|
|
|
if (divides)
|
|
{
|
|
foundDivisor = divisor;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// var printToConsole = (_consolePrintOut++) == 1;
|
|
// if (printToConsole)
|
|
// {
|
|
// sw.Stop();
|
|
Console.WriteLine($"Prime {prime} ({sw.Elapsed}: {divisor})");
|
|
_consolePrintOut = 0;
|
|
// sw.Reset();
|
|
// sw.Start();
|
|
// }
|
|
}
|
|
|
|
// RecordState(currentK);
|
|
MOVE_NEXT:
|
|
// if (hasLimit && currentK >= maxK)
|
|
// {
|
|
// break;
|
|
// }
|
|
|
|
// currentK += BigInteger.One;
|
|
divisor += step;
|
|
kRem8 = (kRem8 + 1) & 7;
|
|
}
|
|
|
|
return false;
|
|
|
|
// bool fits64 = minK <= ulong.MaxValue;
|
|
// bool limitFits64 = allowedMax.IsZero || allowedMax <= ulong.MaxValue;
|
|
|
|
// if (fits64 && limitFits64)
|
|
// {
|
|
// ulong allowedMax64 = allowedMax.IsZero ? ulong.MaxValue : (ulong)allowedMax;
|
|
// return CheckDivisors64Bit(
|
|
// prime,
|
|
// primeDecimalMask,
|
|
// allowedMax64,
|
|
// (ulong)minK,
|
|
// out processedAll,
|
|
// out ulong found64);
|
|
// }
|
|
|
|
// if (fits64 && allowedMax > ulong.MaxValue)
|
|
// {
|
|
// ulong allowedMax64 = ulong.MaxValue;
|
|
// bool composite64 = CheckDivisors64Bit(
|
|
// prime,
|
|
// primeDecimalMask,
|
|
// allowedMax64,
|
|
// (ulong)minK,
|
|
// out processedAll,
|
|
// out ulong found64);
|
|
// if (composite64)
|
|
// {
|
|
// foundDivisor = found64;
|
|
// return true;
|
|
// }
|
|
|
|
// BigInteger iterations = ((BigInteger)(allowedMax64 - (((ulong)prime << 1) * (ulong)minK) - 1UL) / (ulong)step) + BigInteger.One;
|
|
// BigInteger nextK = minK + iterations;
|
|
// return CheckDivisorsLarge(
|
|
// prime,
|
|
// primeDecimalMask,
|
|
// allowedMax,
|
|
// nextK,
|
|
// step,
|
|
// out processedAll,
|
|
// out foundDivisor);
|
|
// }
|
|
|
|
// return CheckDivisorsLarge(
|
|
// prime,
|
|
// primeDecimalMask,
|
|
// allowedMax,
|
|
// minK,
|
|
// step,
|
|
// out processedAll,
|
|
// out foundDivisor);
|
|
}
|
|
|
|
[ThreadStatic]
|
|
private static int[]? _qBits;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, out bool divides)
|
|
{
|
|
divides = false;
|
|
// const ulong DebugTopDownDivisor = 1209708008767UL;
|
|
// const ulong DebugTopDownDivisor = 1007991562955719UL;
|
|
|
|
// Divisors generated by EvenPerfectBitScanner are of the form 2kp+1 and are never even. No need to check these.
|
|
// if (prime == 0 || divisor <= BigInteger.One)
|
|
// {
|
|
// Console.WriteLine($"[bitcontradiction] invalid inputs {prime} / {divisor}");
|
|
// return false;
|
|
// }
|
|
|
|
// divisor = DebugTopDownDivisor;
|
|
int qBitLength = GetBitLengthPortable(divisor);
|
|
if (qBitLength > (int)prime)
|
|
{
|
|
divides = false;
|
|
return true;
|
|
}
|
|
|
|
// Divisors generated by EvenPerfectBitScanner are of the form 2kp+1 and are never even. No need to check it.
|
|
// Fast structural contradictions only: q must be odd and the implied maximum length of a must be positive.
|
|
// if ((divisor & BigInteger.One) == BigInteger.Zero)
|
|
// {
|
|
// divides = false;
|
|
// return true;
|
|
// }
|
|
|
|
if (prime <= (ulong)qBitLength)
|
|
{
|
|
divides = false;
|
|
return true;
|
|
}
|
|
|
|
// if (divisor <= ulong.MaxValue)
|
|
// {
|
|
// // ulong exponent64 = effectiveExponent > 0UL ? effectiveExponent : prime;
|
|
// // ContradictionReason reason64;
|
|
// // bool decided64 = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromDivisor((ulong)divisor, exponent64, out divides, out reason64);
|
|
// // if (decided64 && divides)
|
|
// // {
|
|
// // bool structuralOk = ((divisor - BigInteger.One) % (((BigInteger)prime) << 1)) == BigInteger.Zero;
|
|
// // bool dividesExact = structuralOk && BigInteger.ModPow(2, prime, divisor) == BigInteger.One;
|
|
// divides = BigInteger.ModPow(2, prime, divisor) == BigInteger.One;
|
|
// return true;
|
|
// // if (!dividesExact)
|
|
// // {
|
|
// // divides = false;
|
|
// // reason64 = ContradictionReason.ParityUnreachable;
|
|
// // }
|
|
// // }
|
|
|
|
// // return decided64;
|
|
// }
|
|
|
|
// Console.WriteLine($"Switching to bit-contradiction mode for {prime}");
|
|
// Iterative column-wise propagation without storing all columns; we only keep assigned bits of 'a'.
|
|
int[] oneOffsets = _qBits;
|
|
// Span<int> oneOffsets = stackalloc int[qBitLength];
|
|
int oneCount = 0;
|
|
for (int i = 0; i < qBitLength; i++)
|
|
{
|
|
if (((divisor >> i) & BigInteger.One) == BigInteger.One)
|
|
{
|
|
oneOffsets[oneCount++] = i;
|
|
}
|
|
}
|
|
|
|
if (oneCount == 0)
|
|
{
|
|
divides = false;
|
|
return true;
|
|
}
|
|
|
|
ReadOnlySpan<int> oneOffsetsSlice = oneOffsets[..oneCount];
|
|
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(true);
|
|
bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, prime, out divides, out var reason);
|
|
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(false);
|
|
// bool decided = BitContradictionSolverBaseline.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason);
|
|
if (decided && divides)
|
|
{
|
|
// Verify to avoid false positives on structurally admissible but non-dividing q.
|
|
bool structuralOk = ((divisor - BigInteger.One) % (((BigInteger)prime) << 1)) == BigInteger.Zero;
|
|
bool dividesExact = structuralOk && BigInteger.ModPow(2, prime, divisor) == BigInteger.One;
|
|
if (!dividesExact)
|
|
{
|
|
divides = false;
|
|
reason = ContradictionReason.ParityUnreachable;
|
|
}
|
|
}
|
|
if (decided && !divides)
|
|
{
|
|
#if DETAILED_LOG
|
|
if (reason == ContradictionReason.ParityUnreachable)
|
|
{
|
|
// var failure = BitContradictionSolverWithQMultiplier.LastTopDownFailure;
|
|
// if (failure.HasValue)
|
|
// {
|
|
// var info = failure.Value;
|
|
// Console.WriteLine($"[bit-contradiction] top-down prune failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] unknown={info.Unknown}");
|
|
// }
|
|
|
|
// var bottomUp = BitContradictionSolverWithQMultiplier.LastBottomUpFailure;
|
|
// if (bottomUp.HasValue)
|
|
// {
|
|
// var info = bottomUp.Value;
|
|
// Console.WriteLine($"[bit-contradiction] bottom-up failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] forced={info.Forced} unknown={info.Unknown}");
|
|
// }
|
|
}
|
|
#endif
|
|
|
|
// _bitContradictionStateRepository?.Upsert(prime, "excluded");
|
|
}
|
|
|
|
#if DETAILED_LOG
|
|
Console.WriteLine($"[bitcontradiction ({sw.Elapsed})] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides} Reason={reason}");
|
|
#endif
|
|
return decided;
|
|
}
|
|
#endif
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private void MarkPow2MinusOneChecked()
|
|
{
|
|
_pow2MinusOneChecked = true;
|
|
if (_currentPrime != 0UL)
|
|
{
|
|
_pow2MinusOneRepository?.MarkChecked(_currentPrime);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
private bool TryFindPowerOfTwoMinusOneDivisor(ulong prime, BigInteger allowedMax, out BigInteger divisor)
|
|
{
|
|
divisor = BigInteger.Zero;
|
|
if (_pow2MinusOneChecked)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!_checkedPow2MinusOne.TryAdd(prime, 0))
|
|
{
|
|
return false;
|
|
}
|
|
BigInteger limit = allowedMax.IsZero ? new BigInteger(ulong.MaxValue) : allowedMax;
|
|
|
|
if (limit <= ulong.MaxValue)
|
|
{
|
|
ulong limit64 = (ulong)limit;
|
|
for (int x = 2; x < 64; x++)
|
|
{
|
|
if ((ulong)x == prime)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ulong d = (1UL << x) - 1UL;
|
|
if (d > limit64)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (BigInteger.ModPow(2, prime, d) == BigInteger.One)
|
|
{
|
|
divisor = d;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
BigInteger pow = BigIntegerNumbers.OneShiftedLeft63;
|
|
for (int x = 63; ; x++)
|
|
{
|
|
pow <<= 1;
|
|
BigInteger dBig = pow - BigInteger.One;
|
|
if (dBig > limit)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ((ulong)x == prime)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (BigInteger.ModPow(2, prime, dBig) == BigInteger.One)
|
|
{
|
|
divisor = dBig;
|
|
_checkedPow2MinusOne[prime] = 1;
|
|
MarkPow2MinusOneChecked();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
_checkedPow2MinusOne[prime] = 1;
|
|
_currentPrime = prime;
|
|
MarkPow2MinusOneChecked();
|
|
return false;
|
|
}
|
|
}
|