diff --git a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj index 7e938de2925ef..d61ea2546b168 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -7,12 +7,12 @@ - + @@ -30,5 +30,6 @@ + diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs index d27244837a089..671f4af2699be 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -15,9 +16,6 @@ namespace System.Numerics private const int kcbitUint = 32; private const int kcbitUlong = 64; private const int DecimalScaleFactorMask = 0x00FF0000; - private const int DecimalSignMask = unchecked((int)0x80000000); - - internal const int StackallocUInt32Limit = 64; // For values int.MinValue < n <= int.MaxValue, the value is stored in sign // and _bits is null. For all other values, sign is +1 or -1 and the bits are in _bits @@ -213,6 +211,7 @@ public BigInteger(decimal value) Debug.Assert(bits.Length == 4 && (bits[3] & DecimalScaleFactorMask) == 0); + const int signMask = unchecked((int)kuMaskHighBit); int size = 3; while (size > 0 && bits[size - 1] == 0) size--; @@ -225,7 +224,7 @@ public BigInteger(decimal value) // bits[0] is the absolute value of this decimal // if bits[0] < 0 then it is too large to be packed into _sign _sign = bits[0]; - _sign *= ((bits[3] & DecimalSignMask) != 0) ? -1 : +1; + _sign *= ((bits[3] & signMask) != 0) ? -1 : +1; _bits = null; } else @@ -241,7 +240,7 @@ public BigInteger(decimal value) _bits[2] = (uint)bits[2]; } - _sign = ((bits[3] & DecimalSignMask) != 0) ? -1 : +1; + _sign = ((bits[3] & signMask) != 0) ? -1 : +1; } AssertValid(); } @@ -473,67 +472,51 @@ internal BigInteger(int n, uint[]? rgu) /// /// Constructor used during bit manipulation and arithmetic. - /// When possible the span will be packed into _sign to conserve space. + /// When possible the value will be packed into _sign to conserve space. /// /// The absolute value of the number - /// An array, identical in contents to , for which ownership can be transferred; null if no such array was available. /// The bool indicating the sign of the value. - private BigInteger(ReadOnlySpan value, uint[]? valueArray, bool negative) + private BigInteger(ReadOnlySpan value, bool negative) { - Debug.Assert(valueArray is null || value.SequenceEqual(valueArray)); + int len; - // Try to conserve space as much as possible by checking for wasted leading uint entries - // (sometimes the span has leading zeros from bit manipulation operations & and ^). - if (!value.IsEmpty && value[^1] == 0) - { - int len = value.Length - 1; - while (len > 0 && value[len - 1] == 0) - { - len--; - } - value = value.Slice(0, len); - valueArray = null; - } + // Try to conserve space as much as possible by checking for wasted leading span entries + // sometimes the span has leading zeros from bit manipulation operations & and ^ + for (len = value.Length; len > 0 && value[len - 1] == 0; len--); - if (value.IsEmpty) + if (len == 0) { this = s_bnZeroInt; } - else if (value.Length == 1 && value[0] < kuMaskHighBit) + else if (len == 1 && value[0] < kuMaskHighBit) { - // Values like (Int32.MaxValue+1) are stored as "0x80000000" and as such cannot be packed into _sign. + // Values like (Int32.MaxValue+1) are stored as "0x80000000" and as such cannot be packed into _sign _sign = negative ? -(int)value[0] : (int)value[0]; _bits = null; if (_sign == int.MinValue) { - // Although Int32.MinValue fits in _sign, we represent this case differently for negate. + // Although Int32.MinValue fits in _sign, we represent this case differently for negate this = s_bnMinInt; } } else { _sign = negative ? -1 : +1; - _bits = valueArray ?? value.ToArray(); + _bits = value.Slice(0, len).ToArray(); } - AssertValid(); } /// - /// Create a BigInteger from a little-endian twos-complement UInt32 array. - /// When possible, value is assigned directly to this._bits without an array copy - /// so use this ctor with care. + /// Create a BigInteger from a little-endian twos-complement UInt32 span. /// /// - private BigInteger(uint[] value) + private BigInteger(Span value) { - if (value == null) - throw new ArgumentNullException(nameof(value)); - int dwordCount = value.Length; - bool isNegative = dwordCount > 0 && ((value[dwordCount - 1] & 0x80000000) == 0x80000000); + bool isNegative = dwordCount > 0 && ((value[dwordCount - 1] & kuMaskHighBit) == kuMaskHighBit); - // Try to conserve space as much as possible by checking for wasted leading uint[] entries + // Try to conserve space as much as possible by checking for wasted leading span entries while (dwordCount > 0 && value[dwordCount - 1] == 0) dwordCount--; if (dwordCount == 0) @@ -568,18 +551,9 @@ private BigInteger(uint[] value) if (!isNegative) { // Handle the simple positive value cases where the input is already in sign magnitude - if (dwordCount != value.Length) - { - _sign = +1; - _bits = new uint[dwordCount]; - Array.Copy(value, _bits, dwordCount); - } - // No trimming is possible. Assign value directly to _bits. - else - { - _sign = +1; - _bits = value; - } + _sign = +1; + value = value.Slice(0, dwordCount); + _bits = value.ToArray(); AssertValid(); return; } @@ -608,29 +582,20 @@ private BigInteger(uint[] value) _bits = null; } } - // The number is represented by multiple dwords. - // Trim off any wasted uint values when possible. - else if (len != value.Length) - { - _sign = -1; - _bits = new uint[len]; - Array.Copy(value, _bits, len); - } - // No trimming is possible. Assign value directly to _bits. else { _sign = -1; - _bits = value; + _bits = value.Slice(0, len).ToArray(); } AssertValid(); return; } - public static BigInteger Zero => s_bnZeroInt; + public static BigInteger Zero { get { return s_bnZeroInt; } } - public static BigInteger One => s_bnOneInt; + public static BigInteger One { get { return s_bnOneInt; } } - public static BigInteger MinusOne => s_bnMinusOneInt; + public static BigInteger MinusOne { get { return s_bnMinusOneInt; } } public bool IsPowerOfTwo { @@ -639,12 +604,12 @@ public bool IsPowerOfTwo AssertValid(); if (_bits == null) - return (_sign & (_sign - 1)) == 0 && _sign != 0; + return BitOperations.IsPow2(_sign); if (_sign != 1) return false; int iu = _bits.Length - 1; - if ((_bits[iu] & (_bits[iu] - 1)) != 0) + if (!BitOperations.IsPow2(_bits[iu])) return false; while (--iu >= 0) { @@ -774,10 +739,26 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big if (trivialDivisor) { uint rest; - uint[] bits = BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), out rest); - remainder = dividend._sign < 0 ? -1 * rest : rest; - return new BigInteger(bits, bits, (dividend._sign < 0) ^ (divisor._sign < 0)); + uint[]? bitsFromPool = null; + int size = dividend._bits.Length; + Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + try + { + // may throw DivideByZeroException + BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient, out rest); + + remainder = dividend._sign < 0 ? -1 * rest : rest; + return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); + } + finally + { + if (bitsFromPool != null) + ArrayPool.Shared.Return(bitsFromPool); + } } Debug.Assert(divisor._bits != null); @@ -789,11 +770,30 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big } else { - uint[] rest; - uint[] bits = BigIntegerCalculator.Divide(dividend._bits, divisor._bits, out rest); + uint[]? remainderFromPool = null; + int size = dividend._bits.Length; + Span rest = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : remainderFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - remainder = new BigInteger(rest, rest, dividend._sign < 0); - return new BigInteger(bits, bits, (dividend._sign < 0) ^ (divisor._sign < 0)); + uint[]? quotientFromPool = null; + size = dividend._bits.Length - divisor._bits.Length + 1; + Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Divide(dividend._bits, divisor._bits, quotient, rest); + + remainder = new BigInteger(rest, dividend._sign < 0); + var result = new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); + + if (remainderFromPool != null) + ArrayPool.Shared.Return(remainderFromPool); + + if (quotientFromPool != null) + ArrayPool.Shared.Return(quotientFromPool); + + return result; } } @@ -823,7 +823,7 @@ public static double Log(BigInteger value, double baseValue) ulong l = value._bits.Length > 2 ? value._bits[value._bits.Length - 3] : 0; // Measure the exact bit count - int c = NumericsHelpers.CbitHighZero((uint)h); + int c = BitOperations.LeadingZeroCount((uint)h); long b = (long)value._bits.Length * 32 - c; // Extract most significant bits @@ -857,7 +857,7 @@ public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right Debug.Assert(right._bits != null); return left._sign != 0 ? BigIntegerCalculator.Gcd(right._bits, NumericsHelpers.Abs(left._sign)) - : new BigInteger(right._bits, null, negative: false); + : new BigInteger(right._bits, negative: false); } if (trivialRight) @@ -865,7 +865,7 @@ public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right Debug.Assert(left._bits != null); return right._sign != 0 ? BigIntegerCalculator.Gcd(left._bits, NumericsHelpers.Abs(right._sign)) - : new BigInteger(left._bits, null, negative: false); + : new BigInteger(left._bits, negative: false); } Debug.Assert(left._bits != null && right._bits != null); @@ -880,29 +880,46 @@ public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right } } - private static BigInteger GreatestCommonDivisor(uint[] leftBits, uint[] rightBits) + private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, ReadOnlySpan rightBits) { Debug.Assert(BigIntegerCalculator.Compare(leftBits, rightBits) >= 0); + uint[]? bitsFromPool = null; + BigInteger result; + // Short circuits to spare some allocations... if (rightBits.Length == 1) { uint temp = BigIntegerCalculator.Remainder(leftBits, rightBits[0]); - return BigIntegerCalculator.Gcd(rightBits[0], temp); + result = BigIntegerCalculator.Gcd(rightBits[0], temp); } - - if (rightBits.Length == 2) + else if (rightBits.Length == 2) { - uint[] tempBits = BigIntegerCalculator.Remainder(leftBits, rightBits); + Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); + + BigIntegerCalculator.Remainder(leftBits, rightBits, bits); ulong left = ((ulong)rightBits[1] << 32) | rightBits[0]; - ulong right = ((ulong)tempBits[1] << 32) | tempBits[0]; + ulong right = ((ulong)bits[1] << 32) | bits[0]; + + result = BigIntegerCalculator.Gcd(left, right); + } + else + { + Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); - return BigIntegerCalculator.Gcd(left, right); + BigIntegerCalculator.Gcd(leftBits, rightBits, bits); + result = new BigInteger(bits, negative: false); } - uint[] bits = BigIntegerCalculator.Gcd(leftBits, rightBits); - return new BigInteger(bits, bits, negative: false); + if (bitsFromPool != null) + ArrayPool.Shared.Return(bitsFromPool); + + return result; } public static BigInteger Max(BigInteger left, BigInteger right) @@ -932,6 +949,8 @@ public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigIntege bool trivialExponent = exponent._bits == null; bool trivialModulus = modulus._bits == null; + BigInteger result; + if (trivialModulus) { uint bits = trivialValue && trivialExponent ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) : @@ -939,17 +958,43 @@ public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigIntege trivialExponent ? BigIntegerCalculator.Pow(value._bits!, NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) : BigIntegerCalculator.Pow(value._bits!, exponent._bits!, NumericsHelpers.Abs(modulus._sign)); - return value._sign < 0 && !exponent.IsEven ? -1 * bits : bits; + result = value._sign < 0 && !exponent.IsEven ? -1 * bits : bits; } else { - uint[] bits = trivialValue && trivialExponent ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent._sign), modulus._bits!) : - trivialValue ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), exponent._bits!, modulus._bits!) : - trivialExponent ? BigIntegerCalculator.Pow(value._bits!, NumericsHelpers.Abs(exponent._sign), modulus._bits!) : - BigIntegerCalculator.Pow(value._bits!, exponent._bits!, modulus._bits!); + int size = (modulus._bits?.Length ?? 1) << 1; + uint[]? bitsFromPool = null; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + bits.Clear(); + if (trivialValue) + { + if (trivialExponent) + { + BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent._sign), modulus._bits!, bits); + } + else + { + BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), exponent._bits!, modulus._bits!, bits); + } + } + else if (trivialExponent) + { + BigIntegerCalculator.Pow(value._bits!, NumericsHelpers.Abs(exponent._sign), modulus._bits!, bits); + } + else + { + BigIntegerCalculator.Pow(value._bits!, exponent._bits!, modulus._bits!, bits); + } + + result = new BigInteger(bits, value._sign < 0 && !exponent.IsEven); - return new BigInteger(bits, bits, value._sign < 0 && !exponent.IsEven); + if (bitsFromPool != null) + ArrayPool.Shared.Return(bitsFromPool); } + + return result; } public static BigInteger Pow(BigInteger value, int exponent) @@ -966,6 +1011,10 @@ public static BigInteger Pow(BigInteger value, int exponent) bool trivialValue = value._bits == null; + uint power = NumericsHelpers.Abs(exponent); + uint[]? bitsFromPool = null; + BigInteger result; + if (trivialValue) { if (value._sign == 1) @@ -974,34 +1023,54 @@ public static BigInteger Pow(BigInteger value, int exponent) return (exponent & 1) != 0 ? value : s_bnOneInt; if (value._sign == 0) return value; + + int size = BigIntegerCalculator.PowBound(power, 1); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + bits.Clear(); + + BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), power, bits); + result = new BigInteger(bits, value._sign < 0 && (exponent & 1) != 0); + } + else + { + int size = BigIntegerCalculator.PowBound(power, value._bits!.Length); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + bits.Clear(); + + BigIntegerCalculator.Pow(value._bits, power, bits); + result = new BigInteger(bits, value._sign < 0 && (exponent & 1) != 0); } - uint[] bits = trivialValue - ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent)) - : BigIntegerCalculator.Pow(value._bits!, NumericsHelpers.Abs(exponent)); + if (bitsFromPool != null) + ArrayPool.Shared.Return(bitsFromPool); - return new BigInteger(bits, bits, value._sign < 0 && (exponent & 1) != 0); + return result; } public override int GetHashCode() { AssertValid(); - if (_bits == null) + if (_bits is null) return _sign; + int hash = _sign; for (int iv = _bits.Length; --iv >= 0;) - hash = NumericsHelpers.CombineHash(hash, unchecked((int)_bits[iv])); + hash = unchecked((int)CombineHash((uint)hash, _bits[iv])); return hash; + + static uint CombineHash(uint u1, uint u2) => ((u1 << 7) | (u1 >> 25)) ^ u2; } public override bool Equals([NotNullWhen(true)] object? obj) { AssertValid(); - if (!(obj is BigInteger)) - return false; - return Equals((BigInteger)obj); + return obj is BigInteger other && Equals(other); } public bool Equals(long other) @@ -1124,9 +1193,9 @@ public int CompareTo(object? obj) { if (obj == null) return 1; - if (!(obj is BigInteger)) + if (obj is not BigInteger bigInt) throw new ArgumentException(SR.Argument_MustBeBigInt, nameof(obj)); - return CompareTo((BigInteger)obj); + return CompareTo(bigInt); } /// @@ -1424,79 +1493,59 @@ private enum GetBytesMode { AllocateArray, Count, Span } } /// - /// Return the value of this BigInteger as a little-endian twos-complement - /// uint span, using the fewest number of uints possible. If the value is zero, - /// return a span of one uint whose element is 0. + /// Converts the value of this BigInteger to a little-endian twos-complement + /// uint span allocated by the caller using the fewest number of uints possible. /// - private ReadOnlySpan ToUInt32Span(Span scratch) + /// Pre-allocated buffer by the caller. + /// The actual number of copied elements. + private int WriteTo(Span buffer) { - Debug.Assert(!scratch.IsEmpty); - - if (_bits == null && _sign == 0) - { - scratch[0] = 0; - return scratch.Slice(0, 1); - } + Debug.Assert(_bits is null || _sign == 0 ? buffer.Length == 2 : buffer.Length >= _bits.Length + 1); - Span dwords = scratch; - bool dwordsIsScratch = true; uint highDWord; - if (_bits == null) + if (_bits is null) { - dwords[0] = unchecked((uint)_sign); - dwords = dwords.Slice(0, 1); + buffer[0] = unchecked((uint)_sign); highDWord = (_sign < 0) ? uint.MaxValue : 0; } - else if (_sign == -1) + else { - if (dwords.Length >= _bits.Length) + _bits.CopyTo(buffer); + buffer = buffer.Slice(0, _bits.Length + 1); + if (_sign == -1) { - _bits.AsSpan().CopyTo(dwords); - dwords = dwords.Slice(0, _bits.Length); + NumericsHelpers.DangerousMakeTwosComplement(buffer.Slice(0, buffer.Length - 1)); // Mutates dwords + highDWord = uint.MaxValue; } else - { - dwords = (uint[])_bits.Clone(); - } - NumericsHelpers.DangerousMakeTwosComplement(dwords); // Mutates dwords - highDWord = uint.MaxValue; - } - else - { - dwords = _bits; - highDWord = 0; - dwordsIsScratch = false; + highDWord = 0; } // Find highest significant byte and ensure high bit is 0 if positive, 1 if negative - int msb; - for (msb = dwords.Length - 1; msb > 0 && dwords[msb] == highDWord; msb--); - bool needExtraByte = (dwords[msb] & 0x80000000) != (highDWord & 0x80000000); - - int length = msb + 1 + (needExtraByte ? 1 : 0); - bool copyDwordsToScratch = true; - if (length <= scratch.Length) - { - scratch = scratch.Slice(0, length); - copyDwordsToScratch = !dwordsIsScratch; - } - else + int msb = buffer.Length - 2; + while (msb > 0 && buffer[msb] == highDWord) { - scratch = new uint[length]; + msb--; } - if (copyDwordsToScratch) - { - dwords.Slice(0, msb + 1).CopyTo(scratch); - } + // Ensure high bit is 0 if positive, 1 if negative + bool needExtraByte = (buffer[msb] & 0x80000000) != (highDWord & 0x80000000); + int count; if (needExtraByte) { - scratch[^1] = highDWord; + count = msb + 2; + buffer = buffer.Slice(0, count); + buffer[buffer.Length - 1] = highDWord; + } + else + { + count = msb + 1; + buffer = buffer.Slice(0, count); } - return scratch; + return count; } public override string ToString() @@ -1524,42 +1573,72 @@ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan return BigNumber.TryFormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } - private static BigInteger Add(uint[]? leftBits, int leftSign, uint[]? rightBits, int rightSign) + private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) { - bool trivialLeft = leftBits == null; - bool trivialRight = rightBits == null; + bool trivialLeft = leftBits.IsEmpty; + bool trivialRight = rightBits.IsEmpty; if (trivialLeft && trivialRight) { return (long)leftSign + rightSign; } + BigInteger result; + uint[]? bitsFromPool = null; + if (trivialLeft) { - Debug.Assert(rightBits != null); - uint[] bits = BigIntegerCalculator.Add(rightBits, NumericsHelpers.Abs(leftSign)); - return new BigInteger(bits, bits, leftSign < 0); - } + Debug.Assert(!rightBits.IsEmpty); - if (trivialRight) - { - Debug.Assert(leftBits != null); - uint[] bits = BigIntegerCalculator.Add(leftBits, NumericsHelpers.Abs(rightSign)); - return new BigInteger(bits, bits, leftSign < 0); + int size = rightBits.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Add(rightBits, NumericsHelpers.Abs(leftSign), bits); + result = new BigInteger(bits, leftSign < 0); } + else if (trivialRight) + { + Debug.Assert(!leftBits.IsEmpty); - Debug.Assert(leftBits != null && rightBits != null); + int size = leftBits.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - if (leftBits.Length < rightBits.Length) + BigIntegerCalculator.Add(leftBits, NumericsHelpers.Abs(rightSign), bits); + result = new BigInteger(bits, leftSign < 0); + } + else if (leftBits.Length < rightBits.Length) { - uint[] bits = BigIntegerCalculator.Add(rightBits, leftBits); - return new BigInteger(bits, bits, leftSign < 0); + Debug.Assert(!leftBits.IsEmpty && !rightBits.IsEmpty); + + int size = rightBits.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Add(rightBits, leftBits, bits); + result = new BigInteger(bits, leftSign < 0); } else { - uint[] bits = BigIntegerCalculator.Add(leftBits, rightBits); - return new BigInteger(bits, bits, leftSign < 0); + Debug.Assert(!leftBits.IsEmpty && !rightBits.IsEmpty); + + int size = leftBits.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Add(leftBits, rightBits, bits); + result = new BigInteger(bits, leftSign < 0); } + + if (bitsFromPool != null) + ArrayPool.Shared.Return(bitsFromPool); + + return result; } public static BigInteger operator -(BigInteger left, BigInteger right) @@ -1572,42 +1651,70 @@ private static BigInteger Add(uint[]? leftBits, int leftSign, uint[]? rightBits, return Subtract(left._bits, left._sign, right._bits, right._sign); } - private static BigInteger Subtract(uint[]? leftBits, int leftSign, uint[]? rightBits, int rightSign) + private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) { - bool trivialLeft = leftBits == null; - bool trivialRight = rightBits == null; + bool trivialLeft = leftBits.IsEmpty; + bool trivialRight = rightBits.IsEmpty; if (trivialLeft && trivialRight) { return (long)leftSign - rightSign; } + BigInteger result; + uint[]? bitsFromPool = null; + if (trivialLeft) { - Debug.Assert(rightBits != null); - uint[] bits = BigIntegerCalculator.Subtract(rightBits, NumericsHelpers.Abs(leftSign)); - return new BigInteger(bits, bits, leftSign >= 0); - } + Debug.Assert(!rightBits.IsEmpty); - if (trivialRight) - { - Debug.Assert(leftBits != null); - uint[] bits = BigIntegerCalculator.Subtract(leftBits, NumericsHelpers.Abs(rightSign)); - return new BigInteger(bits, bits, leftSign < 0); + int size = rightBits.Length; + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Subtract(rightBits, NumericsHelpers.Abs(leftSign), bits); + result = new BigInteger(bits, leftSign >= 0); } + else if (trivialRight) + { + Debug.Assert(!leftBits.IsEmpty); - Debug.Assert(leftBits != null && rightBits != null); + int size = leftBits.Length; + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - if (BigIntegerCalculator.Compare(leftBits, rightBits) < 0) + BigIntegerCalculator.Subtract(leftBits, NumericsHelpers.Abs(rightSign), bits); + result = new BigInteger(bits, leftSign < 0); + } + else if (BigIntegerCalculator.Compare(leftBits, rightBits) < 0) { - uint[] bits = BigIntegerCalculator.Subtract(rightBits, leftBits); - return new BigInteger(bits, bits, leftSign >= 0); + int size = rightBits.Length; + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Subtract(rightBits, leftBits, bits); + result = new BigInteger(bits, leftSign >= 0); } else { - uint[] bits = BigIntegerCalculator.Subtract(leftBits, rightBits); - return new BigInteger(bits, bits, leftSign < 0); + Debug.Assert(!leftBits.IsEmpty && !rightBits.IsEmpty); + + int size = leftBits.Length; + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Subtract(leftBits, rightBits, bits); + result = new BigInteger(bits, leftSign < 0); } + + if (bitsFromPool != null) + ArrayPool.Shared.Return(bitsFromPool); + + return result; } public static implicit operator BigInteger(byte value) @@ -1822,7 +1929,7 @@ public static explicit operator double(BigInteger value) ulong m = length > 1 ? bits[length - 2] : 0; ulong l = length > 2 ? bits[length - 3] : 0; - int z = NumericsHelpers.CbitHighZero((uint)h); + int z = BitOperations.LeadingZeroCount((uint)h); int exp = (length - 2) * 32 - z; ulong man = (h << 32 + z) | (m << z) | (l >> 32 - z); @@ -1858,24 +1965,53 @@ public static explicit operator decimal(BigInteger value) return Zero; } - if (left._bits == null && right._bits == null) + if (left._bits is null && right._bits is null) { return left._sign & right._sign; } - ReadOnlySpan x = left.ToUInt32Span(stackalloc uint[StackallocUInt32Limit / 2]); - ReadOnlySpan y = right.ToUInt32Span(stackalloc uint[StackallocUInt32Limit / 2]); - uint[] z = new uint[Math.Max(x.Length, y.Length)]; uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; + uint[]? leftBufferFromPool = null; + int size = (left._bits?.Length ?? 1) + 1; + Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + x = x.Slice(0, left.WriteTo(x)); + + uint[]? rightBufferFromPool = null; + size = (right._bits?.Length ?? 1) + 1; + Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + y = y.Slice(0, right.WriteTo(y)); + + uint[]? resultBufferFromPool = null; + size = Math.Max(x.Length, y.Length); + Span z = (size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + for (int i = 0; i < z.Length; i++) { - uint xu = (i < x.Length) ? x[i] : xExtend; - uint yu = (i < y.Length) ? y[i] : yExtend; + uint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; + uint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; z[i] = xu & yu; } - return new BigInteger(z); + + if (leftBufferFromPool != null) + ArrayPool.Shared.Return(leftBufferFromPool); + + if (rightBufferFromPool != null) + ArrayPool.Shared.Return(rightBufferFromPool); + + var result = new BigInteger(z); + + if (resultBufferFromPool != null) + ArrayPool.Shared.Return(resultBufferFromPool); + + return result; } public static BigInteger operator |(BigInteger left, BigInteger right) @@ -1885,47 +2021,104 @@ public static explicit operator decimal(BigInteger value) if (right.IsZero) return left; - if (left._bits == null && right._bits == null) + if (left._bits is null && right._bits is null) { return left._sign | right._sign; } - ReadOnlySpan x = left.ToUInt32Span(stackalloc uint[StackallocUInt32Limit / 2]); - ReadOnlySpan y = right.ToUInt32Span(stackalloc uint[StackallocUInt32Limit / 2]); - uint[] z = new uint[Math.Max(x.Length, y.Length)]; uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; + uint[]? leftBufferFromPool = null; + int size = (left._bits?.Length ?? 1) + 1; + Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + x = x.Slice(0, left.WriteTo(x)); + + uint[]? rightBufferFromPool = null; + size = (right._bits?.Length ?? 1) + 1; + Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + y = y.Slice(0, right.WriteTo(y)); + + uint[]? resultBufferFromPool = null; + size = Math.Max(x.Length, y.Length); + Span z = (size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + for (int i = 0; i < z.Length; i++) { - uint xu = (i < x.Length) ? x[i] : xExtend; - uint yu = (i < y.Length) ? y[i] : yExtend; + uint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; + uint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; z[i] = xu | yu; } - return new BigInteger(z); + + if (leftBufferFromPool != null) + ArrayPool.Shared.Return(leftBufferFromPool); + + if (rightBufferFromPool != null) + ArrayPool.Shared.Return(rightBufferFromPool); + + var result = new BigInteger(z); + + if (resultBufferFromPool != null) + ArrayPool.Shared.Return(resultBufferFromPool); + + return result; } public static BigInteger operator ^(BigInteger left, BigInteger right) { - if (left._bits == null && right._bits == null) + if (left._bits is null && right._bits is null) { return left._sign ^ right._sign; } - ReadOnlySpan x = left.ToUInt32Span(stackalloc uint[StackallocUInt32Limit / 2]); - ReadOnlySpan y = right.ToUInt32Span(stackalloc uint[StackallocUInt32Limit / 2]); - uint[] z = new uint[Math.Max(x.Length, y.Length)]; uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; + uint[]? leftBufferFromPool = null; + int size = (left._bits?.Length ?? 1) + 1; + Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + x = x.Slice(0, left.WriteTo(x)); + + uint[]? rightBufferFromPool = null; + size = (right._bits?.Length ?? 1) + 1; + Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + y = y.Slice(0, right.WriteTo(y)); + + uint[]? resultBufferFromPool = null; + size = Math.Max(x.Length, y.Length); + Span z = (size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + for (int i = 0; i < z.Length; i++) { - uint xu = (i < x.Length) ? x[i] : xExtend; - uint yu = (i < y.Length) ? y[i] : yExtend; + uint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; + uint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; z[i] = xu ^ yu; } - return new BigInteger(z); + if (leftBufferFromPool != null) + ArrayPool.Shared.Return(leftBufferFromPool); + + if (rightBufferFromPool != null) + ArrayPool.Shared.Return(rightBufferFromPool); + + var result = new BigInteger(z); + + if (resultBufferFromPool != null) + ArrayPool.Shared.Return(resultBufferFromPool); + + return result; } public static BigInteger operator <<(BigInteger value, int shift) @@ -1934,28 +2127,26 @@ public static explicit operator decimal(BigInteger value) return value; if (shift == int.MinValue) - return (value >> int.MaxValue) >> 1; + return ((value >> int.MaxValue) >> 1); if (shift < 0) return value >> -shift; (int digitShift, int smallShift) = Math.DivRem(shift, kcbitUint); - Span xd = stackalloc uint[1]; - bool negx = GetPartsForBitManipulation(ref value, ref xd); + uint[]? xdFromPool = null; + int xl = value._bits?.Length ?? 1; + Span xd = (xl <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : xdFromPool = ArrayPool.Shared.Rent(xl)).Slice(0, xl); + bool negx = value.GetPartsForBitManipulation(xd); - int zl = xd.Length + digitShift + 1; - uint[]? zdArray = null; - Span zd = stackalloc uint[0]; - if (zl <= StackallocUInt32Limit) - { - zd = stackalloc uint[StackallocUInt32Limit].Slice(0, zl); - zd.Slice(0, digitShift).Clear(); - } - else - { - zd = zdArray = new uint[zl]; - } + int zl = xl + digitShift + 1; + uint[]? zdFromPool = null; + Span zd = ((uint)zl <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : zdFromPool = ArrayPool.Shared.Rent(zl)).Slice(0, zl); + zd.Clear(); uint carry = 0; if (smallShift == 0) @@ -1968,16 +2159,25 @@ public static explicit operator decimal(BigInteger value) else { int carryShift = kcbitUint - smallShift; - for (int i = 0; i < xd.Length; i++) + int i; + for (i = 0; i < xd.Length; i++) { uint rot = xd[i]; zd[i + digitShift] = rot << smallShift | carry; carry = rot >> carryShift; } } - zd[^1] = carry; - return new BigInteger(zd, zdArray, negx); + zd[zd.Length - 1] = carry; + + var result = new BigInteger(zd, negx); + + if (xdFromPool != null) + ArrayPool.Shared.Return(xdFromPool); + if (zdFromPool != null) + ArrayPool.Shared.Return(zdFromPool); + + return result; } public static BigInteger operator >>(BigInteger value, int shift) @@ -1986,38 +2186,30 @@ public static explicit operator decimal(BigInteger value) return value; if (shift == int.MinValue) - return value << int.MaxValue << 1; + return ((value << int.MaxValue) << 1); if (shift < 0) return value << -shift; (int digitShift, int smallShift) = Math.DivRem(shift, kcbitUint); - Span stackallocedXd = stackalloc uint[1]; - Span xd = stackallocedXd; - bool negx = GetPartsForBitManipulation(ref value, ref xd); + BigInteger result; + + uint[]? xdFromPool = null; + int xl = value._bits?.Length ?? 1; + Span xd = (xl <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : xdFromPool = ArrayPool.Shared.Rent(xl)).Slice(0, xl); + + bool negx = value.GetPartsForBitManipulation(xd); bool trackSignBit = false; if (negx) { if (shift >= (kcbitUint * xd.Length)) { - return MinusOne; - } - - if (xd != stackallocedXd) - { - // make a copy of the part extracted from GetPartsForBitManipulation - if (xd.Length <= StackallocUInt32Limit) - { - stackallocedXd = stackalloc uint[StackallocUInt32Limit].Slice(0, xd.Length); - xd.CopyTo(stackallocedXd); - xd = stackallocedXd; - } - else - { - xd = xd.ToArray(); - } + result = MinusOne; + goto exit; } NumericsHelpers.DangerousMakeTwosComplement(xd); // Mutates xd @@ -2028,18 +2220,15 @@ public static explicit operator decimal(BigInteger value) // After a 32 bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back. // The expected result is [0x00, 0x00, 0xFFFFFFFF] (2's complement) or [0x00, 0x00, 0x01] when converted back // If the 2's component's last element is a 0, we will track the sign externally - trackSignBit = smallShift == 0 && xd[^1] == 0; + trackSignBit = smallShift == 0 && xd[xd.Length - 1] == 0; } - int zl = xd.Length - digitShift + (trackSignBit ? 1: 0); - uint[]? zdArray = null; - Span zd = stackalloc uint[0]; - if (zl > 0) - { - zd = zl <= StackallocUInt32Limit ? - stackalloc uint[StackallocUInt32Limit].Slice(0, zl) : - zdArray = new uint[zl]; - } + uint[]? zdFromPool = null; + int zl = Math.Max(xl - digitShift, 0) + (trackSignBit ? 1 : 0); + Span zd = ((uint)zl <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : zdFromPool = ArrayPool.Shared.Rent(zl)).Slice(0, zl); + zd.Clear(); if (smallShift == 0) { @@ -2063,17 +2252,25 @@ stackalloc uint[StackallocUInt32Limit].Slice(0, zl) : carry = rot << carryShift; } } + if (negx) { // Set the tracked sign to the last element if (trackSignBit) - { - zd[^1] = 0xFFFFFFFF; - } + zd[zd.Length - 1] = 0xFFFFFFFF; + NumericsHelpers.DangerousMakeTwosComplement(zd); // Mutates zd } - return new BigInteger(zd, zdArray, negx); + result = new BigInteger(zd, negx); + + if (zdFromPool != null) + ArrayPool.Shared.Return(zdFromPool); + exit: + if (xdFromPool != null) + ArrayPool.Shared.Return(xdFromPool); + + return result; } public static BigInteger operator ~(BigInteger value) @@ -2118,46 +2315,87 @@ stackalloc uint[StackallocUInt32Limit].Slice(0, zl) : left.AssertValid(); right.AssertValid(); - bool trivialLeft = left._bits == null; - bool trivialRight = right._bits == null; + return Multiply(left._bits, left._sign, right._bits, right._sign); + } + + private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOnlySpan right, int rightSign) + { + bool trivialLeft = left.IsEmpty; + bool trivialRight = right.IsEmpty; if (trivialLeft && trivialRight) { - return (long)left._sign * right._sign; + return (long)leftSign * rightSign; } + BigInteger result; + uint[]? bitsFromPool = null; + if (trivialLeft) { - Debug.Assert(right._bits != null); - uint[] bits = BigIntegerCalculator.Multiply(right._bits, NumericsHelpers.Abs(left._sign)); - return new BigInteger(bits, bits, (left._sign < 0) ^ (right._sign < 0)); - } + Debug.Assert(!right.IsEmpty); - if (trivialRight) - { - Debug.Assert(left._bits != null); - uint[] bits = BigIntegerCalculator.Multiply(left._bits, NumericsHelpers.Abs(right._sign)); - return new BigInteger(bits, bits, (left._sign < 0) ^ (right._sign < 0)); + int size = right.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Multiply(right, NumericsHelpers.Abs(leftSign), bits); + result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); } + else if (trivialRight) + { + Debug.Assert(!left.IsEmpty); - Debug.Assert(left._bits != null && right._bits != null); + int size = left.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - if (left._bits == right._bits) - { - uint[] bits = BigIntegerCalculator.Square(left._bits); - return new BigInteger(bits, bits, (left._sign < 0) ^ (right._sign < 0)); + BigIntegerCalculator.Multiply(left, NumericsHelpers.Abs(rightSign), bits); + result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); } + else if (left == right) + { + int size = left.Length + right.Length; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - if (left._bits.Length < right._bits.Length) + BigIntegerCalculator.Square(left, bits); + result = new BigInteger(bits, negative: false); + } + else if (left.Length < right.Length) { - uint[] bits = BigIntegerCalculator.Multiply(right._bits, left._bits); - return new BigInteger(bits, bits, (left._sign < 0) ^ (right._sign < 0)); + Debug.Assert(!left.IsEmpty && !right.IsEmpty); + + int size = left.Length + right.Length; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + bits.Clear(); + + BigIntegerCalculator.Multiply(right, left, bits); + result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); } else { - uint[] bits = BigIntegerCalculator.Multiply(left._bits, right._bits); - return new BigInteger(bits, bits, (left._sign < 0) ^ (right._sign < 0)); + Debug.Assert(!left.IsEmpty && !right.IsEmpty); + + int size = left.Length + right.Length; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + bits.Clear(); + + BigIntegerCalculator.Multiply(left, right, bits); + result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); } + + if (bitsFromPool != null) + ArrayPool.Shared.Return(bitsFromPool); + + return result; } public static BigInteger operator /(BigInteger dividend, BigInteger divisor) @@ -2180,11 +2418,28 @@ stackalloc uint[StackallocUInt32Limit].Slice(0, zl) : return s_bnZeroInt; } + uint[]? quotientFromPool = null; + if (trivialDivisor) { Debug.Assert(dividend._bits != null); - uint[] bits = BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign)); - return new BigInteger(bits, bits, (dividend._sign < 0) ^ (divisor._sign < 0)); + + int size = dividend._bits.Length; + Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + try + { + //may throw DivideByZeroException + BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient); + return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); + } + finally + { + if (quotientFromPool != null) + ArrayPool.Shared.Return(quotientFromPool); + } } Debug.Assert(dividend._bits != null && divisor._bits != null); @@ -2195,8 +2450,18 @@ stackalloc uint[StackallocUInt32Limit].Slice(0, zl) : } else { - uint[] bits = BigIntegerCalculator.Divide(dividend._bits, divisor._bits); - return new BigInteger(bits, bits, (dividend._sign < 0) ^ (divisor._sign < 0)); + int size = dividend._bits.Length - divisor._bits.Length + 1; + Span quotient = ((uint)size < BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Divide(dividend._bits, divisor._bits, quotient); + var result = new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); + + if (quotientFromPool != null) + ArrayPool.Shared.Return(quotientFromPool); + + return result; } } @@ -2233,8 +2498,20 @@ stackalloc uint[StackallocUInt32Limit].Slice(0, zl) : { return dividend; } - uint[] bits = BigIntegerCalculator.Remainder(dividend._bits, divisor._bits); - return new BigInteger(bits, bits, dividend._sign < 0); + + uint[]? bitsFromPool = null; + int size = dividend._bits.Length; + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Remainder(dividend._bits, divisor._bits, bits); + var result = new BigInteger(bits, dividend._sign < 0); + + if (bitsFromPool != null) + ArrayPool.Shared.Return(bitsFromPool); + + return result; } public static bool operator <(BigInteger left, BigInteger right) @@ -2451,28 +2728,26 @@ public long GetBitLength() /// Encapsulate the logic of normalizing the "small" and "large" forms of BigInteger /// into the "large" form so that Bit Manipulation algorithms can be simplified. /// - /// /// - /// The UInt32 span containing the entire big integer in "large" (denormalized) form. + /// The UInt32 array containing the entire big integer in "large" (denormalized) form. /// E.g., the number one (1) and negative one (-1) are both stored as 0x00000001 /// BigInteger values Int32.MinValue < x <= Int32.MaxValue are converted to this - /// format for convenience. Expecting to be passed a writeable span of length 1. + /// format for convenience. /// /// True for negative numbers. - private static bool GetPartsForBitManipulation(ref BigInteger x, ref Span xd) + private bool GetPartsForBitManipulation(Span xd) { - Debug.Assert(xd.Length == 1); + Debug.Assert(_bits is null ? xd.Length == 1 : xd.Length == _bits.Length); - if (x._bits == null) + if (_bits is null) { - xd[0] = (uint)(x._sign < 0 ? -x._sign : x._sign); + xd[0] = (uint)(_sign < 0 ? -_sign : _sign); } else { - xd = x._bits; + _bits.CopyTo(xd); } - - return x._sign < 0; + return _sign < 0; } internal static int GetDiffLength(uint[] rgu1, uint[] rgu2, int cu) @@ -2506,4 +2781,4 @@ private void AssertValid() } } } -} +} \ No newline at end of file diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.AddSub.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.AddSub.cs index 4439d67346de6..c2ac712ec9e67 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.AddSub.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.AddSub.cs @@ -2,112 +2,89 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Security; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Numerics { internal static partial class BigIntegerCalculator { - public static uint[] Add(uint[] left, uint right) + public static void Add(ReadOnlySpan left, uint right, Span bits) { - Debug.Assert(left != null); Debug.Assert(left.Length >= 1); + Debug.Assert(bits.Length == left.Length + 1); // Executes the addition for one big and one 32-bit integer. // Thus, we've similar code than below, but there is no loop for // processing the 32-bit integer, since it's a single element. - uint[] bits = new uint[left.Length + 1]; + long carry = right; - long digit = (long)left[0] + right; - bits[0] = unchecked((uint)digit); - long carry = digit >> 32; - - for (int i = 1; i < left.Length; i++) + for (int i = 0; i < left.Length; i++) { - digit = left[i] + carry; + long digit = left[i] + carry; bits[i] = unchecked((uint)digit); carry = digit >> 32; } - bits[left.Length] = (uint)carry; - return bits; + bits[left.Length] = (uint)carry; } - public static unsafe uint[] Add(uint[] left, uint[] right) + public static void Add(ReadOnlySpan left, ReadOnlySpan right, Span bits) { - Debug.Assert(left != null); - Debug.Assert(right != null); Debug.Assert(left.Length >= right.Length); + Debug.Assert(bits.Length == left.Length + 1); - // Switching to unsafe pointers helps sparing - // some nasty index calculations... - - uint[] bits = new uint[left.Length + 1]; - - fixed (uint* l = left, r = right, b = &bits[0]) - { - Add(l, left.Length, - r, right.Length, - b, bits.Length); - } - - return bits; - } + int i = 0; + long carry = 0L; - private static unsafe void Add(uint* left, int leftLength, - uint* right, int rightLength, - uint* bits, int bitsLength) - { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - Debug.Assert(leftLength >= rightLength); - Debug.Assert(bitsLength == leftLength + 1); + // Switching to managed references helps eliminating + // index bounds check... + ref uint leftPtr = ref MemoryMarshal.GetReference(left); + ref uint resultPtr = ref MemoryMarshal.GetReference(bits); // Executes the "grammar-school" algorithm for computing z = a + b. // While calculating z_i = a_i + b_i we take care of overflow: // Since a_i + b_i + c <= 2(2^32 - 1) + 1 = 2^33 - 1, our carry c // has always the value 1 or 0; hence, we're safe here. - int i = 0; - long carry = 0L; - - for (; i < rightLength; i++) + for ( ; i < right.Length; i++) { - long digit = (left[i] + carry) + right[i]; - bits[i] = unchecked((uint)digit); + long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i]; + Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit); carry = digit >> 32; } - for (; i < leftLength; i++) + for ( ; i < left.Length; i++) { long digit = left[i] + carry; - bits[i] = unchecked((uint)digit); + Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit); carry = digit >> 32; } - bits[i] = (uint)carry; + Unsafe.Add(ref resultPtr, i) = (uint)carry; } - private static unsafe void AddSelf(uint* left, int leftLength, - uint* right, int rightLength) + private static void AddSelf(Span left, ReadOnlySpan right) { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - Debug.Assert(leftLength >= rightLength); + Debug.Assert(left.Length >= right.Length); + + int i = 0; + long carry = 0L; + + // Switching to managed references helps eliminating + // index bounds check... + ref uint leftPtr = ref MemoryMarshal.GetReference(left); // Executes the "grammar-school" algorithm for computing z = a + b. // Same as above, but we're writing the result directly to a and // stop execution, if we're out of b and c is already 0. - int i = 0; - long carry = 0L; - - for (; i < rightLength; i++) + for ( ; i < right.Length; i++) { - long digit = (left[i] + carry) + right[i]; - left[i] = unchecked((uint)digit); + long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i]; + Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit); carry = digit >> 32; } - for (; carry != 0 && i < leftLength; i++) + for ( ; carry != 0 && i < left.Length; i++) { long digit = left[i] + carry; left[i] = (uint)digit; @@ -117,110 +94,84 @@ private static unsafe void AddSelf(uint* left, int leftLength, Debug.Assert(carry == 0); } - public static uint[] Subtract(uint[] left, uint right) + public static void Subtract(ReadOnlySpan left, uint right, Span bits) { - Debug.Assert(left != null); Debug.Assert(left.Length >= 1); Debug.Assert(left[0] >= right || left.Length >= 2); + Debug.Assert(bits.Length == left.Length); // Executes the subtraction for one big and one 32-bit integer. // Thus, we've similar code than below, but there is no loop for // processing the 32-bit integer, since it's a single element. - uint[] bits = new uint[left.Length]; + long carry = -right; - long digit = (long)left[0] - right; - bits[0] = unchecked((uint)digit); - long carry = digit >> 32; - - for (int i = 1; i < left.Length; i++) + for (int i = 0; i < left.Length; i++) { - digit = left[i] + carry; + long digit = left[i] + carry; bits[i] = unchecked((uint)digit); carry = digit >> 32; } - - return bits; } - public static unsafe uint[] Subtract(uint[] left, uint[] right) + public static void Subtract(ReadOnlySpan left, ReadOnlySpan right, Span bits) { - Debug.Assert(left != null); - Debug.Assert(right != null); Debug.Assert(left.Length >= right.Length); Debug.Assert(Compare(left, right) >= 0); + Debug.Assert(bits.Length == left.Length); - // Switching to unsafe pointers helps sparing - // some nasty index calculations... - - uint[] bits = new uint[left.Length]; - - fixed (uint* l = left, r = right, b = bits) - { - Subtract(l, left.Length, - r, right.Length, - b, bits.Length); - } - - return bits; - } + int i = 0; + long carry = 0L; - private static unsafe void Subtract(uint* left, int leftLength, - uint* right, int rightLength, - uint* bits, int bitsLength) - { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - Debug.Assert(leftLength >= rightLength); - Debug.Assert(Compare(left, leftLength, right, rightLength) >= 0); - Debug.Assert(bitsLength == leftLength); + // Switching to managed references helps eliminating + // index bounds check... + ref uint leftPtr = ref MemoryMarshal.GetReference(left); + ref uint resultPtr = ref MemoryMarshal.GetReference(bits); // Executes the "grammar-school" algorithm for computing z = a - b. // While calculating z_i = a_i - b_i we take care of overflow: // Since a_i - b_i doesn't need any additional bit, our carry c // has always the value -1 or 0; hence, we're safe here. - int i = 0; - long carry = 0L; - - for (; i < rightLength; i++) + for ( ; i < right.Length; i++) { - long digit = (left[i] + carry) - right[i]; - bits[i] = unchecked((uint)digit); + long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i]; + Unsafe.Add(ref resultPtr, i) = unchecked((uint)digit); carry = digit >> 32; } - for (; i < leftLength; i++) + for ( ; i < left.Length; i++) { long digit = left[i] + carry; - bits[i] = (uint)digit; + Unsafe.Add(ref resultPtr, i) = (uint)digit; carry = digit >> 32; } Debug.Assert(carry == 0); } - private static unsafe void SubtractSelf(uint* left, int leftLength, - uint* right, int rightLength) + private static void SubtractSelf(Span left, ReadOnlySpan right) { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - Debug.Assert(leftLength >= rightLength); - Debug.Assert(Compare(left, leftLength, right, rightLength) >= 0); + Debug.Assert(left.Length >= right.Length); + Debug.Assert(Compare(left, right) >= 0); + + int i = 0; + long carry = 0L; + + // Switching to managed references helps eliminating + // index bounds check... + ref uint leftPtr = ref MemoryMarshal.GetReference(left); // Executes the "grammar-school" algorithm for computing z = a - b. // Same as above, but we're writing the result directly to a and // stop execution, if we're out of b and c is already 0. - int i = 0; - long carry = 0L; - - for (; i < rightLength; i++) + for (; i < right.Length; i++) { - long digit = (left[i] + carry) - right[i]; - left[i] = unchecked((uint)digit); + long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i]; + Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit); carry = digit >> 32; } - for (; carry != 0 && i < leftLength; i++) + for (; carry != 0 && i < left.Length; i++) { long digit = left[i] + carry; left[i] = (uint)digit; @@ -229,48 +180,5 @@ private static unsafe void SubtractSelf(uint* left, int leftLength, Debug.Assert(carry == 0); } - - public static int Compare(uint[] left, uint[] right) - { - Debug.Assert(left != null); - Debug.Assert(right != null); - - if (left.Length < right.Length) - return -1; - if (left.Length > right.Length) - return 1; - - for (int i = left.Length - 1; i >= 0; i--) - { - if (left[i] < right[i]) - return -1; - if (left[i] > right[i]) - return 1; - } - - return 0; - } - - private static unsafe int Compare(uint* left, int leftLength, - uint* right, int rightLength) - { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - - if (leftLength < rightLength) - return -1; - if (leftLength > rightLength) - return 1; - - for (int i = leftLength - 1; i >= 0; i--) - { - if (left[i] < right[i]) - return -1; - if (left[i] > right[i]) - return 1; - } - - return 0; - } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.BitsBuffer.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.BitsBuffer.cs deleted file mode 100644 index ec05118f03007..0000000000000 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.BitsBuffer.cs +++ /dev/null @@ -1,215 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Security; - -namespace System.Numerics -{ - // ATTENTION: always pass BitsBuffer by reference, - // it's a structure for performance reasons. Furthermore - // it's a mutable one, so use it only with care! - - internal static partial class BigIntegerCalculator - { - // To spare memory allocations a buffer helps reusing memory! - // We just create the target array twice and switch between every - // operation. In order to not compute unnecessarily with all those - // leading zeros we take care of the current actual length. - - internal struct BitsBuffer - { - private uint[] _bits; - private int _length; - - public BitsBuffer(int size, uint value) - { - Debug.Assert(size >= 1); - - _bits = new uint[size]; - _length = value != 0 ? 1 : 0; - - _bits[0] = value; - } - - public BitsBuffer(int size, uint[] value) - { - Debug.Assert(value != null); - Debug.Assert(size >= ActualLength(value)); - - _bits = new uint[size]; - _length = ActualLength(value); - - Array.Copy(value, _bits, _length); - } - - public unsafe void MultiplySelf(ref BitsBuffer value, - ref BitsBuffer temp) - { - Debug.Assert(temp._length == 0); - Debug.Assert(_length + value._length <= temp._bits.Length); - - // Executes a multiplication for this and value, writes the - // result to temp. Switches this and temp arrays afterwards. - - fixed (uint* b = _bits, v = value._bits, t = temp._bits) - { - if (_length < value._length) - { - Multiply(v, value._length, - b, _length, - t, _length + value._length); - } - else - { - Multiply(b, _length, - v, value._length, - t, _length + value._length); - } - } - - Apply(ref temp, _length + value._length); - } - - public unsafe void SquareSelf(ref BitsBuffer temp) - { - Debug.Assert(temp._length == 0); - Debug.Assert(_length + _length <= temp._bits.Length); - - // Executes a square for this, writes the result to temp. - // Switches this and temp arrays afterwards. - - fixed (uint* b = _bits, t = temp._bits) - { - Square(b, _length, - t, _length + _length); - } - - Apply(ref temp, _length + _length); - } - - public void Reduce(ref FastReducer reducer) - { - // Executes a modulo operation using an optimized reducer. - // Thus, no need of any switching here, happens in-line. - - _length = reducer.Reduce(_bits, _length); - } - - public unsafe void Reduce(uint[] modulus) - { - Debug.Assert(modulus != null); - - // Executes a modulo operation using the divide operation. - // Thus, no need of any switching here, happens in-line. - - if (_length >= modulus.Length) - { - fixed (uint* b = _bits, m = modulus) - { - Divide(b, _length, - m, modulus.Length, - null, 0); - } - - _length = ActualLength(_bits, modulus.Length); - } - } - - public unsafe void Reduce(ref BitsBuffer modulus) - { - // Executes a modulo operation using the divide operation. - // Thus, no need of any switching here, happens in-line. - - if (_length >= modulus._length) - { - fixed (uint* b = _bits, m = modulus._bits) - { - Divide(b, _length, - m, modulus._length, - null, 0); - } - - _length = ActualLength(_bits, modulus._length); - } - } - - public void Overwrite(ulong value) - { - Debug.Assert(_bits.Length >= 2); - - if (_length > 2) - { - // Ensure leading zeros - Array.Clear(_bits, 2, _length - 2); - } - - uint lo = unchecked((uint)value); - uint hi = (uint)(value >> 32); - - _bits[0] = lo; - _bits[1] = hi; - _length = hi != 0 ? 2 : lo != 0 ? 1 : 0; - } - - public void Overwrite(uint value) - { - Debug.Assert(_bits.Length >= 1); - - if (_length > 1) - { - // Ensure leading zeros - Array.Clear(_bits, 1, _length - 1); - } - - _bits[0] = value; - _length = value != 0 ? 1 : 0; - } - - public uint[] GetBits() - { - return _bits; - } - - public int GetSize() - { - return _bits.Length; - } - - public int GetLength() - { - return _length; - } - - public void Refresh(int maxLength) - { - Debug.Assert(_bits.Length >= maxLength); - - if (_length > maxLength) - { - // Ensure leading zeros - Array.Clear(_bits, maxLength, _length - maxLength); - } - - _length = ActualLength(_bits, maxLength); - } - - private void Apply(ref BitsBuffer temp, int maxLength) - { - Debug.Assert(temp._length == 0); - Debug.Assert(maxLength <= temp._bits.Length); - - // Resets this and switches this and temp afterwards. - // The caller assumed an empty temp, the next will too. - - Array.Clear(_bits, 0, _length); - - uint[] t = temp._bits; - temp._bits = _bits; - _bits = t; - - _length = ActualLength(_bits, maxLength); - } - } - } -} diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs index bb75e77a228d0..9ca65e0813e25 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs @@ -1,26 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; -using System.Security; namespace System.Numerics { internal static partial class BigIntegerCalculator { - public static uint[] Divide(uint[] left, uint right, - out uint remainder) + public static void Divide(ReadOnlySpan left, uint right, Span quotient, out uint remainder) { - Debug.Assert(left != null); Debug.Assert(left.Length >= 1); + Debug.Assert(quotient.Length == left.Length); // Executes the division for one big and one 32-bit integer. // Thus, we've similar code than below, but there is no loop for // processing the 32-bit integer, since it's a single element. - uint[] quotient = new uint[left.Length]; - ulong carry = 0UL; + for (int i = left.Length - 1; i >= 0; i--) { ulong value = (carry << 32) | left[i]; @@ -29,19 +27,15 @@ public static uint[] Divide(uint[] left, uint right, carry = value - digit * right; } remainder = (uint)carry; - - return quotient; } - public static uint[] Divide(uint[] left, uint right) + public static void Divide(ReadOnlySpan left, uint right, Span quotient) { - Debug.Assert(left != null); Debug.Assert(left.Length >= 1); + Debug.Assert(quotient.Length == left.Length); // Same as above, but only computing the quotient. - uint[] quotient = new uint[left.Length]; - ulong carry = 0UL; for (int i = left.Length - 1; i >= 0; i--) { @@ -50,17 +44,13 @@ public static uint[] Divide(uint[] left, uint right) quotient[i] = (uint)digit; carry = value - digit * right; } - - return quotient; } - public static uint Remainder(uint[] left, uint right) + public static uint Remainder(ReadOnlySpan left, uint right) { - Debug.Assert(left != null); Debug.Assert(left.Length >= 1); // Same as above, but only computing the remainder. - ulong carry = 0UL; for (int i = left.Length - 1; i >= 0; i--) { @@ -71,115 +61,79 @@ public static uint Remainder(uint[] left, uint right) return (uint)carry; } - public static unsafe uint[] Divide(uint[] left, uint[] right, - out uint[] remainder) + public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) { - Debug.Assert(left != null); - Debug.Assert(right != null); Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); Debug.Assert(left.Length >= right.Length); + Debug.Assert(quotient.Length == left.Length - right.Length + 1); + Debug.Assert(remainder.Length == left.Length); - // Switching to unsafe pointers helps sparing - // some nasty index calculations... - - uint[] localLeft = left.AsSpan().ToArray(); // left will get overwritten, we need a local copy - uint[] bits = new uint[left.Length - right.Length + 1]; - - fixed (uint* l = &localLeft[0], r = &right[0], b = &bits[0]) - { - Divide(l, localLeft.Length, - r, right.Length, - b, bits.Length); - } - - remainder = localLeft; - - return bits; + left.CopyTo(remainder); + Divide(remainder, right, quotient); } - public static unsafe uint[] Divide(uint[] left, uint[] right) + public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Span quotient) { - Debug.Assert(left != null); - Debug.Assert(right != null); Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); Debug.Assert(left.Length >= right.Length); + Debug.Assert(quotient.Length == left.Length - right.Length + 1); // Same as above, but only returning the quotient. - // left will get overwritten, we need a local copy - Span localLeft = stackalloc uint[0]; - if (left.Length <= BigInteger.StackallocUInt32Limit) - { - localLeft = stackalloc uint[BigInteger.StackallocUInt32Limit].Slice(0, left.Length); - left.AsSpan().CopyTo(localLeft); - } - else - { - localLeft = left.AsSpan().ToArray(); - } + uint[]? leftCopyFromPool = null; - uint[] bits = new uint[left.Length - right.Length + 1]; + // NOTE: left will get overwritten, we need a local copy + // However, mutated left is not used afterwards, so use array pooling or stack alloc + Span leftCopy = (left.Length <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); + left.CopyTo(leftCopy); - fixed (uint* l = &localLeft[0], r = &right[0], b = &bits[0]) - { - Divide(l, localLeft.Length, - r, right.Length, - b, bits.Length); - } + Divide(leftCopy, right, quotient); - return bits; + if (leftCopyFromPool != null) + ArrayPool.Shared.Return(leftCopyFromPool); } - public static unsafe uint[] Remainder(uint[] left, uint[] right) + public static void Remainder(ReadOnlySpan left, ReadOnlySpan right, Span remainder) { - Debug.Assert(left != null); - Debug.Assert(right != null); Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); Debug.Assert(left.Length >= right.Length); + Debug.Assert(remainder.Length >= left.Length); // Same as above, but only returning the remainder. - uint[] localLeft = left.AsSpan().ToArray(); // left will get overwritten, we need a local copy - - fixed (uint* l = &localLeft[0], r = &right[0]) - { - Divide(l, localLeft.Length, - r, right.Length, - null, 0); - } - - return localLeft; + left.CopyTo(remainder); + Divide(remainder, right, default); } - private static unsafe void Divide(uint* left, int leftLength, - uint* right, int rightLength, - uint* bits, int bitsLength) + private static void Divide(Span left, ReadOnlySpan right, Span bits) { - Debug.Assert(leftLength >= 1); - Debug.Assert(rightLength >= 1); - Debug.Assert(leftLength >= rightLength); - Debug.Assert(bitsLength == leftLength - rightLength + 1 - || bitsLength == 0); + Debug.Assert(left.Length >= 1); + Debug.Assert(right.Length >= 1); + Debug.Assert(left.Length >= right.Length); + Debug.Assert(bits.Length == left.Length - right.Length + 1 + || bits.Length == 0); // Executes the "grammar-school" algorithm for computing q = a / b. // Before calculating q_i, we get more bits into the highest bit // block of the divisor. Thus, guessing digits of the quotient // will be more precise. Additionally we'll get r = a % b. - uint divHi = right[rightLength - 1]; - uint divLo = rightLength > 1 ? right[rightLength - 2] : 0; + uint divHi = right[right.Length - 1]; + uint divLo = right.Length > 1 ? right[right.Length - 2] : 0; // We measure the leading zeros of the divisor - int shift = LeadingZeros(divHi); + int shift = BitOperations.LeadingZeroCount(divHi); int backShift = 32 - shift; // And, we make sure the most significant bit is set if (shift > 0) { - uint divNx = rightLength > 2 ? right[rightLength - 3] : 0; + uint divNx = right.Length > 2 ? right[right.Length - 3] : 0; divHi = (divHi << shift) | (divLo >> backShift); divLo = (divLo << shift) | (divNx >> backShift); @@ -187,10 +141,10 @@ private static unsafe void Divide(uint* left, int leftLength, // Then, we divide all of the bits as we would do it using // pen and paper: guessing the next digit, subtracting, ... - for (int i = leftLength; i >= rightLength; i--) + for (int i = left.Length; i >= right.Length; i--) { - int n = i - rightLength; - uint t = i < leftLength ? left[i] : 0; + int n = i - right.Length; + uint t = (uint)i < (uint)left.Length ? left[i] : 0; ulong valHi = ((ulong)t << 32) | left[i - 1]; uint valLo = i > 1 ? left[i - 2] : 0; @@ -217,15 +171,13 @@ private static unsafe void Divide(uint* left, int leftLength, if (digit > 0) { // Now it's time to subtract our current quotient - uint carry = SubtractDivisor(left + n, leftLength - n, - right, rightLength, digit); + uint carry = SubtractDivisor(left.Slice(n), right, digit); if (carry != t) { Debug.Assert(carry == t + 1); // Our guess was still exactly one too high - carry = AddDivisor(left + n, leftLength - n, - right, rightLength); + carry = AddDivisor(left.Slice(n), right); --digit; Debug.Assert(carry == 1); @@ -233,41 +185,36 @@ private static unsafe void Divide(uint* left, int leftLength, } // We have the digit! - if (bitsLength != 0) + if ((uint)n < (uint)bits.Length) bits[n] = (uint)digit; - if (i < leftLength) + + if ((uint)i < (uint)left.Length) left[i] = 0; } } - private static unsafe uint AddDivisor(uint* left, int leftLength, - uint* right, int rightLength) + private static uint AddDivisor(Span left, ReadOnlySpan right) { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - Debug.Assert(leftLength >= rightLength); + Debug.Assert(left.Length >= right.Length); // Repairs the dividend, if the last subtract was too much ulong carry = 0UL; - for (int i = 0; i < rightLength; i++) + for (int i = 0; i < right.Length; i++) { - ulong digit = (left[i] + carry) + right[i]; - left[i] = unchecked((uint)digit); + ref uint leftElement = ref left[i]; + ulong digit = (leftElement + carry) + right[i]; + leftElement = unchecked((uint)digit); carry = digit >> 32; } return (uint)carry; } - private static unsafe uint SubtractDivisor(uint* left, int leftLength, - uint* right, int rightLength, - ulong q) + private static uint SubtractDivisor(Span left, ReadOnlySpan right, ulong q) { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - Debug.Assert(leftLength >= rightLength); + Debug.Assert(left.Length >= right.Length); Debug.Assert(q <= 0xFFFFFFFF); // Combines a subtract and a multiply operation, which is naturally @@ -275,14 +222,15 @@ private static unsafe uint SubtractDivisor(uint* left, int leftLength, ulong carry = 0UL; - for (int i = 0; i < rightLength; i++) + for (int i = 0; i < right.Length; i++) { carry += right[i] * q; uint digit = unchecked((uint)carry); carry = carry >> 32; - if (left[i] < digit) + ref uint leftElement = ref left[i]; + if (leftElement < digit) ++carry; - left[i] = unchecked(left[i] - digit); + leftElement = unchecked(leftElement - digit); } return (uint)carry; @@ -316,39 +264,5 @@ private static bool DivideGuessTooBig(ulong q, ulong valHi, uint valLo, return false; } - - private static int LeadingZeros(uint value) - { - if (value == 0) - return 32; - - int count = 0; - if ((value & 0xFFFF0000) == 0) - { - count += 16; - value = value << 16; - } - if ((value & 0xFF000000) == 0) - { - count += 8; - value = value << 8; - } - if ((value & 0xF0000000) == 0) - { - count += 4; - value = value << 4; - } - if ((value & 0xC0000000) == 0) - { - count += 2; - value = value << 2; - } - if ((value & 0x80000000) == 0) - { - count += 1; - } - - return count; - } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs index 8731794b40299..0b56d2b52e245 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Security; namespace System.Numerics { @@ -14,135 +13,114 @@ internal static partial class BigIntegerCalculator // see https://en.wikipedia.org/wiki/Barrett_reduction - internal readonly struct FastReducer + private readonly ref struct FastReducer { - private readonly uint[] _modulus; - private readonly uint[] _mu; - private readonly uint[] _q1; - private readonly uint[] _q2; + private readonly ReadOnlySpan _modulus; + private readonly ReadOnlySpan _mu; + private readonly Span _q1; + private readonly Span _q2; - private readonly int _muLength; - - public FastReducer(uint[] modulus) + public FastReducer(ReadOnlySpan modulus, Span r, Span mu, Span q1, Span q2) { - Debug.Assert(modulus != null); + Debug.Assert(!modulus.IsEmpty); + Debug.Assert(r.Length == modulus.Length * 2 + 1); + Debug.Assert(mu.Length == r.Length - modulus.Length + 1); + Debug.Assert(q1.Length == modulus.Length * 2 + 2); + Debug.Assert(q2.Length == modulus.Length * 2 + 2); // Let r = 4^k, with 2^k > m - uint[] r = new uint[modulus.Length * 2 + 1]; r[r.Length - 1] = 1; // Let mu = 4^k / m - _mu = Divide(r, modulus); + Divide(r, modulus, mu); _modulus = modulus; - // Allocate memory for quotients once - _q1 = new uint[modulus.Length * 2 + 2]; - _q2 = new uint[modulus.Length * 2 + 1]; + _q1 = q1; + _q2 = q2; - _muLength = ActualLength(_mu); + _mu = mu.Slice(0, ActualLength(mu)); } - public int Reduce(uint[] value, int length) + public int Reduce(Span value) { - Debug.Assert(value != null); - Debug.Assert(length <= value.Length); Debug.Assert(value.Length <= _modulus.Length * 2); // Trivial: value is shorter - if (length < _modulus.Length) - return length; + if (value.Length < _modulus.Length) + return value.Length; // Let q1 = v/2^(k-1) * mu - int l1 = DivMul(value, length, _mu, _muLength, - _q1, _modulus.Length - 1); + _q1.Clear(); + int l1 = DivMul(value, _mu, _q1, _modulus.Length - 1); // Let q2 = q1/2^(k+1) * m - int l2 = DivMul(_q1, l1, _modulus, _modulus.Length, - _q2, _modulus.Length + 1); + _q2.Clear(); + int l2 = DivMul(_q1.Slice(0, l1), _modulus, _q2, _modulus.Length + 1); // Let v = (v - q2) % 2^(k+1) - i*m - return SubMod(value, length, _q2, l2, - _modulus, _modulus.Length + 1); + var length = SubMod(value, _q2.Slice(0, l2), _modulus, _modulus.Length + 1); + value = value.Slice(length); + value.Clear(); + + return length; } - private static unsafe int DivMul(uint[] left, int leftLength, - uint[] right, int rightLength, - uint[] bits, int k) + private static int DivMul(ReadOnlySpan left, ReadOnlySpan right, Span bits, int k) { - Debug.Assert(left != null); - Debug.Assert(left.Length >= leftLength); - Debug.Assert(right != null); - Debug.Assert(right.Length >= rightLength); - Debug.Assert(bits != null); - Debug.Assert(bits.Length + k >= leftLength + rightLength); + Debug.Assert(!right.IsEmpty); + Debug.Assert(!bits.IsEmpty); + Debug.Assert(bits.Length + k >= left.Length + right.Length); // Executes the multiplication algorithm for left and right, // but skips the first k limbs of left, which is equivalent to // preceding division by 2^(32*k). To spare memory allocations // we write the result to an already allocated memory. - Array.Clear(bits); - - if (leftLength > k) + if (left.Length > k) { - leftLength -= k; + left = left.Slice(k); - fixed (uint* l = left, r = right, b = bits) + if (left.Length < right.Length) { - if (leftLength < rightLength) - { - Multiply(r, rightLength, - l + k, leftLength, - b, leftLength + rightLength); - } - else - { - Multiply(l + k, leftLength, - r, rightLength, - b, leftLength + rightLength); - } + Multiply(right, + left, + bits.Slice(0, left.Length + right.Length)); + } + else + { + Multiply(left, + right, + bits.Slice(0, left.Length + right.Length)); } - return ActualLength(bits, leftLength + rightLength); + return ActualLength(bits.Slice(0, left.Length + right.Length)); } return 0; } - private static unsafe int SubMod(uint[] left, int leftLength, - uint[] right, int rightLength, - uint[] modulus, int k) + private static int SubMod(Span left, ReadOnlySpan right, ReadOnlySpan modulus, int k) { - Debug.Assert(left != null); - Debug.Assert(left.Length >= leftLength); - Debug.Assert(right != null); - Debug.Assert(right.Length >= rightLength); - // Executes the subtraction algorithm for left and right, // but considers only the first k limbs, which is equivalent to // preceding reduction by 2^(32*k). Furthermore, if left is // still greater than modulus, further subtractions are used. - if (leftLength > k) - leftLength = k; - if (rightLength > k) - rightLength = k; + if (left.Length > k) + left = left.Slice(0, k); + if (right.Length > k) + right = right.Slice(0, k); - fixed (uint* l = left, r = right, m = modulus) - { - SubtractSelf(l, leftLength, r, rightLength); - leftLength = ActualLength(left, leftLength); + SubtractSelf(left, right); + left = left.Slice(0, ActualLength(left)); - while (Compare(l, leftLength, m, modulus.Length) >= 0) - { - SubtractSelf(l, leftLength, m, modulus.Length); - leftLength = ActualLength(left, leftLength); - } + while (Compare(left, modulus) >= 0) + { + SubtractSelf(left, modulus); + left = left.Slice(0, ActualLength(left)); } - Array.Clear(left, leftLength, left.Length - leftLength); - - return leftLength; + return left.Length; } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs index d984912ec6246..c9944c1ba95bb 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; namespace System.Numerics @@ -40,9 +41,8 @@ public static ulong Gcd(ulong left, ulong right) return left; } - public static uint Gcd(uint[] left, uint right) + public static uint Gcd(ReadOnlySpan left, uint right) { - Debug.Assert(left != null); Debug.Assert(left.Length >= 1); Debug.Assert(right != 0); @@ -54,27 +54,34 @@ public static uint Gcd(uint[] left, uint right) return Gcd(right, temp); } - public static uint[] Gcd(uint[] left, uint[] right) + public static void Gcd(ReadOnlySpan left, ReadOnlySpan right, Span result) { - Debug.Assert(left != null); Debug.Assert(left.Length >= 2); - Debug.Assert(right != null); Debug.Assert(right.Length >= 2); Debug.Assert(Compare(left, right) >= 0); + Debug.Assert(result.Length == left.Length); - BitsBuffer leftBuffer = new BitsBuffer(left.Length, left); - BitsBuffer rightBuffer = new BitsBuffer(right.Length, right); + left.CopyTo(result); - Gcd(ref leftBuffer, ref rightBuffer); + uint[]? rightCopyFromPool = null; + Span rightCopy = (right.Length <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : rightCopyFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); + right.CopyTo(rightCopy); - return leftBuffer.GetBits(); + Gcd(result, rightCopy); + + if (rightCopyFromPool != null) + ArrayPool.Shared.Return(rightCopyFromPool); } - private static void Gcd(ref BitsBuffer left, ref BitsBuffer right) + private static void Gcd(Span left, Span right) { - Debug.Assert(left.GetLength() >= 2); - Debug.Assert(right.GetLength() >= 2); - Debug.Assert(left.GetLength() >= right.GetLength()); + Debug.Assert(left.Length >= 2); + Debug.Assert(right.Length >= 2); + Debug.Assert(left.Length >= right.Length); + + Span result = left; //keep result buffer untouched during computation // Executes Lehmer's gcd algorithm, but uses the most // significant bits to work with 64-bit (not 32-bit) values. @@ -83,11 +90,11 @@ private static void Gcd(ref BitsBuffer left, ref BitsBuffer right) // http://cacr.uwaterloo.ca/hac/about/chap14.pdf (see 14.4.2) // ftp://ftp.risc.uni-linz.ac.at/pub/techreports/1992/92-69.ps.gz - while (right.GetLength() > 2) + while (right.Length > 2) { ulong x, y; - ExtractDigits(ref left, ref right, out x, out y); + ExtractDigits(left, right, out x, out y); uint a = 1U, b = 0U; uint c = 0U, d = 1U; @@ -149,85 +156,112 @@ private static void Gcd(ref BitsBuffer left, ref BitsBuffer right) if (b == 0) { // Euclid's step - left.Reduce(ref right); + left = left.Slice(0, Reduce(left, right)); - BitsBuffer temp = left; + Span temp = left; left = right; right = temp; } else { // Lehmer's step - LehmerCore(ref left, ref right, a, b, c, d); + var count = LehmerCore(left, right, a, b, c, d); + left = left.Slice(0, Refresh(left, count)); + right = right.Slice(0, Refresh(right, count)); if (iteration % 2 == 1) { // Ensure left is larger than right - BitsBuffer temp = left; + Span temp = left; left = right; right = temp; } } } - if (right.GetLength() > 0) + if (right.Length > 0) { // Euclid's step - left.Reduce(ref right); + Reduce(left, right); - uint[] xBits = right.GetBits(); - uint[] yBits = left.GetBits(); + ulong x = ((ulong)right[1] << 32) | right[0]; + ulong y = ((ulong)left[1] << 32) | left[0]; - ulong x = ((ulong)xBits[1] << 32) | xBits[0]; - ulong y = ((ulong)yBits[1] << 32) | yBits[0]; + left = left.Slice(0, Overwrite(left, Gcd(x, y))); + Overwrite(right, 0U); + } + + left.CopyTo(result); + } - left.Overwrite(Gcd(x, y)); - right.Overwrite(0); + private static int Overwrite(Span buffer, ulong value) + { + Debug.Assert(buffer.Length >= 2); + + if (buffer.Length > 2) + { + // Ensure leading zeros in little-endian + buffer.Slice(2).Clear(); } + + uint lo = unchecked((uint)value); + uint hi = (uint)(value >> 32); + + buffer[1] = hi; + buffer[0] = lo; + return hi != 0 ? 2 : lo != 0 ? 1 : 0; } - private static void ExtractDigits(ref BitsBuffer xBuffer, - ref BitsBuffer yBuffer, + private static int Overwrite(Span bits, uint value) + { + Debug.Assert(bits.Length >= 1); + + if (bits.Length > 1) + { + // Ensure leading zeros in little-endian + bits.Slice(1).Clear(); + } + + bits[0] = value; + return value != 0 ? 1 : 0; + } + + private static void ExtractDigits(ReadOnlySpan xBuffer, + ReadOnlySpan yBuffer, out ulong x, out ulong y) { - Debug.Assert(xBuffer.GetLength() >= 3); - Debug.Assert(yBuffer.GetLength() >= 3); - Debug.Assert(xBuffer.GetLength() >= yBuffer.GetLength()); + Debug.Assert(xBuffer.Length >= 3); + Debug.Assert(yBuffer.Length >= 3); + Debug.Assert(xBuffer.Length >= yBuffer.Length); // Extracts the most significant bits of x and y, // but ensures the quotient x / y does not change! - uint[] xBits = xBuffer.GetBits(); - int xLength = xBuffer.GetLength(); - - uint[] yBits = yBuffer.GetBits(); - int yLength = yBuffer.GetLength(); - - ulong xh = xBits[xLength - 1]; - ulong xm = xBits[xLength - 2]; - ulong xl = xBits[xLength - 3]; + ulong xh = xBuffer[xBuffer.Length - 1]; + ulong xm = xBuffer[xBuffer.Length - 2]; + ulong xl = xBuffer[xBuffer.Length - 3]; ulong yh, ym, yl; // arrange the bits - switch (xLength - yLength) + switch (xBuffer.Length - yBuffer.Length) { case 0: - yh = yBits[yLength - 1]; - ym = yBits[yLength - 2]; - yl = yBits[yLength - 3]; + yh = yBuffer[yBuffer.Length - 1]; + ym = yBuffer[yBuffer.Length - 2]; + yl = yBuffer[yBuffer.Length - 3]; break; case 1: yh = 0UL; - ym = yBits[yLength - 1]; - yl = yBits[yLength - 2]; + ym = yBuffer[yBuffer.Length - 1]; + yl = yBuffer[yBuffer.Length - 2]; break; case 2: yh = 0UL; ym = 0UL; - yl = yBits[yLength - 1]; + yl = yBuffer[yBuffer.Length - 1]; break; default: @@ -238,7 +272,7 @@ private static void ExtractDigits(ref BitsBuffer xBuffer, } // Use all the bits but one, see [hac] 14.58 (ii) - int z = LeadingZeros((uint)xh); + int z = BitOperations.LeadingZeroCount((uint)xh); x = ((xh << 32 + z) | (xm << z) | (xl >> 32 - z)) >> 1; y = ((yh << 32 + z) | (ym << z) | (yl >> 32 - z)) >> 1; @@ -246,23 +280,20 @@ private static void ExtractDigits(ref BitsBuffer xBuffer, Debug.Assert(x >= y); } - private static void LehmerCore(ref BitsBuffer xBuffer, - ref BitsBuffer yBuffer, - long a, long b, - long c, long d) + private static int LehmerCore(Span x, + Span y, + long a, long b, + long c, long d) { - Debug.Assert(xBuffer.GetLength() >= 1); - Debug.Assert(yBuffer.GetLength() >= 1); - Debug.Assert(xBuffer.GetLength() >= yBuffer.GetLength()); + Debug.Assert(x.Length >= 1); + Debug.Assert(y.Length >= 1); + Debug.Assert(x.Length >= y.Length); Debug.Assert(a <= 0x7FFFFFFF && b <= 0x7FFFFFFF); Debug.Assert(c <= 0x7FFFFFFF && d <= 0x7FFFFFFF); // Executes the combined calculation of Lehmer's step. - uint[] x = xBuffer.GetBits(); - uint[] y = yBuffer.GetBits(); - - int length = yBuffer.GetLength(); + int length = y.Length; long xCarry = 0L, yCarry = 0L; for (int i = 0; i < length; i++) @@ -275,8 +306,20 @@ private static void LehmerCore(ref BitsBuffer xBuffer, y[i] = unchecked((uint)yDigit); } - xBuffer.Refresh(length); - yBuffer.Refresh(length); + return length; + } + + private static int Refresh(Span bits, int maxLength) + { + Debug.Assert(bits.Length >= maxLength); + + if (bits.Length > maxLength) + { + // Ensure leading zeros + bits.Slice(maxLength).Clear(); + } + + return ActualLength(bits.Slice(0, maxLength)); } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs index c646173430275..0407feff54fe3 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; namespace System.Numerics @@ -12,50 +13,104 @@ internal static partial class BigIntegerCalculator // https://en.wikipedia.org/wiki/Exponentiation_by_squaring - public static uint[] Pow(uint value, uint power) + public static void Pow(uint value, uint power, Span bits) { - // The basic pow method for a 32-bit integer. - // To spare memory allocations we first roughly - // estimate an upper bound for our buffers. + Pow(value != 0U ? stackalloc uint[1] { value } : default, power, bits); + } - int size = PowBound(power, 1, 1); - BitsBuffer v = new BitsBuffer(size, value); - return PowCore(power, ref v); + public static void Pow(ReadOnlySpan value, uint power, Span bits) + { + Debug.Assert(bits.Length == PowBound(power, value.Length)); + + uint[]? tempFromPool = null; + Span temp = (bits.Length <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + temp.Clear(); + + uint[]? valueCopyFromPool = null; + Span valueCopy = (bits.Length <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + value.CopyTo(valueCopy); + valueCopy.Slice(value.Length).Clear(); + + PowCore(valueCopy, value.Length, temp, power, bits).CopyTo(bits); + + if (tempFromPool != null) + ArrayPool.Shared.Return(tempFromPool); + if (valueCopyFromPool != null) + ArrayPool.Shared.Return(valueCopyFromPool); } - public static uint[] Pow(uint[] value, uint power) + private static Span PowCore(Span value, int valueLength, Span temp, uint power, Span result) { - Debug.Assert(value != null); + Debug.Assert(value.Length >= valueLength); + Debug.Assert(temp.Length == result.Length); + Debug.Assert(value.Length == temp.Length); + + result[0] = 1; + int bitsLength = 1; - // The basic pow method for a big integer. - // To spare memory allocations we first roughly - // estimate an upper bound for our buffers. + // The basic pow algorithm using square-and-multiply. + while (power != 0) + { + if ((power & 1) == 1) + bitsLength = MultiplySelf(ref result, bitsLength, value.Slice(0, valueLength), ref temp); + if (power != 1) + valueLength = SquareSelf(ref value, valueLength, ref temp); + power = power >> 1; + } - int size = PowBound(power, value.Length, 1); - BitsBuffer v = new BitsBuffer(size, value); - return PowCore(power, ref v); + return result; } - private static uint[] PowCore(uint power, ref BitsBuffer value) + private static int MultiplySelf(ref Span left, int leftLength, ReadOnlySpan right, ref Span temp) { - // Executes the basic pow algorithm. + Debug.Assert(leftLength <= left.Length); - int size = value.GetSize(); + int resultLength = leftLength + right.Length; - BitsBuffer temp = new BitsBuffer(size, 0); - BitsBuffer result = new BitsBuffer(size, 1); + if (leftLength >= right.Length) + { + Multiply(left.Slice(0, leftLength), right, temp.Slice(0, resultLength)); + } + else + { + Multiply(right, left.Slice(0, leftLength), temp.Slice(0, resultLength)); + } + + left.Clear(); + //switch buffers + Span t = left; + left = temp; + temp = t; + return ActualLength(left.Slice(0, resultLength)); + } - PowCore(power, ref value, ref result, ref temp); + private static int SquareSelf(ref Span value, int valueLength, ref Span temp) + { + Debug.Assert(valueLength <= value.Length); + Debug.Assert(temp.Length >= valueLength + valueLength); - return result.GetBits(); + int resultLength = valueLength + valueLength; + + Square(value.Slice(0, valueLength), temp.Slice(0, resultLength)); + + value.Clear(); + //switch buffers + Span t = value; + value = temp; + temp = t; + return ActualLength(value.Slice(0, resultLength)); } - private static int PowBound(uint power, int valueLength, - int resultLength) + public static int PowBound(uint power, int valueLength) { // The basic pow algorithm, but instead of squaring // and multiplying we just sum up the lengths. + int resultLength = 1; while (power != 0) { checked @@ -71,64 +126,41 @@ private static int PowBound(uint power, int valueLength, return resultLength; } - private static void PowCore(uint power, ref BitsBuffer value, - ref BitsBuffer result, ref BitsBuffer temp) - { - // The basic pow algorithm using square-and-multiply. - - while (power != 0) - { - if ((power & 1) == 1) - result.MultiplySelf(ref value, ref temp); - if (power != 1) - value.SquareSelf(ref temp); - power = power >> 1; - } - } - public static uint Pow(uint value, uint power, uint modulus) { // The 32-bit modulus pow method for a 32-bit integer // raised by a 32-bit integer... - return PowCore(power, modulus, value, 1); + return PowCore(value, power, modulus, 1); } - public static uint Pow(uint[] value, uint power, uint modulus) + public static uint Pow(ReadOnlySpan value, uint power, uint modulus) { - Debug.Assert(value != null); - // The 32-bit modulus pow method for a big integer // raised by a 32-bit integer... uint v = Remainder(value, modulus); - return PowCore(power, modulus, v, 1); + return PowCore(v, power, modulus, 1); } - public static uint Pow(uint value, uint[] power, uint modulus) + public static uint Pow(uint value, ReadOnlySpan power, uint modulus) { - Debug.Assert(power != null); - // The 32-bit modulus pow method for a 32-bit integer // raised by a big integer... - return PowCore(power, modulus, value, 1); + return PowCore(value, power, modulus, 1); } - public static uint Pow(uint[] value, uint[] power, uint modulus) + public static uint Pow(ReadOnlySpan value, ReadOnlySpan power, uint modulus) { - Debug.Assert(value != null); - Debug.Assert(power != null); - // The 32-bit modulus pow method for a big integer // raised by a big integer... uint v = Remainder(value, modulus); - return PowCore(power, modulus, v, 1); + return PowCore(v, power, modulus, 1); } - private static uint PowCore(uint[] power, uint modulus, - ulong value, ulong result) + private static uint PowCore(ulong value, ReadOnlySpan power, uint modulus, ulong result) { // The 32-bit modulus pow algorithm for all but // the last power limb using square-and-multiply. @@ -145,11 +177,10 @@ private static uint PowCore(uint[] power, uint modulus, } } - return PowCore(power[power.Length - 1], modulus, value, result); + return PowCore(value, power[power.Length - 1], modulus, result); } - private static uint PowCore(uint power, uint modulus, - ulong value, ulong result) + private static uint PowCore(ulong value, uint power, uint modulus, ulong result) { // The 32-bit modulus pow algorithm for the last or // the only power limb using square-and-multiply. @@ -166,116 +197,221 @@ private static uint PowCore(uint power, uint modulus, return (uint)(result % modulus); } - public static uint[] Pow(uint value, uint power, uint[] modulus) + public static void Pow(uint value, uint power, + ReadOnlySpan modulus, Span bits) { - Debug.Assert(modulus != null); - - // The big modulus pow method for a 32-bit integer - // raised by a 32-bit integer... - - int size = modulus.Length + modulus.Length; - BitsBuffer v = new BitsBuffer(size, value); - return PowCore(power, modulus, ref v); + Pow(value != 0U ? stackalloc uint[1] { value } : default, power, modulus, bits); } - public static uint[] Pow(uint[] value, uint power, uint[] modulus) + public static void Pow(ReadOnlySpan value, uint power, + ReadOnlySpan modulus, Span bits) { - Debug.Assert(value != null); - Debug.Assert(modulus != null); + Debug.Assert(!modulus.IsEmpty); + Debug.Assert(bits.Length == modulus.Length + modulus.Length); // The big modulus pow method for a big integer // raised by a 32-bit integer... + uint[]? valueCopyFromPool = null; + int size = Math.Max(value.Length, bits.Length); + Span valueCopy = (size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + valueCopy.Clear(); + if (value.Length > modulus.Length) - value = Remainder(value, modulus); + { + Remainder(value, modulus, valueCopy); + } + else + { + value.CopyTo(valueCopy); + } - int size = modulus.Length + modulus.Length; - BitsBuffer v = new BitsBuffer(size, value); - return PowCore(power, modulus, ref v); - } + uint[]? tempFromPool = null; + Span temp = (bits.Length <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + temp.Clear(); - public static uint[] Pow(uint value, uint[] power, uint[] modulus) - { - Debug.Assert(power != null); - Debug.Assert(modulus != null); + PowCore(valueCopy, ActualLength(valueCopy), power, modulus, temp, bits); - // The big modulus pow method for a 32-bit integer - // raised by a big integer... + if (valueCopyFromPool != null) + ArrayPool.Shared.Return(valueCopyFromPool); + if (tempFromPool != null) + ArrayPool.Shared.Return(tempFromPool); + } - int size = modulus.Length + modulus.Length; - BitsBuffer v = new BitsBuffer(size, value); - return PowCore(power, modulus, ref v); + public static void Pow(uint value, ReadOnlySpan power, + ReadOnlySpan modulus, Span bits) + { + Pow(value != 0U ? stackalloc uint[1] { value } : default, power, modulus, bits); } - public static uint[] Pow(uint[] value, uint[] power, uint[] modulus) + public static void Pow(ReadOnlySpan value, ReadOnlySpan power, + ReadOnlySpan modulus, Span bits) { - Debug.Assert(value != null); - Debug.Assert(power != null); - Debug.Assert(modulus != null); + Debug.Assert(!modulus.IsEmpty); + Debug.Assert(bits.Length == modulus.Length + modulus.Length); // The big modulus pow method for a big integer // raised by a big integer... + int size = Math.Max(value.Length, bits.Length); + uint[]? valueCopyFromPool = null; + Span valueCopy = (size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + valueCopy.Clear(); + if (value.Length > modulus.Length) - value = Remainder(value, modulus); + { + Remainder(value, modulus, valueCopy); + } + else + { + value.CopyTo(valueCopy); + } + + uint[]? tempFromPool = null; + Span temp = (bits.Length <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + temp.Clear(); + + PowCore(valueCopy, ActualLength(valueCopy), power, modulus, temp, bits); - int size = modulus.Length + modulus.Length; - BitsBuffer v = new BitsBuffer(size, value); - return PowCore(power, modulus, ref v); + if (valueCopyFromPool != null) + ArrayPool.Shared.Return(valueCopyFromPool); + if (tempFromPool != null) + ArrayPool.Shared.Return(tempFromPool); } +#if DEBUG // Mutable for unit testing... - private static int ReducerThreshold = 32; - - private static uint[] PowCore(uint[] power, uint[] modulus, - ref BitsBuffer value) + private static +#else + private const +#endif + int ReducerThreshold = 32; + + private static void PowCore(Span value, int valueLength, + ReadOnlySpan power, ReadOnlySpan modulus, + Span temp, Span bits) { // Executes the big pow algorithm. - int size = value.GetSize(); - - BitsBuffer temp = new BitsBuffer(size, 0); - BitsBuffer result = new BitsBuffer(size, 1); + bits[0] = 1; if (modulus.Length < ReducerThreshold) { - PowCore(power, modulus, ref value, ref result, ref temp); + PowCore(value, valueLength, power, modulus, bits, 1, temp).CopyTo(bits); } else { - FastReducer reducer = new FastReducer(modulus); - PowCore(power, ref reducer, ref value, ref result, ref temp); + int size = modulus.Length * 2 + 1; + uint[]? rFromPool = null; + Span r = ((uint)size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : rFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + r.Clear(); + + size = r.Length - modulus.Length + 1; + uint[]? muFromPool = null; + Span mu = ((uint)size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + mu.Clear(); + + size = modulus.Length * 2 + 2; + uint[]? q1FromPool = null; + Span q1 = ((uint)size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + q1.Clear(); + + uint[]? q2FromPool = null; + Span q2 = ((uint)size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : q2FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + q2.Clear(); + + FastReducer reducer = new FastReducer(modulus, r, mu, q1, q2); + + if (rFromPool != null) + ArrayPool.Shared.Return(rFromPool); + + PowCore(value, valueLength, power, reducer, bits, 1, temp).CopyTo(bits); + + if (muFromPool != null) + ArrayPool.Shared.Return(muFromPool); + if (q1FromPool != null) + ArrayPool.Shared.Return(q1FromPool); + if (q2FromPool != null) + ArrayPool.Shared.Return(q2FromPool); } - - return result.GetBits(); } - private static uint[] PowCore(uint power, uint[] modulus, - ref BitsBuffer value) + private static void PowCore(Span value, int valueLength, + uint power, ReadOnlySpan modulus, + Span temp, Span bits) { // Executes the big pow algorithm. - - int size = value.GetSize(); - - BitsBuffer temp = new BitsBuffer(size, 0); - BitsBuffer result = new BitsBuffer(size, 1); + bits[0] = 1; if (modulus.Length < ReducerThreshold) { - PowCore(power, modulus, ref value, ref result, ref temp); + PowCore(value, valueLength, power, modulus, bits, 1, temp).CopyTo(bits); } else { - FastReducer reducer = new FastReducer(modulus); - PowCore(power, ref reducer, ref value, ref result, ref temp); + int size = modulus.Length * 2 + 1; + uint[]? rFromPool = null; + Span r = ((uint)size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : rFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + r.Clear(); + + size = r.Length - modulus.Length + 1; + uint[]? muFromPool = null; + Span mu = ((uint)size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + mu.Clear(); + + size = modulus.Length * 2 + 2; + uint[]? q1FromPool = null; + Span q1 = ((uint)size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + q1.Clear(); + + uint[]? q2FromPool = null; + Span q2 = ((uint)size <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : q2FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + q2.Clear(); + + FastReducer reducer = new FastReducer(modulus, r, mu, q1, q2); + + if (rFromPool != null) + ArrayPool.Shared.Return(rFromPool); + + PowCore(value, valueLength, power, reducer, bits, 1, temp).CopyTo(bits); + + if (muFromPool != null) + ArrayPool.Shared.Return(muFromPool); + if (q1FromPool != null) + ArrayPool.Shared.Return(q1FromPool); + if (q2FromPool != null) + ArrayPool.Shared.Return(q2FromPool); } - - return result.GetBits(); } - private static void PowCore(uint[] power, uint[] modulus, - ref BitsBuffer value, ref BitsBuffer result, - ref BitsBuffer temp) + private static Span PowCore(Span value, int valueLength, + ReadOnlySpan power, ReadOnlySpan modulus, + Span result, int resultLength, + Span temp) { // The big modulus pow algorithm for all but // the last power limb using square-and-multiply. @@ -290,22 +426,22 @@ private static void PowCore(uint[] power, uint[] modulus, { if ((p & 1) == 1) { - result.MultiplySelf(ref value, ref temp); - result.Reduce(modulus); + resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); + resultLength = Reduce(result.Slice(0, resultLength), modulus); } - value.SquareSelf(ref temp); - value.Reduce(modulus); + valueLength = SquareSelf(ref value, valueLength, ref temp); + valueLength = Reduce(value.Slice(0, valueLength), modulus); p = p >> 1; } } - PowCore(power[power.Length - 1], modulus, ref value, ref result, - ref temp); + return PowCore(value, valueLength, power[power.Length - 1], modulus, result, resultLength, temp); } - private static void PowCore(uint power, uint[] modulus, - ref BitsBuffer value, ref BitsBuffer result, - ref BitsBuffer temp) + private static Span PowCore(Span value, int valueLength, + uint power, ReadOnlySpan modulus, + Span result, int resultLength, + Span temp) { // The big modulus pow algorithm for the last or // the only power limb using square-and-multiply. @@ -317,21 +453,24 @@ private static void PowCore(uint power, uint[] modulus, { if ((power & 1) == 1) { - result.MultiplySelf(ref value, ref temp); - result.Reduce(modulus); + resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); + resultLength = Reduce(result.Slice(0, resultLength), modulus); } if (power != 1) { - value.SquareSelf(ref temp); - value.Reduce(modulus); + valueLength = SquareSelf(ref value, valueLength, ref temp); + valueLength = Reduce(value.Slice(0, valueLength), modulus); } power = power >> 1; } + + return result.Slice(0, resultLength); } - private static void PowCore(uint[] power, ref FastReducer reducer, - ref BitsBuffer value, ref BitsBuffer result, - ref BitsBuffer temp) + private static Span PowCore(Span value, int valueLength, + ReadOnlySpan power, in FastReducer reducer, + Span result, int resultLength, + Span temp) { // The big modulus pow algorithm for all but // the last power limb using square-and-multiply. @@ -346,22 +485,22 @@ private static void PowCore(uint[] power, ref FastReducer reducer, { if ((p & 1) == 1) { - result.MultiplySelf(ref value, ref temp); - result.Reduce(ref reducer); + resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); + resultLength = reducer.Reduce(result.Slice(0, resultLength)); } - value.SquareSelf(ref temp); - value.Reduce(ref reducer); + valueLength = SquareSelf(ref value, valueLength, ref temp); + valueLength = reducer.Reduce(value.Slice(0, valueLength)); p = p >> 1; } } - PowCore(power[power.Length - 1], ref reducer, ref value, ref result, - ref temp); + return PowCore(value, valueLength, power[power.Length - 1], reducer, result, resultLength, temp); } - private static void PowCore(uint power, ref FastReducer reducer, - ref BitsBuffer value, ref BitsBuffer result, - ref BitsBuffer temp) + private static Span PowCore(Span value, int valueLength, + uint power, in FastReducer reducer, + Span result, int resultLength, + Span temp) { // The big modulus pow algorithm for the last or // the only power limb using square-and-multiply. @@ -373,34 +512,18 @@ private static void PowCore(uint power, ref FastReducer reducer, { if ((power & 1) == 1) { - result.MultiplySelf(ref value, ref temp); - result.Reduce(ref reducer); + resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); + resultLength = reducer.Reduce(result.Slice(0, resultLength)); } if (power != 1) { - value.SquareSelf(ref temp); - value.Reduce(ref reducer); + valueLength = SquareSelf(ref value, valueLength, ref temp); + valueLength = reducer.Reduce(value.Slice(0, valueLength)); } power = power >> 1; } - } - - private static int ActualLength(uint[] value) - { - // Since we're reusing memory here, the actual length - // of a given value may be less then the array's length - - return ActualLength(value, value.Length); - } - - private static int ActualLength(uint[] value, int length) - { - Debug.Assert(value != null); - Debug.Assert(length <= value.Length); - while (length > 0 && value[length - 1] == 0) - --length; - return length; + return result; } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs index f77d02f37ccb3..4aeae650e76e6 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs @@ -1,40 +1,26 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; -using System.Security; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Numerics { internal static partial class BigIntegerCalculator { - public static unsafe uint[] Square(uint[] value) - { - Debug.Assert(value != null); - - // Switching to unsafe pointers helps sparing - // some nasty index calculations... - - uint[] bits = new uint[value.Length + value.Length]; - - fixed (uint* v = value, b = bits) - { - Square(v, value.Length, - b, bits.Length); - } - - return bits; - } - +#if DEBUG // Mutable for unit testing... - private static int SquareThreshold = 32; - private static int AllocationThreshold = 256; + private static +#else + private const +#endif + int SquareThreshold = 32; - private static unsafe void Square(uint* value, int valueLength, - uint* bits, int bitsLength) + public static void Square(ReadOnlySpan value, Span bits) { - Debug.Assert(valueLength >= 0); - Debug.Assert(bitsLength == valueLength + valueLength); + Debug.Assert(bits.Length == value.Length + value.Length); // Executes different algorithms for computing z = a * a // based on the actual length of a. If a is "small" enough @@ -45,8 +31,12 @@ private static unsafe void Square(uint* value, int valueLength, // NOTE: useful thresholds needs some "empirical" testing, // which are smaller in DEBUG mode for testing purpose. - if (valueLength < SquareThreshold) + if (value.Length < SquareThreshold) { + // Switching to managed references helps eliminating + // index bounds check... + ref uint resultPtr = ref MemoryMarshal.GetReference(bits); + // Squares the bits using the "grammar-school" method. // Envisioning the "rhombus" of a pen-and-paper calculation // we see that computing z_i+j += a_j * a_i can be optimized @@ -58,20 +48,20 @@ private static unsafe void Square(uint* value, int valueLength, // = 2^64 - 1 (which perfectly matches with ulong!). But // here we would need an UInt65... Hence, we split these // operation and do some extra shifts. - - for (int i = 0; i < valueLength; i++) + for (int i = 0; i < value.Length; i++) { ulong carry = 0UL; + uint v = value[i]; for (int j = 0; j < i; j++) { - ulong digit1 = bits[i + j] + carry; - ulong digit2 = (ulong)value[j] * value[i]; - bits[i + j] = unchecked((uint)(digit1 + (digit2 << 1))); + ulong digit1 = Unsafe.Add(ref resultPtr, i + j) + carry; + ulong digit2 = (ulong)value[j] * v; + Unsafe.Add(ref resultPtr, i + j) = unchecked((uint)(digit1 + (digit2 << 1))); carry = (digit2 + (digit1 >> 1)) >> 31; } - ulong digits = (ulong)value[i] * value[i] + carry; - bits[i + i] = unchecked((uint)digits); - bits[i + i + 1] = (uint)(digits >> 32); + ulong digits = (ulong)v * v + carry; + Unsafe.Add(ref resultPtr, i + i) = unchecked((uint)digits); + Unsafe.Add(ref resultPtr, i + i + 1) = (uint)(digits >> 32); } } else @@ -88,81 +78,59 @@ private static unsafe void Square(uint* value, int valueLength, // Say we want to compute z = a * a ... // ... we need to determine our new length (just the half) - int n = valueLength >> 1; + int n = value.Length >> 1; int n2 = n << 1; // ... split value like a = (a_1 << n) + a_0 - uint* valueLow = value; - int valueLowLength = n; - uint* valueHigh = value + n; - int valueHighLength = valueLength - n; + ReadOnlySpan valueLow = value.Slice(0, n); + ReadOnlySpan valueHigh = value.Slice(n); // ... prepare our result array (to reuse its memory) - uint* bitsLow = bits; - int bitsLowLength = n2; - uint* bitsHigh = bits + n2; - int bitsHighLength = bitsLength - n2; + Span bitsLow = bits.Slice(0, n2); + Span bitsHigh = bits.Slice(n2); // ... compute z_0 = a_0 * a_0 (squaring again!) - Square(valueLow, valueLowLength, - bitsLow, bitsLowLength); + Square(valueLow, bitsLow); // ... compute z_2 = a_1 * a_1 (squaring again!) - Square(valueHigh, valueHighLength, - bitsHigh, bitsHighLength); + Square(valueHigh, bitsHigh); + + int foldLength = valueHigh.Length + 1; + uint[]? foldFromPool = null; + Span fold = ((uint)foldLength <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : foldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); + fold.Clear(); - int foldLength = valueHighLength + 1; int coreLength = foldLength + foldLength; + uint[]? coreFromPool = null; + Span core = ((uint)coreLength <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); + core.Clear(); - if (coreLength < AllocationThreshold) - { - uint* fold = stackalloc uint[foldLength]; - new Span(fold, foldLength).Clear(); - uint* core = stackalloc uint[coreLength]; - new Span(core, coreLength).Clear(); - - // ... compute z_a = a_1 + a_0 (call it fold...) - Add(valueHigh, valueHighLength, - valueLow, valueLowLength, - fold, foldLength); - - // ... compute z_1 = z_a * z_a - z_0 - z_2 - Square(fold, foldLength, - core, coreLength); - SubtractCore(bitsHigh, bitsHighLength, - bitsLow, bitsLowLength, - core, coreLength); - - // ... and finally merge the result! :-) - AddSelf(bits + n, bitsLength - n, core, coreLength); - } - else - { - fixed (uint* fold = new uint[foldLength], - core = new uint[coreLength]) - { - // ... compute z_a = a_1 + a_0 (call it fold...) - Add(valueHigh, valueHighLength, - valueLow, valueLowLength, - fold, foldLength); - - // ... compute z_1 = z_a * z_a - z_0 - z_2 - Square(fold, foldLength, - core, coreLength); - SubtractCore(bitsHigh, bitsHighLength, - bitsLow, bitsLowLength, - core, coreLength); - - // ... and finally merge the result! :-) - AddSelf(bits + n, bitsLength - n, core, coreLength); - } - } + // ... compute z_a = a_1 + a_0 (call it fold...) + Add(valueHigh, valueLow, fold); + + // ... compute z_1 = z_a * z_a - z_0 - z_2 + Square(fold, core); + + if (foldFromPool != null) + ArrayPool.Shared.Return(foldFromPool); + + SubtractCore(bitsHigh, bitsLow, core); + + // ... and finally merge the result! :-) + AddSelf(bits.Slice(n), core); + + if (coreFromPool != null) + ArrayPool.Shared.Return(coreFromPool); } } - public static uint[] Multiply(uint[] left, uint right) + public static void Multiply(ReadOnlySpan left, uint right, Span bits) { - Debug.Assert(left != null); + Debug.Assert(bits.Length == left.Length + 1); // Executes the multiplication for one big and one 32-bit integer. // Since every step holds the already slightly familiar equation @@ -171,51 +139,28 @@ public static uint[] Multiply(uint[] left, uint right) int i = 0; ulong carry = 0UL; - uint[] bits = new uint[left.Length + 1]; - for (; i < left.Length; i++) + for ( ; i < left.Length; i++) { ulong digits = (ulong)left[i] * right + carry; bits[i] = unchecked((uint)digits); carry = digits >> 32; } bits[i] = (uint)carry; - - return bits; - } - - public static unsafe uint[] Multiply(uint[] left, uint[] right) - { - Debug.Assert(left != null); - Debug.Assert(right != null); - Debug.Assert(left.Length >= right.Length); - - // Switching to unsafe pointers helps sparing - // some nasty index calculations... - - uint[] bits = new uint[left.Length + right.Length]; - - fixed (uint* l = left, r = right, b = bits) - { - Multiply(l, left.Length, - r, right.Length, - b, bits.Length); - } - - return bits; } +#if DEBUG // Mutable for unit testing... - private static int MultiplyThreshold = 32; + private static +#else + private const +#endif + int MultiplyThreshold = 32; - private static unsafe void Multiply(uint* left, int leftLength, - uint* right, int rightLength, - uint* bits, int bitsLength) + public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, Span bits) { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - Debug.Assert(leftLength >= rightLength); - Debug.Assert(bitsLength == leftLength + rightLength); + Debug.Assert(left.Length >= right.Length); + Debug.Assert(bits.Length == left.Length + right.Length); // Executes different algorithms for computing z = a * b // based on the actual length of b. If b is "small" enough @@ -226,8 +171,12 @@ private static unsafe void Multiply(uint* left, int leftLength, // NOTE: useful thresholds needs some "empirical" testing, // which are smaller in DEBUG mode for testing purpose. - if (rightLength < MultiplyThreshold) + if (right.Length < MultiplyThreshold) { + // Switching to managed references helps eliminating + // index bounds check... + ref uint resultPtr = ref MemoryMarshal.GetReference(bits); + // Multiplies the bits using the "grammar-school" method. // Envisioning the "rhombus" of a pen-and-paper calculation // should help getting the idea of these two loops... @@ -235,17 +184,17 @@ private static unsafe void Multiply(uint* left, int leftLength, // z_i+j + a_j * b_i + c <= 2(2^32 - 1) + (2^32 - 1)^2 = // = 2^64 - 1 (which perfectly matches with ulong!). - for (int i = 0; i < rightLength; i++) + for (int i = 0; i < right.Length; i++) { ulong carry = 0UL; - for (int j = 0; j < leftLength; j++) + for (int j = 0; j < left.Length; j++) { - ulong digits = bits[i + j] + carry - + (ulong)left[j] * right[i]; - bits[i + j] = unchecked((uint)digits); + ref uint elementPtr = ref Unsafe.Add(ref resultPtr, i + j); + ulong digits = elementPtr + carry + (ulong)left[j] * right[i]; + elementPtr = unchecked((uint)digits); carry = digits >> 32; } - bits[i + leftLength] = (uint)carry; + Unsafe.Add(ref resultPtr, i + left.Length) = (uint)carry; } } else @@ -262,111 +211,77 @@ private static unsafe void Multiply(uint* left, int leftLength, // Say we want to compute z = a * b ... // ... we need to determine our new length (just the half) - int n = rightLength >> 1; + int n = right.Length >> 1; int n2 = n << 1; // ... split left like a = (a_1 << n) + a_0 - uint* leftLow = left; - int leftLowLength = n; - uint* leftHigh = left + n; - int leftHighLength = leftLength - n; + ReadOnlySpan leftLow = left.Slice(0, n); + ReadOnlySpan leftHigh = left.Slice(n); // ... split right like b = (b_1 << n) + b_0 - uint* rightLow = right; - int rightLowLength = n; - uint* rightHigh = right + n; - int rightHighLength = rightLength - n; + ReadOnlySpan rightLow = right.Slice(0, n); + ReadOnlySpan rightHigh = right.Slice(n); // ... prepare our result array (to reuse its memory) - uint* bitsLow = bits; - int bitsLowLength = n2; - uint* bitsHigh = bits + n2; - int bitsHighLength = bitsLength - n2; + Span bitsLow = bits.Slice(0, n2); + Span bitsHigh = bits.Slice(n2); // ... compute z_0 = a_0 * b_0 (multiply again) - Multiply(leftLow, leftLowLength, - rightLow, rightLowLength, - bitsLow, bitsLowLength); + Multiply(leftLow, rightLow, bitsLow); // ... compute z_2 = a_1 * b_1 (multiply again) - Multiply(leftHigh, leftHighLength, - rightHigh, rightHighLength, - bitsHigh, bitsHighLength); + Multiply(leftHigh, rightHigh, bitsHigh); + + int leftFoldLength = leftHigh.Length + 1; + uint[]? leftFoldFromPool = null; + Span leftFold = ((uint)leftFoldLength <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : leftFoldFromPool = ArrayPool.Shared.Rent(leftFoldLength)).Slice(0, leftFoldLength); + leftFold.Clear(); + + int rightFoldLength = rightHigh.Length + 1; + uint[]? rightFoldFromPool = null; + Span rightFold = ((uint)rightFoldLength <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : rightFoldFromPool = ArrayPool.Shared.Rent(rightFoldLength)).Slice(0, rightFoldLength); + rightFold.Clear(); - int leftFoldLength = leftHighLength + 1; - int rightFoldLength = rightHighLength + 1; int coreLength = leftFoldLength + rightFoldLength; + uint[]? coreFromPool = null; + Span core = ((uint)coreLength <= StackAllocThreshold ? + stackalloc uint[StackAllocThreshold] + : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); + core.Clear(); - if (coreLength < AllocationThreshold) - { - uint* leftFold = stackalloc uint[leftFoldLength]; - new Span(leftFold, leftFoldLength).Clear(); - uint* rightFold = stackalloc uint[rightFoldLength]; - new Span(rightFold, rightFoldLength).Clear(); - uint* core = stackalloc uint[coreLength]; - new Span(core, coreLength).Clear(); - - // ... compute z_a = a_1 + a_0 (call it fold...) - Add(leftHigh, leftHighLength, - leftLow, leftLowLength, - leftFold, leftFoldLength); - - // ... compute z_b = b_1 + b_0 (call it fold...) - Add(rightHigh, rightHighLength, - rightLow, rightLowLength, - rightFold, rightFoldLength); - - // ... compute z_1 = z_a * z_b - z_0 - z_2 - Multiply(leftFold, leftFoldLength, - rightFold, rightFoldLength, - core, coreLength); - SubtractCore(bitsHigh, bitsHighLength, - bitsLow, bitsLowLength, - core, coreLength); - - // ... and finally merge the result! :-) - AddSelf(bits + n, bitsLength - n, core, coreLength); - } - else - { - fixed (uint* leftFold = new uint[leftFoldLength], - rightFold = new uint[rightFoldLength], - core = new uint[coreLength]) - { - // ... compute z_a = a_1 + a_0 (call it fold...) - Add(leftHigh, leftHighLength, - leftLow, leftLowLength, - leftFold, leftFoldLength); - - // ... compute z_b = b_1 + b_0 (call it fold...) - Add(rightHigh, rightHighLength, - rightLow, rightLowLength, - rightFold, rightFoldLength); - - // ... compute z_1 = z_a * z_b - z_0 - z_2 - Multiply(leftFold, leftFoldLength, - rightFold, rightFoldLength, - core, coreLength); - SubtractCore(bitsHigh, bitsHighLength, - bitsLow, bitsLowLength, - core, coreLength); - - // ... and finally merge the result! :-) - AddSelf(bits + n, bitsLength - n, core, coreLength); - } - } + // ... compute z_a = a_1 + a_0 (call it fold...) + Add(leftHigh, leftLow, leftFold); + + // ... compute z_b = b_1 + b_0 (call it fold...) + Add(rightHigh, rightLow, rightFold); + + // ... compute z_1 = z_a * z_b - z_0 - z_2 + Multiply(leftFold, rightFold, core); + + if (leftFoldFromPool != null) + ArrayPool.Shared.Return(leftFoldFromPool); + + if (rightFoldFromPool != null) + ArrayPool.Shared.Return(rightFoldFromPool); + + SubtractCore(bitsHigh, bitsLow, core); + + // ... and finally merge the result! :-) + AddSelf(bits.Slice(n), core); + + if (coreFromPool != null) + ArrayPool.Shared.Return(coreFromPool); } } - private static unsafe void SubtractCore(uint* left, int leftLength, - uint* right, int rightLength, - uint* core, int coreLength) + private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan right, Span core) { - Debug.Assert(leftLength >= 0); - Debug.Assert(rightLength >= 0); - Debug.Assert(coreLength >= 0); - Debug.Assert(leftLength >= rightLength); - Debug.Assert(coreLength >= leftLength); + Debug.Assert(left.Length >= right.Length); + Debug.Assert(core.Length >= left.Length); // Executes a special subtraction algorithm for the multiplication, // which needs to subtract two different values from a core value, @@ -378,19 +293,26 @@ private static unsafe void SubtractCore(uint* left, int leftLength, int i = 0; long carry = 0L; - for (; i < rightLength; i++) + // Switching to managed references helps eliminating + // index bounds check... + ref uint leftPtr = ref MemoryMarshal.GetReference(left); + ref uint corePtr = ref MemoryMarshal.GetReference(core); + + for ( ; i < right.Length; i++) { - long digit = (core[i] + carry) - left[i] - right[i]; - core[i] = unchecked((uint)digit); + long digit = (Unsafe.Add(ref corePtr, i) + carry) - Unsafe.Add(ref leftPtr, i) - right[i]; + Unsafe.Add(ref corePtr, i) = unchecked((uint)digit); carry = digit >> 32; } - for (; i < leftLength; i++) + + for ( ; i < left.Length; i++) { - long digit = (core[i] + carry) - left[i]; - core[i] = unchecked((uint)digit); + long digit = (Unsafe.Add(ref corePtr, i) + carry) - left[i]; + Unsafe.Add(ref corePtr, i) = unchecked((uint)digit); carry = digit >> 32; } - for (; carry != 0 && i < coreLength; i++) + + for ( ; carry != 0 && i < core.Length; i++) { long digit = core[i] + carry; core[i] = (uint)digit; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs new file mode 100644 index 0000000000000..37d4ae7596568 --- /dev/null +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Numerics +{ + internal static partial class BigIntegerCalculator + { +#if DEBUG + // Mutable for unit testing... + internal static +#else + internal const +#endif + int StackAllocThreshold = 64; + + public static int Compare(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length < right.Length) + return -1; + if (left.Length > right.Length) + return 1; + + for (int i = left.Length - 1; i >= 0; i--) + { + uint leftElement = left[i]; + uint rightElement = right[i]; + if (leftElement < rightElement) + return -1; + if (leftElement > rightElement) + return 1; + } + + return 0; + } + + private static int ActualLength(ReadOnlySpan value) + { + // Since we're reusing memory here, the actual length + // of a given value may be less then the array's length + + int length = value.Length; + + while (length > 0 && value[length - 1] == 0) + --length; + return length; + } + + private static int Reduce(Span bits, ReadOnlySpan modulus) + { + // Executes a modulo operation using the divide operation. + + if (bits.Length >= modulus.Length) + { + Divide(bits, modulus, default); + + return ActualLength(bits.Slice(0, modulus.Length)); + } + return bits.Length; + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs index 5ce7919e830bf..2d7395e2e83dd 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -410,11 +410,11 @@ private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInt bool isNegative = HexConverter.FromChar(number.digits[0]) >= 8; uint partialValue = (isNegative && partialDigitCount > 0) ? 0xFFFFFFFFu : 0; - int[]? arrayFromPool = null; + uint[]? arrayFromPool = null; - Span bitsBuffer = (blockCount <= BigInteger.StackallocUInt32Limit) - ? stackalloc uint[blockCount] - : MemoryMarshal.Cast((arrayFromPool = ArrayPool.Shared.Rent(blockCount)).AsSpan(0, blockCount)); + Span bitsBuffer = ((uint)blockCount <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : arrayFromPool = ArrayPool.Shared.Rent(blockCount)).Slice(0, blockCount); int bitsBufferPos = blockCount - 1; @@ -489,14 +489,14 @@ private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInt { if (arrayFromPool != null) { - ArrayPool.Shared.Return(arrayFromPool); + ArrayPool.Shared.Return(arrayFromPool); } } } private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) { - Span stackBuffer = stackalloc uint[BigInteger.StackallocUInt32Limit]; + Span stackBuffer = stackalloc uint[BigIntegerCalculator.StackAllocThreshold]; Span currentBuffer = stackBuffer; int currentBufferSize = 0; int[]? arrayFromPool = null; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs index 9325be0d806c2..d2f207229e313 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Globalization; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -851,4 +850,4 @@ public static explicit operator Complex(decimal value) return new Complex((double)value, 0.0); } } -} +} \ No newline at end of file diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs index e89bc4acd13e8..ff9d263edcb51 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs @@ -59,7 +59,7 @@ public static double GetDoubleFromParts(int sign, int exp, ulong man) else { // Normalize so that 0x0010 0000 0000 0000 is the highest bit set. - int cbitShift = CbitHighZero(man) - 11; + int cbitShift = BitOperations.LeadingZeroCount(man) - 11; if (cbitShift < 0) man >>= -cbitShift; else @@ -108,11 +108,12 @@ public static double GetDoubleFromParts(int sign, int exp, ulong man) // a mutation and needs to be used with care for immutable types. public static void DangerousMakeTwosComplement(Span d) { - if (d != null && d.Length > 0) + if (d.Length > 0) { d[0] = unchecked(~d[0] + 1); int i = 1; + // first do complement and +1 as long as carry is needed for (; d[i - 1] == 0 && i < d.Length; i++) { @@ -139,53 +140,5 @@ public static uint Abs(int a) return ((uint)a ^ mask) - mask; } } - - public static uint CombineHash(uint u1, uint u2) - { - return ((u1 << 7) | (u1 >> 25)) ^ u2; - } - - public static int CombineHash(int n1, int n2) - { - return unchecked((int)CombineHash((uint)n1, (uint)n2)); - } - - public static int CbitHighZero(uint u) - { - if (u == 0) - return 32; - - int cbit = 0; - if ((u & 0xFFFF0000) == 0) - { - cbit += 16; - u <<= 16; - } - if ((u & 0xFF000000) == 0) - { - cbit += 8; - u <<= 8; - } - if ((u & 0xF0000000) == 0) - { - cbit += 4; - u <<= 4; - } - if ((u & 0xC0000000) == 0) - { - cbit += 2; - u <<= 2; - } - if ((u & 0x80000000) == 0) - cbit += 1; - return cbit; - } - - public static int CbitHighZero(ulong uu) - { - if ((uu & 0xFFFFFFFF00000000) == 0) - return 32 + CbitHighZero((uint)uu); - return CbitHighZero((uint)(uu >> 32)); - } } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntTools.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntTools.cs index 519b1d9e5b397..33d6614483717 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntTools.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntTools.cs @@ -59,6 +59,9 @@ public static void RunWithFakeThreshold(string name, int value, Action action) return; // Internal frame types are not reflectable on AoT platforms. Skip the test. FieldInfo field = internalCalculator.GetDeclaredField(name); + if (field is null || field.IsLiteral) + return; // in Release config the field may be const + int lastValue = (int)field.GetValue(null); field.SetValue(null, value); try diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs index 72be36c581923..a2646d5bd6cb8 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs @@ -44,7 +44,7 @@ public static void RunMultiply_TwoLargeBigIntegers_Threshold() // Again, with lower threshold BigIntTools.Utils.RunWithFakeThreshold("SquareThreshold", 8, () => BigIntTools.Utils.RunWithFakeThreshold("MultiplyThreshold", 8, () => - BigIntTools.Utils.RunWithFakeThreshold("AllocationThreshold", 8, RunMultiply_TwoLargeBigIntegers) + BigIntTools.Utils.RunWithFakeThreshold("StackAllocThreshold", 8, RunMultiply_TwoLargeBigIntegers) ) ); }