diff --git a/PerfectNumbers.Core/MutableUInt128.cs b/PerfectNumbers.Core/MutableUInt128.cs new file mode 100644 index 0000000..7922ec1 --- /dev/null +++ b/PerfectNumbers.Core/MutableUInt128.cs @@ -0,0 +1,108 @@ +using System; +using System.Numerics; + +namespace PerfectNumbers.Core; + +/// +/// Mutable helper for 128-bit arithmetic derived from the .NET runtime UInt128 implementation. +/// Portions adapted from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.Private.CoreLib/src/System/UInt128.cs. +/// +internal sealed class MutableUInt128 +{ + public ulong High { get; private set; } + public ulong Low { get; private set; } + + public MutableUInt128() + { + } + + public MutableUInt128(ulong value) + { + Low = value; + } + + public MutableUInt128(ulong high, ulong low) + { + High = high; + Low = low; + } + + public MutableUInt128(UInt128 value) + { + Set(value); + } + + public void Set(UInt128 value) + { + High = (ulong)(value >> 64); + Low = (ulong)value; + } + + public void Set(ulong high, ulong low) + { + High = high; + Low = low; + } + + public UInt128 ToUInt128() => ((UInt128)High << 64) | Low; + + public void Add(ulong value) + { + ulong low = Low + value; + if (low < value) + { + High++; + } + + Low = low; + } + + public void Add(in MutableUInt128 value) + { + ulong low = Low + value.Low; + ulong carry = low < Low ? 1UL : 0UL; + ulong high = High + value.High + carry; + + Low = low; + High = high; + } + + public void Subtract(ulong value) + { + ulong low = Low; + ulong result = low - value; + ulong borrow = result > low ? 1UL : 0UL; + + Low = result; + High -= borrow; + } + + public void Subtract(in MutableUInt128 value) + { + ulong low = Low; + ulong result = low - value.Low; + ulong borrow = low < value.Low ? 1UL : 0UL; + + Low = result; + High = High - value.High - borrow; + } + + public void Multiply(ulong value) + { + ulong high = High; + ulong low = Low; + + ulong highContribution = Math.BigMul(low, value, out ulong newLow); + Math.BigMul(high, value, out ulong shiftedHigh); + ulong newHigh = shiftedHigh + highContribution; + + Low = newLow; + High = newHigh; + } + + public void MultiplyAdd(ulong multiplier, ulong addend) + { + Multiply(multiplier); + Add(addend); + } +} diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index 1312815..7df88c5 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -314,69 +314,84 @@ internal static partial class PrimeOrderCalculator return order; } - [ThreadStatic] - private static ArrayPool? _factorPool; - private static ArrayPool FactorPool => _factorPool ??= ArrayPool.Create(); + [ThreadStatic] + private static ArrayPool? _factorPool; + private static ArrayPool FactorPool => _factorPool ??= ArrayPool.Create(); - [ThreadStatic] - private static ArrayPool? _ulongPool; - private static ArrayPool ULongPool => _ulongPool ??= ArrayPool.Create(); + private const int FactorEntryStackThreshold = 32; - private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) - { - ArrayPool factorPool = FactorPool; + [ThreadStatic] + private static ArrayPool? _ulongPool; + private static ArrayPool ULongPool => _ulongPool ??= ArrayPool.Create(); - FactorEntry[]? tempArray = null; - try - { - ReadOnlySpan factorSpan = factors.Factors; - int length = factors.Count; - tempArray = factorPool.Rent(length + 1); - Span buffer = tempArray; - factorSpan.CopyTo(buffer); + private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) + { + ArrayPool factorPool = FactorPool; - bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factors.Cofactor); - // bool isPrime = PrimeTester.IsPrimeInternal(factors.Cofactor, CancellationToken.None); - if (!factors.FullyFactored && factors.Cofactor > 1UL && isPrime) - { - buffer[length] = new FactorEntry(factors.Cofactor); - length++; - } + FactorEntry[]? tempArray = null; + try + { + ReadOnlySpan factorSpan = factors.Factors; + int length = factors.Count; + int capacity = length + 1; - buffer.Slice(0, length).Sort(static (a, b) => a.Value.CompareTo(b.Value)); + Span buffer; + if (capacity <= FactorEntryStackThreshold) + { + buffer = stackalloc FactorEntry[capacity]; + } + else + { + tempArray = factorPool.Rent(capacity); + buffer = tempArray.AsSpan(0, capacity); + } - for (int i = 0; i < length; i++) - { - ulong primeFactor = buffer[i].Value; - int exponent = buffer[i].Exponent; - for (int iteration = 0; iteration < exponent; iteration++) - { - if ((order % primeFactor) != 0UL) - { - break; - } + factorSpan.CopyTo(buffer); - ulong reduced = order / primeFactor; - if (Pow2EqualsOneCpu(reduced, prime, divisorData)) - { - order = reduced; - continue; - } + bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factors.Cofactor); + // bool isPrime = PrimeTester.IsPrimeInternal(factors.Cofactor, CancellationToken.None); + if (!factors.FullyFactored && factors.Cofactor > 1UL && isPrime) + { + buffer[length] = new FactorEntry(factors.Cofactor); + length++; + } - break; - } - } + buffer = buffer.Slice(0, length); // Reuse the same span restricted to populated entries. + buffer.Sort(static (a, b) => a.Value.CompareTo(b.Value)); - return order; - } - finally - { - if (tempArray is not null) - { - factorPool.Return(tempArray, clearArray: false); - } - } - } + for (int i = 0; i < buffer.Length; i++) + { + FactorEntry entry = buffer[i]; + ulong primeFactor = entry.Value; + int exponent = entry.Exponent; + for (int iteration = 0; iteration < exponent; iteration++) + { + if ((order % primeFactor) != 0UL) + { + break; + } + + ulong reduced = order / primeFactor; + if (Pow2EqualsOneCpu(reduced, prime, divisorData)) + { + order = reduced; + continue; + } + + break; + } + } + + return order; + } + finally + { + if (tempArray is not null) + { + factorPool.Return(tempArray, clearArray: false); + } + } + } private static bool TryConfirmOrderCpu(ulong prime, ulong order, in MontgomeryDivisorData divisorData, ref PartialFactorResult orderFactors) {