103 lines
3.5 KiB
C#
103 lines
3.5 KiB
C#
using System;
|
|
using System.Numerics;
|
|
|
|
namespace PerfectNumbers.Core;
|
|
|
|
internal struct CycleRemainderStepper
|
|
{
|
|
private readonly ulong _cycleLength;
|
|
private ulong _previousPrime;
|
|
private ulong _currentRemainder;
|
|
private bool _hasState;
|
|
|
|
public CycleRemainderStepper(ulong cycleLength)
|
|
{
|
|
if (cycleLength == 0UL)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(cycleLength), "Cycle length must be non-zero.");
|
|
}
|
|
|
|
_cycleLength = cycleLength;
|
|
_previousPrime = 0UL;
|
|
_currentRemainder = 0UL;
|
|
_hasState = false;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
// TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
|
|
// measured fastest in MersenneDivisorCycleLengthGpuBenchmarks, avoiding the extra
|
|
// method call when scanners need to restart stepping.
|
|
_previousPrime = 0UL;
|
|
_currentRemainder = 0UL;
|
|
_hasState = false;
|
|
}
|
|
|
|
public ulong Initialize(ulong prime)
|
|
{
|
|
ulong cycleLength = _cycleLength;
|
|
ulong remainder = prime;
|
|
if (remainder >= cycleLength)
|
|
{
|
|
// TODO: Swap this `%` for the shared divisor-cycle remainder helper so initialization reuses the cached
|
|
// cycle deltas benchmarked faster than on-the-fly modulo work, matching the
|
|
// MersenneDivisorCycleLengthGpuBenchmarks winner for both CPU and GPU call sites.
|
|
remainder %= cycleLength;
|
|
}
|
|
_previousPrime = prime;
|
|
_currentRemainder = remainder;
|
|
_hasState = true;
|
|
return remainder;
|
|
}
|
|
|
|
public ulong ComputeNext(ulong prime)
|
|
{
|
|
if (!_hasState)
|
|
{
|
|
throw new InvalidOperationException("CycleRemainderStepper must be initialized before computing.");
|
|
}
|
|
|
|
if (prime <= _previousPrime)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(prime), "Primes must be processed in strictly increasing order.");
|
|
}
|
|
|
|
ulong cycleLength = _cycleLength;
|
|
ulong delta = prime - _previousPrime;
|
|
_previousPrime = prime;
|
|
|
|
if (ulong.MaxValue - _currentRemainder < delta)
|
|
{
|
|
UInt128 extended = (UInt128)_currentRemainder + delta;
|
|
// TODO: Replace this `%` with the UInt128-aware cycle reducer so large deltas use the cached subtraction
|
|
// ladder instead of falling back to the slower modulo implementation.
|
|
_currentRemainder = (ulong)(extended % cycleLength);
|
|
return _currentRemainder;
|
|
}
|
|
|
|
_currentRemainder += delta;
|
|
|
|
if (_currentRemainder >= cycleLength)
|
|
{
|
|
_currentRemainder -= cycleLength;
|
|
if (_currentRemainder >= cycleLength)
|
|
{
|
|
// TODO: Route this `%` through the shared divisor-cycle helper so repeated wrap-arounds avoid
|
|
// modulo operations and match the benchmarked fast path highlighted in
|
|
// MersenneDivisorCycleLengthGpuBenchmarks.
|
|
_currentRemainder %= cycleLength;
|
|
}
|
|
}
|
|
return _currentRemainder;
|
|
}
|
|
|
|
public void CaptureState(out ulong previousPrime, out ulong previousRemainder, out bool hasState)
|
|
{
|
|
// TODO: Expose these fields directly once the residue scanners adopt the single-cycle helper
|
|
// so the hot path can read them without paying for an additional wrapper call per iteration.
|
|
previousPrime = _previousPrime;
|
|
previousRemainder = _currentRemainder;
|
|
hasState = _hasState;
|
|
}
|
|
}
|