Tiny cleanup
This commit is contained in:
1 parent
d78503eade
commit
fb7734528f
1 file changed
+229
-229
@@ -11,63 +11,63 @@ namespace PerfectNumbers.Core;
|
|||||||
|
|
||||||
public enum GpuKernelType
|
public enum GpuKernelType
|
||||||
{
|
{
|
||||||
Incremental,
|
Incremental,
|
||||||
Pow2Mod,
|
Pow2Mod,
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MersenneNumberTester(
|
public sealed class MersenneNumberTester(
|
||||||
bool useIncremental = true,
|
bool useIncremental = true,
|
||||||
bool useOrderCache = false,
|
bool useOrderCache = false,
|
||||||
GpuKernelType kernelType = GpuKernelType.Incremental,
|
GpuKernelType kernelType = GpuKernelType.Incremental,
|
||||||
bool useModuloWorkaround = false,
|
bool useModuloWorkaround = false,
|
||||||
bool useOrder = false,
|
bool useOrder = false,
|
||||||
bool useGpuLucas = true,
|
bool useGpuLucas = true,
|
||||||
bool useGpuScan = true,
|
bool useGpuScan = true,
|
||||||
bool useGpuOrder = true,
|
bool useGpuOrder = true,
|
||||||
bool useResidue = true,
|
bool useResidue = true,
|
||||||
UInt128? maxK = null,
|
UInt128? maxK = null,
|
||||||
UInt128? residueDivisorSets = null)
|
UInt128? residueDivisorSets = null)
|
||||||
{
|
{
|
||||||
private readonly bool _useResidue = useResidue;
|
private readonly bool _useResidue = useResidue;
|
||||||
private readonly bool _useIncremental = useIncremental && !useResidue;
|
private readonly bool _useIncremental = useIncremental && !useResidue;
|
||||||
private readonly UInt128 _maxK = maxK ?? (UInt128)5_000_000UL;
|
private readonly UInt128 _maxK = maxK ?? (UInt128)5_000_000UL;
|
||||||
private readonly UInt128 _residueDivisorSetCount = residueDivisorSets ?? (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
|
private readonly UInt128 _residueDivisorSetCount = residueDivisorSets ?? (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
|
||||||
private readonly GpuKernelType _kernelType = kernelType;
|
private readonly GpuKernelType _kernelType = kernelType;
|
||||||
private readonly bool _useModuloWorkaround = useModuloWorkaround;
|
private readonly bool _useModuloWorkaround = useModuloWorkaround;
|
||||||
private readonly bool _useGpuLucas = useGpuLucas;
|
private readonly bool _useGpuLucas = useGpuLucas;
|
||||||
private readonly bool _useGpuScan = useGpuScan; // device for pow2mod/incremental scanning
|
private readonly bool _useGpuScan = useGpuScan; // device for pow2mod/incremental scanning
|
||||||
private readonly bool _useGpuOrder = useGpuOrder; // device for order computations
|
private readonly bool _useGpuOrder = useGpuOrder; // device for order computations
|
||||||
private static readonly ConcurrentDictionary<UInt128, ulong> OrderCache = new();
|
private static readonly ConcurrentDictionary<UInt128, ulong> OrderCache = new();
|
||||||
private readonly MersenneNumberIncrementalGpuTester _incrementalGpuTester = new(kernelType, useGpuOrder);
|
private readonly MersenneNumberIncrementalGpuTester _incrementalGpuTester = new(kernelType, useGpuOrder);
|
||||||
private readonly MersenneNumberIncrementalCpuTester _incrementalCpuTester = new(kernelType);
|
private readonly MersenneNumberIncrementalCpuTester _incrementalCpuTester = new(kernelType);
|
||||||
private readonly MersenneNumberOrderGpuTester _orderGpuTester = new(kernelType, useGpuOrder);
|
private readonly MersenneNumberOrderGpuTester _orderGpuTester = new(kernelType, useGpuOrder);
|
||||||
private readonly MersenneNumberOrderCpuTester _orderCpuTester = new(kernelType);
|
private readonly MersenneNumberOrderCpuTester _orderCpuTester = new(kernelType);
|
||||||
private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerGpuTester = new();
|
private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerGpuTester = new();
|
||||||
private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerCpuTester = new();
|
private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerCpuTester = new();
|
||||||
private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null;
|
private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null;
|
||||||
private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null;
|
private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null;
|
||||||
|
|
||||||
// TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags.
|
// TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags.
|
||||||
// Also consider injecting a shared cycles cache (MersenneDivisorCycles.Shared) to both CPU and GPU testers.
|
// Also consider injecting a shared cycles cache (MersenneDivisorCycles.Shared) to both CPU and GPU testers.
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static UInt128 DivideRoundUp(UInt128 value, UInt128 divisor)
|
private static UInt128 DivideRoundUp(UInt128 value, UInt128 divisor)
|
||||||
{
|
{
|
||||||
if (divisor == UInt128.Zero)
|
if (divisor == UInt128.Zero)
|
||||||
{
|
{
|
||||||
return UInt128.Zero;
|
return UInt128.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 result = value / divisor;
|
UInt128 result = value / divisor;
|
||||||
if (result * divisor == value)
|
if (result * divisor == value)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result + UInt128.One;
|
return result + UInt128.One;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL)
|
public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL)
|
||||||
{
|
{
|
||||||
bool cacheEnabled = useOrderCache;
|
bool cacheEnabled = useOrderCache;
|
||||||
if (!cacheEnabled)
|
if (!cacheEnabled)
|
||||||
@@ -158,168 +158,168 @@ public sealed class MersenneNumberTester(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128[] qs = qsBuffer;
|
UInt128[] qs = qsBuffer;
|
||||||
|
|
||||||
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
||||||
var accelerator = gpuLease.Accelerator;
|
var accelerator = gpuLease.Accelerator;
|
||||||
var orderKernel = gpuLease.OrderKernel!;
|
var orderKernel = gpuLease.OrderKernel!;
|
||||||
|
|
||||||
// Guard long-running kernels by chunking the warm-up across batches.
|
// Guard long-running kernels by chunking the warm-up across batches.
|
||||||
// Reuse the same ScanBatchSize knob used for scanning.
|
// Reuse the same ScanBatchSize knob used for scanning.
|
||||||
int batchSize = GpuConstants.ScanBatchSize;
|
int batchSize = GpuConstants.ScanBatchSize;
|
||||||
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
|
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
while (offset < idx)
|
while (offset < idx)
|
||||||
{
|
{
|
||||||
int count = Math.Min(batchSize, idx - offset);
|
int count = Math.Min(batchSize, idx - offset);
|
||||||
var qBuffer = accelerator.Allocate1D<PerfectNumbers.Core.Gpu.GpuUInt128>(count);
|
var qBuffer = accelerator.Allocate1D<PerfectNumbers.Core.Gpu.GpuUInt128>(count);
|
||||||
var orderBuffer = accelerator.Allocate1D<ulong>(count);
|
var orderBuffer = accelerator.Allocate1D<ulong>(count);
|
||||||
// Convert to device-friendly GpuUInt128 on the fly
|
// Convert to device-friendly GpuUInt128 on the fly
|
||||||
var tmp = System.Buffers.ArrayPool<PerfectNumbers.Core.Gpu.GpuUInt128>.Shared.Rent(count);
|
var tmp = System.Buffers.ArrayPool<PerfectNumbers.Core.Gpu.GpuUInt128>.Shared.Rent(count);
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
tmp[i] = (PerfectNumbers.Core.Gpu.GpuUInt128)qs[offset + i];
|
tmp[i] = (PerfectNumbers.Core.Gpu.GpuUInt128)qs[offset + i];
|
||||||
}
|
}
|
||||||
// Copy only the populated elements to the device buffer to avoid overrun on pooled arrays.
|
// Copy only the populated elements to the device buffer to avoid overrun on pooled arrays.
|
||||||
qBuffer.View.CopyFromCPU(ref tmp[0], count);
|
qBuffer.View.CopyFromCPU(ref tmp[0], count);
|
||||||
System.Buffers.ArrayPool<PerfectNumbers.Core.Gpu.GpuUInt128>.Shared.Return(tmp);
|
System.Buffers.ArrayPool<PerfectNumbers.Core.Gpu.GpuUInt128>.Shared.Return(tmp);
|
||||||
orderKernel(count, exponent, divMul, qBuffer.View, orderBuffer.View);
|
orderKernel(count, exponent, divMul, qBuffer.View, orderBuffer.View);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
|
|
||||||
ulong[] orders = orderBuffer.GetAsArray1D();
|
ulong[] orders = orderBuffer.GetAsArray1D();
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
ulong order = orders[i];
|
ulong order = orders[i];
|
||||||
if (order == 0UL)
|
if (order == 0UL)
|
||||||
{
|
{
|
||||||
order = qs[offset + i].CalculateOrder();
|
order = qs[offset + i].CalculateOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useOrderCache)
|
if (useOrderCache)
|
||||||
{
|
{
|
||||||
OrderCache[qs[offset + i]] = order;
|
OrderCache[qs[offset + i]] = order;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += count;
|
offset += count;
|
||||||
orderBuffer.Dispose();
|
orderBuffer.Dispose();
|
||||||
qBuffer.Dispose();
|
qBuffer.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
gpuLease.Dispose();
|
gpuLease.Dispose();
|
||||||
ArrayPool<UInt128>.Shared.Return(qs);
|
ArrayPool<UInt128>.Shared.Return(qs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsMersennePrime(ulong exponent) => IsMersennePrime(exponent, out _);
|
public bool IsMersennePrime(ulong exponent) => IsMersennePrime(exponent, out _);
|
||||||
|
|
||||||
public bool IsMersennePrime(ulong exponent, out bool divisorsExhausted)
|
public bool IsMersennePrime(ulong exponent, out bool divisorsExhausted)
|
||||||
{
|
{
|
||||||
divisorsExhausted = false;
|
divisorsExhausted = false;
|
||||||
|
|
||||||
// Safe early rejections using known orders for tiny primes:
|
// Safe early rejections using known orders for tiny primes:
|
||||||
// 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime.
|
// 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime.
|
||||||
if ((exponent % 3UL) == 0UL && exponent != 3UL)
|
if ((exponent % 3UL) == 0UL && exponent != 3UL)
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
|
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip even exponents: for even p > 2, M_p is composite; here p is large.
|
// Skip even exponents: for even p > 2, M_p is composite; here p is large.
|
||||||
if ((exponent & 1UL) == 0UL)
|
if ((exponent & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool prePrime = true;
|
bool prePrime = true;
|
||||||
UInt128 twoP = (UInt128)exponent << 1; // 2 * p
|
UInt128 twoP = (UInt128)exponent << 1; // 2 * p
|
||||||
// UInt128 maxDivisor = new(ulong.MaxValue, ulong.MaxValue);
|
// UInt128 maxDivisor = new(ulong.MaxValue, ulong.MaxValue);
|
||||||
UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1;
|
UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1;
|
||||||
bool lastIsSeven = (exponent & 3UL) == 3UL;
|
bool lastIsSeven = (exponent & 3UL) == 3UL;
|
||||||
|
|
||||||
// In residue mode the residue scan is the final primality check.
|
// In residue mode the residue scan is the final primality check.
|
||||||
if (_useResidue)
|
if (_useResidue)
|
||||||
{
|
{
|
||||||
if (_maxK < maxK)
|
if (_maxK < maxK)
|
||||||
{
|
{
|
||||||
maxK = _maxK;
|
maxK = _maxK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_maxK == UInt128.Zero || _residueDivisorSetCount == UInt128.Zero)
|
if (_maxK == UInt128.Zero || _residueDivisorSetCount == UInt128.Zero)
|
||||||
{
|
{
|
||||||
return prePrime;
|
return prePrime;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 totalLimit = _maxK;
|
UInt128 totalLimit = _maxK;
|
||||||
if (totalLimit > maxK)
|
if (totalLimit > maxK)
|
||||||
{
|
{
|
||||||
totalLimit = maxK;
|
totalLimit = maxK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalLimit == UInt128.Zero)
|
if (totalLimit == UInt128.Zero)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 requestedSets = _residueDivisorSetCount;
|
UInt128 requestedSets = _residueDivisorSetCount;
|
||||||
if (_useGpuScan)
|
if (_useGpuScan)
|
||||||
{
|
{
|
||||||
UInt128 maxUsefulSets = DivideRoundUp(totalLimit, (UInt128)GpuConstants.ScanBatchSize);
|
UInt128 maxUsefulSets = DivideRoundUp(totalLimit, (UInt128)GpuConstants.ScanBatchSize);
|
||||||
if (maxUsefulSets == UInt128.Zero)
|
if (maxUsefulSets == UInt128.Zero)
|
||||||
{
|
{
|
||||||
maxUsefulSets = UInt128.One;
|
maxUsefulSets = UInt128.One;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestedSets == UInt128.Zero || requestedSets > maxUsefulSets)
|
if (requestedSets == UInt128.Zero || requestedSets > maxUsefulSets)
|
||||||
{
|
{
|
||||||
requestedSets = maxUsefulSets;
|
requestedSets = maxUsefulSets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestedSets == UInt128.Zero)
|
if (requestedSets == UInt128.Zero)
|
||||||
{
|
{
|
||||||
requestedSets = UInt128.One;
|
requestedSets = UInt128.One;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 perSetLimit = DivideRoundUp(totalLimit, requestedSets);
|
UInt128 perSetLimit = DivideRoundUp(totalLimit, requestedSets);
|
||||||
if (perSetLimit == UInt128.Zero)
|
if (perSetLimit == UInt128.Zero)
|
||||||
{
|
{
|
||||||
perSetLimit = UInt128.One;
|
perSetLimit = UInt128.One;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit);
|
UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit);
|
||||||
UInt128 effectiveSets = requestedSets;
|
UInt128 effectiveSets = requestedSets;
|
||||||
if (setsNeeded < effectiveSets)
|
if (setsNeeded < effectiveSets)
|
||||||
{
|
{
|
||||||
effectiveSets = setsNeeded;
|
effectiveSets = setsNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool scanCompleted = false;
|
bool scanCompleted = false;
|
||||||
|
|
||||||
if (_useGpuScan)
|
if (_useGpuScan)
|
||||||
{
|
{
|
||||||
_residueGpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted);
|
_residueGpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_residueCpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted);
|
_residueCpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prePrime)
|
if (!prePrime)
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
divisorsExhausted = scanCompleted;
|
divisorsExhausted = scanCompleted;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_useIncremental)
|
if (!_useIncremental)
|
||||||
{
|
{
|
||||||
@@ -337,64 +337,64 @@ public sealed class MersenneNumberTester(
|
|||||||
_incrementalCpuTester.Scan(exponent, twoPPre, lastIsSevenPre, maxKPre, ref prePrime);
|
_incrementalCpuTester.Scan(exponent, twoPPre, lastIsSevenPre, maxKPre, ref prePrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prePrime)
|
if (!prePrime)
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lucasPrime = _useGpuLucas
|
bool lucasPrime = _useGpuLucas
|
||||||
? _lucasLehmerGpuTester.IsPrime(exponent, runOnGpu: true)
|
? _lucasLehmerGpuTester.IsPrime(exponent, runOnGpu: true)
|
||||||
: _lucasLehmerCpuTester.IsPrime(exponent);
|
: _lucasLehmerCpuTester.IsPrime(exponent);
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return lucasPrime;
|
return lucasPrime;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isPrime = true;
|
bool isPrime = true;
|
||||||
|
|
||||||
if (useOrder)
|
if (useOrder)
|
||||||
{
|
{
|
||||||
if (_useGpuOrder)
|
if (_useGpuOrder)
|
||||||
{
|
{
|
||||||
_orderGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime);
|
_orderGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_orderCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime);
|
_orderCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_useGpuScan)
|
if (_useGpuScan)
|
||||||
{
|
{
|
||||||
_incrementalGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime);
|
_incrementalGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_incrementalCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime);
|
_incrementalCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return isPrime;
|
return isPrime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit of k to scan in the pre-Lucas prescan
|
// Limit of k to scan in the pre-Lucas prescan
|
||||||
public const int PreLucasPreScanK = 5_000_000;
|
public const int PreLucasPreScanK = 5_000_000;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool DividesMersenne(ulong exponent, ulong prime)
|
private static bool DividesMersenne(ulong exponent, ulong prime)
|
||||||
{
|
{
|
||||||
return 2UL.ModPow64(exponent, prime) == 1UL;
|
return 2UL.ModPow64(exponent, prime) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView<UInt128> qs, ArrayView<ulong> orders)
|
private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView<UInt128> qs, ArrayView<ulong> orders)
|
||||||
{
|
{
|
||||||
UInt128 q = qs[index];
|
UInt128 q = qs[index];
|
||||||
UInt128 pow = exponent.PowMod(q);
|
UInt128 pow = exponent.PowMod(q);
|
||||||
orders[index] = pow == 1UL ? exponent : 0UL;
|
orders[index] = pow == 1UL ? exponent : 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user