Reuse Pollard rho reciprocals for fast Montgomery stepping

This commit is contained in:
Marek Lemańczyk committed 2025-10-10 13:44:23 +02:00
1 parent 01ac80466b
commit 645969309c
5 files changed
+352 -43

No files matched your search

+18 -14
View File
@@ -527,20 +527,31 @@ public class MersenneDivisorCycles
return 2UL;
}
PollardRhoMontgomeryReducer reducer = PollardRhoMontgomeryReducer.Create(n);
ulong diff = 0UL;
ulong d = 0UL;
while (true)
{
ulong c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
ulong x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
ulong y = x;
ulong d = 1UL;
ulong xStandard = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
ulong yStandard = xStandard;
ulong cMontgomery = reducer.ConvertToMontgomery(c);
ulong xMontgomery = reducer.ConvertToMontgomery(xStandard);
ulong yMontgomery = xMontgomery;
d = 1UL;
while (d == 1UL)
{
x = AdvancePolynomial(x, c, n);
y = AdvancePolynomial(y, c, n);
y = AdvancePolynomial(y, c, n);
xMontgomery = reducer.Advance(xMontgomery, cMontgomery);
xStandard = reducer.AdvanceStandard(xStandard, c);
ulong diff = x > y ? x - y : y - x;
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
yStandard = reducer.AdvanceStandard(yStandard, c);
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
yStandard = reducer.AdvanceStandard(yStandard, c);
diff = xStandard > yStandard ? xStandard - yStandard : yStandard - xStandard;
d = BinaryGcd(diff, n);
}
@@ -551,13 +562,6 @@ public class MersenneDivisorCycles
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong AdvancePolynomial(ulong x, ulong c, ulong modulus)
{
UInt128 value = (UInt128)x * x + c;
return (ulong)(value % modulus);
}
private static ulong BinaryGcd(ulong a, ulong b)
{
if (a == 0UL)
@@ -0,0 +1,119 @@
using System;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
internal readonly struct PollardRhoMontgomeryReducer
{
private readonly MontgomeryDivisorData _divisorData;
private readonly ulong _reciprocal;
[ThreadStatic]
private static bool s_hasCachedReciprocal;
[ThreadStatic]
private static ulong s_cachedReciprocal;
public PollardRhoMontgomeryReducer(in MontgomeryDivisorData divisorData, ulong reciprocal)
{
_divisorData = divisorData;
_reciprocal = reciprocal == 0UL || reciprocal == ulong.MaxValue
? ReciprocalMath.ComputeReciprocalEstimate(divisorData.Modulus)
: reciprocal;
}
public ulong Modulus => _divisorData.Modulus;
private ulong NPrime => _divisorData.NPrime;
private ulong MontgomeryTwoSquared => _divisorData.MontgomeryTwoSquared;
private ulong MontgomeryOne => _divisorData.MontgomeryOne;
private ulong MontgomeryTwo => _divisorData.MontgomeryTwo;
public ulong Reciprocal => _reciprocal;
public static PollardRhoMontgomeryReducer Create(ulong modulus)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(modulus);
ulong reciprocal;
if (s_hasCachedReciprocal)
{
reciprocal = ReciprocalMath.RefineReciprocalEstimate(s_cachedReciprocal, modulus);
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
{
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(modulus);
}
}
else
{
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(modulus);
}
s_cachedReciprocal = reciprocal;
s_hasCachedReciprocal = true;
return new PollardRhoMontgomeryReducer(divisorData, reciprocal);
}
public static PollardRhoMontgomeryReducer Create(in MontgomeryDivisorData divisorData, ulong previousReciprocal)
{
ulong reciprocal = ReciprocalMath.RefineReciprocalEstimate(previousReciprocal, divisorData.Modulus);
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
{
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(divisorData.Modulus);
}
s_cachedReciprocal = reciprocal;
s_hasCachedReciprocal = true;
return new PollardRhoMontgomeryReducer(divisorData, reciprocal);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ConvertToMontgomery(ulong value)
{
if (value == 0UL)
{
return 0UL;
}
if (value == 1UL)
{
return MontgomeryOne;
}
if (value == 2UL)
{
return MontgomeryTwo;
}
return value.MontgomeryMultiply(MontgomeryTwoSquared, Modulus, NPrime);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReduceFromMontgomery(ulong montgomeryValue)
{
return montgomeryValue.MontgomeryMultiply(1UL, Modulus, NPrime);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong Advance(ulong montgomeryValue, ulong cMontgomery)
{
ulong squared = montgomeryValue.MontgomeryMultiply(montgomeryValue, Modulus, NPrime);
return MontgomeryAdd(squared, cMontgomery, Modulus);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong AdvanceStandard(ulong standardValue, ulong c)
{
UInt128 polynomial = (UInt128)standardValue * standardValue + c;
return ReciprocalMath.Reduce(polynomial, Modulus, _reciprocal);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong MontgomeryAdd(ulong a, ulong b, ulong modulus)
{
UInt128 total = (UInt128)a + b;
if (total >= modulus)
{
total -= modulus;
}
return (ulong)total;
}
}
+34 -29
View File
@@ -1164,11 +1164,9 @@ internal static partial class PrimeOrderCalculator
return true;
}
ulong c;
ulong x;
ulong y;
ulong d;
PollardRhoMontgomeryReducer reducer = PollardRhoMontgomeryReducer.Create(n);
ulong diff = 0UL;
ulong d = 0UL;
while (true)
{
@@ -1177,9 +1175,12 @@ internal static partial class PrimeOrderCalculator
return false;
}
c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
y = x;
ulong c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
ulong xStandard = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
ulong yStandard = xStandard;
ulong cMontgomery = reducer.ConvertToMontgomery(c);
ulong xMontgomery = reducer.ConvertToMontgomery(xStandard);
ulong yMontgomery = xMontgomery;
d = 1UL;
while (d == 1UL)
@@ -1189,10 +1190,15 @@ internal static partial class PrimeOrderCalculator
return false;
}
x = AdvancePolynomial(x, c, n);
y = AdvancePolynomial(y, c, n);
y = AdvancePolynomial(y, c, n);
diff = x > y ? x - y : y - x;
xMontgomery = reducer.Advance(xMontgomery, cMontgomery);
xStandard = reducer.AdvanceStandard(xStandard, c);
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
yStandard = reducer.AdvanceStandard(yStandard, c);
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
yStandard = reducer.AdvanceStandard(yStandard, c);
diff = xStandard > yStandard ? xStandard - yStandard : yStandard - xStandard;
d = BinaryGcd(diff, n);
}
@@ -1204,13 +1210,6 @@ internal static partial class PrimeOrderCalculator
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong AdvancePolynomial(ulong x, ulong c, ulong modulus)
{
UInt128 value = (UInt128)x * x + c;
return (ulong)(value % modulus);
}
private static ulong BinaryGcd(ulong a, ulong b)
{
if (a == 0UL)
@@ -1397,25 +1396,31 @@ internal static partial class PrimeOrderCalculator
return 2UL;
}
ulong c;
ulong x;
ulong y;
ulong d;
PollardRhoMontgomeryReducer reducer = PollardRhoMontgomeryReducer.Create(n);
ulong diff = 0UL;
ulong d = 0UL;
while (true)
{
c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
y = x;
ulong c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
ulong xStandard = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
ulong yStandard = xStandard;
ulong cMontgomery = reducer.ConvertToMontgomery(c);
ulong xMontgomery = reducer.ConvertToMontgomery(xStandard);
ulong yMontgomery = xMontgomery;
d = 1UL;
while (d == 1UL)
{
x = AdvancePolynomial(x, c, n);
y = AdvancePolynomial(y, c, n);
y = AdvancePolynomial(y, c, n);
diff = x > y ? x - y : y - x;
xMontgomery = reducer.Advance(xMontgomery, cMontgomery);
xStandard = reducer.AdvanceStandard(xStandard, c);
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
yStandard = reducer.AdvanceStandard(yStandard, c);
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
yStandard = reducer.AdvanceStandard(yStandard, c);
diff = xStandard > yStandard ? xStandard - yStandard : yStandard - xStandard;
d = BinaryGcd(diff, n);
}
+125
View File
@@ -0,0 +1,125 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
internal static class ReciprocalMath
{
private const int ReciprocalShift = 64;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ComputeReciprocalEstimate(ulong divisor)
{
if (divisor <= 1UL)
{
return ulong.MaxValue;
}
UInt128 scaledOne = UInt128.One << ReciprocalShift;
UInt128 estimate = scaledOne / divisor;
if ((scaledOne % divisor) != UInt128.Zero)
{
estimate += UInt128.One;
}
if (estimate == UInt128.Zero)
{
return 1UL;
}
if (estimate > ulong.MaxValue)
{
return ulong.MaxValue;
}
return (ulong)estimate;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong RefineReciprocalEstimate(ulong reciprocal, ulong divisor)
{
if (divisor <= 1UL)
{
return ulong.MaxValue;
}
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
{
return ComputeReciprocalEstimate(divisor);
}
UInt128 reciprocal128 = reciprocal;
UInt128 product = (UInt128)divisor * reciprocal128;
UInt128 scaled = product >> ReciprocalShift;
UInt128 twoScaled = UInt128.One << (ReciprocalShift + 1);
if (scaled >= twoScaled)
{
return ComputeReciprocalEstimate(divisor);
}
UInt128 correction = twoScaled - scaled;
UInt128 refined = (reciprocal128 * correction) >> ReciprocalShift;
if (refined == UInt128.Zero)
{
return 1UL;
}
return refined > ulong.MaxValue ? ulong.MaxValue : (ulong)refined;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Reduce(UInt128 numerator, ulong divisor, ulong reciprocal)
{
if (divisor <= 1UL)
{
return 0UL;
}
if (numerator == UInt128.Zero)
{
return 0UL;
}
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
{
return (ulong)(numerator % divisor);
}
ulong high = (ulong)(numerator >> ReciprocalShift);
ulong low = (ulong)numerator;
if (high >= divisor)
{
return (ulong)(numerator % divisor);
}
UInt128 highProduct = (UInt128)high * reciprocal;
UInt128 lowProduct = (UInt128)low * reciprocal;
UInt128 estimate = highProduct + (lowProduct >> ReciprocalShift);
if (estimate == UInt128.Zero)
{
return (ulong)(numerator % divisor);
}
UInt128 product = estimate * divisor;
while (product > numerator)
{
if (estimate == UInt128.Zero)
{
return (ulong)(numerator % divisor);
}
estimate--;
product -= divisor;
}
UInt128 remainder = numerator - product;
while (remainder >= divisor)
{
remainder -= divisor;
}
return (ulong)remainder;
}
}