From 3a85f20e18c5dac7b92e6e65eff5a54d8679f4c2 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Mar 2026 14:06:50 -0400 Subject: [PATCH 01/27] Convert Number.BigInteger.cs from uint limbs to nuint limbs - Change all span/array types for limbs: uint->nuint, Span->Span, ReadOnlySpan->ReadOnlySpan, ArrayPool->ArrayPool, stackalloc uint[]->stackalloc nuint[] - PowersOf1e9: platform-dependent Indexes and LeadingPowers (32-bit/64-bit) using MemoryMarshal.Cast for ReadOnlySpan from fixed-size backing - MultiplyAdd: use UInt128 on 64-bit, ulong on 32-bit for widening multiply - Naive base conversion: use BigIntegerCalculator.DivRem for widening divide - OmittedLength: divide by kcbitNuint instead of hardcoded 32 - digitRatio constants: scale by 32/kcbitNuint for platform-appropriate ratios - IBigIntegerHexOrBinaryParser: nuint blocks (DigitsPerBlock uses nint.Size) - BigIntegerToDecChars: cast nuint base1E9 values to uint for UInt32ToDecChars - Fix pre-existing unused variable in BigInteger.cs Log method Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.BigInteger.cs | 414 ++-- .../src/System/Numerics/BigInteger.cs | 1679 ++++++++++------- 2 files changed, 1259 insertions(+), 834 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 9596786fcf516c..404a73dba6aa47 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -23,7 +23,18 @@ internal static partial class Number | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier); - private static ReadOnlySpan UInt32PowersOfTen => [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; + private static ReadOnlySpan UInt32PowersOfTen + { + get + { + if (nint.Size == 8) + return MemoryMarshal.Cast(UInt64PowersOfTen); + return MemoryMarshal.Cast(UInt32PowersOfTenCore); + } + } + + private static ReadOnlySpan UInt32PowersOfTenCore => [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; + private static ReadOnlySpan UInt64PowersOfTen => [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; [DoesNotReturn] internal static void ThrowOverflowOrFormatException(ParsingStatus status) => throw GetException(status); @@ -176,12 +187,12 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0) + if ((nint)(leading ^ signBits) >= 0) { - // Small value that fits in Int32. + // Small value that fits in nint. // Delegate to the constructor for int.MinValue handling. - result = new BigInteger((int)leading); + result = new BigInteger((nint)leading, null); return ParsingStatus.OK; } else if (leading != 0) @@ -222,18 +233,18 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle wholeBlockDestination = bits.AsSpan(0, wholeBlockCount); + nuint[] bits = new nuint[totalUIntCount]; + Span wholeBlockDestination = bits.AsSpan(0, wholeBlockCount); if (!TParser.TryParseWholeBlocks(value, wholeBlockDestination)) { @@ -257,7 +268,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle base1E9; + nuint[]? base1E9FromPool = null; + scoped Span base1E9; { ReadOnlySpan intDigits = number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); @@ -356,14 +367,15 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big int base1E9Length = (intDigits.Length + PowersOf1e9.MaxPartialDigits - 1) / PowersOf1e9.MaxPartialDigits; base1E9 = ( base1E9Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : base1E9FromPool = ArrayPool.Shared.Rent(base1E9Length)).Slice(0, base1E9Length); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : base1E9FromPool = ArrayPool.Shared.Rent(base1E9Length)).Slice(0, base1E9Length); int di = base1E9Length; ReadOnlySpan leadingDigits = intDigits[..(intDigits.Length % PowersOf1e9.MaxPartialDigits)]; if (leadingDigits.Length != 0) { - uint.TryParse(leadingDigits, out base1E9[--di]); + uint.TryParse(leadingDigits, out uint leadingVal); + base1E9[--di] = leadingVal; } intDigits = intDigits.Slice(leadingDigits.Length); @@ -371,19 +383,20 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big for (--di; di >= 0; --di) { - uint.TryParse(intDigits.Slice(0, PowersOf1e9.MaxPartialDigits), out base1E9[di]); + uint.TryParse(intDigits.Slice(0, PowersOf1e9.MaxPartialDigits), out uint partialVal); + base1E9[di] = partialVal; intDigits = intDigits.Slice(PowersOf1e9.MaxPartialDigits); } Debug.Assert(intDigits.Length == 0); } - const double digitRatio = 0.10381025297; // log_{2^32}(10) + double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.kcbitNuint; // log_{2^kcbitNuint}(10) int resultLength = checked((int)(digitRatio * number.Scale) + 1 + 2); - uint[]? resultBufferFromPool = null; - Span resultBuffer = ( + nuint[]? resultBufferFromPool = null; + Span resultBuffer = ( resultLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(resultLength)).Slice(0, resultLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(resultLength)).Slice(0, resultLength); resultBuffer.Clear(); int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); @@ -401,34 +414,35 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big result = new BigInteger(resultBuffer, number.IsNegative); if (base1E9FromPool != null) - ArrayPool.Shared.Return(base1E9FromPool); + ArrayPool.Shared.Return(base1E9FromPool); if (resultBufferFromPool != null) - ArrayPool.Shared.Return(resultBufferFromPool); + ArrayPool.Shared.Return(resultBufferFromPool); return ParsingStatus.OK; - static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) + static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) { int valueDigits = (base1E9.Length - 1) * PowersOf1e9.MaxPartialDigits + FormattingHelpers.CountDigits(base1E9[^1]); int powersOf1e9BufferLength = PowersOf1e9.GetBufferSize(Math.Max(valueDigits, trailingZeroCount + 1), out int maxIndex); - uint[]? powersOf1e9BufferFromPool = null; - Span powersOf1e9Buffer = ( + nuint[]? powersOf1e9BufferFromPool = null; + Span powersOf1e9Buffer = ( (uint)powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : powersOf1e9BufferFromPool = ArrayPool.Shared.Rent(powersOf1e9BufferLength)).Slice(0, powersOf1e9BufferLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : powersOf1e9BufferFromPool = ArrayPool.Shared.Rent(powersOf1e9BufferLength)).Slice(0, powersOf1e9BufferLength); powersOf1e9Buffer.Clear(); PowersOf1e9 powersOf1e9 = new PowersOf1e9(powersOf1e9Buffer); if (trailingZeroCount > 0) { + double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.kcbitNuint; int leadingLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * base1E9.Length) + 3); - uint[]? leadingFromPool = null; - Span leading = ( + nuint[]? leadingFromPool = null; + Span leading = ( leadingLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : leadingFromPool = ArrayPool.Shared.Rent(leadingLength)).Slice(0, leadingLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : leadingFromPool = ArrayPool.Shared.Rent(leadingLength)).Slice(0, leadingLength); leading.Clear(); Recursive(powersOf1e9, maxIndex, base1E9, leading); @@ -437,7 +451,7 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, powersOf1e9.MultiplyPowerOfTen(leading, trailingZeroCount, bits); if (leadingFromPool != null) - ArrayPool.Shared.Return(leadingFromPool); + ArrayPool.Shared.Return(leadingFromPool); } else { @@ -445,12 +459,12 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, } if (powersOf1e9BufferFromPool != null) - ArrayPool.Shared.Return(powersOf1e9BufferFromPool); + ArrayPool.Shared.Return(powersOf1e9BufferFromPool); } - static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnlySpan base1E9, Span bits) + static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnlySpan base1E9, Span bits) { - Debug.Assert(bits.Trim(0u).Length == 0); + Debug.Assert(bits.Trim((nuint)0).Length == 0); Debug.Assert(BigIntegerParseNaiveThresholdInRecursive > 1); base1E9 = base1E9.Slice(0, BigIntegerCalculator.ActualLength(base1E9)); @@ -465,23 +479,24 @@ static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnly { multiplier1E9Length = 1 << (--powersOf1e9Index); } - ReadOnlySpan multiplier = powersOf1e9.GetSpan(powersOf1e9Index); + ReadOnlySpan multiplier = powersOf1e9.GetSpan(powersOf1e9Index); int multiplierTrailingZeroCount = PowersOf1e9.OmittedLength(powersOf1e9Index); Debug.Assert(multiplier1E9Length < base1E9.Length && base1E9.Length <= multiplier1E9Length * 2); + double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.kcbitNuint; int bufferLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * multiplier1E9Length) + 1 + 2); - uint[]? bufferFromPool = null; - scoped Span buffer = ( + nuint[]? bufferFromPool = null; + scoped Span buffer = ( bufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bufferFromPool = ArrayPool.Shared.Rent(bufferLength)).Slice(0, bufferLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bufferFromPool = ArrayPool.Shared.Rent(bufferLength)).Slice(0, bufferLength); buffer.Clear(); Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[multiplier1E9Length..], buffer); - ReadOnlySpan buffer2 = buffer.Slice(0, BigIntegerCalculator.ActualLength(buffer)); - Span bitsUpper = bits.Slice(multiplierTrailingZeroCount, buffer2.Length + multiplier.Length); + ReadOnlySpan buffer2 = buffer.Slice(0, BigIntegerCalculator.ActualLength(buffer)); + Span bitsUpper = bits.Slice(multiplierTrailingZeroCount, buffer2.Length + multiplier.Length); BigIntegerCalculator.Multiply(buffer2, multiplier, bitsUpper); buffer.Clear(); @@ -491,10 +506,10 @@ static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnly BigIntegerCalculator.AddSelf(bits, buffer.Slice(0, BigIntegerCalculator.ActualLength(buffer))); if (bufferFromPool != null) - ArrayPool.Shared.Return(bufferFromPool); + ArrayPool.Shared.Return(bufferFromPool); } - static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) + static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) { if (base1E9.Length == 0) { @@ -506,7 +521,7 @@ static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span int trailingPartialCount = Math.DivRem(trailingZeroCount, PowersOf1e9.MaxPartialDigits, out int remainingTrailingZeroCount); for (int i = 0; i < trailingPartialCount; i++) { - uint carry = MultiplyAdd(bits.Slice(0, resultLength), PowersOf1e9.TenPowMaxPartial, 0); + nuint carry = MultiplyAdd(bits.Slice(0, resultLength), PowersOf1e9.TenPowMaxPartial, 0); Debug.Assert(bits[resultLength] == 0); if (carry != 0) bits[resultLength++] = carry; @@ -514,15 +529,15 @@ static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span if (remainingTrailingZeroCount != 0) { - uint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; - uint carry = MultiplyAdd(bits.Slice(0, resultLength), multiplier, 0); + nuint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; + nuint carry = MultiplyAdd(bits.Slice(0, resultLength), multiplier, 0); Debug.Assert(bits[resultLength] == 0); if (carry != 0) bits[resultLength++] = carry; } } - static int NaiveBase1E9ToBits(ReadOnlySpan base1E9, Span bits) + static int NaiveBase1E9ToBits(ReadOnlySpan base1E9, Span bits) { if (base1E9.Length == 0) return 0; @@ -531,7 +546,7 @@ static int NaiveBase1E9ToBits(ReadOnlySpan base1E9, Span bits) bits[0] = base1E9[^1]; for (int i = base1E9.Length - 2; i >= 0; i--) { - uint carry = MultiplyAdd(bits.Slice(0, resultLength), PowersOf1e9.TenPowMaxPartial, base1E9[i]); + nuint carry = MultiplyAdd(bits.Slice(0, resultLength), PowersOf1e9.TenPowMaxPartial, base1E9[i]); Debug.Assert(bits[resultLength] == 0); if (carry != 0) bits[resultLength++] = carry; @@ -539,15 +554,27 @@ static int NaiveBase1E9ToBits(ReadOnlySpan base1E9, Span bits) return resultLength; } - static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) + static nuint MultiplyAdd(Span bits, nuint multiplier, nuint addValue) { - uint carry = addValue; + nuint carry = addValue; - for (int i = 0; i < bits.Length; i++) + if (nint.Size == 8) + { + for (int i = 0; i < bits.Length; i++) + { + UInt128 p = (UInt128)bits[i] * multiplier + carry; + bits[i] = (nuint)(ulong)p; + carry = (nuint)(ulong)(p >> 64); + } + } + else { - ulong p = (ulong)multiplier * bits[i] + carry; - bits[i] = (uint)p; - carry = (uint)(p >> 32); + for (int i = 0; i < bits.Length; i++) + { + ulong p = (ulong)multiplier * bits[i] + carry; + bits[i] = (nuint)(uint)p; + carry = (nuint)(uint)(p >> 32); + } } return carry; } @@ -795,33 +822,33 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan base1E9Buffer = ((uint)base1E9BufferLength <= BigIntegerCalculator.StackAllocThreshold ? - stackalloc uint[base1E9BufferLength] : - (base1E9BufferFromPool = ArrayPool.Shared.Rent(base1E9BufferLength))).Slice(0, base1E9BufferLength); + nuint[]? base1E9BufferFromPool = null; + Span base1E9Buffer = ((uint)base1E9BufferLength <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc nuint[base1E9BufferLength] : + (base1E9BufferFromPool = ArrayPool.Shared.Rent(base1E9BufferLength))).Slice(0, base1E9BufferLength); base1E9Buffer.Clear(); BigIntegerToBase1E9(value._bits, base1E9Buffer, out int written); - ReadOnlySpan base1E9Value = base1E9Buffer[..written]; + ReadOnlySpan base1E9Value = base1E9Buffer[..written]; int valueDigits = (base1E9Value.Length - 1) * PowersOf1e9.MaxPartialDigits + FormattingHelpers.CountDigits(base1E9Value[^1]); @@ -859,7 +886,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan(BigInteger value, ReadOnlySpan.Shared.Return(base1E9BufferFromPool); + ArrayPool.Shared.Return(base1E9BufferFromPool); } return strResult; @@ -945,11 +972,11 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan base1E9Value; + public ReadOnlySpan base1E9Value; public ReadOnlySpan sNegative; } - private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) + private static unsafe TChar* BigIntegerToDecChars(TChar* bufferEnd, ReadOnlySpan base1E9Value, int digits) where TChar : unmanaged, IUtfChar { Debug.Assert(base1E9Value[^1] != 0, "Leading zeros should be trimmed by caller."); @@ -957,11 +984,11 @@ private unsafe ref struct InterpolatedStringHandlerState // The base 10^9 value is in reverse order for (int i = 0; i < base1E9Value.Length - 1; i++) { - bufferEnd = UInt32ToDecChars(bufferEnd, base1E9Value[i], PowersOf1e9.MaxPartialDigits); + bufferEnd = UInt32ToDecChars(bufferEnd, (uint)base1E9Value[i], PowersOf1e9.MaxPartialDigits); digits -= PowersOf1e9.MaxPartialDigits; } - return UInt32ToDecChars(bufferEnd, base1E9Value[^1], digits); + return UInt32ToDecChars(bufferEnd, (uint)base1E9Value[^1], digits); } #if DEBUG @@ -971,7 +998,7 @@ public static public const #endif int ToStringNaiveThreshold = BigIntegerCalculator.DivideBurnikelZieglerThreshold; - private static void BigIntegerToBase1E9(ReadOnlySpan bits, Span base1E9Buffer, out int base1E9Written) + private static void BigIntegerToBase1E9(ReadOnlySpan bits, Span base1E9Buffer, out int base1E9Written) { Debug.Assert(ToStringNaiveThreshold >= 2); @@ -982,11 +1009,11 @@ private static void BigIntegerToBase1E9(ReadOnlySpan bits, Span base } PowersOf1e9.FloorBufferSize(bits.Length, out int powersOf1e9BufferLength, out int maxIndex); - uint[]? powersOf1e9BufferFromPool = null; - Span powersOf1e9Buffer = ( + nuint[]? powersOf1e9BufferFromPool = null; + Span powersOf1e9Buffer = ( powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : powersOf1e9BufferFromPool = ArrayPool.Shared.Rent(powersOf1e9BufferLength)).Slice(0, powersOf1e9BufferLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : powersOf1e9BufferFromPool = ArrayPool.Shared.Rent(powersOf1e9BufferLength)).Slice(0, powersOf1e9BufferLength); powersOf1e9Buffer.Clear(); PowersOf1e9 powersOf1e9 = new PowersOf1e9(powersOf1e9Buffer); @@ -995,10 +1022,10 @@ private static void BigIntegerToBase1E9(ReadOnlySpan bits, Span base if (powersOf1e9BufferFromPool != null) { - ArrayPool.Shared.Return(powersOf1e9BufferFromPool); + ArrayPool.Shared.Return(powersOf1e9BufferFromPool); } - static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOnlySpan bits, Span base1E9Buffer, out int base1E9Written) + static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOnlySpan bits, Span base1E9Buffer, out int base1E9Written) { Debug.Assert(bits.Length == 0 || bits[^1] != 0); Debug.Assert(powersIndex >= 0); @@ -1009,7 +1036,7 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn return; } - ReadOnlySpan powOfTen = powersOf1e9.GetSpan(powersIndex); + ReadOnlySpan powOfTen = powersOf1e9.GetSpan(powersIndex); int omittedLength = PowersOf1e9.OmittedLength(powersIndex); while (bits.Length < powOfTen.Length + omittedLength || BigIntegerCalculator.Compare(bits.Slice(omittedLength), powOfTen) < 0) @@ -1020,21 +1047,21 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn } int upperLength = bits.Length - powOfTen.Length - omittedLength + 1; - uint[]? upperFromPool = null; - Span upper = ((uint)upperLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : upperFromPool = ArrayPool.Shared.Rent(upperLength)).Slice(0, upperLength); + nuint[]? upperFromPool = null; + Span upper = ((uint)upperLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : upperFromPool = ArrayPool.Shared.Rent(upperLength)).Slice(0, upperLength); int lowerLength = bits.Length; - uint[]? lowerFromPool = null; - Span lower = ((uint)lowerLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : lowerFromPool = ArrayPool.Shared.Rent(lowerLength)).Slice(0, lowerLength); + nuint[]? lowerFromPool = null; + Span lower = ((uint)lowerLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : lowerFromPool = ArrayPool.Shared.Rent(lowerLength)).Slice(0, lowerLength); bits.Slice(0, omittedLength).CopyTo(lower); BigIntegerCalculator.Divide(bits.Slice(omittedLength), powOfTen, upper, lower.Slice(omittedLength)); - Debug.Assert(!upper.Trim(0u).IsEmpty); + Debug.Assert(!upper.Trim((nuint)0).IsEmpty); int lower1E9Length = 1 << powersIndex; @@ -1046,7 +1073,7 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn out int lowerWritten); if (lowerFromPool != null) - ArrayPool.Shared.Return(lowerFromPool); + ArrayPool.Shared.Return(lowerFromPool); Debug.Assert(lower1E9Length >= lowerWritten); @@ -1058,32 +1085,31 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn out base1E9Written); if (upperFromPool != null) - ArrayPool.Shared.Return(upperFromPool); + ArrayPool.Shared.Return(upperFromPool); base1E9Written += lower1E9Length; } - static void Naive(ReadOnlySpan bits, Span base1E9Buffer, out int base1E9Written) + static void Naive(ReadOnlySpan bits, Span base1E9Buffer, out int base1E9Written) { base1E9Written = 0; for (int iuSrc = bits.Length; --iuSrc >= 0;) { - uint uCarry = bits[iuSrc]; - Span base1E9 = base1E9Buffer.Slice(0, base1E9Written); + nuint uCarry = bits[iuSrc]; + Span base1E9 = base1E9Buffer.Slice(0, base1E9Written); for (int iuDst = 0; iuDst < base1E9.Length; iuDst++) { Debug.Assert(base1E9[iuDst] < PowersOf1e9.TenPowMaxPartial); - // Use X86Base.DivRem when stable - ulong uuRes = NumericsHelpers.MakeUInt64(base1E9[iuDst], uCarry); - (ulong quo, ulong rem) = Math.DivRem(uuRes, PowersOf1e9.TenPowMaxPartial); - uCarry = (uint)quo; - base1E9[iuDst] = (uint)rem; + nuint hi = base1E9[iuDst]; + uCarry = BigIntegerCalculator.DivRem(hi, uCarry, PowersOf1e9.TenPowMaxPartial, out base1E9[iuDst]); } if (uCarry != 0) { - (uCarry, base1E9Buffer[base1E9Written++]) = Math.DivRem(uCarry, PowersOf1e9.TenPowMaxPartial); + (nuint quo, nuint rem) = Math.DivRem(uCarry, PowersOf1e9.TenPowMaxPartial); + base1E9Buffer[base1E9Written++] = rem; + uCarry = quo; if (uCarry != 0) base1E9Buffer[base1E9Written++] = uCarry; } @@ -1094,24 +1120,26 @@ static void Naive(ReadOnlySpan bits, Span base1E9Buffer, out int bas internal readonly ref struct PowersOf1e9 { // Holds 1000000000^(1<< pow1E9; - public const uint TenPowMaxPartial = 1000000000; + private readonly ReadOnlySpan pow1E9; + public const nuint TenPowMaxPartial = 1000000000; public const int MaxPartialDigits = 9; // indexes[i] is pre-calculated length of (10^9)^i // This means that pow1E9[indexes[i-1]..indexes[i]] equals 1000000000 * (1<> 5; + // length -= (9*(1< Indexes => + private static ReadOnlySpan Indexes => nint.Size == 8 ? Indexes64 : Indexes32; + + private static ReadOnlySpan Indexes32 => [ 0, 1, @@ -1147,12 +1175,52 @@ internal readonly ref struct PowersOf1e9 1939268536, ]; + private static ReadOnlySpan Indexes64 => + [ + 0, + 1, + 2, + 4, + 7, + 13, + 24, + 45, + 87, + 171, + 339, + 674, + 1343, + 2681, + 5356, + 10706, + 21406, + 42805, + 85603, + 171199, + 342391, + 684774, + 1369539, + 2739068, + 5478126, + 10956242, + 21912474, + 43824937, + 87649863, + 175299714, + 350599416, + 701198819, + ]; + // The PowersOf1e9 structure holds 1000000000^(1<< LeadingPowers1E9 => + // during the calculation process, since 1000000000^(1<<< LeadingPowers1E9 => nint.Size == 8 + ? MemoryMarshal.Cast(LeadingPowers1E9_64) + : MemoryMarshal.Cast(LeadingPowers1E9_32); + + private static ReadOnlySpan LeadingPowers1E9_32 => [ // 1000000000^(1<<0) 1000000000, @@ -1206,7 +1274,41 @@ internal readonly ref struct PowersOf1e9 440721283, ]; - public PowersOf1e9(Span pow1E9) + private static ReadOnlySpan LeadingPowers1E9_64 => + [ + // 1000000000^(1<<0) = 10^9 + 1000000000, + // 1000000000^(1<<1) = 10^18 + 1000000000000000000, + // 1000000000^(1<<2) = 10^36 + 12919594847110692864, + 54210108624275221, + // 1000000000^(1<<3) = 10^72 + 3588752519208427776, + 4200376900514301694, + 159309191113245, + // 1000000000^(1<<4) = 10^144 + 18215643600950198272, + 10916841479303902820, + 7716856585087471704, + 5634289913586612151, + 15305997302415167542, + 1375821026, + // 1000000000^(1<<5) = 10^288 + 16923801176523145216, + 12337672902340997949, + 164319060048154006, + 490773073942565311, + 1005362712726180797, + 8369612250809081371, + 3712817362244264426, + 5673683396597986240, + 4342685653585896496, + 10263815553021896226, + 1892883497866839537, + ]; + + public PowersOf1e9(Span pow1E9) { Debug.Assert(pow1E9.Length >= 1); Debug.Assert(Indexes[6] == LeadingPowers1E9.Length); @@ -1218,14 +1320,14 @@ public PowersOf1e9(Span pow1E9) LeadingPowers1E9.CopyTo(pow1E9.Slice(0, LeadingPowers1E9.Length)); this.pow1E9 = pow1E9; - ReadOnlySpan src = pow1E9.Slice(Indexes[5], Indexes[6] - Indexes[5]); + ReadOnlySpan src = pow1E9.Slice(Indexes[5], Indexes[6] - Indexes[5]); int toExclusive = Indexes[6]; for (int i = 6; i + 1 < Indexes.Length; i++) { Debug.Assert(2 * src.Length - (Indexes[i + 1] - Indexes[i]) is 0 or 1); if (pow1E9.Length - toExclusive < (src.Length << 1)) break; - Span dst = pow1E9.Slice(toExclusive, src.Length << 1); + Span dst = pow1E9.Slice(toExclusive, src.Length << 1); BigIntegerCalculator.Square(src, dst); int from = toExclusive; toExclusive = Indexes[i + 1]; @@ -1251,9 +1353,9 @@ public static int GetBufferSize(int digits, out int maxIndex) return ++bufferSize; } - public ReadOnlySpan GetSpan(int index) + public ReadOnlySpan GetSpan(int index) { - // Returns 1E9^(1<> (32*(9*(1<> (kcbitNuint*(9*(1< GetSpan(int index) public static int OmittedLength(int index) { - // Returns 9*(1<> 5; + // Returns 9*(1< left, int trailingZeroCount, Span bits) + public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, Span bits) { Debug.Assert(trailingZeroCount >= 0); if (trailingZeroCount < UInt32PowersOfTen.Length) @@ -1298,13 +1400,13 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, S return; } - uint[]? powersOfTenFromPool = null; + nuint[]? powersOfTenFromPool = null; - Span powersOfTen = ( + Span powersOfTen = ( bits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : powersOfTenFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); - scoped Span powersOfTen2 = bits; + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : powersOfTenFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + scoped Span powersOfTen2 = bits; int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); @@ -1312,7 +1414,7 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, S int omittedLength = OmittedLength(fi); // Copy first - ReadOnlySpan first = GetSpan(fi); + ReadOnlySpan first = GetSpan(fi); int curLength = first.Length; trailingPartialCount >>= fi; trailingPartialCount >>= 1; @@ -1333,13 +1435,13 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, S { omittedLength += OmittedLength(fi); - ReadOnlySpan power = GetSpan(fi); - Span src = powersOfTen.Slice(0, curLength); - Span dst = powersOfTen2.Slice(0, curLength += power.Length); + ReadOnlySpan power = GetSpan(fi); + Span src = powersOfTen.Slice(0, curLength); + Span dst = powersOfTen2.Slice(0, curLength += power.Length); BigIntegerCalculator.Multiply(src, power, dst); - Span tmp = powersOfTen; + Span tmp = powersOfTen; powersOfTen = powersOfTen2; powersOfTen2 = tmp; powersOfTen2.Clear(); @@ -1353,22 +1455,34 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, S Debug.Assert(Unsafe.AreSame(ref bits[0], ref powersOfTen2[0])); powersOfTen = powersOfTen.Slice(0, curLength); - Span bits2 = bits.Slice(omittedLength, curLength += left.Length); + Span bits2 = bits.Slice(omittedLength, curLength += left.Length); BigIntegerCalculator.Multiply(left, powersOfTen, bits2); if (powersOfTenFromPool != null) - ArrayPool.Shared.Return(powersOfTenFromPool); + ArrayPool.Shared.Return(powersOfTenFromPool); if (remainingTrailingZeroCount > 0) { - uint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; - uint carry = 0; - for (int i = 0; i < bits2.Length; i++) + nuint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; + nuint carry = 0; + if (nint.Size == 8) { - ulong p = (ulong)multiplier * bits2[i] + carry; - bits2[i] = (uint)p; - carry = (uint)(p >> 32); + for (int i = 0; i < bits2.Length; i++) + { + UInt128 p = (UInt128)multiplier * bits2[i] + carry; + bits2[i] = (nuint)(ulong)p; + carry = (nuint)(ulong)(p >> 64); + } + } + else + { + for (int i = 0; i < bits2.Length; i++) + { + ulong p = (ulong)multiplier * bits2[i] + carry; + bits2[i] = (nuint)(uint)p; + carry = (nuint)(uint)(p >> 32); + } } if (carry != 0) @@ -1386,31 +1500,31 @@ internal interface IBigIntegerHexOrBinaryParser { static abstract int BitsPerDigit { get; } - static virtual int DigitsPerBlock => sizeof(uint) * 8 / TParser.BitsPerDigit; + static virtual int DigitsPerBlock => nint.Size * 8 / TParser.BitsPerDigit; static abstract NumberStyles BlockNumberStyle { get; } - static abstract uint GetSignBitsIfValid(uint ch); + static abstract nuint GetSignBitsIfValid(uint ch); [MethodImpl(MethodImplOptions.AggressiveInlining)] - static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out uint result) + static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out nuint result) { if (typeof(TChar) == typeof(Utf8Char)) { - return uint.TryParse(Unsafe.BitCast, ReadOnlySpan>(input), TParser.BlockNumberStyle, null, out result); + return nuint.TryParse(Unsafe.BitCast, ReadOnlySpan>(input), TParser.BlockNumberStyle, null, out result); } else { Debug.Assert(typeof(TChar) == typeof(Utf16Char)); - return uint.TryParse(Unsafe.BitCast, ReadOnlySpan>(input), TParser.BlockNumberStyle, null, out result); + return nuint.TryParse(Unsafe.BitCast, ReadOnlySpan>(input), TParser.BlockNumberStyle, null, out result); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static virtual bool TryParseSingleBlock(ReadOnlySpan input, out uint result) + static virtual bool TryParseSingleBlock(ReadOnlySpan input, out nuint result) => TParser.TryParseUnalignedBlock(input, out result); - static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) + static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) { Debug.Assert(destination.Length * TParser.DigitsPerBlock == input.Length); ref TChar lastWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(input), input.Length - TParser.DigitsPerBlock); @@ -1437,10 +1551,10 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span de public static NumberStyles BlockNumberStyle => NumberStyles.AllowHexSpecifier; // A valid ASCII hex digit is positive (0-7) if it starts with 00110 - public static uint GetSignBitsIfValid(uint ch) => (uint)((ch & 0b_1111_1000) == 0b_0011_0000 ? 0 : -1); + public static nuint GetSignBitsIfValid(uint ch) => (nuint)(nint)((ch & 0b_1111_1000) == 0b_0011_0000 ? 0 : -1); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) + public static bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) { if ((typeof(TChar) == typeof(Utf8Char)) ? (Convert.FromHexString(Unsafe.BitCast, ReadOnlySpan>(input), MemoryMarshal.AsBytes(destination), out _, out _) != OperationStatus.Done) @@ -1470,6 +1584,6 @@ public static bool TryParseWholeBlocks(ReadOnlySpan input, Span des public static NumberStyles BlockNumberStyle => NumberStyles.AllowBinarySpecifier; // Taking the LSB is enough for distinguishing 0/1 - public static uint GetSignBitsIfValid(uint ch) => (uint)(((int)ch << 31) >> 31); + public static nuint GetSignBitsIfValid(uint ch) => (nuint)(nint)(((int)ch << 31) >> 31); } } 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 2262614b9c4ee4..b351003b07fde9 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -34,28 +34,30 @@ public readonly struct BigInteger // Such a value allows for almost 646,456,974 digits, which is more than large enough // for typical scenarios. If user code requires more than this, they should likely // roll their own type that utilizes native memory and other specialized techniques. - internal static int MaxLength => Array.MaxLength / kcbitUint; + internal static int MaxLength => Array.MaxLength / (nint.Size * 8); - // For values int.MinValue < n <= int.MaxValue, the value is stored in sign + // For values nint.MinValue < n <= nint.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 - internal readonly int _sign; // Do not rename (binary serialization) - internal readonly uint[]? _bits; // Do not rename (binary serialization) + internal readonly nint _sign; // Do not rename (binary serialization) + internal readonly nuint[]? _bits; // Do not rename (binary serialization) - // We have to make a choice of how to represent int.MinValue. This is the one - // value that fits in an int, but whose negation does not fit in an int. + // We have to make a choice of how to represent nint.MinValue. This is the one + // value that fits in an nint, but whose negation does not fit in an nint. // We choose to use a large representation, so we're symmetric with respect to negation. - private static readonly BigInteger s_bnMinInt = new BigInteger(-1, new uint[] { kuMaskHighBit }); + private static readonly BigInteger s_bnMinInt = nint.Size == 8 + ? new BigInteger(-1, new nuint[] { unchecked((nuint)long.MinValue) }) + : new BigInteger(-1, new nuint[] { kuMaskHighBit }); private static readonly BigInteger s_bnOneInt = new BigInteger(1); private static readonly BigInteger s_bnZeroInt = new BigInteger(0); private static readonly BigInteger s_bnMinusOneInt = new BigInteger(-1); public BigInteger(int value) { - if (value == int.MinValue) + if (nint.Size == 4 && value == int.MinValue) this = s_bnMinInt; else { - _sign = value; + _sign = (nint)value; _bits = null; } AssertValid(); @@ -64,25 +66,36 @@ public BigInteger(int value) [CLSCompliant(false)] public BigInteger(uint value) { - if (value <= int.MaxValue) + if (nint.Size == 8 || value <= int.MaxValue) { - _sign = (int)value; + _sign = (nint)value; _bits = null; } else { _sign = +1; - _bits = new uint[1]; - _bits[0] = value; + _bits = [(nuint)value]; } AssertValid(); } public BigInteger(long value) { - if (int.MinValue < value && value <= int.MaxValue) + if (nint.Size == 8) { - _sign = (int)value; + if (value == long.MinValue) + { + this = s_bnMinInt; + } + else + { + _sign = (nint)value; + _bits = null; + } + } + else if (int.MinValue < value && value <= int.MaxValue) + { + _sign = (nint)(int)value; _bits = null; } else if (value == int.MinValue) @@ -105,14 +118,13 @@ public BigInteger(long value) if (x <= uint.MaxValue) { - _bits = new uint[1]; - _bits[0] = (uint)x; + _bits = [(nuint)(uint)x]; } else { - _bits = new uint[2]; - _bits[0] = unchecked((uint)x); - _bits[1] = (uint)(x >> kcbitUint); + _bits = new nuint[2]; + _bits[0] = unchecked((nuint)(uint)x); + _bits[1] = (nuint)(uint)(x >> kcbitUint); } } @@ -122,23 +134,35 @@ public BigInteger(long value) [CLSCompliant(false)] public BigInteger(ulong value) { - if (value <= int.MaxValue) + if (nint.Size == 8) + { + if (value <= (ulong)long.MaxValue) + { + _sign = (nint)value; + _bits = null; + } + else + { + _sign = +1; + _bits = [(nuint)value]; + } + } + else if (value <= int.MaxValue) { - _sign = (int)value; + _sign = (nint)(int)value; _bits = null; } else if (value <= uint.MaxValue) { _sign = +1; - _bits = new uint[1]; - _bits[0] = (uint)value; + _bits = [(nuint)(uint)value]; } else { _sign = +1; - _bits = new uint[2]; - _bits[0] = unchecked((uint)value); - _bits[1] = (uint)(value >> kcbitUint); + _bits = new nuint[2]; + _bits[0] = unchecked((nuint)(uint)value); + _bits[1] = (nuint)(uint)(value >> kcbitUint); } AssertValid(); @@ -198,23 +222,37 @@ public BigInteger(double value) } else { - // Overflow into at least 3 uints. + // Overflow into multiple limbs. // Move the leading 1 to the high bit. man <<= 11; exp -= 11; - // Compute cu and cbit so that exp == 32 * cu - cbit and 0 <= cbit < 32. - int cu = (exp - 1) / kcbitUint + 1; - int cbit = cu * kcbitUint - exp; - Debug.Assert(0 <= cbit && cbit < kcbitUint); + int bitsPerLimb = nint.Size * 8; + + // Compute cu and cbit so that exp == bitsPerLimb * cu - cbit and 0 <= cbit < bitsPerLimb. + int cu = (exp - 1) / bitsPerLimb + 1; + int cbit = cu * bitsPerLimb - exp; + Debug.Assert(0 <= cbit && cbit < bitsPerLimb); Debug.Assert(cu >= 1); - // Populate the uints. - _bits = new uint[cu + 2]; - _bits[cu + 1] = (uint)(man >> (cbit + kcbitUint)); - _bits[cu] = unchecked((uint)(man >> cbit)); - if (cbit > 0) - _bits[cu - 1] = unchecked((uint)man) << (kcbitUint - cbit); + // Populate the limbs. + if (nint.Size == 8) + { + // 64-bit: mantissa (64 bits) fits in 1-2 nuint limbs + _bits = new nuint[cu + 1]; + _bits[cu] = (nuint)(man >> cbit); + if (cbit > 0) + _bits[cu - 1] = (nuint)(man << (64 - cbit)); + } + else + { + // 32-bit: mantissa (64 bits) spans 2-3 nuint limbs + _bits = new nuint[cu + 2]; + _bits[cu + 1] = (nuint)(uint)(man >> (cbit + kcbitUint)); + _bits[cu] = unchecked((nuint)(uint)(man >> cbit)); + if (cbit > 0) + _bits[cu - 1] = unchecked((nuint)(uint)man) << (kcbitUint - cbit); + } _sign = sign; } @@ -241,21 +279,37 @@ 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 = (nint)bits[0]; _sign *= ((bits[3] & signMask) != 0) ? -1 : +1; _bits = null; } else { - _bits = new uint[size]; - - unchecked + if (nint.Size == 8) { - _bits[0] = (uint)bits[0]; - if (size > 1) - _bits[1] = (uint)bits[1]; - if (size > 2) - _bits[2] = (uint)bits[2]; + // 64-bit: pack up to 3 uint-sized values into 1-2 nuint limbs + int nuintSize = (size + 1) / 2; + _bits = new nuint[nuintSize]; + unchecked + { + _bits[0] = (nuint)(uint)bits[0]; + if (size > 1) + _bits[0] |= (nuint)(uint)bits[1] << 32; + if (size > 2) + _bits[nuintSize - 1] = (nuint)(uint)bits[2]; + } + } + else + { + _bits = new nuint[size]; + unchecked + { + _bits[0] = (nuint)(uint)bits[0]; + if (size > 1) + _bits[1] = (nuint)(uint)bits[1]; + if (size > 2) + _bits[2] = (nuint)(uint)bits[2]; + } } _sign = ((bits[3] & signMask) != 0) ? -1 : +1; @@ -325,9 +379,9 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE return; } - if (byteCount <= 4) + if (byteCount <= nint.Size) { - _sign = isNegative ? unchecked((int)0xffffffff) : 0; + _sign = isNegative ? (nint)(-1) : 0; if (isBigEndian) { @@ -347,59 +401,66 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE _bits = null; if (_sign < 0 && !isNegative) { - // Int32 overflow - // Example: Int64 value 2362232011 (0xCB, 0xCC, 0xCC, 0x8C, 0x0) - // can be naively packed into 4 bytes (due to the leading 0x0) - // it overflows into the int32 sign bit - _bits = new uint[1] { unchecked((uint)_sign) }; + // nint overflow: unsigned value overflows into the nint sign bit + _bits = new nuint[1] { unchecked((nuint)_sign) }; _sign = +1; } - if (_sign == int.MinValue) + if (_sign == nint.MinValue) { this = s_bnMinInt; } } else { - int wholeUInt32Count = Math.DivRem(byteCount, 4, out int unalignedBytes); - uint[] val = new uint[wholeUInt32Count + (unalignedBytes == 0 ? 0 : 1)]; + int bytesPerLimb = nint.Size; + int wholeLimbCount = Math.DivRem(byteCount, bytesPerLimb, out int unalignedBytes); + nuint[] val = new nuint[wholeLimbCount + (unalignedBytes == 0 ? 0 : 1)]; - // Copy the bytes to the uint array, apart from those which represent the - // most significant uint if it's not a full four bytes. - // The uints are stored in 'least significant first' order. + // Copy the bytes to the nuint array, apart from those which represent the + // most significant limb if it's not a full limb. + // The limbs are stored in 'least significant first' order. if (isBigEndian) { // The bytes parameter is in big-endian byte order. - // We need to read the uints out in reverse. + // We need to read the limbs out in reverse. - Span uintBytes = MemoryMarshal.AsBytes(val.AsSpan(0, wholeUInt32Count)); + Span limbBytes = MemoryMarshal.AsBytes(val.AsSpan(0, wholeLimbCount)); // We need to slice off the remainder from the beginning. - value.Slice(unalignedBytes).CopyTo(uintBytes); + value.Slice(unalignedBytes).CopyTo(limbBytes); - uintBytes.Reverse(); + limbBytes.Reverse(); } else { // The bytes parameter is in little-endian byte order. - // We can just copy the bytes directly into the uint array. + // We can just copy the bytes directly into the nuint array. - value.Slice(0, wholeUInt32Count * 4).CopyTo(MemoryMarshal.AsBytes(val.AsSpan())); + value.Slice(0, wholeLimbCount * bytesPerLimb).CopyTo(MemoryMarshal.AsBytes(val.AsSpan())); } // In both of the above cases on big-endian architecture, we need to perform - // an endianness swap on the resulting uints. + // an endianness swap on the resulting limbs. if (!BitConverter.IsLittleEndian) { - BinaryPrimitives.ReverseEndianness(val.AsSpan(0, wholeUInt32Count), val); + if (nint.Size == 8) + { + Span ulongSpan = MemoryMarshal.Cast(val.AsSpan(0, wholeLimbCount)); + BinaryPrimitives.ReverseEndianness((ReadOnlySpan)ulongSpan, ulongSpan); + } + else + { + Span uintSpan = MemoryMarshal.Cast(val.AsSpan(0, wholeLimbCount)); + BinaryPrimitives.ReverseEndianness((ReadOnlySpan)uintSpan, uintSpan); + } } - // Copy the last uint specially if it's not aligned + // Copy the last limb specially if it's not aligned if (unalignedBytes != 0) { if (isNegative) { - val[wholeUInt32Count] = 0xffffffff; + val[wholeLimbCount] = nuint.MaxValue; } if (isBigEndian) @@ -407,7 +468,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE for (int curByte = 0; curByte < unalignedBytes; curByte++) { byte curByteValue = value[curByte]; - val[wholeUInt32Count] = (val[wholeUInt32Count] << 8) | curByteValue; + val[wholeLimbCount] = (val[wholeLimbCount] << 8) | curByteValue; } } else @@ -415,7 +476,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE for (int curByte = byteCount - 1; curByte >= byteCount - unalignedBytes; curByte--) { byte curByteValue = value[curByte]; - val[wholeUInt32Count] = (val[wholeUInt32Count] << 8) | curByteValue; + val[wholeLimbCount] = (val[wholeLimbCount] << 8) | curByteValue; } } } @@ -429,35 +490,33 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE while (len >= 0 && val[len] == 0) len--; len++; + nuint nintMinMagnitude = unchecked((nuint)nint.MinValue); + if (len == 1) { - switch (val[0]) + if (val[0] == 1) // abs(-1) + { + this = s_bnMinusOneInt; + return; + } + else if (val[0] == nintMinMagnitude) // abs(nint.MinValue) { - case 1: // abs(-1) - this = s_bnMinusOneInt; - return; - - case kuMaskHighBit: // abs(Int32.MinValue) - this = s_bnMinInt; - return; - - default: - if (unchecked((int)val[0]) > 0) - { - _sign = (-1) * ((int)val[0]); - _bits = null; - AssertValid(); - return; - } - - break; + this = s_bnMinInt; + return; + } + else if (val[0] < nintMinMagnitude) // fits in nint as negative + { + _sign = -(nint)val[0]; + _bits = null; + AssertValid(); + return; } } if (len != val.Length) { _sign = -1; - _bits = new uint[len]; + _bits = new nuint[len]; Array.Copy(val, _bits, len); } else @@ -481,7 +540,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE /// /// the sign field /// the bits field - internal BigInteger(int sign, uint[]? bits) + internal BigInteger(nint sign, nuint[]? bits) { // Runtime check is converted to assertions because only one call from TryParseBigIntegerHexOrBinaryNumberStyle may fail the length check. // Validation in TryParseBigIntegerHexOrBinaryNumberStyle is also added in the accompanying PR. @@ -498,12 +557,12 @@ internal BigInteger(int sign, uint[]? bits) /// /// The absolute value of the number /// The bool indicating the sign of the value. - internal BigInteger(ReadOnlySpan value, bool negative) + internal BigInteger(ReadOnlySpan value, bool negative) { // 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 ^ - int length = value.LastIndexOfAnyExcept(0u) + 1; + int length = value.LastIndexOfAnyExcept((nuint)0) + 1; value = value[..length]; if (value.Length > MaxLength) @@ -511,20 +570,22 @@ internal BigInteger(ReadOnlySpan value, bool negative) ThrowHelper.ThrowOverflowException(); } + nuint nintMinMagnitude = unchecked((nuint)nint.MinValue); + if (value.Length == 0) { this = default; } else if (value.Length == 1) { - if (value[0] < kuMaskHighBit) + if (value[0] < nintMinMagnitude) { - _sign = negative ? -(int)value[0] : (int)value[0]; + _sign = negative ? -(nint)value[0] : (nint)value[0]; _bits = null; } - else if (negative && value[0] == kuMaskHighBit) + else if (negative && value[0] == nintMinMagnitude) { - // Although Int32.MinValue fits in _sign, we represent this case differently for negate + // Although nint.MinValue fits in _sign, we represent this case differently for negate this = s_bnMinInt; } else @@ -542,30 +603,30 @@ internal BigInteger(ReadOnlySpan value, bool negative) } /// - /// Create a BigInteger from a little-endian twos-complement UInt32 span. + /// Create a BigInteger from a little-endian twos-complement nuint span. /// /// - private BigInteger(Span value) + private BigInteger(Span value) { bool isNegative; int length; - if ((value.Length > 0) && ((int)value[^1] < 0)) + if ((value.Length > 0) && ((nint)value[^1] < 0)) { isNegative = true; - length = value.LastIndexOfAnyExcept(uint.MaxValue) + 1; + length = value.LastIndexOfAnyExcept(nuint.MaxValue) + 1; - if ((length == 0) || ((int)value[length - 1] >= 0)) + if ((length == 0) || ((nint)value[length - 1] >= 0)) { // We need to preserve the sign bit length++; } - Debug.Assert((int)value[length - 1] < 0); + Debug.Assert((nint)value[length - 1] < 0); } else { isNegative = false; - length = value.LastIndexOfAnyExcept(0u) + 1; + length = value.LastIndexOfAnyExcept((nuint)0) + 1; } value = value[..length]; @@ -574,6 +635,8 @@ private BigInteger(Span value) ThrowHelper.ThrowOverflowException(); } + nuint nintMinMagnitude = unchecked((nuint)nint.MinValue); + if (value.Length == 0) { // 0 @@ -583,30 +646,30 @@ private BigInteger(Span value) { if (isNegative) { - if (value[0] == uint.MaxValue) + if (value[0] == nuint.MaxValue) { // -1 this = s_bnMinusOneInt; } - else if (value[0] == kuMaskHighBit) + else if (value[0] == nintMinMagnitude) { - // int.MinValue + // nint.MinValue this = s_bnMinInt; } else { - _sign = unchecked((int)value[0]); + _sign = unchecked((nint)value[0]); _bits = null; } } - else if (unchecked((int)value[0]) < 0) + else if (value[0] >= nintMinMagnitude) { _sign = +1; _bits = [value[0]]; } else { - _sign = unchecked((int)value[0]); + _sign = unchecked((nint)value[0]); _bits = null; } } @@ -617,7 +680,7 @@ private BigInteger(Span value) NumericsHelpers.DangerousMakeTwosComplement(value); // Retrim any leading zeros carried from the sign - length = value.LastIndexOfAnyExcept(0u) + 1; + length = value.LastIndexOfAnyExcept((nuint)0) + 1; value = value[..length]; _sign = -1; @@ -651,7 +714,7 @@ public bool IsPowerOfTwo int iu = _bits.Length - 1; - return BitOperations.IsPow2(_bits[iu]) && !_bits.AsSpan(0, iu).ContainsAnyExcept(0u); + return BitOperations.IsPow2(_bits[iu]) && !_bits.AsSpan(0, iu).ContainsAnyExcept((nuint)0); } } @@ -663,7 +726,7 @@ public bool IsPowerOfTwo public int Sign { - get { AssertValid(); return (_sign >> (kcbitUint - 1)) - (-_sign >> (kcbitUint - 1)); } + get { AssertValid(); return (int)((_sign >> (nint.Size * 8 - 1)) - (-_sign >> (nint.Size * 8 - 1))); } } public static BigInteger Parse(string value) @@ -735,7 +798,7 @@ public static int Compare(BigInteger left, BigInteger right) public static BigInteger Abs(BigInteger value) { value.AssertValid(); - return new BigInteger(unchecked((int)NumericsHelpers.Abs(value._sign)), value._bits); + return new BigInteger(unchecked((nint)NumericsHelpers.Abs(value._sign)), value._bits); } public static BigInteger Add(BigInteger left, BigInteger right) @@ -774,7 +837,9 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big if (trivialDividend && trivialDivisor) { BigInteger quotient; - (quotient, remainder) = Math.DivRem(dividend._sign, divisor._sign); + (nint q, nint r) = Math.DivRem(dividend._sign, divisor._sign); + quotient = q; + remainder = r; return quotient; } @@ -790,26 +855,26 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big if (trivialDivisor) { - uint rest; + nuint rest; - uint[]? bitsFromPool = null; + nuint[]? 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); + Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[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; + remainder = dividend._sign < 0 ? -1 * (long)rest : (long)rest; return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); } finally { if (bitsFromPool != null) - ArrayPool.Shared.Return(bitsFromPool); + ArrayPool.Shared.Return(bitsFromPool); } } @@ -822,17 +887,17 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big } else { - uint[]? remainderFromPool = null; + nuint[]? 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); + Span rest = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : remainderFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - uint[]? quotientFromPool = null; + nuint[]? 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); + Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Divide(dividend._bits, divisor._bits, quotient, rest); @@ -840,10 +905,10 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big var result = new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); if (remainderFromPool != null) - ArrayPool.Shared.Return(remainderFromPool); + ArrayPool.Shared.Return(remainderFromPool); if (quotientFromPool != null) - ArrayPool.Shared.Return(quotientFromPool); + ArrayPool.Shared.Return(quotientFromPool); return result; } @@ -868,18 +933,37 @@ public static double Log(BigInteger value, double baseValue) if (baseValue == 0.0D && !value.IsOne) return double.NaN; if (value._bits == null) - return Math.Log(value._sign, baseValue); + return Math.Log((double)value._sign, baseValue); - ulong h = value._bits[value._bits.Length - 1]; - ulong m = value._bits.Length > 1 ? value._bits[value._bits.Length - 2] : 0; - ulong l = value._bits.Length > 2 ? value._bits[value._bits.Length - 3] : 0; + ulong h, m, l; + int c; + long b; + ulong x; - // Measure the exact bit count - int c = BitOperations.LeadingZeroCount((uint)h); - long b = (long)value._bits.Length * 32 - c; + if (nint.Size == 8) + { + h = (ulong)value._bits[value._bits.Length - 1]; + m = value._bits.Length > 1 ? (ulong)value._bits[value._bits.Length - 2] : 0; + + c = BitOperations.LeadingZeroCount(h); + b = (long)value._bits.Length * 64 - c; + + // Extract most significant 64 bits + x = c == 0 ? h : (h << c) | (m >> (64 - c)); + } + else + { + h = (uint)value._bits[value._bits.Length - 1]; + m = value._bits.Length > 1 ? (uint)value._bits[value._bits.Length - 2] : 0; + l = value._bits.Length > 2 ? (uint)value._bits[value._bits.Length - 3] : 0; + + // Measure the exact bit count + c = BitOperations.LeadingZeroCount((uint)h); + b = (long)value._bits.Length * 32 - c; - // Extract most significant bits - ulong x = (h << 32 + c) | (m << c) | (l >> 32 - c); + // Extract most significant bits + x = (h << 32 + c) | (m << c) | (l >> 32 - c); + } // Let v = value, b = bit count, x = v/2^b-64 // log ( v/2^b-64 * 2^b-64 ) = log ( x ) + log ( 2^b-64 ) @@ -932,44 +1016,44 @@ public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right } } - private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, ReadOnlySpan rightBits) + private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, ReadOnlySpan rightBits) { Debug.Assert(BigIntegerCalculator.Compare(leftBits, rightBits) >= 0); - uint[]? bitsFromPool = null; + nuint[]? bitsFromPool = null; BigInteger result; // Short circuits to spare some allocations... if (rightBits.Length == 1) { - uint temp = BigIntegerCalculator.Remainder(leftBits, rightBits[0]); + nuint temp = BigIntegerCalculator.Remainder(leftBits, rightBits[0]); result = BigIntegerCalculator.Gcd(rightBits[0], temp); } else if (rightBits.Length == 2) { - Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); + Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[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)bits[1] << 32) | bits[0]; + ulong left = ((ulong)rightBits[1] << 32) | (uint)rightBits[0]; + ulong right = ((ulong)bits[1] << 32) | (uint)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); + Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); BigIntegerCalculator.Gcd(leftBits, rightBits, bits); result = new BigInteger(bits, negative: false); } if (bitsFromPool != null) - ArrayPool.Shared.Return(bitsFromPool); + ArrayPool.Shared.Return(bitsFromPool); return result; } @@ -1004,20 +1088,20 @@ public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigIntege if (trivialModulus) { - uint bits = trivialValue && trivialExponent ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) : + nuint bitsResult = trivialValue && trivialExponent ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) : trivialValue ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), exponent._bits!, NumericsHelpers.Abs(modulus._sign)) : trivialExponent ? BigIntegerCalculator.Pow(value._bits!, NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) : BigIntegerCalculator.Pow(value._bits!, exponent._bits!, NumericsHelpers.Abs(modulus._sign)); - result = value._sign < 0 && !exponent.IsEven ? -1 * bits : bits; + result = value._sign < 0 && !exponent.IsEven ? -1 * (long)bitsResult : (long)bitsResult; } else { 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); + nuint[]? bitsFromPool = null; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); bits.Clear(); if (trivialValue) { @@ -1042,7 +1126,7 @@ public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigIntege result = new BigInteger(bits, value._sign < 0 && !exponent.IsEven); if (bitsFromPool != null) - ArrayPool.Shared.Return(bitsFromPool); + ArrayPool.Shared.Return(bitsFromPool); } return result; @@ -1061,8 +1145,8 @@ public static BigInteger Pow(BigInteger value, int exponent) bool trivialValue = value._bits == null; - uint power = NumericsHelpers.Abs(exponent); - uint[]? bitsFromPool = null; + nuint power = NumericsHelpers.Abs(exponent); + nuint[]? bitsFromPool = null; BigInteger result; if (trivialValue) @@ -1075,9 +1159,9 @@ public static BigInteger Pow(BigInteger value, int exponent) 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); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); bits.Clear(); BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), power, bits); @@ -1086,9 +1170,9 @@ public static BigInteger Pow(BigInteger value, int exponent) 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); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); bits.Clear(); BigIntegerCalculator.Pow(value._bits, power, bits); @@ -1096,7 +1180,7 @@ public static BigInteger Pow(BigInteger value, int exponent) } if (bitsFromPool != null) - ArrayPool.Shared.Return(bitsFromPool); + ArrayPool.Shared.Return(bitsFromPool); return result; } @@ -1106,7 +1190,7 @@ public override int GetHashCode() AssertValid(); if (_bits is null) - return _sign; + return (int)_sign ^ (int)((long)_sign >> 32); HashCode hash = default; hash.AddBytes(MemoryMarshal.AsBytes(_bits.AsSpan())); @@ -1126,17 +1210,25 @@ public bool Equals(long other) AssertValid(); if (_bits == null) - return _sign == other; + return (long)_sign == other; int cu; - if ((_sign ^ other) < 0 || (cu = _bits.Length) > 2) + int maxLimbs = 8 / nint.Size; + if (((long)_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) return false; ulong uu = other < 0 ? (ulong)-other : (ulong)other; - if (cu == 1) - return _bits[0] == uu; - return NumericsHelpers.MakeUInt64(_bits[1], _bits[0]) == uu; + if (nint.Size == 8) + { + return (ulong)_bits[0] == uu; + } + else + { + if (cu == 1) + return (uint)_bits[0] == uu; + return ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == uu; + } } [CLSCompliant(false)] @@ -1150,11 +1242,20 @@ public bool Equals(ulong other) return (ulong)_sign == other; int cu = _bits.Length; - if (cu > 2) + int maxLimbs = 8 / nint.Size; + if (cu > maxLimbs) return false; - if (cu == 1) - return _bits[0] == other; - return NumericsHelpers.MakeUInt64(_bits[1], _bits[0]) == other; + + if (nint.Size == 8) + { + return (ulong)_bits[0] == other; + } + else + { + if (cu == 1) + return (uint)_bits[0] == other; + return ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == other; + } } public bool Equals(BigInteger other) @@ -1171,12 +1272,24 @@ public int CompareTo(long other) if (_bits == null) return ((long)_sign).CompareTo(other); + int cu; - if ((_sign ^ other) < 0 || (cu = _bits.Length) > 2) - return _sign; + int maxLimbs = 8 / nint.Size; + if (((long)_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) + return (int)_sign; + ulong uu = other < 0 ? (ulong)-other : (ulong)other; - ulong uuTmp = cu == 2 ? NumericsHelpers.MakeUInt64(_bits[1], _bits[0]) : _bits[0]; - return _sign * uuTmp.CompareTo(uu); + ulong uuTmp; + + if (nint.Size == 8) + { + uuTmp = (ulong)_bits[0]; + } + else + { + uuTmp = cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : (uint)_bits[0]; + } + return (int)_sign * uuTmp.CompareTo(uu); } [CLSCompliant(false)] @@ -1188,10 +1301,21 @@ public int CompareTo(ulong other) return -1; if (_bits == null) return ((ulong)_sign).CompareTo(other); + int cu = _bits.Length; - if (cu > 2) + int maxLimbs = 8 / nint.Size; + if (cu > maxLimbs) return +1; - ulong uuTmp = cu == 2 ? NumericsHelpers.MakeUInt64(_bits[1], _bits[0]) : _bits[0]; + + ulong uuTmp; + if (nint.Size == 8) + { + uuTmp = (ulong)_bits[0]; + } + else + { + uuTmp = cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : (uint)_bits[0]; + } return uuTmp.CompareTo(other); } @@ -1211,11 +1335,11 @@ public int CompareTo(BigInteger other) { if (other._bits == null) return _sign < other._sign ? -1 : _sign > other._sign ? +1 : 0; - return -other._sign; + return -(int)other._sign; } if (other._bits == null) - return _sign; + return (int)_sign; int bitsResult = BigIntegerCalculator.Compare(_bits, other._bits); return _sign < 0 ? -bitsResult : bitsResult; @@ -1348,7 +1472,7 @@ private enum GetBytesMode Debug.Assert(mode == GetBytesMode.AllocateArray || mode == GetBytesMode.Count || mode == GetBytesMode.Span, $"Unexpected mode {mode}."); Debug.Assert(mode == GetBytesMode.Span || destination.IsEmpty, $"If we're not in span mode, we shouldn't have been passed a destination."); - int sign = _sign; + nint sign = _sign; if (sign == 0) { switch (mode) @@ -1374,14 +1498,15 @@ private enum GetBytesMode throw new OverflowException(SR.Overflow_Negative_Unsigned); } + int bytesPerLimb = nint.Size; byte highByte; - int nonZeroDwordIndex = 0; - uint highDword; - uint[]? bits = _bits; + int nonZeroLimbIndex = 0; + nuint highLimb; + nuint[]? bits = _bits; if (bits == null) { highByte = (byte)((sign < 0) ? 0xff : 0x00); - highDword = unchecked((uint)sign); + highLimb = unchecked((nuint)sign); } else if (sign == -1) { @@ -1393,57 +1518,51 @@ private enum GetBytesMode // creating a temporary copy of bits just to hold the two's complement. // One special case in DangerousMakeTwosComplement() is that if the array // is all zeros, then it would allocate a new array with the high-order - // uint set to 1 (for the carry). In our usage, we will not hit this case + // limb set to 1 (for the carry). In our usage, we will not hit this case // because a bits array of all zeros would represent 0, and this case // would be encoded as _bits = null and _sign = 0. Debug.Assert(bits.Length > 0); Debug.Assert(bits[bits.Length - 1] != 0); - while (bits[nonZeroDwordIndex] == 0U) + while (bits[nonZeroLimbIndex] == 0) { - nonZeroDwordIndex++; + nonZeroLimbIndex++; } - highDword = ~bits[bits.Length - 1]; - if (bits.Length - 1 == nonZeroDwordIndex) + highLimb = ~bits[bits.Length - 1]; + if (bits.Length - 1 == nonZeroLimbIndex) { - // This will not overflow because highDword is less than or equal to uint.MaxValue - 1. - Debug.Assert(highDword <= uint.MaxValue - 1); - highDword += 1U; + // This will not overflow because highLimb is less than or equal to nuint.MaxValue - 1. + Debug.Assert(highLimb <= nuint.MaxValue - 1); + highLimb += 1; } } else { Debug.Assert(sign == 1); highByte = 0x00; - highDword = bits[bits.Length - 1]; + highLimb = bits[bits.Length - 1]; } + // Find the most significant byte index within the high limb byte msb; int msbIndex; - if ((msb = unchecked((byte)(highDword >> 24))) != highByte) - { - msbIndex = 3; - } - else if ((msb = unchecked((byte)(highDword >> 16))) != highByte) + int maxByteIndex = bytesPerLimb - 1; + msbIndex = maxByteIndex; + while (msbIndex > 0) { - msbIndex = 2; - } - else if ((msb = unchecked((byte)(highDword >> 8))) != highByte) - { - msbIndex = 1; - } - else - { - msb = unchecked((byte)highDword); - msbIndex = 0; + msb = unchecked((byte)(highLimb >> (msbIndex * 8))); + if (msb != highByte) + break; + msbIndex--; } + msb = unchecked((byte)(highLimb >> (msbIndex * 8))); // Ensure high bit is 0 if positive, 1 if negative bool needExtraByte = (msb & 0x80) != (highByte & 0x80) && !isUnsigned; int length = msbIndex + 1 + (needExtraByte ? 1 : 0); if (bits != null) { - length = checked(4 * (bits.Length - 1) + length); + length = checked(bytesPerLimb * (bits.Length - 1) + length); } byte[] array; @@ -1491,26 +1610,32 @@ private enum GetBytesMode { for (int i = 0; i < bits.Length - 1; i++) { - uint dword = bits[i]; + nuint limb = bits[i]; if (sign == -1) { - dword = ~dword; - if (i <= nonZeroDwordIndex) + limb = ~limb; + if (i <= nonZeroLimbIndex) { - dword = unchecked(dword + 1U); + limb = unchecked(limb + 1); } } if (isBigEndian) { - curByte -= 4; - BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(curByte), dword); + curByte -= bytesPerLimb; + if (nint.Size == 8) + BinaryPrimitives.WriteUInt64BigEndian(destination.Slice(curByte), (ulong)limb); + else + BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(curByte), (uint)limb); } else { - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(curByte), dword); - curByte += 4; + if (nint.Size == 8) + BinaryPrimitives.WriteUInt64LittleEndian(destination.Slice(curByte), (ulong)limb); + else + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(curByte), (uint)limb); + curByte += bytesPerLimb; } } } @@ -1521,22 +1646,13 @@ private enum GetBytesMode curByte--; } - Debug.Assert(msbIndex >= 0 && msbIndex <= 3); - destination[curByte] = unchecked((byte)highDword); - if (msbIndex != 0) + Debug.Assert(msbIndex >= 0 && msbIndex < bytesPerLimb); + // Write significant bytes of the high limb + for (int byteIdx = 0; byteIdx <= msbIndex; byteIdx++) { - curByte += increment; - destination[curByte] = unchecked((byte)(highDword >> 8)); - if (msbIndex != 1) - { + destination[curByte] = unchecked((byte)(highLimb >> (byteIdx * 8))); + if (byteIdx < msbIndex) curByte += increment; - destination[curByte] = unchecked((byte)(highDword >> 16)); - if (msbIndex != 2) - { - curByte += increment; - destination[curByte] = unchecked((byte)(highDword >> 24)); - } - } } // Assert we're big endian, or little endian consistency holds. @@ -1555,20 +1671,20 @@ private enum GetBytesMode /// /// 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. + /// nuint span allocated by the caller using the fewest number of nuints possible. /// /// Pre-allocated buffer by the caller. /// The actual number of copied elements. - private int WriteTo(Span buffer) + private int WriteTo(Span buffer) { Debug.Assert(_bits is null || _sign == 0 ? buffer.Length == 2 : buffer.Length >= _bits.Length + 1); - uint highDWord; + nuint highLimb; if (_bits is null) { - buffer[0] = unchecked((uint)_sign); - highDWord = (_sign < 0) ? uint.MaxValue : 0; + buffer[0] = unchecked((nuint)_sign); + highLimb = (_sign < 0) ? nuint.MaxValue : 0; } else { @@ -1576,29 +1692,30 @@ private int WriteTo(Span buffer) buffer = buffer.Slice(0, _bits.Length + 1); if (_sign == -1) { - NumericsHelpers.DangerousMakeTwosComplement(buffer.Slice(0, buffer.Length - 1)); // Mutates dwords - highDWord = uint.MaxValue; + NumericsHelpers.DangerousMakeTwosComplement(buffer.Slice(0, buffer.Length - 1)); // Mutates limbs + highLimb = nuint.MaxValue; } else - highDWord = 0; + highLimb = 0; } - // Find highest significant byte and ensure high bit is 0 if positive, 1 if negative + // Find highest significant limb and ensure high bit is 0 if positive, 1 if negative int msb = buffer.Length - 2; - while (msb > 0 && buffer[msb] == highDWord) + while (msb > 0 && buffer[msb] == highLimb) { msb--; } // Ensure high bit is 0 if positive, 1 if negative - bool needExtraByte = (buffer[msb] & 0x80000000) != (highDWord & 0x80000000); + nuint highBitMask = (nuint)1 << (nint.Size * 8 - 1); + bool needExtraLimb = (buffer[msb] & highBitMask) != (highLimb & highBitMask); int count; - if (needExtraByte) + if (needExtraLimb) { count = msb + 2; buffer = buffer.Slice(0, count); - buffer[buffer.Length - 1] = highDWord; + buffer[buffer.Length - 1] = highLimb; } else { @@ -1647,9 +1764,18 @@ private string DebuggerDisplay // Let `m = n * log10(2)`, the final result would be `x = (k * 10^(m - [m])) * 10^(i+[m])` const double log10Of2 = 0.3010299956639812; // Log10(2) - ulong highBits = ((ulong)_bits[^1] << kcbitUint) + _bits[^2]; - double lowBitsCount32 = _bits.Length - 2; // if Length > int.MaxValue/32, counting in bits can cause overflow - double exponentLow = lowBitsCount32 * kcbitUint * log10Of2; + int bitsPerLimb = nint.Size * 8; + ulong highBits; + if (nint.Size == 8) + { + highBits = (ulong)_bits[^1]; + } + else + { + highBits = ((ulong)_bits[^1] << kcbitUint) + (uint)_bits[^2]; + } + double lowBitsCount = _bits.Length - (nint.Size == 8 ? 1 : 2); + double exponentLow = lowBitsCount * bitsPerLimb * log10Of2; // Max possible length of _bits is int.MaxValue of bytes, // thus max possible value of BigInteger is 2^(8*Array.MaxLength)-1 which is larger than 10^(2^33) @@ -1693,7 +1819,7 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS return Number.TryFormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider), MemoryMarshal.Cast(utf8Destination), out bytesWritten); } - private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) + private static BigInteger Add(ReadOnlySpan leftBits, nint leftSign, ReadOnlySpan rightBits, nint rightSign) { bool trivialLeft = leftBits.IsEmpty; bool trivialRight = rightBits.IsEmpty; @@ -1701,16 +1827,16 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnl Debug.Assert(!(trivialLeft && trivialRight), "Trivial cases should be handled on the caller operator"); BigInteger result; - uint[]? bitsFromPool = null; + nuint[]? bitsFromPool = null; if (trivialLeft) { Debug.Assert(!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); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Add(rightBits, NumericsHelpers.Abs(leftSign), bits); result = new BigInteger(bits, leftSign < 0); @@ -1720,9 +1846,9 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnl Debug.Assert(!leftBits.IsEmpty); int size = leftBits.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Add(leftBits, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, leftSign < 0); @@ -1732,9 +1858,9 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnl 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); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Add(rightBits, leftBits, bits); result = new BigInteger(bits, leftSign < 0); @@ -1744,16 +1870,16 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnl 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); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[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); + ArrayPool.Shared.Return(bitsFromPool); return result; } @@ -1764,14 +1890,14 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnl right.AssertValid(); if (left._bits == null && right._bits == null) - return (long)left._sign - right._sign; + return (Int128)left._sign - right._sign; if (left._sign < 0 != right._sign < 0) return Add(left._bits, left._sign, right._bits, -1 * right._sign); return Subtract(left._bits, left._sign, right._bits, right._sign); } - private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) + private static BigInteger Subtract(ReadOnlySpan leftBits, nint leftSign, ReadOnlySpan rightBits, nint rightSign) { bool trivialLeft = leftBits.IsEmpty; bool trivialRight = rightBits.IsEmpty; @@ -1779,16 +1905,16 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, Re Debug.Assert(!(trivialLeft && trivialRight), "Trivial cases should be handled on the caller operator"); BigInteger result; - uint[]? bitsFromPool = null; + nuint[]? bitsFromPool = null; if (trivialLeft) { Debug.Assert(!rightBits.IsEmpty); int size = rightBits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Subtract(rightBits, NumericsHelpers.Abs(leftSign), bits); result = new BigInteger(bits, leftSign >= 0); @@ -1798,9 +1924,9 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, Re Debug.Assert(!leftBits.IsEmpty); int size = leftBits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Subtract(leftBits, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, leftSign < 0); @@ -1808,9 +1934,9 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, Re else if (BigIntegerCalculator.Compare(leftBits, rightBits) < 0) { int size = rightBits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Subtract(rightBits, leftBits, bits); result = new BigInteger(bits, leftSign >= 0); @@ -1820,16 +1946,16 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, Re 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); + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[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); + ArrayPool.Shared.Return(bitsFromPool); return result; } @@ -1855,18 +1981,35 @@ public static explicit operator decimal(BigInteger value) { value.AssertValid(); if (value._bits == null) - return value._sign; + return (long)value._sign; int length = value._bits.Length; - if (length > 3) throw new OverflowException(SR.Overflow_Decimal); int lo = 0, mi = 0, hi = 0; - unchecked + if (nint.Size == 8) { - if (length > 2) hi = (int)value._bits[2]; - if (length > 1) mi = (int)value._bits[1]; - if (length > 0) lo = (int)value._bits[0]; + // 64-bit: at most 2 nuint limbs for 96 bits + if (length > 2) throw new OverflowException(SR.Overflow_Decimal); + + unchecked + { + lo = (int)(uint)value._bits[0]; + mi = (int)(uint)(value._bits[0] >> 32); + if (length > 1) + hi = (int)(uint)value._bits[1]; + } + } + else + { + if (length > 3) throw new OverflowException(SR.Overflow_Decimal); + + unchecked + { + if (length > 2) hi = (int)(uint)value._bits[2]; + if (length > 1) mi = (int)(uint)value._bits[1]; + if (length > 0) lo = (int)(uint)value._bits[0]; + } } return new decimal(lo, mi, hi, value._sign < 0, 0); @@ -1876,20 +2019,20 @@ public static explicit operator double(BigInteger value) { value.AssertValid(); - int sign = value._sign; - uint[]? bits = value._bits; + nint sign = value._sign; + nuint[]? bits = value._bits; if (bits == null) - return sign; + return (double)(long)sign; int length = bits.Length; + int bitsPerLimb = nint.Size * 8; - // The maximum exponent for doubles is 1023, which corresponds to a uint bit length of 32. - // All BigIntegers with bits[] longer than 32 evaluate to Double.Infinity (or NegativeInfinity). - // Cases where the exponent is between 1024 and 1035 are handled in NumericsHelpers.GetDoubleFromParts. - const int InfinityLength = 1024 / kcbitUint; + // The maximum exponent for doubles is 1023, which corresponds to a limb bit length of 1024. + // All BigIntegers with bits[] longer than this evaluate to Double.Infinity (or NegativeInfinity). + int infinityLength = 1024 / bitsPerLimb; - if (length > InfinityLength) + if (length > infinityLength) { if (sign == 1) return double.PositiveInfinity; @@ -1897,16 +2040,31 @@ public static explicit operator double(BigInteger value) return double.NegativeInfinity; } - ulong h = bits[length - 1]; - ulong m = length > 1 ? bits[length - 2] : 0; - ulong l = length > 2 ? bits[length - 3] : 0; + ulong h, m, l; + int z, exp; + ulong man; + + if (nint.Size == 8) + { + h = (ulong)bits[length - 1]; + m = length > 1 ? (ulong)bits[length - 2] : 0; - int z = BitOperations.LeadingZeroCount((uint)h); + z = BitOperations.LeadingZeroCount(h); + exp = (length - 2) * 64 - z; + man = z == 0 ? h : (h << z) | (m >> (64 - z)); + } + else + { + h = (uint)bits[length - 1]; + m = length > 1 ? (uint)bits[length - 2] : 0; + l = length > 2 ? (uint)bits[length - 3] : 0; - int exp = (length - 2) * 32 - z; - ulong man = (h << 32 + z) | (m << z) | (l >> 32 - z); + z = BitOperations.LeadingZeroCount((uint)h); + exp = (length - 2) * 32 - z; + man = (h << 32 + z) | (m << z) | (l >> 32 - z); + } - return NumericsHelpers.GetDoubleFromParts(sign, exp, man); + return NumericsHelpers.GetDoubleFromParts((int)sign, exp, man); } /// Explicitly converts a big integer to a value. @@ -1935,11 +2093,11 @@ public static explicit operator int(BigInteger value) value.AssertValid(); if (value._bits == null) { - return value._sign; // Value packed into int32 sign + return checked((int)value._sign); // nint to int; may overflow on 64-bit } if (value._bits.Length > 1) { - // More than 32 bits + // More than one limb throw new OverflowException(SR.Overflow_Int32); } if (value._sign > 0) @@ -1959,23 +2117,28 @@ public static explicit operator long(BigInteger value) value.AssertValid(); if (value._bits == null) { - return value._sign; + return (long)value._sign; } int len = value._bits.Length; - if (len > 2) + int maxLimbs = 8 / nint.Size; + if (len > maxLimbs) { throw new OverflowException(SR.Overflow_Int64); } ulong uu; - if (len > 1) + if (nint.Size == 8) + { + uu = (ulong)value._bits[0]; + } + else if (len > 1) { - uu = NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]); + uu = ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]); } else { - uu = value._bits[0]; + uu = (uint)value._bits[0]; } long ll = value._sign > 0 ? unchecked((long)uu) : unchecked(-(long)uu); @@ -1996,32 +2159,44 @@ public static explicit operator Int128(BigInteger value) if (value._bits is null) { - return value._sign; + return (long)value._sign; } int len = value._bits.Length; + int maxLimbs = 16 / nint.Size; - if (len > 4) + if (len > maxLimbs) { throw new OverflowException(SR.Overflow_Int128); } UInt128 uu; - if (len > 2) + if (nint.Size == 8) + { + if (len > 1) + { + uu = new UInt128((ulong)value._bits[1], (ulong)value._bits[0]); + } + else + { + uu = (ulong)value._bits[0]; + } + } + else if (len > 2) { uu = new UInt128( - NumericsHelpers.MakeUInt64((len > 3) ? value._bits[3] : 0, value._bits[2]), - NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]) + ((ulong)((len > 3) ? (uint)value._bits[3] : 0) << 32 | (uint)value._bits[2]), + ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) ); } else if (len > 1) { - uu = NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]); + uu = ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]); } else { - uu = value._bits[0]; + uu = (uint)value._bits[0]; } Int128 ll = (value._sign > 0) ? unchecked((Int128)uu) : unchecked(-(Int128)uu); @@ -2080,7 +2255,7 @@ public static explicit operator uint(BigInteger value) } else { - return value._bits[0]; + return checked((uint)value._bits[0]); } } @@ -2094,16 +2269,21 @@ public static explicit operator ulong(BigInteger value) } int len = value._bits.Length; - if (len > 2 || value._sign < 0) + int maxLimbs = 8 / nint.Size; + if (len > maxLimbs || value._sign < 0) { throw new OverflowException(SR.Overflow_UInt64); } - if (len > 1) + if (nint.Size == 8) { - return NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]); + return (ulong)value._bits[0]; } - return value._bits[0]; + else if (len > 1) + { + return ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]); + } + return (uint)value._bits[0]; } /// Explicitly converts a big integer to a value. @@ -2120,24 +2300,33 @@ public static explicit operator UInt128(BigInteger value) } int len = value._bits.Length; + int maxLimbs = 16 / nint.Size; - if ((len > 4) || (value._sign < 0)) + if ((len > maxLimbs) || (value._sign < 0)) { throw new OverflowException(SR.Overflow_UInt128); } - if (len > 2) + if (nint.Size == 8) + { + if (len > 1) + { + return new UInt128((ulong)value._bits[1], (ulong)value._bits[0]); + } + return (ulong)value._bits[0]; + } + else if (len > 2) { return new UInt128( - NumericsHelpers.MakeUInt64((len > 3) ? value._bits[3] : 0, value._bits[2]), - NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]) + ((ulong)((len > 3) ? (uint)value._bits[3] : 0) << 32 | (uint)value._bits[2]), + ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) ); } else if (len > 1) { - return NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]); + return ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]); } - return value._bits[0]; + return (uint)value._bits[0]; } /// Explicitly converts a big integer to a value. @@ -2240,18 +2429,18 @@ public static implicit operator BigInteger(long value) /// converted to a big integer. public static implicit operator BigInteger(Int128 value) { - int sign; - uint[]? bits; + nint sign; + nuint[]? bits; - if ((int.MinValue < value) && (value <= int.MaxValue)) + if ((nint.MinValue < value) && (value <= nint.MaxValue)) { - sign = (int)value; + if (value == nint.MinValue) + { + return s_bnMinInt; + } + sign = (nint)(long)value; bits = null; } - else if (value == int.MinValue) - { - return s_bnMinInt; - } else { UInt128 x; @@ -2266,31 +2455,48 @@ public static implicit operator BigInteger(Int128 value) sign = +1; } - if (x <= uint.MaxValue) + if (nint.Size == 8) { - bits = new uint[1]; - bits[0] = (uint)(x >> (kcbitUint * 0)); - } - else if (x <= ulong.MaxValue) - { - bits = new uint[2]; - bits[0] = (uint)(x >> (kcbitUint * 0)); - bits[1] = (uint)(x >> (kcbitUint * 1)); - } - else if (x <= new UInt128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) - { - bits = new uint[3]; - bits[0] = (uint)(x >> (kcbitUint * 0)); - bits[1] = (uint)(x >> (kcbitUint * 1)); - bits[2] = (uint)(x >> (kcbitUint * 2)); + if (x <= ulong.MaxValue) + { + bits = new nuint[1]; + bits[0] = (nuint)(ulong)x; + } + else + { + bits = new nuint[2]; + bits[0] = (nuint)(ulong)x; + bits[1] = (nuint)(ulong)(x >> 64); + } } else { - bits = new uint[4]; - bits[0] = (uint)(x >> (kcbitUint * 0)); - bits[1] = (uint)(x >> (kcbitUint * 1)); - bits[2] = (uint)(x >> (kcbitUint * 2)); - bits[3] = (uint)(x >> (kcbitUint * 3)); + if (x <= uint.MaxValue) + { + bits = new nuint[1]; + bits[0] = (nuint)(uint)(x >> (kcbitUint * 0)); + } + else if (x <= ulong.MaxValue) + { + bits = new nuint[2]; + bits[0] = (nuint)(uint)(x >> (kcbitUint * 0)); + bits[1] = (nuint)(uint)(x >> (kcbitUint * 1)); + } + else if (x <= new UInt128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) + { + bits = new nuint[3]; + bits[0] = (nuint)(uint)(x >> (kcbitUint * 0)); + bits[1] = (nuint)(uint)(x >> (kcbitUint * 1)); + bits[2] = (nuint)(uint)(x >> (kcbitUint * 2)); + } + else + { + bits = new nuint[4]; + bits[0] = (nuint)(uint)(x >> (kcbitUint * 0)); + bits[1] = (nuint)(uint)(x >> (kcbitUint * 1)); + bits[2] = (nuint)(uint)(x >> (kcbitUint * 2)); + bits[3] = (nuint)(uint)(x >> (kcbitUint * 3)); + } } } @@ -2302,14 +2508,11 @@ public static implicit operator BigInteger(Int128 value) /// converted to a big integer. public static implicit operator BigInteger(nint value) { - if (Environment.Is64BitProcess) + if (value == nint.MinValue) { - return new BigInteger(value); - } - else - { - return new BigInteger((int)value); + return s_bnMinInt; } + return new BigInteger(value, null); } [CLSCompliant(false)] @@ -2342,39 +2545,53 @@ public static implicit operator BigInteger(ulong value) [CLSCompliant(false)] public static implicit operator BigInteger(UInt128 value) { - int sign = +1; - uint[]? bits; + nint sign = +1; + nuint[]? bits; - if (value <= (uint)int.MaxValue) + if (value <= (ulong)nint.MaxValue) { - sign = (int)value; + sign = (nint)(ulong)value; bits = null; } + else if (nint.Size == 8) + { + if (value <= ulong.MaxValue) + { + bits = new nuint[1]; + bits[0] = (nuint)(ulong)value; + } + else + { + bits = new nuint[2]; + bits[0] = (nuint)(ulong)value; + bits[1] = (nuint)(ulong)(value >> 64); + } + } else if (value <= uint.MaxValue) { - bits = new uint[1]; - bits[0] = (uint)(value >> (kcbitUint * 0)); + bits = new nuint[1]; + bits[0] = (nuint)(uint)(value >> (kcbitUint * 0)); } else if (value <= ulong.MaxValue) { - bits = new uint[2]; - bits[0] = (uint)(value >> (kcbitUint * 0)); - bits[1] = (uint)(value >> (kcbitUint * 1)); + bits = new nuint[2]; + bits[0] = (nuint)(uint)(value >> (kcbitUint * 0)); + bits[1] = (nuint)(uint)(value >> (kcbitUint * 1)); } else if (value <= new UInt128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) { - bits = new uint[3]; - bits[0] = (uint)(value >> (kcbitUint * 0)); - bits[1] = (uint)(value >> (kcbitUint * 1)); - bits[2] = (uint)(value >> (kcbitUint * 2)); + bits = new nuint[3]; + bits[0] = (nuint)(uint)(value >> (kcbitUint * 0)); + bits[1] = (nuint)(uint)(value >> (kcbitUint * 1)); + bits[2] = (nuint)(uint)(value >> (kcbitUint * 2)); } else { - bits = new uint[4]; - bits[0] = (uint)(value >> (kcbitUint * 0)); - bits[1] = (uint)(value >> (kcbitUint * 1)); - bits[2] = (uint)(value >> (kcbitUint * 2)); - bits[3] = (uint)(value >> (kcbitUint * 3)); + bits = new nuint[4]; + bits[0] = (nuint)(uint)(value >> (kcbitUint * 0)); + bits[1] = (nuint)(uint)(value >> (kcbitUint * 1)); + bits[2] = (nuint)(uint)(value >> (kcbitUint * 2)); + bits[3] = (nuint)(uint)(value >> (kcbitUint * 3)); } return new BigInteger(sign, bits); @@ -2386,13 +2603,13 @@ public static implicit operator BigInteger(UInt128 value) [CLSCompliant(false)] public static implicit operator BigInteger(nuint value) { - if (Environment.Is64BitProcess) + if (value <= (nuint)nint.MaxValue) { - return new BigInteger(value); + return new BigInteger((nint)value, null); } else { - return new BigInteger((uint)value); + return new BigInteger(+1, new nuint[] { value }); } } @@ -2408,46 +2625,46 @@ public static implicit operator BigInteger(nuint value) return left._sign & right._sign; } - uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; - uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; + nuint xExtend = (left._sign < 0) ? nuint.MaxValue : 0; + nuint yExtend = (right._sign < 0) ? nuint.MaxValue : 0; - uint[]? leftBufferFromPool = null; + nuint[]? 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); + Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); x = x.Slice(0, left.WriteTo(x)); - uint[]? rightBufferFromPool = null; + nuint[]? 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); + Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); y = y.Slice(0, right.WriteTo(y)); - uint[]? resultBufferFromPool = null; + nuint[]? 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); + Span z = (size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); for (int i = 0; i < z.Length; i++) { - uint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; - uint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; + nuint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; + nuint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; z[i] = xu & yu; } if (leftBufferFromPool != null) - ArrayPool.Shared.Return(leftBufferFromPool); + ArrayPool.Shared.Return(leftBufferFromPool); if (rightBufferFromPool != null) - ArrayPool.Shared.Return(rightBufferFromPool); + ArrayPool.Shared.Return(rightBufferFromPool); var result = new BigInteger(z); if (resultBufferFromPool != null) - ArrayPool.Shared.Return(resultBufferFromPool); + ArrayPool.Shared.Return(resultBufferFromPool); return result; } @@ -2464,46 +2681,46 @@ public static implicit operator BigInteger(nuint value) return left._sign | right._sign; } - uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; - uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; + nuint xExtend = (left._sign < 0) ? nuint.MaxValue : 0; + nuint yExtend = (right._sign < 0) ? nuint.MaxValue : 0; - uint[]? leftBufferFromPool = null; + nuint[]? 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); + Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); x = x.Slice(0, left.WriteTo(x)); - uint[]? rightBufferFromPool = null; + nuint[]? 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); + Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); y = y.Slice(0, right.WriteTo(y)); - uint[]? resultBufferFromPool = null; + nuint[]? 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); + Span z = (size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); for (int i = 0; i < z.Length; i++) { - uint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; - uint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; + nuint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; + nuint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; z[i] = xu | yu; } if (leftBufferFromPool != null) - ArrayPool.Shared.Return(leftBufferFromPool); + ArrayPool.Shared.Return(leftBufferFromPool); if (rightBufferFromPool != null) - ArrayPool.Shared.Return(rightBufferFromPool); + ArrayPool.Shared.Return(rightBufferFromPool); var result = new BigInteger(z); if (resultBufferFromPool != null) - ArrayPool.Shared.Return(resultBufferFromPool); + ArrayPool.Shared.Return(resultBufferFromPool); return result; } @@ -2515,46 +2732,46 @@ public static implicit operator BigInteger(nuint value) return left._sign ^ right._sign; } - uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; - uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; + nuint xExtend = (left._sign < 0) ? nuint.MaxValue : 0; + nuint yExtend = (right._sign < 0) ? nuint.MaxValue : 0; - uint[]? leftBufferFromPool = null; + nuint[]? 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); + Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); x = x.Slice(0, left.WriteTo(x)); - uint[]? rightBufferFromPool = null; + nuint[]? 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); + Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); y = y.Slice(0, right.WriteTo(y)); - uint[]? resultBufferFromPool = null; + nuint[]? 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); + Span z = (size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); for (int i = 0; i < z.Length; i++) { - uint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; - uint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; + nuint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; + nuint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; z[i] = xu ^ yu; } if (leftBufferFromPool != null) - ArrayPool.Shared.Return(leftBufferFromPool); + ArrayPool.Shared.Return(leftBufferFromPool); if (rightBufferFromPool != null) - ArrayPool.Shared.Return(rightBufferFromPool); + ArrayPool.Shared.Return(rightBufferFromPool); var result = new BigInteger(z); if (resultBufferFromPool != null) - ArrayPool.Shared.Return(resultBufferFromPool); + ArrayPool.Shared.Return(resultBufferFromPool); return result; } @@ -2570,75 +2787,75 @@ public static implicit operator BigInteger(nuint value) if (shift < 0) return value >> -shift; - (int digitShift, int smallShift) = Math.DivRem(shift, kcbitUint); + (int digitShift, int smallShift) = Math.DivRem(shift, BigIntegerCalculator.kcbitNuint); if (value._bits is null) return LeftShift(value._sign, digitShift, smallShift); - ReadOnlySpan bits = value._bits; + ReadOnlySpan bits = value._bits; Debug.Assert(bits.Length > 0); - uint over = smallShift == 0 + nuint over = smallShift == 0 ? 0 - : bits[^1] >> (kcbitUint - smallShift); + : bits[^1] >> (BigIntegerCalculator.kcbitNuint - smallShift); - uint[] z; + nuint[] z; int zLength = bits.Length + digitShift; if (over != 0) { - z = new uint[++zLength]; + z = new nuint[++zLength]; z[^1] = over; } else { - z = new uint[zLength]; + z = new nuint[zLength]; } - Span zd = z.AsSpan(digitShift, bits.Length); + Span zd = z.AsSpan(digitShift, bits.Length); bits.CopyTo(zd); - BigIntegerCalculator.LeftShiftSelf(zd, smallShift, out uint carry); + BigIntegerCalculator.LeftShiftSelf(zd, smallShift, out nuint carry); Debug.Assert(carry == over); Debug.Assert(z[^1] != 0); return new BigInteger(value._sign, z); } - private static BigInteger LeftShift(int value, int digitShift, int smallShift) + private static BigInteger LeftShift(nint value, int digitShift, int smallShift) { if (value == 0) return s_bnZeroInt; - uint m = NumericsHelpers.Abs(value); + nuint m = NumericsHelpers.Abs(value); - uint r = m << smallShift; - uint over = + nuint r = m << smallShift; + nuint over = smallShift == 0 ? 0 - : m >> (kcbitUint - smallShift); + : m >> (BigIntegerCalculator.kcbitNuint - smallShift); - uint[] rgu; + nuint[] rgu; if (over == 0) { - if (digitShift == 0 && r < kuMaskHighBit) + if (digitShift == 0 && (nint)r >= 0) return new BigInteger(value << smallShift, null); - rgu = new uint[digitShift + 1]; + rgu = new nuint[digitShift + 1]; } else { - rgu = new uint[digitShift + 2]; + rgu = new nuint[digitShift + 2]; rgu[^1] = over; } rgu[digitShift] = r; - return new BigInteger(Math.Sign(value), rgu); + return new BigInteger(value > 0 ? 1 : -1, rgu); } public static BigInteger operator >>(BigInteger value, int shift) @@ -2652,7 +2869,7 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) if (shift < 0) return value << -shift; - (int digitShift, int smallShift) = Math.DivRem(shift, kcbitUint); + (int digitShift, int smallShift) = Math.DivRem(shift, BigIntegerCalculator.kcbitNuint); if (value._bits is null) { @@ -2660,38 +2877,38 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) { // If the shift length exceeds the bit width, non-negative values result // in 0, and negative values result in -1. This behavior can be implemented - // using a 31-bit right shift on an int type. - smallShift = kcbitUint - 1; + // using a (kcbitNuint - 1)-bit right shift on an nint type. + smallShift = BigIntegerCalculator.kcbitNuint - 1; } return new BigInteger(value._sign >> smallShift, null); } - ReadOnlySpan bits = value._bits; + ReadOnlySpan bits = value._bits; Debug.Assert(bits.Length > 0); int zLength = bits.Length - digitShift + 1; if (zLength <= 1) - return new BigInteger(value._sign >> (kcbitUint - 1), null); + return new BigInteger(value._sign >> (BigIntegerCalculator.kcbitNuint - 1), null); - uint[]? zFromPool = null; - Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + nuint[]? zFromPool = null; + Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); zd[^1] = 0; bits.Slice(digitShift).CopyTo(zd); - BigIntegerCalculator.RightShiftSelf(zd, smallShift, out uint carry); + BigIntegerCalculator.RightShiftSelf(zd, smallShift, out nuint carry); bool neg = value._sign < 0; - if (neg && (carry != 0 || bits.Slice(0, digitShift).ContainsAnyExcept(0u))) + if (neg && (carry != 0 || bits.Slice(0, digitShift).ContainsAnyExcept((nuint)0))) { // Since right shift rounds towards zero, rounding up is performed // if the number is negative and the shifted-out bits are not all zeros. - int leastSignificant = zd.IndexOfAnyExcept(uint.MaxValue); + int leastSignificant = zd.IndexOfAnyExcept(nuint.MaxValue); Debug.Assert((uint)leastSignificant < (uint)zd.Length); ++zd[leastSignificant]; zd.Slice(0, leastSignificant).Clear(); @@ -2700,7 +2917,7 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) BigInteger result = new BigInteger(zd, neg); if (zFromPool != null) - ArrayPool.Shared.Return(zFromPool); + ArrayPool.Shared.Return(zFromPool); return result; } @@ -2738,7 +2955,11 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) right.AssertValid(); if (left._bits == null && right._bits == null) - return (long)left._sign + right._sign; + { + if (nint.Size == 4) + return (long)left._sign + right._sign; + return (BigInteger)((Int128)left._sign + right._sign); + } if (left._sign < 0 != right._sign < 0) return Subtract(left._bits, left._sign, right._bits, -1 * right._sign); @@ -2751,12 +2972,16 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) right.AssertValid(); if (left._bits == null && right._bits == null) - return (long)left._sign * right._sign; + { + if (nint.Size == 4) + return (long)left._sign * right._sign; + return (BigInteger)((Int128)left._sign * right._sign); + } return Multiply(left._bits, left._sign, right._bits, right._sign); } - private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOnlySpan right, int rightSign) + private static BigInteger Multiply(ReadOnlySpan left, nint leftSign, ReadOnlySpan right, nint rightSign) { bool trivialLeft = left.IsEmpty; bool trivialRight = right.IsEmpty; @@ -2764,16 +2989,16 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn Debug.Assert(!(trivialLeft && trivialRight), "Trivial cases should be handled on the caller operator"); BigInteger result; - uint[]? bitsFromPool = null; + nuint[]? bitsFromPool = null; if (trivialLeft) { Debug.Assert(!right.IsEmpty); int size = right.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[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)); @@ -2783,9 +3008,9 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn Debug.Assert(!left.IsEmpty); int size = left.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Multiply(left, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); @@ -2793,9 +3018,9 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn 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); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); bits.Clear(); BigIntegerCalculator.Square(left, bits); @@ -2806,9 +3031,9 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn 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); + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); bits.Clear(); BigIntegerCalculator.Multiply(left, right, bits); @@ -2816,7 +3041,7 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn } if (bitsFromPool != null) - ArrayPool.Shared.Return(bitsFromPool); + ArrayPool.Shared.Return(bitsFromPool); return result; } @@ -2841,16 +3066,16 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn return s_bnZeroInt; } - uint[]? quotientFromPool = null; + nuint[]? quotientFromPool = null; if (trivialDivisor) { Debug.Assert(dividend._bits != null); int size = dividend._bits.Length; - Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); try { @@ -2861,7 +3086,7 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn finally { if (quotientFromPool != null) - ArrayPool.Shared.Return(quotientFromPool); + ArrayPool.Shared.Return(quotientFromPool); } } @@ -2874,15 +3099,15 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn else { 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); + Span quotient = ((uint)size < BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[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); + ArrayPool.Shared.Return(quotientFromPool); return result; } @@ -2911,8 +3136,8 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn if (trivialDivisor) { Debug.Assert(dividend._bits != null); - uint remainder = BigIntegerCalculator.Remainder(dividend._bits, NumericsHelpers.Abs(divisor._sign)); - return dividend._sign < 0 ? -1 * remainder : remainder; + nuint remainder = BigIntegerCalculator.Remainder(dividend._bits, NumericsHelpers.Abs(divisor._sign)); + return dividend._sign < 0 ? -1 * (long)remainder : (long)remainder; } Debug.Assert(dividend._bits != null && divisor._bits != null); @@ -2922,17 +3147,17 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn return dividend; } - uint[]? bitsFromPool = null; + nuint[]? bitsFromPool = null; int size = dividend._bits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = (size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[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); + ArrayPool.Shared.Return(bitsFromPool); return result; } @@ -3107,15 +3332,15 @@ public long GetBitLength() { AssertValid(); - uint highValue; + nuint highValue; int bitsArrayLength; - int sign = _sign; - uint[]? bits = _bits; + nint sign = _sign; + nuint[]? bits = _bits; if (bits == null) { bitsArrayLength = 1; - highValue = (uint)(sign < 0 ? -sign : sign); + highValue = (nuint)(sign < 0 ? -sign : sign); } else { @@ -3123,7 +3348,8 @@ public long GetBitLength() highValue = bits[bitsArrayLength - 1]; } - long bitLength = bitsArrayLength * 32L - BitOperations.LeadingZeroCount(highValue); + long bitLength = (long)bitsArrayLength * BigIntegerCalculator.kcbitNuint - + (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)highValue) : BitOperations.LeadingZeroCount((uint)highValue)); if (sign >= 0) return bitLength; @@ -3157,7 +3383,7 @@ private void AssertValid() // _bits must contain at least 1 element or be null Debug.Assert(_bits.Length > 0); // Wasted space: _bits[0] could have been packed into _sign - Debug.Assert(_bits.Length > 1 || _bits[0] >= kuMaskHighBit); + Debug.Assert(_bits.Length > 1 || (nint)_bits[0] < 0); // Wasted space: leading zeros could have been truncated Debug.Assert(_bits[_bits.Length - 1] != 0); // Arrays larger than this can't fit into a Span @@ -3165,8 +3391,8 @@ private void AssertValid() } else { - // Int32.MinValue should not be stored in the _sign field - Debug.Assert(_sign > int.MinValue); + // nint.MinValue should not be stored in the _sign field + Debug.Assert(_sign > nint.MinValue); } } @@ -3195,13 +3421,17 @@ public static BigInteger LeadingZeroCount(BigInteger value) if (value._bits is null) { - return int.LeadingZeroCount(value._sign); + return nint.Size == 8 + ? long.LeadingZeroCount((long)value._sign) + : int.LeadingZeroCount((int)value._sign); } // When the value is positive, we just need to get the lzcnt of the most significant bits // Otherwise, we're negative and the most significant bit is always set. - return (value._sign >= 0) ? uint.LeadingZeroCount(value._bits[^1]) : 0; + return (value._sign >= 0) + ? (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)value._bits[^1]) : BitOperations.LeadingZeroCount((uint)value._bits[^1])) + : 0; } /// @@ -3211,7 +3441,9 @@ public static BigInteger PopCount(BigInteger value) if (value._bits is null) { - return int.PopCount(value._sign); + return nint.Size == 8 + ? long.PopCount((long)value._sign) + : int.PopCount((int)value._sign); } ulong result = 0; @@ -3222,8 +3454,10 @@ public static BigInteger PopCount(BigInteger value) for (int i = 0; i < value._bits.Length; i++) { - uint part = value._bits[i]; - result += uint.PopCount(part); + nuint part = value._bits[i]; + result += nint.Size == 8 + ? (ulong)BitOperations.PopCount((ulong)part) + : (ulong)BitOperations.PopCount((uint)part); } } else @@ -3232,14 +3466,16 @@ public static BigInteger PopCount(BigInteger value) // We'll do this "inline" to avoid needing to unnecessarily allocate. int i = 0; - uint part; + nuint part; do { // Simply process bits, adding the carry while the previous value is zero part = ~value._bits[i] + 1; - result += uint.PopCount(part); + result += nint.Size == 8 + ? (ulong)BitOperations.PopCount((ulong)part) + : (ulong)BitOperations.PopCount((uint)part); i++; } @@ -3250,7 +3486,9 @@ public static BigInteger PopCount(BigInteger value) // Then process the remaining bits only utilizing the one's complement part = ~value._bits[i]; - result += uint.PopCount(part); + result += nint.Size == 8 + ? (ulong)BitOperations.PopCount((ulong)part) + : (ulong)BitOperations.PopCount((uint)part); i++; } } @@ -3270,9 +3508,11 @@ public static BigInteger RotateLeft(BigInteger value, int rotateAmount) if (value._bits is null) { - uint rs = BitOperations.RotateLeft((uint)value._sign, rotateAmount); + nuint rs = nint.Size == 8 + ? (nuint)BitOperations.RotateLeft((ulong)(nuint)value._sign, rotateAmount) + : (nuint)BitOperations.RotateLeft((uint)value._sign, rotateAmount); return neg - ? new BigInteger((int)rs) + ? new BigInteger((nint)rs) : new BigInteger(rs); } @@ -3291,39 +3531,41 @@ public static BigInteger RotateRight(BigInteger value, int rotateAmount) if (value._bits is null) { - uint rs = BitOperations.RotateRight((uint)value._sign, rotateAmount); + nuint rs = nint.Size == 8 + ? (nuint)BitOperations.RotateRight((ulong)(nuint)value._sign, rotateAmount) + : (nuint)BitOperations.RotateRight((uint)value._sign, rotateAmount); return neg - ? new BigInteger((int)rs) + ? new BigInteger((nint)rs) : new BigInteger(rs); } return Rotate(value._bits, neg, -(long)rotateAmount); } - private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long rotateLeftAmount) + private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long rotateLeftAmount) { Debug.Assert(bits.Length > 0); Debug.Assert(Math.Abs(rotateLeftAmount) <= 0x80000000); int zLength = bits.Length; - int leadingZeroCount = negative ? bits.IndexOfAnyExcept(0u) : 0; + int leadingZeroCount = negative ? bits.IndexOfAnyExcept((nuint)0) : 0; - if (negative && bits[^1] >= kuMaskHighBit - && (leadingZeroCount != bits.Length - 1 || bits[^1] != kuMaskHighBit)) + if (negative && (nint)bits[^1] < 0 + && (leadingZeroCount != bits.Length - 1 || bits[^1] != ((nuint)1 << (BigIntegerCalculator.kcbitNuint - 1)))) { - // For a shift of N x 32 bit, - // We check for a special case where its sign bit could be outside the uint array after 2's complement conversion. - // For example given [0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF], its 2's complement is [0x01, 0x00, 0x00] - // 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 + // For a shift of N x kcbitNuint bit, + // We check for a special case where its sign bit could be outside the nuint array after 2's complement conversion. + // For example given [nuint.MaxValue, nuint.MaxValue, nuint.MaxValue], its 2's complement is [0x01, 0x00, 0x00] + // After a kcbitNuint bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back. + // The expected result is [0x00, 0x00, nuint.MaxValue] (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 ++zLength; } - uint[]? zFromPool = null; - Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + nuint[]? zFromPool = null; + Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); zd[^1] = 0; bits.CopyTo(zd); @@ -3334,13 +3576,13 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long ro // Same as NumericsHelpers.DangerousMakeTwosComplement(zd); // Leading zero count is already calculated. - zd[leadingZeroCount] = (uint)(-(int)zd[leadingZeroCount]); + zd[leadingZeroCount] = (nuint)(-(nint)zd[leadingZeroCount]); NumericsHelpers.DangerousMakeOnesComplement(zd.Slice(leadingZeroCount + 1)); } BigIntegerCalculator.RotateLeft(zd, rotateLeftAmount); - if (negative && (int)zd[^1] < 0) + if (negative && (nint)zd[^1] < 0) { NumericsHelpers.DangerousMakeTwosComplement(zd); } @@ -3352,7 +3594,7 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long ro var result = new BigInteger(zd, negative); if (zFromPool != null) - ArrayPool.Shared.Return(zFromPool); + ArrayPool.Shared.Return(zFromPool); return result; } @@ -3364,7 +3606,9 @@ public static BigInteger TrailingZeroCount(BigInteger value) if (value._bits is null) { - return int.TrailingZeroCount(value._sign); + return nint.Size == 8 + ? long.TrailingZeroCount((long)value._sign) + : int.TrailingZeroCount((int)value._sign); } ulong result = 0; @@ -3372,15 +3616,17 @@ public static BigInteger TrailingZeroCount(BigInteger value) // Both positive values and their two's-complement negative representation will share the same TrailingZeroCount, // so the sign of value does not matter and both cases can be handled in the same way - uint part = value._bits[0]; + nuint part = value._bits[0]; for (int i = 1; (part == 0) && (i < value._bits.Length); i++) { part = value._bits[i]; - result += (sizeof(uint) * 8); + result += (uint)(nint.Size * 8); } - result += uint.TrailingZeroCount(part); + result += nint.Size == 8 + ? (ulong)BitOperations.TrailingZeroCount((ulong)part) + : (ulong)BitOperations.TrailingZeroCount((uint)part); return result; } @@ -3403,31 +3649,31 @@ static bool IBinaryInteger.TryReadLittleEndian(ReadOnlySpan so int IBinaryInteger.GetShortestBitLength() { AssertValid(); - uint[]? bits = _bits; + nuint[]? bits = _bits; if (bits is null) { - int value = _sign; + nint value = _sign; if (value >= 0) { - return (sizeof(int) * 8) - BitOperations.LeadingZeroCount((uint)(value)); + return (nint.Size * 8) - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)(nuint)value) : BitOperations.LeadingZeroCount((uint)(nuint)value)); } else { - return (sizeof(int) * 8) + 1 - BitOperations.LeadingZeroCount((uint)(~value)); + return (nint.Size * 8) + 1 - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)(nuint)~value) : BitOperations.LeadingZeroCount((uint)(nuint)~value)); } } - int result = (bits.Length - 1) * 32; + int result = (bits.Length - 1) * BigIntegerCalculator.kcbitNuint; if (_sign >= 0) { - result += (sizeof(uint) * 8) - BitOperations.LeadingZeroCount(bits[^1]); + result += (nint.Size * 8) - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)bits[^1]) : BitOperations.LeadingZeroCount((uint)bits[^1])); } else { - uint part = ~bits[^1] + 1; + nuint part = ~bits[^1] + 1; // We need to remove the "carry" (the +1) if any of the initial // bytes are not zero. This ensures we get the correct two's complement @@ -3442,7 +3688,7 @@ int IBinaryInteger.GetShortestBitLength() } } - result += (sizeof(uint) * 8) + 1 - BitOperations.LeadingZeroCount(~part); + result += (nint.Size * 8) + 1 - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)~part) : BitOperations.LeadingZeroCount((uint)~part)); } return result; @@ -3455,7 +3701,7 @@ int IBinaryInteger.GetShortestBitLength() bool IBinaryInteger.TryWriteBigEndian(Span destination, out int bytesWritten) { AssertValid(); - uint[]? bits = _bits; + nuint[]? bits = _bits; int byteCount = GetGenericMathByteCount(); @@ -3463,7 +3709,14 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in { if (bits is null) { - int value = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(_sign) : _sign; + nint value = _sign; + if (BitConverter.IsLittleEndian) + { + if (nint.Size == 8) + value = (nint)BinaryPrimitives.ReverseEndianness((long)value); + else + value = (nint)BinaryPrimitives.ReverseEndianness((int)value); + } Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value); } else if (_sign >= 0) @@ -3471,19 +3724,22 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in // When the value is positive, we simply need to copy all bits as big endian ref byte startAddress = ref MemoryMarshal.GetReference(destination); - ref byte address = ref Unsafe.Add(ref startAddress, (bits.Length - 1) * sizeof(uint)); + ref byte address = ref Unsafe.Add(ref startAddress, (bits.Length - 1) * nint.Size); for (int i = 0; i < bits.Length; i++) { - uint part = bits[i]; + nuint part = bits[i]; if (BitConverter.IsLittleEndian) { - part = BinaryPrimitives.ReverseEndianness(part); + if (nint.Size == 8) + part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); + else + part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); } Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Subtract(ref address, sizeof(uint)); + address = ref Unsafe.Subtract(ref address, nint.Size); } } else @@ -3492,10 +3748,10 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in // We'll do this "inline" to avoid needing to unnecessarily allocate. ref byte startAddress = ref MemoryMarshal.GetReference(destination); - ref byte address = ref Unsafe.Add(ref startAddress, byteCount - sizeof(uint)); + ref byte address = ref Unsafe.Add(ref startAddress, byteCount - nint.Size); int i = 0; - uint part; + nuint part; do { @@ -3504,11 +3760,14 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in if (BitConverter.IsLittleEndian) { - part = BinaryPrimitives.ReverseEndianness(part); + if (nint.Size == 8) + part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); + else + part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); } Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Subtract(ref address, sizeof(uint)); + address = ref Unsafe.Subtract(ref address, nint.Size); i++; } @@ -3521,11 +3780,14 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in if (BitConverter.IsLittleEndian) { - part = BinaryPrimitives.ReverseEndianness(part); + if (nint.Size == 8) + part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); + else + part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); } Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Subtract(ref address, sizeof(uint)); + address = ref Unsafe.Subtract(ref address, nint.Size); i++; } @@ -3534,12 +3796,12 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in { // We need one extra part to represent the sign as the most // significant bit of the two's complement value was 0. - Unsafe.WriteUnaligned(ref address, uint.MaxValue); + Unsafe.WriteUnaligned(ref address, nuint.MaxValue); } else { // Otherwise we should have been precisely one part behind address - Debug.Assert(Unsafe.AreSame(ref startAddress, ref Unsafe.Add(ref address, sizeof(uint)))); + Debug.Assert(Unsafe.AreSame(ref startAddress, ref Unsafe.Add(ref address, nint.Size))); } } @@ -3557,7 +3819,7 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in bool IBinaryInteger.TryWriteLittleEndian(Span destination, out int bytesWritten) { AssertValid(); - uint[]? bits = _bits; + nuint[]? bits = _bits; int byteCount = GetGenericMathByteCount(); @@ -3565,7 +3827,14 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out { if (bits is null) { - int value = BitConverter.IsLittleEndian ? _sign : BinaryPrimitives.ReverseEndianness(_sign); + nint value = _sign; + if (!BitConverter.IsLittleEndian) + { + if (nint.Size == 8) + value = (nint)BinaryPrimitives.ReverseEndianness((long)value); + else + value = (nint)BinaryPrimitives.ReverseEndianness((int)value); + } Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value); } else if (_sign >= 0) @@ -3576,15 +3845,18 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out for (int i = 0; i < bits.Length; i++) { - uint part = bits[i]; + nuint part = bits[i]; if (!BitConverter.IsLittleEndian) { - part = BinaryPrimitives.ReverseEndianness(part); + if (nint.Size == 8) + part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); + else + part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); } Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, sizeof(uint)); + address = ref Unsafe.Add(ref address, nint.Size); } } else @@ -3593,10 +3865,10 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out // We'll do this "inline" to avoid needing to unnecessarily allocate. ref byte address = ref MemoryMarshal.GetReference(destination); - ref byte lastAddress = ref Unsafe.Add(ref address, byteCount - sizeof(uint)); + ref byte lastAddress = ref Unsafe.Add(ref address, byteCount - nint.Size); int i = 0; - uint part; + nuint part; do { @@ -3605,11 +3877,14 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out if (!BitConverter.IsLittleEndian) { - part = BinaryPrimitives.ReverseEndianness(part); + if (nint.Size == 8) + part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); + else + part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); } Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, sizeof(uint)); + address = ref Unsafe.Add(ref address, nint.Size); i++; } @@ -3622,11 +3897,14 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out if (!BitConverter.IsLittleEndian) { - part = BinaryPrimitives.ReverseEndianness(part); + if (nint.Size == 8) + part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); + else + part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); } Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, sizeof(uint)); + address = ref Unsafe.Add(ref address, nint.Size); i++; } @@ -3635,12 +3913,12 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out { // We need one extra part to represent the sign as the most // significant bit of the two's complement value was 0. - Unsafe.WriteUnaligned(ref address, uint.MaxValue); + Unsafe.WriteUnaligned(ref address, nuint.MaxValue); } else { // Otherwise we should have been precisely one part ahead address - Debug.Assert(Unsafe.AreSame(ref lastAddress, ref Unsafe.Subtract(ref address, sizeof(uint)))); + Debug.Assert(Unsafe.AreSame(ref lastAddress, ref Unsafe.Subtract(ref address, nint.Size))); } } @@ -3657,18 +3935,18 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out private int GetGenericMathByteCount() { AssertValid(); - uint[]? bits = _bits; + nuint[]? bits = _bits; if (bits is null) { - return sizeof(int); + return nint.Size; } - int result = bits.Length * 4; + int result = bits.Length * nint.Size; if (_sign < 0) { - uint part = ~bits[^1] + 1; + nuint part = ~bits[^1] + 1; // We need to remove the "carry" (the +1) if any of the initial // bytes are not zero. This ensures we get the correct two's complement @@ -3683,11 +3961,11 @@ private int GetGenericMathByteCount() } } - if ((int)part >= 0) + if ((nint)part >= 0) { // When the most significant bit of the part is zero // we need another part to represent the value. - result += sizeof(uint); + result += nint.Size; } } @@ -3716,10 +3994,16 @@ public static BigInteger Log2(BigInteger value) if (value._bits is null) { - return 31 ^ uint.LeadingZeroCount((uint)(value._sign | 1)); + return (BigIntegerCalculator.kcbitNuint - 1) ^ + (nint.Size == 8 + ? BitOperations.LeadingZeroCount((ulong)((nuint)value._sign | 1)) + : BitOperations.LeadingZeroCount((uint)((nuint)value._sign | 1))); } - return ((value._bits.Length * 32) - 1) ^ uint.LeadingZeroCount(value._bits[^1]); + return ((long)value._bits.Length * BigIntegerCalculator.kcbitNuint - 1) ^ + (nint.Size == 8 + ? BitOperations.LeadingZeroCount((ulong)value._bits[^1]) + : BitOperations.LeadingZeroCount((uint)value._bits[^1])); } // @@ -3770,14 +4054,14 @@ public static BigInteger CopySign(BigInteger value, BigInteger sign) value.AssertValid(); sign.AssertValid(); - int currentSign = value._sign; + nint currentSign = value._sign; if (value._bits is null) { currentSign = (currentSign >= 0) ? 1 : -1; } - int targetSign = sign._sign; + nint targetSign = sign._sign; if (sign._bits is null) { @@ -3800,10 +4084,10 @@ static int INumber.Sign(BigInteger value) if (value._bits is null) { - return int.Sign(value._sign); + return value._sign > 0 ? 1 : (value._sign < 0 ? -1 : 0); } - return value._sign; + return (int)value._sign; } // @@ -4683,7 +4967,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits is not null) { - uint bits = value._bits[0]; + nuint bits = value._bits[0]; if (IsNegative(value)) { @@ -4706,7 +4990,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits is not null) { - uint bits = value._bits[0]; + nuint bits = value._bits[0]; if (IsNegative(value)) { @@ -4774,7 +5058,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - actualResult = value._sign; + actualResult = (int)value._sign; } result = (TOther)(object)actualResult; @@ -4786,15 +5070,24 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits is not null) { - ulong bits = 0; + ulong bits; - if (value._bits.Length >= 2) + if (nint.Size == 8) { - bits = value._bits[1]; - bits <<= 32; + bits = (ulong)value._bits[0]; } + else + { + bits = 0; - bits |= value._bits[0]; + if (value._bits.Length >= 2) + { + bits = (ulong)value._bits[1]; + bits <<= 32; + } + + bits |= (ulong)value._bits[0]; + } if (IsNegative(value)) { @@ -4805,7 +5098,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - actualResult = value._sign; + actualResult = (long)value._sign; } result = (TOther)(object)actualResult; @@ -4820,24 +5113,36 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va ulong lowerBits = 0; ulong upperBits = 0; - if (value._bits.Length >= 4) + if (nint.Size == 8) { - upperBits = value._bits[3]; - upperBits <<= 32; - } + lowerBits = (ulong)value._bits[0]; - if (value._bits.Length >= 3) - { - upperBits |= value._bits[2]; + if (value._bits.Length >= 2) + { + upperBits = (ulong)value._bits[1]; + } } - - if (value._bits.Length >= 2) + else { - lowerBits = value._bits[1]; - lowerBits <<= 32; - } + if (value._bits.Length >= 4) + { + upperBits = (ulong)value._bits[3]; + upperBits <<= 32; + } - lowerBits |= value._bits[0]; + if (value._bits.Length >= 3) + { + upperBits |= (ulong)value._bits[2]; + } + + if (value._bits.Length >= 2) + { + lowerBits = (ulong)value._bits[1]; + lowerBits <<= 32; + } + + lowerBits |= (ulong)value._bits[0]; + } UInt128 bits = new UInt128(upperBits, lowerBits); @@ -4850,7 +5155,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - actualResult = value._sign; + actualResult = (Int128)(long)value._sign; } result = (TOther)(object)actualResult; @@ -4862,15 +5167,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits is not null) { - nuint bits = 0; - - if (Environment.Is64BitProcess && (value._bits.Length >= 2)) - { - bits = value._bits[1]; - bits <<= 32; - } - - bits |= value._bits[0]; + nuint bits = value._bits[0]; if (IsNegative(value)) { @@ -4921,7 +5218,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits is not null) { - uint bits = value._bits[0]; + nuint bits = value._bits[0]; if (IsNegative(value)) { @@ -4944,7 +5241,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits is not null) { - uint bits = value._bits[0]; + uint bits = (uint)value._bits[0]; if (IsNegative(value)) { @@ -4967,15 +5264,24 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits is not null) { - ulong bits = 0; + ulong bits; - if (value._bits.Length >= 2) + if (nint.Size == 8) { - bits = value._bits[1]; - bits <<= 32; + bits = (ulong)value._bits[0]; } + else + { + bits = 0; - bits |= value._bits[0]; + if (value._bits.Length >= 2) + { + bits = (ulong)value._bits[1]; + bits <<= 32; + } + + bits |= (ulong)value._bits[0]; + } if (IsNegative(value)) { @@ -5001,24 +5307,36 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va ulong lowerBits = 0; ulong upperBits = 0; - if (value._bits.Length >= 4) + if (nint.Size == 8) { - upperBits = value._bits[3]; - upperBits <<= 32; - } + lowerBits = (ulong)value._bits[0]; - if (value._bits.Length >= 3) - { - upperBits |= value._bits[2]; + if (value._bits.Length >= 2) + { + upperBits = (ulong)value._bits[1]; + } } - - if (value._bits.Length >= 2) + else { - lowerBits = value._bits[1]; - lowerBits <<= 32; - } + if (value._bits.Length >= 4) + { + upperBits = (ulong)value._bits[3]; + upperBits <<= 32; + } + + if (value._bits.Length >= 3) + { + upperBits |= (ulong)value._bits[2]; + } + + if (value._bits.Length >= 2) + { + lowerBits = (ulong)value._bits[1]; + lowerBits <<= 32; + } - lowerBits |= value._bits[0]; + lowerBits |= (ulong)value._bits[0]; + } UInt128 bits = new UInt128(upperBits, lowerBits); @@ -5031,7 +5349,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - actualResult = (UInt128)value._sign; + actualResult = (UInt128)(Int128)(long)value._sign; } result = (TOther)(object)actualResult; @@ -5043,15 +5361,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits is not null) { - nuint bits = 0; - - if (Environment.Is64BitProcess && (value._bits.Length >= 2)) - { - bits = value._bits[1]; - bits <<= 32; - } - - bits |= value._bits[0]; + nuint bits = value._bits[0]; if (IsNegative(value)) { @@ -5100,7 +5410,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (shiftAmount < 0) return value << -shiftAmount; - (int digitShift, int smallShift) = Math.DivRem(shiftAmount, kcbitUint); + (int digitShift, int smallShift) = Math.DivRem(shiftAmount, BigIntegerCalculator.kcbitNuint); if (value._bits is null) { @@ -5112,7 +5422,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va return new BigInteger(value._sign >>> smallShift, null); } - ReadOnlySpan bits = value._bits; + ReadOnlySpan bits = value._bits; Debug.Assert(bits.Length > 0); @@ -5124,32 +5434,33 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } bool neg = value._sign < 0; - int negLeadingZeroCount = neg ? bits.IndexOfAnyExcept(0u) : 0; + int negLeadingZeroCount = neg ? bits.IndexOfAnyExcept((nuint)0) : 0; Debug.Assert(negLeadingZeroCount >= 0); - if (neg && bits[^1] >= kuMaskHighBit) + if (neg && (nint)bits[^1] < 0) { - // For a shift of N x 32 bit, - // We check for a special case where its sign bit could be outside the uint array after 2's complement conversion. - // For example given [0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF], its 2's complement is [0x01, 0x00, 0x00] - // 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 + // For a shift of N x kcbitNuint bit, + // We check for a special case where its sign bit could be outside the nuint array after 2's complement conversion. + // For example given [nuint.MaxValue, nuint.MaxValue, nuint.MaxValue], its 2's complement is [0x01, 0x00, 0x00] + // After a kcbitNuint bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back. + // The expected result is [0x00, 0x00, nuint.MaxValue] (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 ++zLength; - if (bits[^1] == kuMaskHighBit && negLeadingZeroCount == bits.Length - 1) + nuint signBit = (nuint)1 << (BigIntegerCalculator.kcbitNuint - 1); + if (bits[^1] == signBit && negLeadingZeroCount == bits.Length - 1) { - // When bits are [0, ..., 0, 0x80000000], special handling is required. + // When bits are [0, ..., 0, signBit], special handling is required. // Since the bit length remains unchanged in two's complement, the result must be computed directly. --zLength; if (zLength <= 0) return s_bnMinusOneInt; if (zLength == 1) - return new BigInteger(int.MinValue >>> smallShift); + return new BigInteger(nint.MinValue >>> smallShift); - uint[] rgu = new uint[zLength]; - rgu[^1] = kuMaskHighBit >>> smallShift; + nuint[] rgu = new nuint[zLength]; + rgu[^1] = signBit >>> smallShift; return new BigInteger(smallShift == 0 ? -1 : +1, rgu); } } @@ -5158,10 +5469,10 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va goto Excess; } - uint[]? zFromPool = null; - Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + nuint[]? zFromPool = null; + Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); zd[^1] = 0; bits.Slice(digitShift).CopyTo(zd); @@ -5175,7 +5486,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va { // negLeadingZeroCount >= zd.Length should never be true, so this can be rewritten // as the case where the least significant nonzero bit is included in zd. - zd[negLeadingZeroCount] = (uint)(-(int)zd[negLeadingZeroCount]); + zd[negLeadingZeroCount] = (nuint)(-(nint)zd[negLeadingZeroCount]); NumericsHelpers.DangerousMakeOnesComplement(zd.Slice(negLeadingZeroCount + 1)); } else @@ -5186,14 +5497,14 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } BigIntegerCalculator.RightShiftSelf(zd, smallShift, out _); - zd = zd.TrimEnd(0u); + zd = zd.TrimEnd((nuint)0); BigInteger result; if (zd.IsEmpty) { result = neg ? s_bnMinusOneInt : default; } - else if (neg && (int)zd[^1] < 0) + else if (neg && (nint)zd[^1] < 0) { NumericsHelpers.DangerousMakeTwosComplement(zd); result = new BigInteger(zd, true); @@ -5204,12 +5515,12 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } if (zFromPool != null) - ArrayPool.Shared.Return(zFromPool); + ArrayPool.Shared.Return(zFromPool); return result; Excess: // Return -1 if the value is negative; otherwise, return 0. - return new BigInteger(value._sign >> (kcbitUint - 1), null); + return new BigInteger(value._sign >> (BigIntegerCalculator.kcbitNuint - 1), null); } // From 95f6d3c91f477c8490a738518ec0178a0d20d240 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Mar 2026 17:22:46 -0400 Subject: [PATCH 02/27] Convert BigInteger internal representation from uint to nuint limbs Change the internal limb type of BigInteger from uint (32-bit) to nuint (native-width), so that on 64-bit platforms each limb holds a full 64-bit value. This halves loop iterations for all arithmetic operations on 64-bit systems while remaining identical on 32-bit (nuint == uint). Key changes: - Struct fields: int _sign -> nint _sign, uint[]? _bits -> nuint[]? _bits - All BigIntegerCalculator methods: ReadOnlySpan -> ReadOnlySpan - Widening multiply uses Math.BigMul(ulong,ulong)->UInt128 on 64-bit - Squaring carry propagation uses UInt128 to prevent overflow on 64-bit - PowersOf1e9 constructor strips trailing zero limbs after squaring - GCD 2-limb short-circuit restricted to 32-bit only (nint.Size == 4) - explicit operator decimal: overflow check for high limb on 64-bit Test updates for platform-dependent behavior: - Rotation tests: ring size is now nint.Size*8 bits per limb - GenericMath tests: GetByteCount, PopCount, LeadingZeroCount, TryWrite, UnsignedRightShift all reflect nuint-width inline values - DebuggerDisplay tests: 4 nuint limbs covers larger values on 64-bit - MyBigInt reference implementation: alignment changed to nint.Size All 2647 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.BigInteger.cs | 16 +- .../src/System/Numerics/BigInteger.cs | 15 +- .../Numerics/BigIntegerCalculator.AddSub.cs | 141 ++--- .../Numerics/BigIntegerCalculator.DivRem.cs | 331 +++++----- .../BigIntegerCalculator.FastReducer.cs | 16 +- .../Numerics/BigIntegerCalculator.GcdInv.cs | 253 +++++--- .../Numerics/BigIntegerCalculator.PowMod.cs | 319 +++++----- .../Numerics/BigIntegerCalculator.ShiftRot.cs | 128 ++-- .../Numerics/BigIntegerCalculator.SquMul.cs | 574 ++++++++++-------- .../Numerics/BigIntegerCalculator.Utils.cs | 132 +++- .../src/System/Numerics/NumericsHelpers.cs | 41 +- .../tests/BigInteger/DebuggerDisplayTests.cs | 55 +- .../tests/BigInteger/MyBigInt.cs | 6 +- .../tests/BigInteger/Rotate.cs | 502 ++++++++------- .../tests/BigInteger/op_rightshift.cs | 33 +- .../tests/BigIntegerTests.GenericMath.cs | 84 +-- 16 files changed, 1492 insertions(+), 1154 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 404a73dba6aa47..d1a04456908fa6 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -1105,13 +1105,11 @@ static void Naive(ReadOnlySpan bits, Span base1E9Buffer, out int b nuint hi = base1E9[iuDst]; uCarry = BigIntegerCalculator.DivRem(hi, uCarry, PowersOf1e9.TenPowMaxPartial, out base1E9[iuDst]); } - if (uCarry != 0) + while (uCarry != 0) { (nuint quo, nuint rem) = Math.DivRem(uCarry, PowersOf1e9.TenPowMaxPartial); base1E9Buffer[base1E9Written++] = rem; uCarry = quo; - if (uCarry != 0) - base1E9Buffer[base1E9Written++] = uCarry; } } } @@ -1329,6 +1327,18 @@ public PowersOf1e9(Span pow1E9) break; Span dst = pow1E9.Slice(toExclusive, src.Length << 1); BigIntegerCalculator.Square(src, dst); + + // When 9*(1<<(i-1)) is not evenly divisible by kcbitNuint, the stored + // power at index i-1 carries a residual factor of 2^r. Squaring doubles + // that residual; if 2r >= kcbitNuint the result has extra trailing zero + // limbs that must be stripped to yield the correct stored representation. + int shift = OmittedLength(i) - 2 * OmittedLength(i - 1); + if (shift > 0) + { + dst.Slice(shift).CopyTo(dst); + dst.Slice(dst.Length - shift).Clear(); + } + int from = toExclusive; toExclusive = Indexes[i + 1]; src = pow1E9.Slice(from, toExclusive - from); 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 b351003b07fde9..1ace0ab8286eee 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -313,6 +313,13 @@ public BigInteger(decimal value) } _sign = ((bits[3] & signMask) != 0) ? -1 : +1; + + // Canonicalize: single-limb values that fit in nint should be stored inline + if (_bits is { Length: 1 } && (nint)_bits[0] >= 0) + { + _sign = _sign < 0 ? -(nint)_bits[0] : (nint)_bits[0]; + _bits = null; + } } AssertValid(); } @@ -1029,7 +1036,7 @@ private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, Re nuint temp = BigIntegerCalculator.Remainder(leftBits, rightBits[0]); result = BigIntegerCalculator.Gcd(rightBits[0], temp); } - else if (rightBits.Length == 2) + else if (nint.Size == 4 && rightBits.Length == 2) { Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] @@ -1997,7 +2004,11 @@ public static explicit operator decimal(BigInteger value) lo = (int)(uint)value._bits[0]; mi = (int)(uint)(value._bits[0] >> 32); if (length > 1) + { + if (value._bits[1] > uint.MaxValue) + throw new OverflowException(SR.Overflow_Decimal); hi = (int)(uint)value._bits[1]; + } } } else @@ -2050,7 +2061,7 @@ public static explicit operator double(BigInteger value) m = length > 1 ? (ulong)bits[length - 2] : 0; z = BitOperations.LeadingZeroCount(h); - exp = (length - 2) * 64 - z; + exp = (length - 1) * 64 - z; man = z == 0 ? h : (h << z) | (m >> (64 - z)); } else 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 c088f325321982..163eb004ce51a3 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 @@ -11,12 +11,12 @@ internal static partial class BigIntegerCalculator { private const int CopyToThreshold = 8; - private static void CopyTail(ReadOnlySpan source, Span dest, int start) + private static void CopyTail(ReadOnlySpan source, Span dest, int start) { source.Slice(start).CopyTo(dest.Slice(start)); } - public static void Add(ReadOnlySpan left, uint right, Span bits) + public static void Add(ReadOnlySpan left, nuint right, Span bits) { Debug.Assert(left.Length >= 1); Debug.Assert(bits.Length == left.Length + 1); @@ -24,7 +24,7 @@ public static void Add(ReadOnlySpan left, uint right, Span bits) Add(left, bits, ref MemoryMarshal.GetReference(bits), startIndex: 0, initialCarry: right); } - public static void Add(ReadOnlySpan left, ReadOnlySpan right, Span bits) + public static void Add(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(right.Length >= 1); Debug.Assert(left.Length >= right.Length); @@ -32,40 +32,40 @@ public static void Add(ReadOnlySpan left, ReadOnlySpan right, Span>= 32; + Unsafe.Add(ref resultPtr, i) = AddWithCarry( + Unsafe.Add(ref leftPtr, i), + Unsafe.Add(ref rightPtr, i), + carry, out carry); i++; } while (i < right.Length); Add(left, bits, ref resultPtr, startIndex: i, initialCarry: carry); } - public static void AddSelf(Span left, ReadOnlySpan right) + public static void AddSelf(Span left, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); int i = 0; - long carry = 0L; + nuint carry = 0; // Switching to managed references helps eliminating // index bounds check... - ref uint leftPtr = ref MemoryMarshal.GetReference(left); + ref nuint 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 @@ -73,30 +73,29 @@ public static void AddSelf(Span left, ReadOnlySpan right) for (; i < right.Length; i++) { - long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i]; - Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit); - carry = digit >> 32; + Unsafe.Add(ref leftPtr, i) = AddWithCarry( + Unsafe.Add(ref leftPtr, i), right[i], carry, out carry); } for (; carry != 0 && i < left.Length; i++) { - long digit = left[i] + carry; - left[i] = (uint)digit; - carry = digit >> 32; + nuint sum = left[i] + carry; + carry = (sum < carry) ? (nuint)1 : (nuint)0; + left[i] = sum; } Debug.Assert(carry == 0); } - public static void Subtract(ReadOnlySpan left, uint right, Span bits) + public static void Subtract(ReadOnlySpan left, nuint right, Span bits) { Debug.Assert(left.Length >= 1); Debug.Assert(left[0] >= right || left.Length >= 2); Debug.Assert(bits.Length == left.Length); - Subtract(left, bits, ref MemoryMarshal.GetReference(bits), startIndex: 0, initialCarry: -right); + Subtract(left, bits, ref MemoryMarshal.GetReference(bits), startIndex: 0, initialBorrow: right); } - public static void Subtract(ReadOnlySpan left, ReadOnlySpan right, Span bits) + public static void Subtract(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(right.Length >= 1); Debug.Assert(left.Length >= right.Length); @@ -105,31 +104,28 @@ public static void Subtract(ReadOnlySpan left, ReadOnlySpan right, S // Switching to managed references helps eliminating // index bounds check for all buffers. - ref uint resultPtr = ref MemoryMarshal.GetReference(bits); - ref uint rightPtr = ref MemoryMarshal.GetReference(right); - ref uint leftPtr = ref MemoryMarshal.GetReference(left); + ref nuint resultPtr = ref MemoryMarshal.GetReference(bits); + ref nuint rightPtr = ref MemoryMarshal.GetReference(right); + ref nuint leftPtr = ref MemoryMarshal.GetReference(left); int i = 0; - long carry = 0; + nuint borrow = 0; - // 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. + // Executes the "grammar-school" algorithm for computing z = a - b. do { - carry += Unsafe.Add(ref leftPtr, i); - carry -= Unsafe.Add(ref rightPtr, i); - Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry); - carry >>= 32; + Unsafe.Add(ref resultPtr, i) = SubWithBorrow( + Unsafe.Add(ref leftPtr, i), + Unsafe.Add(ref rightPtr, i), + borrow, out borrow); i++; } while (i < right.Length); - Subtract(left, bits, ref resultPtr, startIndex: i, initialCarry: carry); + Subtract(left, bits, ref resultPtr, startIndex: i, initialBorrow: borrow); } - public static void SubtractSelf(Span left, ReadOnlySpan right) + public static void SubtractSelf(Span left, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); @@ -137,11 +133,11 @@ public static void SubtractSelf(Span left, ReadOnlySpan right) // Debug.Assert(CompareActual(left, right) >= 0); int i = 0; - long carry = 0L; + nuint borrow = 0; // Switching to managed references helps eliminating // index bounds check... - ref uint leftPtr = ref MemoryMarshal.GetReference(left); + ref nuint 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 @@ -149,50 +145,47 @@ public static void SubtractSelf(Span left, ReadOnlySpan right) for (; i < right.Length; i++) { - long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i]; - Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit); - carry = digit >> 32; + Unsafe.Add(ref leftPtr, i) = SubWithBorrow( + Unsafe.Add(ref leftPtr, i), right[i], borrow, out borrow); } - for (; carry != 0 && i < left.Length; i++) + for (; borrow != 0 && i < left.Length; i++) { - long digit = left[i] + carry; - left[i] = (uint)digit; - carry = digit >> 32; + nuint val = left[i]; + left[i] = val - borrow; + borrow = (val < borrow) ? (nuint)1 : (nuint)0; } // Assertion failing per https://github.com/dotnet/runtime/issues/97780 - //Debug.Assert(carry == 0); + //Debug.Assert(borrow == 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Add(ReadOnlySpan left, Span bits, ref uint resultPtr, int startIndex, long initialCarry) + private static void Add(ReadOnlySpan left, Span bits, ref nuint resultPtr, int startIndex, nuint initialCarry) { - // 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. + // Executes the addition for one big and one single-limb integer. int i = startIndex; - long carry = initialCarry; + nuint carry = initialCarry; if (left.Length <= CopyToThreshold) { for (; i < left.Length; i++) { - carry += left[i]; - Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry); - carry >>= 32; + nuint sum = left[i] + carry; + carry = (sum < carry) ? (nuint)1 : (nuint)0; + Unsafe.Add(ref resultPtr, i) = sum; } - Unsafe.Add(ref resultPtr, left.Length) = unchecked((uint)carry); + Unsafe.Add(ref resultPtr, left.Length) = carry; } else { for (; i < left.Length;) { - carry += left[i]; - Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry); + nuint sum = left[i] + carry; + carry = (sum < carry) ? (nuint)1 : (nuint)0; + Unsafe.Add(ref resultPtr, i) = sum; i++; - carry >>= 32; // Once carry is set to 0 it can not be 1 anymore. // So the tail of the loop is just the movement of argument values to result span. @@ -202,7 +195,7 @@ private static void Add(ReadOnlySpan left, Span bits, ref uint resul } } - Unsafe.Add(ref resultPtr, left.Length) = unchecked((uint)carry); + Unsafe.Add(ref resultPtr, left.Length) = carry; if (i < left.Length) { @@ -212,36 +205,36 @@ private static void Add(ReadOnlySpan left, Span bits, ref uint resul } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Subtract(ReadOnlySpan left, Span bits, ref uint resultPtr, int startIndex, long initialCarry) + private static void Subtract(ReadOnlySpan left, Span bits, ref nuint resultPtr, int startIndex, nuint initialBorrow) { - // 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. + // Executes the subtraction for one big and one single-limb integer. int i = startIndex; - long carry = initialCarry; + nuint borrow = initialBorrow; if (left.Length <= CopyToThreshold) { for (; i < left.Length; i++) { - carry += left[i]; - Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry); - carry >>= 32; + nuint val = left[i]; + nuint diff = val - borrow; + borrow = (diff > val) ? (nuint)1 : (nuint)0; + Unsafe.Add(ref resultPtr, i) = diff; } } else { for (; i < left.Length;) { - carry += left[i]; - Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry); + nuint val = left[i]; + nuint diff = val - borrow; + borrow = (diff > val) ? (nuint)1 : (nuint)0; + Unsafe.Add(ref resultPtr, i) = diff; i++; - carry >>= 32; - // Once carry is set to 0 it can not be 1 anymore. + // Once borrow is set to 0 it can not be 1 anymore. // So the tail of the loop is just the movement of argument values to result span. - if (carry == 0) + if (borrow == 0) { break; } 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 0cfec0719933bc..fd7174f6e8f6fa 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 @@ -16,56 +16,53 @@ internal const #endif int DivideBurnikelZieglerThreshold = 32; - public static void Divide(ReadOnlySpan left, uint right, Span quotient, out uint remainder) + public static void Divide(ReadOnlySpan left, nuint right, Span quotient, out nuint remainder) { InitializeForDebug(quotient); - ulong carry = 0UL; - Divide(left, right, quotient, ref carry); - remainder = (uint)carry; + nuint carry = (nuint)0; + DivideCore(left, right, quotient, ref carry); + remainder = carry; } - public static void Divide(ReadOnlySpan left, uint right, Span quotient) + public static void Divide(ReadOnlySpan left, nuint right, Span quotient) { InitializeForDebug(quotient); - ulong carry = 0UL; - Divide(left, right, quotient, ref carry); + nuint carry = (nuint)0; + DivideCore(left, right, quotient, ref carry); } - private static void Divide(ReadOnlySpan left, uint right, Span quotient, ref ulong carry) + private static void DivideCore(ReadOnlySpan left, nuint right, Span quotient, ref nuint carry) { Debug.Assert(left.Length >= 1); Debug.Assert(quotient.Length == left.Length); InitializeForDebug(quotient); - // Executes the division for one big and one 32-bit integer. + // Executes the division for one big and one native-width 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. + // processing the native-width integer, since it's a single element. for (int i = left.Length - 1; i >= 0; i--) { - ulong value = (carry << 32) | left[i]; - ulong digit = value / right; - quotient[i] = (uint)digit; - carry = value - digit * right; + quotient[i] = DivRem(carry, left[i], right, out nuint rem); + carry = rem; } } - public static uint Remainder(ReadOnlySpan left, uint right) + public static nuint Remainder(ReadOnlySpan left, nuint right) { Debug.Assert(left.Length >= 1); // Same as above, but only computing the remainder. - ulong carry = 0UL; + nuint carry = (nuint)0; for (int i = left.Length - 1; i >= 0; i--) { - ulong value = (carry << 32) | left[i]; - carry = value % right; + DivRem(carry, left[i], right, out carry); } - return (uint)carry; + return carry; } - public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) + public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) { Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); @@ -86,7 +83,7 @@ public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Spa } } - public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Span quotient) + public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Span quotient) { Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); @@ -98,19 +95,19 @@ public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Spa { // Same as above, but only returning the quotient. - uint[]? leftCopyFromPool = null; + nuint[]? leftCopyFromPool = null; // 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); + Span leftCopy = (left.Length <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); left.CopyTo(leftCopy); DivideGrammarSchool(leftCopy, right, quotient); if (leftCopyFromPool != null) - ArrayPool.Shared.Return(leftCopyFromPool); + ArrayPool.Shared.Return(leftCopyFromPool); } else { @@ -118,7 +115,7 @@ stackalloc uint[StackAllocThreshold] } } - public static void Remainder(ReadOnlySpan left, ReadOnlySpan right, Span remainder) + public static void Remainder(ReadOnlySpan left, ReadOnlySpan right, Span remainder) { Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); @@ -136,16 +133,16 @@ public static void Remainder(ReadOnlySpan left, ReadOnlySpan right, else { int quotientLength = left.Length - right.Length + 1; - uint[]? quotientFromPool = null; + nuint[]? quotientFromPool = null; - Span quotient = (quotientLength <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); + Span quotient = (quotientLength <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : quotientFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); DivideBurnikelZiegler(left, right, quotient, remainder); if (quotientFromPool != null) - ArrayPool.Shared.Return(quotientFromPool); + ArrayPool.Shared.Return(quotientFromPool); } } @@ -156,7 +153,7 @@ stackalloc uint[StackAllocThreshold] /// left %= right; /// /// - private static void DivRem(Span left, ReadOnlySpan right, Span quotient) + private static void DivRem(Span left, ReadOnlySpan right, Span quotient) { Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); @@ -171,25 +168,25 @@ private static void DivRem(Span left, ReadOnlySpan right, Span } else { - uint[]? leftCopyFromPool = null; + nuint[]? leftCopyFromPool = null; // 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); + Span leftCopy = (left.Length <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); left.CopyTo(leftCopy); - uint[]? quotientActualFromPool = null; - scoped Span quotientActual; + nuint[]? quotientActualFromPool = null; + scoped Span quotientActual; if (quotient.Length == 0) { int quotientLength = left.Length - right.Length + 1; quotientActual = (quotientLength <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : quotientActualFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); + stackalloc nuint[StackAllocThreshold] + : quotientActualFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); } else { @@ -199,13 +196,13 @@ stackalloc uint[StackAllocThreshold] DivideBurnikelZiegler(leftCopy, right, quotientActual, left); if (quotientActualFromPool != null) - ArrayPool.Shared.Return(quotientActualFromPool); + ArrayPool.Shared.Return(quotientActualFromPool); if (leftCopyFromPool != null) - ArrayPool.Shared.Return(leftCopyFromPool); + ArrayPool.Shared.Return(leftCopyFromPool); } } - private static void DivideGrammarSchool(Span left, ReadOnlySpan right, Span quotient) + private static void DivideGrammarSchool(Span left, ReadOnlySpan right, Span quotient) { Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); @@ -220,17 +217,19 @@ private static void DivideGrammarSchool(Span left, ReadOnlySpan righ // block of the divisor. Thus, guessing digits of the quotient // will be more precise. Additionally we'll get r = a % b. - uint divHi = right[right.Length - 1]; - uint divLo = right.Length > 1 ? right[right.Length - 2] : 0; + nuint divHi = right[right.Length - 1]; + nuint divLo = right.Length > 1 ? right[right.Length - 2] : (nuint)0; // We measure the leading zeros of the divisor - int shift = BitOperations.LeadingZeroCount(divHi); - int backShift = 32 - shift; + int shift = nint.Size == 8 + ? BitOperations.LeadingZeroCount((ulong)divHi) + : BitOperations.LeadingZeroCount((uint)divHi); + int backShift = kcbitNuint - shift; // And, we make sure the most significant bit is set if (shift > 0) { - uint divNx = right.Length > 2 ? right[right.Length - 3] : 0; + nuint divNx = right.Length > 2 ? right[right.Length - 3] : (nuint)0; divHi = (divHi << shift) | (divLo >> backShift); divLo = (divLo << shift) | (divNx >> backShift); @@ -241,34 +240,34 @@ private static void DivideGrammarSchool(Span left, ReadOnlySpan righ for (int i = left.Length; i >= right.Length; i--) { int n = i - right.Length; - uint t = (uint)i < (uint)left.Length ? left[i] : 0; + nuint t = (uint)i < (uint)left.Length ? left[i] : (nuint)0; - ulong valHi = ((ulong)t << 32) | left[i - 1]; - uint valLo = i > 1 ? left[i - 2] : 0; + nuint valHi1 = t; + nuint valHi0 = left[i - 1]; + nuint valLo = i > 1 ? left[i - 2] : (nuint)0; // We shifted the divisor, we shift the dividend too if (shift > 0) { - uint valNx = i > 2 ? left[i - 3] : 0; + nuint valNx = i > 2 ? left[i - 3] : (nuint)0; - valHi = (valHi << shift) | (valLo >> backShift); + valHi1 = (valHi1 << shift) | (valHi0 >> backShift); + valHi0 = (valHi0 << shift) | (valLo >> backShift); valLo = (valLo << shift) | (valNx >> backShift); } // First guess for the current digit of the quotient, - // which naturally must have only 32 bits... - ulong digit = valHi / divHi; - if (digit > 0xFFFFFFFF) - digit = 0xFFFFFFFF; + // which naturally must have only native-width bits... + nuint digit = (valHi1 >= divHi) ? nuint.MaxValue : DivRem(valHi1, valHi0, divHi, out _); // Our first guess may be a little bit to big - while (DivideGuessTooBig(digit, valHi, valLo, divHi, divLo)) + while (DivideGuessTooBig(digit, valHi1, valHi0, valLo, divHi, divLo)) --digit; if (digit > 0) { // Now it's time to subtract our current quotient - uint carry = SubtractDivisor(left.Slice(n), right, digit); + nuint carry = SubtractDivisor(left.Slice(n), right, digit); if (carry != t) { Debug.Assert(carry == t + 1); @@ -283,76 +282,75 @@ private static void DivideGrammarSchool(Span left, ReadOnlySpan righ // We have the digit! if ((uint)n < (uint)quotient.Length) - quotient[n] = (uint)digit; + quotient[n] = digit; if ((uint)i < (uint)left.Length) left[i] = 0; } } - private static uint AddDivisor(Span left, ReadOnlySpan right) + private static nuint AddDivisor(Span left, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); // Repairs the dividend, if the last subtract was too much - ulong carry = 0UL; + nuint carry = (nuint)0; for (int i = 0; i < right.Length; i++) { - ref uint leftElement = ref left[i]; - ulong digit = (leftElement + carry) + right[i]; - leftElement = unchecked((uint)digit); - carry = digit >> 32; + ref nuint leftElement = ref left[i]; + leftElement = AddWithCarry(leftElement, right[i], carry, out carry); } - return (uint)carry; + return carry; } - private static uint SubtractDivisor(Span left, ReadOnlySpan right, ulong q) + private static nuint SubtractDivisor(Span left, ReadOnlySpan right, nuint q) { Debug.Assert(left.Length >= right.Length); - Debug.Assert(q <= 0xFFFFFFFF); // Combines a subtract and a multiply operation, which is naturally // more efficient than multiplying and then subtracting... - ulong carry = 0UL; + nuint carry = (nuint)0; for (int i = 0; i < right.Length; i++) { - carry += right[i] * q; - uint digit = unchecked((uint)carry); - carry >>= 32; - ref uint leftElement = ref left[i]; - if (leftElement < digit) + nuint hi = BigMul(right[i], q, out nuint lo); + lo += carry; + if (lo < carry) + hi++; + carry = hi; + ref nuint leftElement = ref left[i]; + if (leftElement < lo) ++carry; - leftElement -= digit; + leftElement -= lo; } - return (uint)carry; + return carry; } - private static bool DivideGuessTooBig(ulong q, ulong valHi, uint valLo, - uint divHi, uint divLo) + private static bool DivideGuessTooBig(nuint q, nuint valHi1, nuint valHi0, + nuint valLo, nuint divHi, nuint divLo) { - Debug.Assert(q <= 0xFFFFFFFF); - // We multiply the two most significant limbs of the divisor // with the current guess for the quotient. If those are bigger // than the three most significant limbs of the current dividend // we return true, which means the current guess is still too big. - ulong chkHi = divHi * q; - ulong chkLo = divLo * q; + nuint chkHiHi = BigMul(divHi, q, out nuint chkHiLo); + nuint chkLoHi = BigMul(divLo, q, out nuint chkLoLo); - chkHi += (chkLo >> 32); - uint chkLoUInt32 = (uint)(chkLo); + chkHiLo += chkLoHi; + if (chkHiLo < chkLoHi) + chkHiHi++; - return (chkHi > valHi) || ((chkHi == valHi) && (chkLoUInt32 > valLo)); + return (chkHiHi > valHi1) + || ((chkHiHi == valHi1) && ((chkHiLo > valHi0) || ((chkHiLo == valHi0) && (chkLoLo > valLo)))); } - private static void DivideBurnikelZiegler(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) + private static void DivideBurnikelZiegler(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) { Debug.Assert(left.Length >= 1); Debug.Assert(right.Length >= 1); @@ -377,48 +375,53 @@ private static void DivideBurnikelZiegler(ReadOnlySpan left, ReadOnlySpan< } int sigmaDigit = n - right.Length; - int sigmaSmall = BitOperations.LeadingZeroCount(right[^1]); + int sigmaSmall = nint.Size == 8 + ? BitOperations.LeadingZeroCount((ulong)right[^1]) + : BitOperations.LeadingZeroCount((uint)right[^1]); - uint[]? bFromPool = null; + nuint[]? bFromPool = null; - Span b = (n <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : bFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); + Span b = (n <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : bFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); int aLength = left.Length + sigmaDigit; - // if: BitOperations.LeadingZeroCount(left[^1]) < sigmaSmall, requires one more digit obviously. - // if: BitOperations.LeadingZeroCount(left[^1]) == sigmaSmall, requires one more digit, because the leftmost bit of a must be 0. + // if: LeadingZeroCount(left[^1]) < sigmaSmall, requires one more digit obviously. + // if: LeadingZeroCount(left[^1]) == sigmaSmall, requires one more digit, because the leftmost bit of a must be 0. - if (BitOperations.LeadingZeroCount(left[^1]) <= sigmaSmall) + int leftLzc = nint.Size == 8 + ? BitOperations.LeadingZeroCount((ulong)left[^1]) + : BitOperations.LeadingZeroCount((uint)left[^1]); + if (leftLzc <= sigmaSmall) ++aLength; - uint[]? aFromPool = null; + nuint[]? aFromPool = null; - Span a = (aLength <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : aFromPool = ArrayPool.Shared.Rent(aLength)).Slice(0, aLength); + Span a = (aLength <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : aFromPool = ArrayPool.Shared.Rent(aLength)).Slice(0, aLength); // 4. normalize - static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, Span bits) + static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, Span bits) { - Debug.Assert((uint)sigmaSmall <= 32); + Debug.Assert((uint)sigmaSmall <= kcbitNuint); Debug.Assert(src.Length + sigmaDigit <= bits.Length); bits.Slice(0, sigmaDigit).Clear(); - Span dst = bits.Slice(sigmaDigit); + Span dst = bits.Slice(sigmaDigit); src.CopyTo(dst); dst.Slice(src.Length).Clear(); if (sigmaSmall != 0) { // Left shift - int carryShift = 32 - sigmaSmall; - uint carry = 0; + int carryShift = kcbitNuint - sigmaSmall; + nuint carry = (nuint)0; for (int i = 0; i < bits.Length; i++) { - uint carryTmp = bits[i] >> carryShift; + nuint carryTmp = bits[i] >> carryShift; bits[i] = bits[i] << sigmaSmall | carry; carry = carryTmp; } @@ -432,35 +435,35 @@ static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, Sp int t = Math.Max(2, (a.Length + n - 1) / n); // Max(2, Ceil(a.Length/n)) - Debug.Assert(t < a.Length || (t == a.Length && (int)a[^1] >= 0)); + Debug.Assert(t < a.Length || (t == a.Length && (nint)a[^1] >= 0)); - uint[]? rFromPool = null; - Span r = ((n + 1) <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : rFromPool = ArrayPool.Shared.Rent(n + 1)).Slice(0, n + 1); + nuint[]? rFromPool = null; + Span r = ((n + 1) <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : rFromPool = ArrayPool.Shared.Rent(n + 1)).Slice(0, n + 1); - uint[]? zFromPool = null; - Span z = (2 * n <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(2 * n)).Slice(0, 2 * n); + nuint[]? zFromPool = null; + Span z = (2 * n <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : zFromPool = ArrayPool.Shared.Rent(2 * n)).Slice(0, 2 * n); a.Slice((t - 2) * n).CopyTo(z); z.Slice(a.Length - (t - 2) * n).Clear(); - Span quotientUpper = quotient.Slice((t - 2) * n); + Span quotientUpper = quotient.Slice((t - 2) * n); if (quotientUpper.Length < n) { - uint[]? qFromPool = null; - Span q = (n <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : qFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); + nuint[]? qFromPool = null; + Span q = (n <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : qFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); BurnikelZieglerD2n1n(z, b, q, r); - Debug.Assert(!q.Slice(quotientUpper.Length).ContainsAnyExcept(0u)); + Debug.Assert(!q.Slice(quotientUpper.Length).ContainsAnyExcept((nuint)0)); q.Slice(0, quotientUpper.Length).CopyTo(quotientUpper); if (qFromPool != null) - ArrayPool.Shared.Return(qFromPool); + ArrayPool.Shared.Return(qFromPool); } else { @@ -476,26 +479,26 @@ stackalloc uint[StackAllocThreshold] } if (zFromPool != null) - ArrayPool.Shared.Return(zFromPool); + ArrayPool.Shared.Return(zFromPool); if (bFromPool != null) - ArrayPool.Shared.Return(bFromPool); + ArrayPool.Shared.Return(bFromPool); if (aFromPool != null) - ArrayPool.Shared.Return(aFromPool); + ArrayPool.Shared.Return(aFromPool); Debug.Assert(r[^1] == 0); - Debug.Assert(!r.Slice(0, sigmaDigit).ContainsAnyExcept(0u)); + Debug.Assert(!r.Slice(0, sigmaDigit).ContainsAnyExcept((nuint)0)); if (remainder.Length != 0) { - Span rt = r.Slice(sigmaDigit); + Span rt = r.Slice(sigmaDigit); remainder.Slice(rt.Length).Clear(); if (sigmaSmall != 0) { // Right shift - Debug.Assert((uint)sigmaSmall <= 32); + Debug.Assert((uint)sigmaSmall <= kcbitNuint); - int carryShift = 32 - sigmaSmall; - uint carry = 0; + int carryShift = kcbitNuint - sigmaSmall; + nuint carry = (nuint)0; for (int i = rt.Length - 1; i >= 0; i--) { @@ -512,10 +515,10 @@ stackalloc uint[StackAllocThreshold] } if (rFromPool != null) - ArrayPool.Shared.Return(rFromPool); + ArrayPool.Shared.Return(rFromPool); } - private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) + private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) { // Fast recursive division: Algorithm 1 // 1. If n is odd or smaller than some convenient constant @@ -537,7 +540,7 @@ private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySpa } else if (right.Length == 1) { - ulong carry; + nuint carry; if (quotient.Length < left.Length) { @@ -545,27 +548,27 @@ private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySpa Debug.Assert(left[^1] < right[0]); carry = left[^1]; - Divide(left.Slice(0, quotient.Length), right[0], quotient, ref carry); + DivideCore(left.Slice(0, quotient.Length), right[0], quotient, ref carry); } else { - carry = 0; + carry = (nuint)0; quotient.Slice(left.Length).Clear(); - Divide(left, right[0], quotient, ref carry); + DivideCore(left, right[0], quotient, ref carry); } if (remainder.Length != 0) { remainder.Slice(1).Clear(); - remainder[0] = (uint)carry; + remainder[0] = carry; } } else { - uint[]? r1FromPool = null; - Span r1 = (left.Length <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : r1FromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); + nuint[]? r1FromPool = null; + Span r1 = (left.Length <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : r1FromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); left.CopyTo(r1); int quotientLength = Math.Min(left.Length - right.Length + 1, quotient.Length); @@ -580,16 +583,16 @@ stackalloc uint[StackAllocThreshold] } else { - Debug.Assert(!r1.Slice(remainder.Length).ContainsAnyExcept(0u)); + Debug.Assert(!r1.Slice(remainder.Length).ContainsAnyExcept((nuint)0)); r1.Slice(0, remainder.Length).CopyTo(remainder); } if (r1FromPool != null) - ArrayPool.Shared.Return(r1FromPool); + ArrayPool.Shared.Return(r1FromPool); } } - private static void BurnikelZieglerD2n1n(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) + private static void BurnikelZieglerD2n1n(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) { // Fast recursive division: Algorithm 1 Debug.Assert(left.Length == 2 * right.Length); @@ -606,19 +609,19 @@ private static void BurnikelZieglerD2n1n(ReadOnlySpan left, ReadOnlySpan> 1; - uint[]? r1FromPool = null; - Span r1 = ((right.Length + 1) <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : r1FromPool = ArrayPool.Shared.Rent(right.Length + 1)).Slice(0, right.Length + 1); + nuint[]? r1FromPool = null; + Span r1 = ((right.Length + 1) <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : r1FromPool = ArrayPool.Shared.Rent(right.Length + 1)).Slice(0, right.Length + 1); BurnikelZieglerD3n2n(left.Slice(right.Length), left.Slice(halfN, halfN), right, quotient.Slice(halfN), r1); BurnikelZieglerD3n2n(r1.Slice(0, right.Length), left.Slice(0, halfN), right, quotient.Slice(0, halfN), remainder); if (r1FromPool != null) - ArrayPool.Shared.Return(r1FromPool); + ArrayPool.Shared.Return(r1FromPool); } - private static void BurnikelZieglerD3n2n(ReadOnlySpan left12, ReadOnlySpan left3, ReadOnlySpan right, Span quotient, Span remainder) + private static void BurnikelZieglerD3n2n(ReadOnlySpan left12, ReadOnlySpan left3, ReadOnlySpan right, Span quotient, Span remainder) { // Fast recursive division: Algorithm 2 Debug.Assert(right.Length % 2 == 0); @@ -631,14 +634,14 @@ private static void BurnikelZieglerD3n2n(ReadOnlySpan left12, ReadOnlySpan int n = right.Length >> 1; - ReadOnlySpan a1 = left12.Slice(n); - ReadOnlySpan b1 = right.Slice(n); - ReadOnlySpan b2 = right.Slice(0, n); - Span r1 = remainder.Slice(n); - uint[]? dFromPool = null; - Span d = (right.Length <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : dFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); + ReadOnlySpan a1 = left12.Slice(n); + ReadOnlySpan b1 = right.Slice(n); + ReadOnlySpan b2 = right.Slice(0, n); + Span r1 = remainder.Slice(n); + nuint[]? dFromPool = null; + Span d = (right.Length <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : dFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); if (CompareActual(a1, b1) < 0) { @@ -650,9 +653,9 @@ stackalloc uint[StackAllocThreshold] else { Debug.Assert(CompareActual(a1, b1) == 0); - quotient.Fill(uint.MaxValue); + quotient.Fill(nuint.MaxValue); - ReadOnlySpan a2 = left12.Slice(0, n); + ReadOnlySpan a2 = left12.Slice(0, n); Add(a2, b1, r1); d.Slice(0, n).Clear(); @@ -663,7 +666,7 @@ stackalloc uint[StackAllocThreshold] // R = [R1, A3] left3.CopyTo(remainder.Slice(0, n)); - Span rr = remainder.Slice(0, d.Length + 1); + Span rr = remainder.Slice(0, d.Length + 1); while (CompareActual(rr, d) < 0) { @@ -672,15 +675,15 @@ stackalloc uint[StackAllocThreshold] while (quotient[++qi] == 0) ; Debug.Assert((uint)qi < (uint)quotient.Length); --quotient[qi]; - quotient.Slice(0, qi).Fill(uint.MaxValue); + quotient.Slice(0, qi).Fill(nuint.MaxValue); } SubtractSelf(rr, d); if (dFromPool != null) - ArrayPool.Shared.Return(dFromPool); + ArrayPool.Shared.Return(dFromPool); - static void MultiplyActual(ReadOnlySpan left, ReadOnlySpan right, Span bits) + static void MultiplyActual(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(bits.Length == left.Length + right.Length); 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 2782e8be55eb64..a7368e12cc79e1 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 @@ -15,12 +15,12 @@ internal static partial class BigIntegerCalculator private readonly ref struct FastReducer { - private readonly ReadOnlySpan _modulus; - private readonly ReadOnlySpan _mu; - private readonly Span _q1; - private readonly Span _q2; + private readonly ReadOnlySpan _modulus; + private readonly ReadOnlySpan _mu; + private readonly Span _q1; + private readonly Span _q2; - public FastReducer(ReadOnlySpan modulus, Span r, Span mu, Span q1, Span q2) + public FastReducer(ReadOnlySpan modulus, Span r, Span mu, Span q1, Span q2) { Debug.Assert(!modulus.IsEmpty); Debug.Assert(r.Length == modulus.Length * 2 + 1); @@ -41,7 +41,7 @@ public FastReducer(ReadOnlySpan modulus, Span r, Span mu, Span _mu = mu.Slice(0, ActualLength(mu)); } - public int Reduce(Span value) + public int Reduce(Span value) { Debug.Assert(value.Length <= _modulus.Length * 2); @@ -65,7 +65,7 @@ public int Reduce(Span value) return length; } - private static int DivMul(ReadOnlySpan left, ReadOnlySpan right, Span bits, int k) + private static int DivMul(ReadOnlySpan left, ReadOnlySpan right, Span bits, int k) { Debug.Assert(!right.IsEmpty); Debug.Assert(!bits.IsEmpty); @@ -89,7 +89,7 @@ private static int DivMul(ReadOnlySpan left, ReadOnlySpan right, Spa return 0; } - private static int SubMod(Span left, ReadOnlySpan right, ReadOnlySpan modulus, int k) + private static int SubMod(Span left, ReadOnlySpan right, ReadOnlySpan modulus, int k) { // Executes the subtraction algorithm for left and right, // but considers only the first k limbs, which is equivalent to 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 e17ff2e363e995..8b5b7b02654624 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 @@ -8,7 +8,7 @@ namespace System.Numerics { internal static partial class BigIntegerCalculator { - public static uint Gcd(uint left, uint right) + public static nuint Gcd(nuint left, nuint right) { // Executes the classic Euclidean algorithm. @@ -16,7 +16,7 @@ public static uint Gcd(uint left, uint right) while (right != 0) { - uint temp = left % right; + nuint temp = left % right; left = right; right = temp; } @@ -41,7 +41,7 @@ public static ulong Gcd(ulong left, ulong right) return left; } - public static uint Gcd(ReadOnlySpan left, uint right) + public static nuint Gcd(ReadOnlySpan left, nuint right) { Debug.Assert(left.Length >= 1); Debug.Assert(right != 0); @@ -49,12 +49,12 @@ public static uint Gcd(ReadOnlySpan left, uint right) // A common divisor cannot be greater than right; // we compute the remainder and continue above... - uint temp = Remainder(left, right); + nuint temp = Remainder(left, right); return Gcd(right, temp); } - public static void Gcd(ReadOnlySpan left, ReadOnlySpan right, Span result) + public static void Gcd(ReadOnlySpan left, ReadOnlySpan right, Span result) { Debug.Assert(left.Length >= 2); Debug.Assert(right.Length >= 2); @@ -63,25 +63,25 @@ public static void Gcd(ReadOnlySpan left, ReadOnlySpan right, Span rightCopy = (right.Length <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : rightCopyFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); + nuint[]? rightCopyFromPool = null; + Span rightCopy = (right.Length <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : rightCopyFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); right.CopyTo(rightCopy); Gcd(result, rightCopy); if (rightCopyFromPool != null) - ArrayPool.Shared.Return(rightCopyFromPool); + ArrayPool.Shared.Return(rightCopyFromPool); } - private static void Gcd(Span left, Span right) + private static void Gcd(Span left, Span right) { 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 + 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. @@ -90,7 +90,7 @@ private static void Gcd(Span left, Span 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.Length > 2) + while (right.Length > (nint.Size == 4 ? 2 : 1)) { ulong x, y; @@ -158,7 +158,7 @@ private static void Gcd(Span left, Span right) // Euclid's step left = left.Slice(0, Reduce(left, right)); - Span temp = left; + Span temp = left; left = right; right = temp; } @@ -172,7 +172,7 @@ private static void Gcd(Span left, Span right) if (iteration % 2 == 1) { // Ensure left is larger than right - Span temp = left; + Span temp = left; left = right; right = temp; } @@ -184,13 +184,23 @@ private static void Gcd(Span left, Span right) // Euclid's step Reduce(left, right); - ulong x = right[0]; - ulong y = left[0]; + ulong x, y; + + if (nint.Size == 4) + { + x = (ulong)right[0]; + y = (ulong)left[0]; - if (right.Length > 1) + if (right.Length > 1) + { + x |= (ulong)right[1] << 32; + y |= (ulong)left[1] << 32; + } + } + else { - x |= (ulong)right[1] << 32; - y |= (ulong)left[1] << 32; + x = (ulong)right[0]; + y = (ulong)left[0]; } left = left.Slice(0, Overwrite(left, Gcd(x, y))); @@ -200,80 +210,143 @@ private static void Gcd(Span left, Span right) left.CopyTo(result); } - private static int Overwrite(Span buffer, ulong value) + private static int Overwrite(Span buffer, ulong value) { - Debug.Assert(buffer.Length >= 2); - - if (buffer.Length > 2) + if (nint.Size == 4) { - // Ensure leading zeros in little-endian - buffer.Slice(2).Clear(); + Debug.Assert(buffer.Length >= 2); + + if (buffer.Length > 2) + { + // Ensure leading zeros in little-endian + buffer.Slice(2).Clear(); + } + + nuint lo = unchecked((nuint)value); + nuint hi = (nuint)(value >> 32); + + buffer[1] = hi; + buffer[0] = lo; + return hi != 0 ? 2 : lo != 0 ? 1 : 0; } + else + { + Debug.Assert(buffer.Length >= 1); - uint lo = unchecked((uint)value); - uint hi = (uint)(value >> 32); + if (buffer.Length > 1) + { + // Ensure leading zeros in little-endian + buffer.Slice(1).Clear(); + } - buffer[1] = hi; - buffer[0] = lo; - return hi != 0 ? 2 : lo != 0 ? 1 : 0; + buffer[0] = (nuint)value; + return value != 0 ? 1 : 0; + } } - private static void ExtractDigits(ReadOnlySpan xBuffer, - ReadOnlySpan yBuffer, + private static void ExtractDigits(ReadOnlySpan xBuffer, + ReadOnlySpan yBuffer, out ulong x, out ulong y) { - 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! - ulong xh = xBuffer[xBuffer.Length - 1]; - ulong xm = xBuffer[xBuffer.Length - 2]; - ulong xl = xBuffer[xBuffer.Length - 3]; + if (nint.Size == 4) + { + Debug.Assert(xBuffer.Length >= 3); + Debug.Assert(yBuffer.Length >= 3); + Debug.Assert(xBuffer.Length >= yBuffer.Length); - ulong yh, ym, yl; + ulong xh = xBuffer[xBuffer.Length - 1]; + ulong xm = xBuffer[xBuffer.Length - 2]; + ulong xl = xBuffer[xBuffer.Length - 3]; - // arrange the bits - switch (xBuffer.Length - yBuffer.Length) - { - case 0: - yh = yBuffer[yBuffer.Length - 1]; - ym = yBuffer[yBuffer.Length - 2]; - yl = yBuffer[yBuffer.Length - 3]; - break; - - case 1: - yh = 0UL; - ym = yBuffer[yBuffer.Length - 1]; - yl = yBuffer[yBuffer.Length - 2]; - break; - - case 2: - yh = 0UL; - ym = 0UL; - yl = yBuffer[yBuffer.Length - 1]; - break; - - default: - yh = 0UL; - ym = 0UL; - yl = 0UL; - break; + ulong yh, ym, yl; + + // arrange the bits + switch (xBuffer.Length - yBuffer.Length) + { + case 0: + yh = yBuffer[yBuffer.Length - 1]; + ym = yBuffer[yBuffer.Length - 2]; + yl = yBuffer[yBuffer.Length - 3]; + break; + + case 1: + yh = 0UL; + ym = yBuffer[yBuffer.Length - 1]; + yl = yBuffer[yBuffer.Length - 2]; + break; + + case 2: + yh = 0UL; + ym = 0UL; + yl = yBuffer[yBuffer.Length - 1]; + break; + + default: + yh = 0UL; + ym = 0UL; + yl = 0UL; + break; + } + + // Use all the bits but one, see [hac] 14.58 (ii) + 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; } + else + { + Debug.Assert(xBuffer.Length >= 2); + Debug.Assert(yBuffer.Length >= 2); + Debug.Assert(xBuffer.Length >= yBuffer.Length); + + ulong xh = (ulong)xBuffer[xBuffer.Length - 1]; + ulong xl = (ulong)xBuffer[xBuffer.Length - 2]; + + ulong yh, yl; - // Use all the bits but one, see [hac] 14.58 (ii) - int z = BitOperations.LeadingZeroCount((uint)xh); + // arrange the bits + switch (xBuffer.Length - yBuffer.Length) + { + case 0: + yh = (ulong)yBuffer[yBuffer.Length - 1]; + yl = (ulong)yBuffer[yBuffer.Length - 2]; + break; + + case 1: + yh = 0UL; + yl = (ulong)yBuffer[yBuffer.Length - 1]; + break; + + default: + yh = 0UL; + yl = 0UL; + break; + } - x = ((xh << 32 + z) | (xm << z) | (xl >> 32 - z)) >> 1; - y = ((yh << 32 + z) | (ym << z) | (yl >> 32 - z)) >> 1; + // Use all the bits but one, see [hac] 14.58 (ii) + int z = BitOperations.LeadingZeroCount(xh); + + if (z == 0) + { + x = xh >> 1; + y = yh >> 1; + } + else + { + x = ((xh << z) | (xl >> (64 - z))) >> 1; + y = ((yh << z) | (yl >> (64 - z))) >> 1; + } + } Debug.Assert(x >= y); } - private static int LehmerCore(Span x, - Span y, + private static int LehmerCore(Span x, + Span y, long a, long b, long c, long d) { @@ -287,21 +360,37 @@ private static int LehmerCore(Span x, int length = y.Length; - long xCarry = 0L, yCarry = 0L; - for (int i = 0; i < length; i++) + if (nint.Size == 4) { - long xDigit = a * x[i] - b * y[i] + xCarry; - long yDigit = d * y[i] - c * x[i] + yCarry; - xCarry = xDigit >> 32; - yCarry = yDigit >> 32; - x[i] = unchecked((uint)xDigit); - y[i] = unchecked((uint)yDigit); + long xCarry = 0L, yCarry = 0L; + for (int i = 0; i < length; i++) + { + long xDigit = a * (long)x[i] - b * (long)y[i] + xCarry; + long yDigit = d * (long)y[i] - c * (long)x[i] + yCarry; + xCarry = xDigit >> 32; + yCarry = yDigit >> 32; + x[i] = unchecked((nuint)xDigit); + y[i] = unchecked((nuint)yDigit); + } + } + else + { + Int128 xCarry = 0, yCarry = 0; + for (int i = 0; i < length; i++) + { + Int128 xDigit = a * (Int128)(ulong)x[i] - b * (Int128)(ulong)y[i] + xCarry; + Int128 yDigit = d * (Int128)(ulong)y[i] - c * (Int128)(ulong)x[i] + yCarry; + xCarry = xDigit >> 64; + yCarry = yDigit >> 64; + x[i] = unchecked((nuint)(ulong)xDigit); + y[i] = unchecked((nuint)(ulong)yDigit); + } } return length; } - private static int Refresh(Span bits, int maxLength) + private static int Refresh(Span bits, int maxLength) { Debug.Assert(bits.Length >= 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 49413312fec26c..97cb384f9c180c 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 @@ -13,39 +13,39 @@ internal static partial class BigIntegerCalculator // https://en.wikipedia.org/wiki/Exponentiation_by_squaring - public static void Pow(uint value, uint power, Span bits) + public static void Pow(nuint value, nuint power, Span bits) { - Pow(value != 0U ? new ReadOnlySpan(in value) : default, power, bits); + Pow(value != 0 ? new ReadOnlySpan(in value) : default, power, bits); } - public static void Pow(ReadOnlySpan value, uint power, Span bits) + public static void Pow(ReadOnlySpan value, nuint 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); + nuint[]? tempFromPool = null; + Span temp = (bits.Length <= StackAllocThreshold ? + stackalloc nuint[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); + nuint[]? valueCopyFromPool = null; + Span valueCopy = (bits.Length <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); value.CopyTo(valueCopy); valueCopy.Slice(value.Length).Clear(); - Span result = PowCore(valueCopy, value.Length, temp, power, bits); + Span result = PowCore(valueCopy, value.Length, temp, power, bits); result.CopyTo(bits); bits.Slice(result.Length).Clear(); if (tempFromPool != null) - ArrayPool.Shared.Return(tempFromPool); + ArrayPool.Shared.Return(tempFromPool); if (valueCopyFromPool != null) - ArrayPool.Shared.Return(valueCopyFromPool); + ArrayPool.Shared.Return(valueCopyFromPool); } - private static Span PowCore(Span value, int valueLength, Span temp, uint power, Span result) + private static Span PowCore(Span value, int valueLength, Span temp, nuint power, Span result) { Debug.Assert(value.Length >= valueLength); Debug.Assert(temp.Length == result.Length); @@ -67,7 +67,7 @@ private static Span PowCore(Span value, int valueLength, Span return result.Slice(0, resultLength); } - private static int MultiplySelf(ref Span left, int leftLength, ReadOnlySpan right, ref Span temp) + private static int MultiplySelf(ref Span left, int leftLength, ReadOnlySpan right, ref Span temp) { Debug.Assert(leftLength <= left.Length); @@ -77,13 +77,13 @@ private static int MultiplySelf(ref Span left, int leftLength, ReadOnlySpa left.Clear(); //switch buffers - Span t = left; + Span t = left; left = temp; temp = t; return ActualLength(left.Slice(0, resultLength)); } - private static int SquareSelf(ref Span value, int valueLength, ref Span temp) + private static int SquareSelf(ref Span value, int valueLength, ref Span temp) { Debug.Assert(valueLength <= value.Length); Debug.Assert(temp.Length >= valueLength + valueLength); @@ -94,13 +94,13 @@ private static int SquareSelf(ref Span value, int valueLength, ref Span t = value; + Span t = value; value = temp; temp = t; return ActualLength(value.Slice(0, resultLength)); } - public static int PowBound(uint power, int valueLength) + public static int PowBound(nuint power, int valueLength) { // The basic pow algorithm, but instead of squaring // and multiplying we just sum up the lengths. @@ -121,53 +121,68 @@ public static int PowBound(uint power, int valueLength) return resultLength; } - public static uint Pow(uint value, uint power, uint modulus) + public static nuint Pow(nuint value, nuint power, nuint modulus) { - // The 32-bit modulus pow method for a 32-bit integer + // The single-limb modulus pow method for a single-limb integer // raised by a 32-bit integer... return PowCore(value, power, modulus, 1); } - public static uint Pow(ReadOnlySpan value, uint power, uint modulus) + public static nuint Pow(ReadOnlySpan value, nuint power, nuint modulus) { - // The 32-bit modulus pow method for a big integer + // The single-limb modulus pow method for a big integer // raised by a 32-bit integer... - uint v = Remainder(value, modulus); + nuint v = Remainder(value, modulus); return PowCore(v, power, modulus, 1); } - public static uint Pow(uint value, ReadOnlySpan power, uint modulus) + public static nuint Pow(nuint value, ReadOnlySpan power, nuint modulus) { - // The 32-bit modulus pow method for a 32-bit integer + // The single-limb modulus pow method for a single-limb integer // raised by a big integer... return PowCore(value, power, modulus, 1); } - public static uint Pow(ReadOnlySpan value, ReadOnlySpan power, uint modulus) + public static nuint Pow(ReadOnlySpan value, ReadOnlySpan power, nuint modulus) { - // The 32-bit modulus pow method for a big integer + // The single-limb modulus pow method for a big integer // raised by a big integer... - uint v = Remainder(value, modulus); + nuint v = Remainder(value, modulus); return PowCore(v, power, modulus, 1); } - private static uint PowCore(ulong value, ReadOnlySpan power, uint modulus, ulong result) + private static nuint PowCore(nuint value, ReadOnlySpan power, nuint modulus, nuint result) { - // The 32-bit modulus pow algorithm for all but + // The single-limb modulus pow algorithm for all but // the last power limb using square-and-multiply. for (int i = 0; i < power.Length - 1; i++) { - uint p = power[i]; - for (int j = 0; j < 32; j++) + nuint p = power[i]; + for (int j = 0; j < kcbitNuint; j++) { - if ((p & 1) == 1) - result = (result * value) % modulus; - value = (value * value) % modulus; + if (nint.Size == 8) + { + if ((p & 1) == 1) + { + UInt128 prod = (UInt128)(ulong)result * (ulong)value; + result = (nuint)(ulong)(prod % (ulong)modulus); + } + { + UInt128 sq = (UInt128)(ulong)value * (ulong)value; + value = (nuint)(ulong)(sq % (ulong)modulus); + } + } + else + { + if ((p & 1) == 1) + result = (nuint)(uint)(((ulong)result * value) % modulus); + value = (nuint)(uint)(((ulong)value * value) % modulus); + } p >>= 1; } } @@ -175,31 +190,47 @@ private static uint PowCore(ulong value, ReadOnlySpan power, uint modulus, return PowCore(value, power[power.Length - 1], modulus, result); } - private static uint PowCore(ulong value, uint power, uint modulus, ulong result) + private static nuint PowCore(nuint value, nuint power, nuint modulus, nuint result) { - // The 32-bit modulus pow algorithm for the last or + // The single-limb modulus pow algorithm for the last or // the only power limb using square-and-multiply. while (power != 0) { - if ((power & 1) == 1) - result = (result * value) % modulus; - if (power != 1) - value = (value * value) % modulus; + if (nint.Size == 8) + { + if ((power & 1) == 1) + { + UInt128 prod = (UInt128)(ulong)result * (ulong)value; + result = (nuint)(ulong)(prod % (ulong)modulus); + } + if (power != 1) + { + UInt128 sq = (UInt128)(ulong)value * (ulong)value; + value = (nuint)(ulong)(sq % (ulong)modulus); + } + } + else + { + if ((power & 1) == 1) + result = (nuint)(uint)(((ulong)result * value) % modulus); + if (power != 1) + value = (nuint)(uint)(((ulong)value * value) % modulus); + } power >>= 1; } - return (uint)(result % modulus); + return result % modulus; } - public static void Pow(uint value, uint power, - ReadOnlySpan modulus, Span bits) + public static void Pow(nuint value, nuint power, + ReadOnlySpan modulus, Span bits) { - Pow(value != 0U ? new ReadOnlySpan(in value) : default, power, modulus, bits); + Pow(value != 0 ? new ReadOnlySpan(in value) : default, power, modulus, bits); } - public static void Pow(ReadOnlySpan value, uint power, - ReadOnlySpan modulus, Span bits) + public static void Pow(ReadOnlySpan value, nuint power, + ReadOnlySpan modulus, Span bits) { Debug.Assert(!modulus.IsEmpty); Debug.Assert(bits.Length == modulus.Length + modulus.Length); @@ -207,11 +238,11 @@ public static void Pow(ReadOnlySpan value, uint power, // The big modulus pow method for a big integer // raised by a 32-bit integer... - uint[]? valueCopyFromPool = null; + nuint[]? 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); + Span valueCopy = (size <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); // smallish optimization here: // subsequent operations will copy the elements to the beginning of the buffer, @@ -227,28 +258,28 @@ stackalloc uint[StackAllocThreshold] value.CopyTo(valueCopy); } - uint[]? tempFromPool = null; - Span temp = (bits.Length <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + nuint[]? tempFromPool = null; + Span temp = (bits.Length <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); temp.Clear(); PowCore(valueCopy, ActualLength(valueCopy), power, modulus, temp, bits); if (valueCopyFromPool != null) - ArrayPool.Shared.Return(valueCopyFromPool); + ArrayPool.Shared.Return(valueCopyFromPool); if (tempFromPool != null) - ArrayPool.Shared.Return(tempFromPool); + ArrayPool.Shared.Return(tempFromPool); } - public static void Pow(uint value, ReadOnlySpan power, - ReadOnlySpan modulus, Span bits) + public static void Pow(nuint value, ReadOnlySpan power, + ReadOnlySpan modulus, Span bits) { - Pow(value != 0U ? new ReadOnlySpan(in value) : default, power, modulus, bits); + Pow(value != 0 ? new ReadOnlySpan(in value) : default, power, modulus, bits); } - public static void Pow(ReadOnlySpan value, ReadOnlySpan power, - ReadOnlySpan modulus, Span bits) + public static void Pow(ReadOnlySpan value, ReadOnlySpan power, + ReadOnlySpan modulus, Span bits) { Debug.Assert(!modulus.IsEmpty); Debug.Assert(bits.Length == modulus.Length + modulus.Length); @@ -257,10 +288,10 @@ public static void Pow(ReadOnlySpan value, ReadOnlySpan power, // 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); + nuint[]? valueCopyFromPool = null; + Span valueCopy = (size <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); // smallish optimization here: // subsequent operations will copy the elements to the beginning of the buffer, @@ -276,18 +307,18 @@ stackalloc uint[StackAllocThreshold] value.CopyTo(valueCopy); } - uint[]? tempFromPool = null; - Span temp = (bits.Length <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + nuint[]? tempFromPool = null; + Span temp = (bits.Length <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); temp.Clear(); PowCore(valueCopy, ActualLength(valueCopy), power, modulus, temp, bits); if (valueCopyFromPool != null) - ArrayPool.Shared.Return(valueCopyFromPool); + ArrayPool.Shared.Return(valueCopyFromPool); if (tempFromPool != null) - ArrayPool.Shared.Return(tempFromPool); + ArrayPool.Shared.Return(tempFromPool); } #if DEBUG @@ -298,9 +329,9 @@ internal const #endif int ReducerThreshold = 32; - private static void PowCore(Span value, int valueLength, - ReadOnlySpan power, ReadOnlySpan modulus, - Span temp, Span bits) + private static void PowCore(Span value, int valueLength, + ReadOnlySpan power, ReadOnlySpan modulus, + Span temp, Span bits) { // Executes the big pow algorithm. @@ -308,121 +339,121 @@ private static void PowCore(Span value, int valueLength, if (modulus.Length < ReducerThreshold) { - Span result = PowCore(value, valueLength, power, modulus, bits, 1, temp); + Span result = PowCore(value, valueLength, power, modulus, bits, 1, temp); result.CopyTo(bits); bits.Slice(result.Length).Clear(); } else { 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); + nuint[]? rFromPool = null; + Span r = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + nuint[]? muFromPool = null; + Span mu = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + nuint[]? q1FromPool = null; + Span q1 = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + nuint[]? q2FromPool = null; + Span q2 = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + ArrayPool.Shared.Return(rFromPool); - Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); + Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); result.CopyTo(bits); bits.Slice(result.Length).Clear(); if (muFromPool != null) - ArrayPool.Shared.Return(muFromPool); + ArrayPool.Shared.Return(muFromPool); if (q1FromPool != null) - ArrayPool.Shared.Return(q1FromPool); + ArrayPool.Shared.Return(q1FromPool); if (q2FromPool != null) - ArrayPool.Shared.Return(q2FromPool); + ArrayPool.Shared.Return(q2FromPool); } } - private static void PowCore(Span value, int valueLength, - uint power, ReadOnlySpan modulus, - Span temp, Span bits) + private static void PowCore(Span value, int valueLength, + nuint power, ReadOnlySpan modulus, + Span temp, Span bits) { // Executes the big pow algorithm. bits[0] = 1; if (modulus.Length < ReducerThreshold) { - Span result = PowCore(value, valueLength, power, modulus, bits, 1, temp); + Span result = PowCore(value, valueLength, power, modulus, bits, 1, temp); result.CopyTo(bits); bits.Slice(result.Length).Clear(); } else { 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); + nuint[]? rFromPool = null; + Span r = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + nuint[]? muFromPool = null; + Span mu = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + nuint[]? q1FromPool = null; + Span q1 = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + nuint[]? q2FromPool = null; + Span q2 = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + ArrayPool.Shared.Return(rFromPool); - Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); + Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); result.CopyTo(bits); bits.Slice(result.Length).Clear(); if (muFromPool != null) - ArrayPool.Shared.Return(muFromPool); + ArrayPool.Shared.Return(muFromPool); if (q1FromPool != null) - ArrayPool.Shared.Return(q1FromPool); + ArrayPool.Shared.Return(q1FromPool); if (q2FromPool != null) - ArrayPool.Shared.Return(q2FromPool); + ArrayPool.Shared.Return(q2FromPool); } } - private static Span PowCore(Span value, int valueLength, - ReadOnlySpan power, ReadOnlySpan modulus, - Span result, int resultLength, - Span 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. @@ -432,8 +463,8 @@ private static Span PowCore(Span value, int valueLength, for (int i = 0; i < power.Length - 1; i++) { - uint p = power[i]; - for (int j = 0; j < 32; j++) + nuint p = power[i]; + for (int j = 0; j < kcbitNuint; j++) { if ((p & 1) == 1) { @@ -449,10 +480,10 @@ private static Span PowCore(Span value, int valueLength, return PowCore(value, valueLength, power[power.Length - 1], modulus, result, resultLength, temp); } - private static Span PowCore(Span value, int valueLength, - uint power, ReadOnlySpan modulus, - Span result, int resultLength, - Span temp) + private static Span PowCore(Span value, int valueLength, + nuint 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. @@ -478,10 +509,10 @@ private static Span PowCore(Span value, int valueLength, return result.Slice(0, resultLength); } - private static Span PowCore(Span value, int valueLength, - ReadOnlySpan power, in FastReducer reducer, - Span result, int resultLength, - Span 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. @@ -491,8 +522,8 @@ private static Span PowCore(Span value, int valueLength, for (int i = 0; i < power.Length - 1; i++) { - uint p = power[i]; - for (int j = 0; j < 32; j++) + nuint p = power[i]; + for (int j = 0; j < kcbitNuint; j++) { if ((p & 1) == 1) { @@ -508,10 +539,10 @@ private static Span PowCore(Span value, int valueLength, return PowCore(value, valueLength, power[power.Length - 1], reducer, result, resultLength, temp); } - private static Span PowCore(Span value, int valueLength, - uint power, in FastReducer reducer, - Span result, int resultLength, - Span temp) + private static Span PowCore(Span value, int valueLength, + nuint 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. diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs index 9ef5ea7c359d47..b634c501f1f1e2 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs @@ -10,11 +10,11 @@ namespace System.Numerics { internal static partial class BigIntegerCalculator { - public static void RotateLeft(Span bits, long rotateLeftAmount) + public static void RotateLeft(Span bits, long rotateLeftAmount) { Debug.Assert(Math.Abs(rotateLeftAmount) <= 0x80000000); - const int digitShiftMax = (int)(0x80000000 / 32); + int digitShiftMax = (int)((long)0x80000000 / kcbitNuint); int digitShift = digitShiftMax; int smallShift = 0; @@ -22,24 +22,24 @@ public static void RotateLeft(Span bits, long rotateLeftAmount) if (rotateLeftAmount < 0) { if (rotateLeftAmount != -0x80000000) - (digitShift, smallShift) = Math.DivRem(-(int)rotateLeftAmount, 32); + (digitShift, smallShift) = Math.DivRem(-(int)rotateLeftAmount, kcbitNuint); RotateRight(bits, digitShift % bits.Length, smallShift); } else { if (rotateLeftAmount != 0x80000000) - (digitShift, smallShift) = Math.DivRem((int)rotateLeftAmount, 32); + (digitShift, smallShift) = Math.DivRem((int)rotateLeftAmount, kcbitNuint); RotateLeft(bits, digitShift % bits.Length, smallShift); } } - public static void RotateLeft(Span bits, int digitShift, int smallShift) + public static void RotateLeft(Span bits, int digitShift, int smallShift) { Debug.Assert(bits.Length > 0); - LeftShiftSelf(bits, smallShift, out uint carry); + LeftShiftSelf(bits, smallShift, out nuint carry); bits[0] |= carry; if (digitShift == 0) @@ -48,11 +48,11 @@ public static void RotateLeft(Span bits, int digitShift, int smallShift) SwapUpperAndLower(bits, bits.Length - digitShift); } - public static void RotateRight(Span bits, int digitShift, int smallShift) + public static void RotateRight(Span bits, int digitShift, int smallShift) { Debug.Assert(bits.Length > 0); - RightShiftSelf(bits, smallShift, out uint carry); + RightShiftSelf(bits, smallShift, out nuint carry); bits[^1] |= carry; if (digitShift == 0) @@ -61,23 +61,23 @@ public static void RotateRight(Span bits, int digitShift, int smallShift) SwapUpperAndLower(bits, digitShift); } - private static void SwapUpperAndLower(Span bits, int lowerLength) + private static void SwapUpperAndLower(Span bits, int lowerLength) { Debug.Assert(lowerLength > 0); Debug.Assert(lowerLength < bits.Length); int upperLength = bits.Length - lowerLength; - Span lower = bits.Slice(0, lowerLength); - Span upper = bits.Slice(lowerLength); + Span lower = bits.Slice(0, lowerLength); + Span upper = bits.Slice(lowerLength); - Span lowerDst = bits.Slice(upperLength); + Span lowerDst = bits.Slice(upperLength); int tmpLength = Math.Min(lowerLength, upperLength); - uint[]? tmpFromPool = null; - Span tmp = ((uint)tmpLength <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : tmpFromPool = ArrayPool.Shared.Rent(tmpLength)).Slice(0, tmpLength); + nuint[]? tmpFromPool = null; + Span tmp = ((uint)tmpLength <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : tmpFromPool = ArrayPool.Shared.Rent(tmpLength)).Slice(0, tmpLength); if (upperLength < lowerLength) { @@ -93,63 +93,63 @@ stackalloc uint[StackAllocThreshold] } if (tmpFromPool != null) - ArrayPool.Shared.Return(tmpFromPool); + ArrayPool.Shared.Return(tmpFromPool); } - public static void LeftShiftSelf(Span bits, int shift, out uint carry) + public static void LeftShiftSelf(Span bits, int shift, out nuint carry) { - Debug.Assert((uint)shift < 32); + Debug.Assert((uint)shift < kcbitNuint); carry = 0; if (shift == 0 || bits.IsEmpty) return; - int back = 32 - shift; + int back = kcbitNuint - shift; if (Vector128.IsHardwareAccelerated) { carry = bits[^1] >> back; - ref uint start = ref MemoryMarshal.GetReference(bits); + ref nuint start = ref MemoryMarshal.GetReference(bits); int offset = bits.Length; - while (Vector512.IsHardwareAccelerated && offset >= Vector512.Count + 1) + while (Vector512.IsHardwareAccelerated && offset >= Vector512.Count + 1) { - Vector512 current = Vector512.LoadUnsafe(ref start, (nuint)(offset - Vector512.Count)) << shift; - Vector512 carries = Vector512.LoadUnsafe(ref start, (nuint)(offset - (Vector512.Count + 1))) >> back; + Vector512 current = Vector512.LoadUnsafe(ref start, (nuint)(offset - Vector512.Count)) << shift; + Vector512 carries = Vector512.LoadUnsafe(ref start, (nuint)(offset - (Vector512.Count + 1))) >> back; - Vector512 newValue = current | carries; + Vector512 newValue = current | carries; - Vector512.StoreUnsafe(newValue, ref start, (nuint)(offset - Vector512.Count)); - offset -= Vector512.Count; + Vector512.StoreUnsafe(newValue, ref start, (nuint)(offset - Vector512.Count)); + offset -= Vector512.Count; } - while (Vector256.IsHardwareAccelerated && offset >= Vector256.Count + 1) + while (Vector256.IsHardwareAccelerated && offset >= Vector256.Count + 1) { - Vector256 current = Vector256.LoadUnsafe(ref start, (nuint)(offset - Vector256.Count)) << shift; - Vector256 carries = Vector256.LoadUnsafe(ref start, (nuint)(offset - (Vector256.Count + 1))) >> back; + Vector256 current = Vector256.LoadUnsafe(ref start, (nuint)(offset - Vector256.Count)) << shift; + Vector256 carries = Vector256.LoadUnsafe(ref start, (nuint)(offset - (Vector256.Count + 1))) >> back; - Vector256 newValue = current | carries; + Vector256 newValue = current | carries; - Vector256.StoreUnsafe(newValue, ref start, (nuint)(offset - Vector256.Count)); - offset -= Vector256.Count; + Vector256.StoreUnsafe(newValue, ref start, (nuint)(offset - Vector256.Count)); + offset -= Vector256.Count; } - while (Vector128.IsHardwareAccelerated && offset >= Vector128.Count + 1) + while (Vector128.IsHardwareAccelerated && offset >= Vector128.Count + 1) { - Vector128 current = Vector128.LoadUnsafe(ref start, (nuint)(offset - Vector128.Count)) << shift; - Vector128 carries = Vector128.LoadUnsafe(ref start, (nuint)(offset - (Vector128.Count + 1))) >> back; + Vector128 current = Vector128.LoadUnsafe(ref start, (nuint)(offset - Vector128.Count)) << shift; + Vector128 carries = Vector128.LoadUnsafe(ref start, (nuint)(offset - (Vector128.Count + 1))) >> back; - Vector128 newValue = current | carries; + Vector128 newValue = current | carries; - Vector128.StoreUnsafe(newValue, ref start, (nuint)(offset - Vector128.Count)); - offset -= Vector128.Count; + Vector128.StoreUnsafe(newValue, ref start, (nuint)(offset - Vector128.Count)); + offset -= Vector128.Count; } - uint carry2 = 0; + nuint carry2 = 0; for (int i = 0; i < offset; i++) { - uint value = carry2 | bits[i] << shift; + nuint value = carry2 | bits[i] << shift; carry2 = bits[i] >> back; bits[i] = value; } @@ -159,66 +159,66 @@ public static void LeftShiftSelf(Span bits, int shift, out uint carry) carry = 0; for (int i = 0; i < bits.Length; i++) { - uint value = carry | bits[i] << shift; + nuint value = carry | bits[i] << shift; carry = bits[i] >> back; bits[i] = value; } } } - public static void RightShiftSelf(Span bits, int shift, out uint carry) + public static void RightShiftSelf(Span bits, int shift, out nuint carry) { - Debug.Assert((uint)shift < 32); + Debug.Assert((uint)shift < kcbitNuint); carry = 0; if (shift == 0 || bits.IsEmpty) return; - int back = 32 - shift; + int back = kcbitNuint - shift; if (Vector128.IsHardwareAccelerated) { carry = bits[0] << back; - ref uint start = ref MemoryMarshal.GetReference(bits); + ref nuint start = ref MemoryMarshal.GetReference(bits); int offset = 0; - while (Vector512.IsHardwareAccelerated && bits.Length - offset >= Vector512.Count + 1) + while (Vector512.IsHardwareAccelerated && bits.Length - offset >= Vector512.Count + 1) { - Vector512 current = Vector512.LoadUnsafe(ref start, (nuint)offset) >> shift; - Vector512 carries = Vector512.LoadUnsafe(ref start, (nuint)(offset + 1)) << back; + Vector512 current = Vector512.LoadUnsafe(ref start, (nuint)offset) >> shift; + Vector512 carries = Vector512.LoadUnsafe(ref start, (nuint)(offset + 1)) << back; - Vector512 newValue = current | carries; + Vector512 newValue = current | carries; Vector512.StoreUnsafe(newValue, ref start, (nuint)offset); - offset += Vector512.Count; + offset += Vector512.Count; } - while (Vector256.IsHardwareAccelerated && bits.Length - offset >= Vector256.Count + 1) + while (Vector256.IsHardwareAccelerated && bits.Length - offset >= Vector256.Count + 1) { - Vector256 current = Vector256.LoadUnsafe(ref start, (nuint)offset) >> shift; - Vector256 carries = Vector256.LoadUnsafe(ref start, (nuint)(offset + 1)) << back; + Vector256 current = Vector256.LoadUnsafe(ref start, (nuint)offset) >> shift; + Vector256 carries = Vector256.LoadUnsafe(ref start, (nuint)(offset + 1)) << back; - Vector256 newValue = current | carries; + Vector256 newValue = current | carries; Vector256.StoreUnsafe(newValue, ref start, (nuint)offset); - offset += Vector256.Count; + offset += Vector256.Count; } - while (Vector128.IsHardwareAccelerated && bits.Length - offset >= Vector128.Count + 1) + while (Vector128.IsHardwareAccelerated && bits.Length - offset >= Vector128.Count + 1) { - Vector128 current = Vector128.LoadUnsafe(ref start, (nuint)offset) >> shift; - Vector128 carries = Vector128.LoadUnsafe(ref start, (nuint)(offset + 1)) << back; + Vector128 current = Vector128.LoadUnsafe(ref start, (nuint)offset) >> shift; + Vector128 carries = Vector128.LoadUnsafe(ref start, (nuint)(offset + 1)) << back; - Vector128 newValue = current | carries; + Vector128 newValue = current | carries; Vector128.StoreUnsafe(newValue, ref start, (nuint)offset); - offset += Vector128.Count; + offset += Vector128.Count; } - uint carry2 = 0; + nuint carry2 = 0; for (int i = bits.Length - 1; i >= offset; i--) { - uint value = carry2 | bits[i] >> shift; + nuint value = carry2 | bits[i] >> shift; carry2 = bits[i] << back; bits[i] = value; } @@ -228,7 +228,7 @@ public static void RightShiftSelf(Span bits, int shift, out uint carry) carry = 0; for (int i = bits.Length - 1; i >= 0; i--) { - uint value = carry | bits[i] >> shift; + nuint value = carry | bits[i] >> shift; carry = bits[i] << back; bits[i] = value; } 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 cf6d463bf40048..659fd9e4c1410c 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 @@ -20,10 +20,10 @@ internal static partial class BigIntegerCalculator #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Square(ReadOnlySpan value, Span bits) + public static void Square(ReadOnlySpan value, Span bits) { Debug.Assert(bits.Length == value.Length + value.Length); - Debug.Assert(!bits.ContainsAnyExcept(0u)); + Debug.Assert(!bits.ContainsAnyExcept((nuint)0)); // Executes different algorithms for computing z = a * a // based on the actual length of a. If a is "small" enough @@ -47,11 +47,11 @@ public static void Square(ReadOnlySpan value, Span bits) Toom3(value, bits); } - static void Toom3(ReadOnlySpan value, Span bits) + static void Toom3(ReadOnlySpan value, Span bits) { Debug.Assert(value.Length >= 3); Debug.Assert(bits.Length >= value.Length + value.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); // Based on the Toom-Cook multiplication we split left/right // into some smaller values, doing recursive multiplication. @@ -66,8 +66,8 @@ static void Toom3(ReadOnlySpan value, Span bits) // StackAllocThreshold, so ArrayPool is always used. int pAndQAllLength = pLength * 3; - uint[] pAndQAllFromPool = ArrayPool.Shared.Rent(pAndQAllLength); - Span pAndQAll = pAndQAllFromPool.AsSpan(0, pAndQAllLength); + nuint[] pAndQAllFromPool = ArrayPool.Shared.Rent(pAndQAllLength); + Span pAndQAll = pAndQAllFromPool.AsSpan(0, pAndQAllLength); pAndQAll.Clear(); Toom3Data p = Toom3Data.Build(value, n, pAndQAll.Slice(0, 3 * pLength)); @@ -75,20 +75,20 @@ static void Toom3(ReadOnlySpan value, Span bits) // Replace r_n in Wikipedia with z_n int rLength = pLength + pLength + 1; int rAndZAllLength = rLength * 3; - uint[] rAndZAllFromPool = ArrayPool.Shared.Rent(rAndZAllLength); - Span rAndZAll = rAndZAllFromPool.AsSpan(0, rAndZAllLength); + nuint[] rAndZAllFromPool = ArrayPool.Shared.Rent(rAndZAllLength); + Span rAndZAll = rAndZAllFromPool.AsSpan(0, rAndZAllLength); rAndZAll.Clear(); p.Square(n, bits, rAndZAll); - ArrayPool.Shared.Return(pAndQAllFromPool); - ArrayPool.Shared.Return(rAndZAllFromPool); + ArrayPool.Shared.Return(pAndQAllFromPool); + ArrayPool.Shared.Return(rAndZAllFromPool); } - static void Karatsuba(ReadOnlySpan value, Span bits) + static void Karatsuba(ReadOnlySpan value, Span bits) { Debug.Assert(bits.Length == value.Length + value.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); // The special form of the Toom-Cook multiplication, where we // split both operands into two operands, is also known @@ -102,12 +102,12 @@ static void Karatsuba(ReadOnlySpan value, Span bits) int n2 = n << 1; // ... split value like a = (a_1 << n) + a_0 - ReadOnlySpan valueLow = value.Slice(0, n); - ReadOnlySpan valueHigh = value.Slice(n); + ReadOnlySpan valueLow = value.Slice(0, n); + ReadOnlySpan valueHigh = value.Slice(n); // ... prepare our result array (to reuse its memory) - Span bitsLow = bits.Slice(0, n2); - Span bitsHigh = bits.Slice(n2); + Span bitsLow = bits.Slice(0, n2); + Span bitsHigh = bits.Slice(n2); // ... compute z_0 = a_0 * a_0 (squaring again!) Square(valueLow, bitsLow); @@ -116,17 +116,17 @@ static void Karatsuba(ReadOnlySpan value, Span bits) 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); + nuint[]? foldFromPool = null; + Span fold = ((uint)foldLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : foldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); fold.Clear(); int coreLength = foldLength + foldLength; - uint[]? coreFromPool = null; - Span core = ((uint)coreLength <= StackAllocThreshold - ? stackalloc uint[StackAllocThreshold] - : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); + nuint[]? coreFromPool = null; + Span core = ((uint)coreLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); core.Clear(); // ... compute z_a = a_1 + a_0 (call it fold...) @@ -136,7 +136,7 @@ static void Karatsuba(ReadOnlySpan value, Span bits) Square(fold, core); if (foldFromPool != null) - ArrayPool.Shared.Return(foldFromPool); + ArrayPool.Shared.Return(foldFromPool); SubtractCore(bitsHigh, bitsLow, core); @@ -144,17 +144,17 @@ static void Karatsuba(ReadOnlySpan value, Span bits) AddSelf(bits.Slice(n), core); if (coreFromPool != null) - ArrayPool.Shared.Return(coreFromPool); + ArrayPool.Shared.Return(coreFromPool); } - static void Naive(ReadOnlySpan value, Span bits) + static void Naive(ReadOnlySpan value, Span bits) { Debug.Assert(bits.Length == value.Length + value.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); // Switching to managed references helps eliminating // index bounds check... - ref uint resultPtr = ref MemoryMarshal.GetReference(bits); + ref nuint resultPtr = ref MemoryMarshal.GetReference(bits); // Squares the bits using the "grammar-school" method. // Envisioning the "rhombus" of a pen-and-paper calculation @@ -163,55 +163,77 @@ static void Naive(ReadOnlySpan value, Span bits) // Thus, we directly get z_i+j += 2 * a_j * a_i + c. // ATTENTION: an ordinary multiplication is safe, because - // z_i+j + a_j * a_i + c <= 2(2^32 - 1) + (2^32 - 1)^2 = - // = 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 < value.Length; i++) + // z_i+j + a_j * a_i + c <= 2(2^n - 1) + (2^n - 1)^2 = + // = 2^(2n) - 1, where n = kcbitNuint. But here we would need + // one extra bit... Hence, we split these operation and do some + // extra shifts. + if (nint.Size == 8) { - ulong carry = 0UL; - uint v = value[i]; - for (int j = 0; j < i; j++) + // On 64-bit, carry needs 65 bits (one more than the limb width), + // so we use UInt128 for the carry accumulator. + for (int i = 0; i < value.Length; i++) { - 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; + UInt128 carry = 0; + nuint v = value[i]; + for (int j = 0; j < i; j++) + { + UInt128 digit1 = (UInt128)(ulong)Unsafe.Add(ref resultPtr, i + j) + carry; + UInt128 digit2 = (UInt128)(ulong)value[j] * (ulong)v; + Unsafe.Add(ref resultPtr, i + j) = (nuint)(ulong)(digit1 + (digit2 << 1)); + carry = (digit2 + (digit1 >> 1)) >> 63; + } + UInt128 digits = (UInt128)(ulong)v * (ulong)v + carry; + Unsafe.Add(ref resultPtr, i + i) = (nuint)(ulong)digits; + Unsafe.Add(ref resultPtr, i + i + 1) = (nuint)(ulong)(digits >> 64); + } + } + else + { + // On 32-bit, carry needs 33 bits, so ulong suffices. + for (int i = 0; i < value.Length; i++) + { + ulong carry = 0; + nuint v = value[i]; + for (int j = 0; j < i; j++) + { + ulong digit1 = Unsafe.Add(ref resultPtr, i + j) + carry; + ulong digit2 = (ulong)value[j] * v; + Unsafe.Add(ref resultPtr, i + j) = (nuint)(uint)(digit1 + (digit2 << 1)); + carry = (digit2 + (digit1 >> 1)) >> 31; + } + ulong digits = (ulong)v * v + carry; + Unsafe.Add(ref resultPtr, i + i) = (nuint)(uint)digits; + Unsafe.Add(ref resultPtr, i + i + 1) = (nuint)(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); } } } - public static void Multiply(ReadOnlySpan left, uint right, Span bits) + public static void Multiply(ReadOnlySpan left, nuint right, Span bits) { Debug.Assert(bits.Length == left.Length + 1); - // Executes the multiplication for one big and one 32-bit integer. + // Executes the multiplication for one big and one native-width integer. // Since every step holds the already slightly familiar equation - // a_i * b + c <= 2^32 - 1 + (2^32 - 1)^2 < 2^64 - 1, - // we are safe regarding to overflows. + // a_i * b + c <= 2^n - 1 + (2^n - 1)^2 < 2^(2n) - 1, + // we are safe regarding to overflows (n = kcbitNuint). int i = 0; - ulong carry = 0UL; + nuint carry = 0; for (; i < left.Length; i++) { - ulong digits = (ulong)left[i] * right + carry; - bits[i] = unchecked((uint)digits); - carry = digits >> 32; + bits[i] = MulAdd(left[i], right, (nuint)0, ref carry); } - bits[i] = (uint)carry; + bits[i] = carry; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, Span bits) + public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, Span bits) { if (left.Length < right.Length) { - ReadOnlySpan tmp = right; + ReadOnlySpan tmp = right; right = left; left = tmp; } @@ -219,7 +241,7 @@ public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, S Debug.Assert(left.Length >= right.Length); Debug.Assert(right.Length >= 0); Debug.Assert(right.IsEmpty || bits.Length >= left.Length + right.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); Debug.Assert(MultiplyKaratsubaThreshold >= 2); Debug.Assert(MultiplyToom3Threshold >= 9); Debug.Assert(MultiplyKaratsubaThreshold <= MultiplyToom3Threshold); @@ -242,12 +264,12 @@ public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, S else Toom3(left, right, bits); - static void Toom3(ReadOnlySpan left, ReadOnlySpan right, Span bits) + static void Toom3(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(left.Length >= 3); Debug.Assert(left.Length >= right.Length); Debug.Assert(bits.Length >= left.Length + right.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); // Based on the Toom-Cook multiplication we split left/right // into some smaller values, doing recursive multiplication. @@ -268,8 +290,8 @@ static void Toom3(ReadOnlySpan left, ReadOnlySpan right, Span // The threshold for Toom-3 is expected to be greater than // StackAllocThreshold, so ArrayPool is always used. - uint[] pAndQAllFromPool = ArrayPool.Shared.Rent(pAndQAllLength); - Span pAndQAll = pAndQAllFromPool.AsSpan(0, pAndQAllLength); + nuint[] pAndQAllFromPool = ArrayPool.Shared.Rent(pAndQAllLength); + Span pAndQAll = pAndQAllFromPool.AsSpan(0, pAndQAllLength); pAndQAll.Clear(); Toom3Data p = Toom3Data.Build(left, n, pAndQAll.Slice(0, 3 * pLength)); @@ -278,17 +300,17 @@ static void Toom3(ReadOnlySpan left, ReadOnlySpan right, Span // Replace r_n in Wikipedia with z_n int rLength = pLength + pLength + 1; int rAndZAllLength = rLength * 3; - uint[] rAndZAllFromPool = ArrayPool.Shared.Rent(rAndZAllLength); - Span rAndZAll = rAndZAllFromPool.AsSpan(0, rAndZAllLength); + nuint[] rAndZAllFromPool = ArrayPool.Shared.Rent(rAndZAllLength); + Span rAndZAll = rAndZAllFromPool.AsSpan(0, rAndZAllLength); rAndZAll.Clear(); p.MultiplyOther(q, n, bits, rAndZAll); - ArrayPool.Shared.Return(pAndQAllFromPool); - ArrayPool.Shared.Return(rAndZAllFromPool); + ArrayPool.Shared.Return(pAndQAllFromPool); + ArrayPool.Shared.Return(rAndZAllFromPool); } - static void Toom25(ReadOnlySpan left, ReadOnlySpan right, Span bits, int n) + static void Toom25(ReadOnlySpan left, ReadOnlySpan right, Span bits, int n) { // Toom 2.5 @@ -296,17 +318,17 @@ static void Toom25(ReadOnlySpan left, ReadOnlySpan right, Span Debug.Assert(right.Length > n); Debug.Assert(right.Length <= 2 * n); Debug.Assert(bits.Length >= left.Length + right.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); - ReadOnlySpan left0 = left.Slice(0, n).TrimEnd(0u); - ReadOnlySpan left1 = left.Slice(n, n).TrimEnd(0u); - ReadOnlySpan left2 = left.Slice(n + n); + ReadOnlySpan left0 = left.Slice(0, n).TrimEnd((nuint)0); + ReadOnlySpan left1 = left.Slice(n, n).TrimEnd((nuint)0); + ReadOnlySpan left2 = left.Slice(n + n); - ReadOnlySpan right0 = right.Slice(0, n).TrimEnd(0u); - ReadOnlySpan right1 = right.Slice(n); + ReadOnlySpan right0 = right.Slice(0, n).TrimEnd((nuint)0); + ReadOnlySpan right1 = right.Slice(n); - Span z0 = bits.Slice(0, left0.Length + right0.Length); - Span z3 = bits.Slice(n * 3); + Span z0 = bits.Slice(0, left0.Length + right0.Length); + Span z3 = bits.Slice(n * 3); Multiply(left0, right0, z0); Multiply(left2, right1, z3); @@ -315,14 +337,14 @@ static void Toom25(ReadOnlySpan left, ReadOnlySpan right, Span // The threshold for Toom-3 is expected to be greater than // StackAllocThreshold, so ArrayPool is always used. - uint[] pAndQAllFromPool = ArrayPool.Shared.Rent(pAndQAllLength); - Span pAndQAll = pAndQAllFromPool.AsSpan(0, pAndQAllLength); + nuint[] pAndQAllFromPool = ArrayPool.Shared.Rent(pAndQAllLength); + Span pAndQAll = pAndQAllFromPool.AsSpan(0, pAndQAllLength); pAndQAll.Clear(); - Span p1 = pAndQAll.Slice(0, pLength); - Span pm1 = pAndQAll.Slice(pLength, pLength); - Span q1 = pAndQAll.Slice(pLength * 2, pLength); - Span qm1 = pAndQAll.Slice(pLength * 3, pLength); + Span p1 = pAndQAll.Slice(0, pLength); + Span pm1 = pAndQAll.Slice(pLength, pLength); + Span q1 = pAndQAll.Slice(pLength * 2, pLength); + Span qm1 = pAndQAll.Slice(pLength * 3, pLength); int pm1Sign = 1; int qm1Sign = 1; @@ -335,27 +357,27 @@ static void Toom25(ReadOnlySpan left, ReadOnlySpan right, Span pm1.CopyTo(p1); AddSelf(p1, left1); SubtractSelf(pm1, ref pm1Sign, left1); - p1 = p1.TrimEnd(0u); - pm1 = pm1.TrimEnd(0u); + p1 = p1.TrimEnd((nuint)0); + pm1 = pm1.TrimEnd((nuint)0); right0.CopyTo(q1); right0.CopyTo(qm1); AddSelf(q1, right1); SubtractSelf(qm1, ref qm1Sign, right1); - q1 = q1.TrimEnd(0u); - qm1 = qm1.TrimEnd(0u); + q1 = q1.TrimEnd((nuint)0); + qm1 = qm1.TrimEnd((nuint)0); int cLength = pLength * 2 + 1; int cAllLength = cLength * 3; - uint[] cAllFromPool = ArrayPool.Shared.Rent(cAllLength); - Span cAll = cAllFromPool.AsSpan(0, cAllLength); + nuint[] cAllFromPool = ArrayPool.Shared.Rent(cAllLength); + Span cAll = cAllFromPool.AsSpan(0, cAllLength); cAll.Clear(); - Span z1 = cAll.Slice(0, cLength); - Span c1 = z1.Slice(0, p1.Length + q1.Length); + Span z1 = cAll.Slice(0, cLength); + Span c1 = z1.Slice(0, p1.Length + q1.Length); - Span z2 = cAll.Slice(cLength, cLength); - Span cm1 = cAll.Slice(cLength * 2, pm1.Length + qm1.Length); + Span z2 = cAll.Slice(cLength, cLength); + Span cm1 = cAll.Slice(cLength * 2, pm1.Length + qm1.Length); Multiply(p1, q1, c1); Multiply(pm1, qm1, cm1); @@ -367,21 +389,21 @@ static void Toom25(ReadOnlySpan left, ReadOnlySpan right, Span AddSelf(z2, ref z2Sign, cm1, -cm1Sign); Debug.Assert(z2Sign >= 0); RightShiftOne(z2); - SubtractSelf(z2, z3.TrimEnd(0u)); + SubtractSelf(z2, z3.TrimEnd((nuint)0)); AddSelf(z1, cm1); RightShiftOne(z1); - AddSelf(z1, z0.TrimEnd(0u)); + AddSelf(z1, z0.TrimEnd((nuint)0)); - ArrayPool.Shared.Return(pAndQAllFromPool); + ArrayPool.Shared.Return(pAndQAllFromPool); - AddSelf(bits.Slice(n), z1.TrimEnd(0u)); - AddSelf(bits.Slice(n * 2), z2.TrimEnd(0u)); + AddSelf(bits.Slice(n), z1.TrimEnd((nuint)0)); + AddSelf(bits.Slice(n * 2), z2.TrimEnd((nuint)0)); - ArrayPool.Shared.Return(cAllFromPool); + ArrayPool.Shared.Return(cAllFromPool); } - static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span bits, int n) + static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span bits, int n) { // upper lower // A= | | | a1 = a[n..2n] | a0 = a[0..n] | @@ -405,23 +427,23 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span= right.Length); Debug.Assert(bits.Length >= left.Length + right.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); // ... we need to determine our new length (just the half) Debug.Assert(2 * n - left.Length is 0 or 1); Debug.Assert(right.Length > n); // ... split left like a = (a_1 << n) + a_0 - ReadOnlySpan leftLow = left.Slice(0, n); - ReadOnlySpan leftHigh = left.Slice(n); + ReadOnlySpan leftLow = left.Slice(0, n); + ReadOnlySpan leftHigh = left.Slice(n); // ... split right like b = (b_1 << n) + b_0 - ReadOnlySpan rightLow = right.Slice(0, n); - ReadOnlySpan rightHigh = right.Slice(n); + ReadOnlySpan rightLow = right.Slice(0, n); + ReadOnlySpan rightHigh = right.Slice(n); // ... prepare our result array (to reuse its memory) - Span bitsLow = bits.Slice(0, n + n); - Span bitsHigh = bits.Slice(n + n); + Span bitsLow = bits.Slice(0, n + n); + Span bitsHigh = bits.Slice(n + n); Debug.Assert(leftLow.Length >= leftHigh.Length); Debug.Assert(rightLow.Length >= rightHigh.Length); @@ -434,16 +456,16 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span leftFold = ((uint)foldLength <= StackAllocThreshold - ? stackalloc uint[StackAllocThreshold] - : leftFoldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); + nuint[]? leftFoldFromPool = null; + Span leftFold = ((uint)foldLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : leftFoldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); leftFold.Clear(); - uint[]? rightFoldFromPool = null; - Span rightFold = ((uint)foldLength <= StackAllocThreshold - ? stackalloc uint[StackAllocThreshold] - : rightFoldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); + nuint[]? rightFoldFromPool = null; + Span rightFold = ((uint)foldLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : rightFoldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); rightFold.Clear(); // ... compute z_a = a_1 + a_0 (call it fold...) @@ -453,20 +475,20 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span core = ((uint)coreLength <= StackAllocThreshold - ? stackalloc uint[StackAllocThreshold] - : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); + nuint[]? coreFromPool = null; + Span core = ((uint)coreLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); core.Clear(); // ... compute z_ab = z_a * z_b Multiply(leftFold, rightFold, core); if (leftFoldFromPool != null) - ArrayPool.Shared.Return(leftFoldFromPool); + ArrayPool.Shared.Return(leftFoldFromPool); if (rightFoldFromPool != null) - ArrayPool.Shared.Return(rightFoldFromPool); + ArrayPool.Shared.Return(rightFoldFromPool); // ... compute z_1 = z_a * z_b - z_0 - z_2 = a_0 * b_1 + a_1 * b_0 SubtractCore(bitsLow, bitsHigh, core); @@ -474,39 +496,39 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span.Shared.Return(coreFromPool); + ArrayPool.Shared.Return(coreFromPool); } - static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span bits, int n) + static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span bits, int n) { Debug.Assert(left.Length >= right.Length); Debug.Assert(2 * n - left.Length is 0 or 1); Debug.Assert(right.Length <= n); Debug.Assert(bits.Length >= left.Length + right.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); // ... split left like a = (a_1 << n) + a_0 - ReadOnlySpan leftLow = left.Slice(0, n); - ReadOnlySpan leftHigh = left.Slice(n); + ReadOnlySpan leftLow = left.Slice(0, n); + ReadOnlySpan leftHigh = left.Slice(n); Debug.Assert(leftLow.Length >= leftHigh.Length); // ... prepare our result array (to reuse its memory) - Span bitsLow = bits.Slice(0, n + right.Length); - Span bitsHigh = bits.Slice(n); + Span bitsLow = bits.Slice(0, n + right.Length); + Span bitsHigh = bits.Slice(n); // ... compute low Multiply(leftLow, right, bitsLow); int carryLength = right.Length; - uint[]? carryFromPool = null; - Span carry = ((uint)carryLength <= StackAllocThreshold - ? stackalloc uint[StackAllocThreshold] - : carryFromPool = ArrayPool.Shared.Rent(carryLength)).Slice(0, carryLength); + nuint[]? carryFromPool = null; + Span carry = ((uint)carryLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : carryFromPool = ArrayPool.Shared.Rent(carryLength)).Slice(0, carryLength); - Span carryOrig = bitsHigh.Slice(0, right.Length); + Span carryOrig = bitsHigh.Slice(0, right.Length); carryOrig.CopyTo(carry); carryOrig.Clear(); @@ -516,70 +538,68 @@ static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span.Shared.Return(carryFromPool); + ArrayPool.Shared.Return(carryFromPool); } - static void Naive(ReadOnlySpan left, ReadOnlySpan right, Span bits) + static void Naive(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(right.Length < MultiplyKaratsubaThreshold); // Switching to managed references helps eliminating // index bounds check... - ref uint resultPtr = ref MemoryMarshal.GetReference(bits); + ref nuint 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... // The inner multiplication operations are safe, because - // z_i+j + a_j * b_i + c <= 2(2^32 - 1) + (2^32 - 1)^2 = - // = 2^64 - 1 (which perfectly matches with ulong!). + // z_i+j + a_j * b_i + c <= 2(2^n - 1) + (2^n - 1)^2 = + // = 2^(2n) - 1, where n = kcbitNuint. for (int i = 0; i < right.Length; i++) { - uint rv = right[i]; - ulong carry = 0UL; + nuint rv = right[i]; + nuint carry = 0; for (int j = 0; j < left.Length; j++) { - ref uint elementPtr = ref Unsafe.Add(ref resultPtr, i + j); - ulong digits = elementPtr + carry + (ulong)left[j] * rv; - elementPtr = unchecked((uint)digits); - carry = digits >> 32; + ref nuint elementPtr = ref Unsafe.Add(ref resultPtr, i + j); + elementPtr = MulAdd(left[j], rv, elementPtr, ref carry); } - Unsafe.Add(ref resultPtr, i + left.Length) = (uint)carry; + Unsafe.Add(ref resultPtr, i + left.Length) = carry; } } } [StructLayout(LayoutKind.Auto)] private readonly ref struct Toom3Data( - ReadOnlySpan c0, - ReadOnlySpan cInf, - ReadOnlySpan c1, - ReadOnlySpan cm1, + ReadOnlySpan c0, + ReadOnlySpan cInf, + ReadOnlySpan c1, + ReadOnlySpan cm1, int cm1Sign, - ReadOnlySpan cm2, + ReadOnlySpan cm2, int cm2Sign) { - private readonly ReadOnlySpan c0 = c0; - private readonly ReadOnlySpan c1 = c1; - private readonly ReadOnlySpan cInf = cInf; - private readonly ReadOnlySpan cm1 = cm1; - private readonly ReadOnlySpan cm2 = cm2; + private readonly ReadOnlySpan c0 = c0; + private readonly ReadOnlySpan c1 = c1; + private readonly ReadOnlySpan cInf = cInf; + private readonly ReadOnlySpan cm1 = cm1; + private readonly ReadOnlySpan cm2 = cm2; private readonly int cm1Sign = cm1Sign; private readonly int cm2Sign = cm2Sign; - public static Toom3Data Build(ReadOnlySpan value, int n, Span buffer) + public static Toom3Data Build(ReadOnlySpan value, int n, Span buffer) { - Debug.Assert(!buffer.ContainsAnyExcept(0u)); + Debug.Assert(!buffer.ContainsAnyExcept((nuint)0)); Debug.Assert(buffer.Length == 3 * (n + 1)); Debug.Assert(value.Length > n); Debug.Assert(value[^1] != 0); int pLength = n + 1; - ReadOnlySpan v0, v1, v2; + ReadOnlySpan v0, v1, v2; - v0 = value.Slice(0, n).TrimEnd(0u); + v0 = value.Slice(0, n).TrimEnd((nuint)0); if (value.Length <= n + n) { v1 = value.Slice(n); @@ -587,12 +607,12 @@ public static Toom3Data Build(ReadOnlySpan value, int n, Span buffer } else { - v1 = value.Slice(n, n).TrimEnd(0u); + v1 = value.Slice(n, n).TrimEnd((nuint)0); v2 = value.Slice(n + n); } - Span p1 = buffer.Slice(0, pLength); - Span pm1 = buffer.Slice(pLength, pLength); + Span p1 = buffer.Slice(0, pLength); + Span pm1 = buffer.Slice(pLength, pLength); // Calculate p(1) = p_0 + m_1, p(-1) = p_0 - m_1 int pm1Sign = 1; @@ -605,14 +625,14 @@ public static Toom3Data Build(ReadOnlySpan value, int n, Span buffer SubtractSelf(pm1, ref pm1Sign, v1); - pm1 = pm1Sign != 0 ? pm1.TrimEnd(0u) : default; + pm1 = pm1Sign != 0 ? pm1.TrimEnd((nuint)0) : default; } // Calculate p(-2) = (p(-1) + m_2)*2 - m_0 int pm2Sign = pm1Sign; - Span pm2 = buffer.Slice(pLength + pLength, pLength); + Span pm2 = buffer.Slice(pLength + pLength, pLength); { - Debug.Assert(!pm2.ContainsAnyExcept(0u)); + Debug.Assert(!pm2.ContainsAnyExcept((nuint)0)); Debug.Assert(pm1.IsEmpty || pm1[^1] != 0); Debug.Assert(v0.IsEmpty || v0[^1] != 0); Debug.Assert(v2.IsEmpty || v2[^1] != 0); @@ -624,61 +644,61 @@ public static Toom3Data Build(ReadOnlySpan value, int n, Span buffer // Calculate p(-2) = (p(-1) + m_2)*2 { - Debug.Assert(pm2[^1] < 0x8000_0000); + Debug.Assert(pm2[^1] < ((nuint)1 << (kcbitNuint - 1))); LeftShiftOne(pm2); } - Debug.Assert(pm2[^1] != uint.MaxValue); + Debug.Assert(pm2[^1] != nuint.MaxValue); // Calculate p(-2) = (p(-1) + m_2)*2 - m_0 SubtractSelf(pm2, ref pm2Sign, v0); - pm2 = pm2.TrimEnd(0u); + pm2 = pm2.TrimEnd((nuint)0); } return new Toom3Data( c0: v0, - c1: p1.TrimEnd(0u), + c1: p1.TrimEnd((nuint)0), cInf: v2, - cm1: pm1.TrimEnd(0u), + cm1: pm1.TrimEnd((nuint)0), cm2: pm2, cm1Sign: pm1Sign, cm2Sign: pm2Sign ); } - public void MultiplyOther(in Toom3Data right, int n, Span bits, Span buffer) + public void MultiplyOther(in Toom3Data right, int n, Span bits, Span buffer) { - Debug.Assert(!buffer.ContainsAnyExcept(0u)); + Debug.Assert(!buffer.ContainsAnyExcept((nuint)0)); Debug.Assert(cInf.Length >= right.cInf.Length); int rLength = n + n + 3; - ReadOnlySpan p0 = c0; - ReadOnlySpan q0 = right.c0; + ReadOnlySpan p0 = c0; + ReadOnlySpan q0 = right.c0; - ReadOnlySpan p1 = c1; - ReadOnlySpan q1 = right.c1; + ReadOnlySpan p1 = c1; + ReadOnlySpan q1 = right.c1; - ReadOnlySpan pm1 = cm1; - ReadOnlySpan qm1 = right.cm1; + ReadOnlySpan pm1 = cm1; + ReadOnlySpan qm1 = right.cm1; - ReadOnlySpan pm2 = cm2; - ReadOnlySpan qm2 = right.cm2; + ReadOnlySpan pm2 = cm2; + ReadOnlySpan qm2 = right.cm2; - ReadOnlySpan pInf = cInf; - ReadOnlySpan qInf = right.cInf; + ReadOnlySpan pInf = cInf; + ReadOnlySpan qInf = right.cInf; - Span r0 = bits.Slice(0, p0.Length + q0.Length); - Span rInf = + Span r0 = bits.Slice(0, p0.Length + q0.Length); + Span rInf = !qInf.IsEmpty ? bits.Slice(4 * n, pInf.Length + qInf.Length) : default; - Span r1 = buffer.Slice(0, p1.Length + q1.Length); - Span rm1 = buffer.Slice(rLength, pm1.Length + qm1.Length); - Span rm2 = buffer.Slice(rLength * 2, pm2.Length + qm2.Length); + Span r1 = buffer.Slice(0, p1.Length + q1.Length); + Span rm1 = buffer.Slice(rLength, pm1.Length + qm1.Length); + Span rm2 = buffer.Slice(rLength * 2, pm2.Length + qm2.Length); Multiply(p0, q0, r0); Multiply(p1, q1, r1); @@ -688,8 +708,8 @@ public void MultiplyOther(in Toom3Data right, int n, Span bits, Span Toom3CalcResult( n, - r0: r0.TrimEnd(0u), - rInf: rInf.TrimEnd(0u), + r0: r0.TrimEnd((nuint)0), + rInf: rInf.TrimEnd((nuint)0), z1: buffer.Slice(0, rLength), r1Length: ActualLength(r1), z2: buffer.Slice(rLength, rLength), @@ -700,25 +720,25 @@ public void MultiplyOther(in Toom3Data right, int n, Span bits, Span bits ); } - public void Square(int n, Span bits, Span buffer) + public void Square(int n, Span bits, Span buffer) { - Debug.Assert(!buffer.ContainsAnyExcept(0u)); + Debug.Assert(!buffer.ContainsAnyExcept((nuint)0)); Debug.Assert(!cInf.IsEmpty); int rLength = n + n + 3; - ReadOnlySpan p0 = c0; - ReadOnlySpan p1 = c1; - ReadOnlySpan pm1 = cm1; - ReadOnlySpan pm2 = cm2; - ReadOnlySpan pInf = cInf; + ReadOnlySpan p0 = c0; + ReadOnlySpan p1 = c1; + ReadOnlySpan pm1 = cm1; + ReadOnlySpan pm2 = cm2; + ReadOnlySpan pInf = cInf; - Span r0 = bits.Slice(0, p0.Length << 1); - Span rInf = bits.Slice(4 * n, pInf.Length << 1); + Span r0 = bits.Slice(0, p0.Length << 1); + Span rInf = bits.Slice(4 * n, pInf.Length << 1); - Span r1 = buffer.Slice(0, p1.Length << 1); - Span rm1 = buffer.Slice(rLength, pm1.Length << 1); - Span rm2 = buffer.Slice(rLength * 2, pm2.Length << 1); + Span r1 = buffer.Slice(0, p1.Length << 1); + Span rm1 = buffer.Slice(rLength, pm1.Length << 1); + Span rm2 = buffer.Slice(rLength * 2, pm2.Length << 1); BigIntegerCalculator.Square(p0, r0); BigIntegerCalculator.Square(p1, r1); @@ -728,8 +748,8 @@ public void Square(int n, Span bits, Span buffer) Toom3CalcResult( n, - r0: r0.TrimEnd(0u), - rInf: rInf.TrimEnd(0u), + r0: r0.TrimEnd((nuint)0), + rInf: rInf.TrimEnd((nuint)0), z1: buffer.Slice(0, rLength), r1Length: ActualLength(r1), z2: buffer.Slice(rLength, rLength), @@ -743,16 +763,16 @@ public void Square(int n, Span bits, Span buffer) private static void Toom3CalcResult( int n, - ReadOnlySpan r0, - ReadOnlySpan rInf, - Span z1, + ReadOnlySpan r0, + ReadOnlySpan rInf, + Span z1, int r1Length, - Span z2, + Span z2, int z2Sign, int rm1Length, - Span z3, + Span z3, int z3Sign, - Span bits) + Span bits) { int z1Sign = Math.Sign(r1Length); @@ -762,7 +782,7 @@ private static void Toom3CalcResult( SubtractSelf(z3, ref z3Sign, z1.Slice(0, r1Length)); // Calc (r(-2) - r(1))/3 - DivideThreeSelf(z3.TrimEnd(0u)); + DivideThreeSelf(z3.TrimEnd((nuint)0)); } // Calc z_1 = (r(1) - r(-1))/2 @@ -795,34 +815,44 @@ private static void Toom3CalcResult( // Calc z_2 = z_2 + z_1 - r(Inf) { - AddSelf(z2, ref z2Sign, z1.TrimEnd(0u)); + AddSelf(z2, ref z2Sign, z1.TrimEnd((nuint)0)); SubtractSelf(z2, ref z2Sign, rInf); } // Calc z_1 = z_1 - z_3 - SubtractSelf(z1, ref z1Sign, z3.TrimEnd(0u)); + SubtractSelf(z1, ref z1Sign, z3.TrimEnd((nuint)0)); Debug.Assert(z1Sign >= 0); Debug.Assert(z2Sign >= 0); Debug.Assert(z3Sign >= 0); - AddSelf(bits.Slice(n), z1.TrimEnd(0u)); - AddSelf(bits.Slice(2 * n), z2.TrimEnd(0u)); + AddSelf(bits.Slice(n), z1.TrimEnd((nuint)0)); + AddSelf(bits.Slice(2 * n), z2.TrimEnd((nuint)0)); if (bits.Length >= 3 * n) - AddSelf(bits.Slice(3 * n), z3.TrimEnd(0u)); + AddSelf(bits.Slice(3 * n), z3.TrimEnd((nuint)0)); } } - private static void DivideThreeSelf(Span bits) + private static void DivideThreeSelf(Span bits) { - const uint oneThird = (uint)((1ul << 32) / 3); - const uint twoThirds = (uint)((2ul << 32) / 3); + nuint oneThird, twoThirds; + if (nint.Size == 8) + { + oneThird = unchecked((nuint)0x5555_5555_5555_5555); + twoThirds = unchecked((nuint)0xAAAA_AAAA_AAAA_AAAA); + } + else + { + oneThird = (nuint)0x5555_5555; + twoThirds = (nuint)0xAAAA_AAAA; + } - uint carry = 0; + nuint carry = 0; for (int i = bits.Length - 1; i >= 0; i--) { - (uint quo, uint rem) = Math.DivRem(bits[i], 3); + nuint quo = bits[i] / (nuint)3; + nuint rem = bits[i] - quo * (nuint)3; Debug.Assert(carry < 3); @@ -856,7 +886,7 @@ private static void DivideThreeSelf(Span bits) Debug.Assert(carry == 0); } - private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan right, Span core) + private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan right, Span core) { Debug.Assert(left.Length >= right.Length); Debug.Assert(core.Length >= left.Length); @@ -869,37 +899,66 @@ private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan rig // one "run", if we do this computation within a single one... int i = 0; - long carry = 0L; // Switching to managed references helps eliminating // index bounds check... - ref uint leftPtr = ref MemoryMarshal.GetReference(left); - ref uint corePtr = ref MemoryMarshal.GetReference(core); + ref nuint leftPtr = ref MemoryMarshal.GetReference(left); + ref nuint corePtr = ref MemoryMarshal.GetReference(core); - for (; i < right.Length; i++) + if (nint.Size == 8) { - 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; - } + Int128 carry = 0; - for (; i < left.Length; i++) - { - long digit = (Unsafe.Add(ref corePtr, i) + carry) - left[i]; - Unsafe.Add(ref corePtr, i) = unchecked((uint)digit); - carry = digit >> 32; - } + for (; i < right.Length; i++) + { + Int128 digit = (Int128)(ulong)Unsafe.Add(ref corePtr, i) + carry - (ulong)Unsafe.Add(ref leftPtr, i) - (ulong)right[i]; + Unsafe.Add(ref corePtr, i) = (nuint)(ulong)digit; + carry = digit >> 64; + } + + for (; i < left.Length; i++) + { + Int128 digit = (Int128)(ulong)Unsafe.Add(ref corePtr, i) + carry - (ulong)left[i]; + Unsafe.Add(ref corePtr, i) = (nuint)(ulong)digit; + carry = digit >> 64; + } - for (; carry != 0 && i < core.Length; i++) + for (; carry != 0 && i < core.Length; i++) + { + Int128 digit = (Int128)(ulong)core[i] + carry; + core[i] = (nuint)(ulong)digit; + carry = digit >> 64; + } + } + else { - long digit = core[i] + carry; - core[i] = (uint)digit; - carry = digit >> 32; + long carry = 0L; + + for (; i < right.Length; i++) + { + long digit = ((long)(uint)Unsafe.Add(ref corePtr, i) + carry) - (uint)Unsafe.Add(ref leftPtr, i) - (uint)right[i]; + Unsafe.Add(ref corePtr, i) = (nuint)(uint)digit; + carry = digit >> 32; + } + + for (; i < left.Length; i++) + { + long digit = ((long)(uint)Unsafe.Add(ref corePtr, i) + carry) - (uint)left[i]; + Unsafe.Add(ref corePtr, i) = (nuint)(uint)digit; + carry = digit >> 32; + } + + for (; carry != 0 && i < core.Length; i++) + { + long digit = (uint)core[i] + carry; + core[i] = (nuint)(uint)digit; + carry = digit >> 32; + } } } - private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan right, int rightSign) + private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan right, int rightSign) { Debug.Assert(left.Length >= right.Length); @@ -911,15 +970,15 @@ private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan left, ref int leftSign, ReadOnlySpan right) + private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); - right = right.TrimEnd(0u); + right = right.TrimEnd((nuint)0); if (leftSign == 0) { - Debug.Assert(!left.ContainsAnyExcept(0u)); + Debug.Assert(!left.ContainsAnyExcept((nuint)0)); if (!right.IsEmpty) { @@ -946,15 +1005,15 @@ private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan left, ref int leftSign, ReadOnlySpan right) + private static void SubtractSelf(Span left, ref int leftSign, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); - right = right.TrimEnd(0u); + right = right.TrimEnd((nuint)0); if (leftSign == 0) { @@ -983,30 +1042,41 @@ private static void SubtractSelf(Span left, ref int leftSign, ReadOnlySpan { left = left.Slice(0, right.Length); SubtractSelf(left, right); - NumericsHelpers.DangerousMakeTwosComplement(left); + MakeTwosComplement(left); } } } - private static void LeftShiftOne(Span bits) + private static void LeftShiftOne(Span bits) { - uint carry = 0; + nuint carry = 0; for (int i = 0; i < bits.Length; i++) { - uint value = carry | bits[i] << 1; - carry = bits[i] >> 31; + nuint value = carry | bits[i] << 1; + carry = bits[i] >> (kcbitNuint - 1); bits[i] = value; } } - private static void RightShiftOne(Span bits) + private static void RightShiftOne(Span bits) { - uint carry = 0; + nuint carry = 0; for (int i = bits.Length - 1; i >= 0; i--) { - uint value = carry | bits[i] >> 1; - carry = bits[i] << 31; + nuint value = carry | bits[i] >> 1; + carry = bits[i] << (kcbitNuint - 1); bits[i] = value; } } + + private static void MakeTwosComplement(Span d) + { + int i = d.IndexOfAnyExcept((nuint)0); + if ((uint)i >= (uint)d.Length) + return; + d[i] = (nuint)0 - d[i]; + d = d.Slice(i + 1); + for (int j = 0; j < d.Length; j++) + d[j] = ~d[j]; + } } } 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 index fc1b6b1011a81a..1fbdc37c1f3fb4 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System.Numerics { @@ -15,10 +16,17 @@ internal const #endif int StackAllocThreshold = 64; - public static int Compare(ReadOnlySpan left, ReadOnlySpan right) + // Number of bits per native-width limb: 32 on 32-bit, 64 on 64-bit. + internal static int kcbitNuint { - Debug.Assert(left.Length <= right.Length || left.Slice(right.Length).ContainsAnyExcept(0u)); - Debug.Assert(left.Length >= right.Length || right.Slice(left.Length).ContainsAnyExcept(0u)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => nint.Size * 8; + } + + public static int Compare(ReadOnlySpan left, ReadOnlySpan right) + { + Debug.Assert(left.Length <= right.Length || left.Slice(right.Length).ContainsAnyExcept((nuint)0)); + Debug.Assert(left.Length >= right.Length || right.Slice(left.Length).ContainsAnyExcept((nuint)0)); if (left.Length != right.Length) return left.Length < right.Length ? -1 : 1; @@ -31,7 +39,7 @@ public static int Compare(ReadOnlySpan left, ReadOnlySpan right) return left[iv] < right[iv] ? -1 : 1; } - private static int CompareActual(ReadOnlySpan left, ReadOnlySpan right) + private static int CompareActual(ReadOnlySpan left, ReadOnlySpan right) { if (left.Length != right.Length) { @@ -51,7 +59,7 @@ private static int CompareActual(ReadOnlySpan left, ReadOnlySpan rig return Compare(left, right); } - public static int ActualLength(ReadOnlySpan value) + public 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 @@ -63,7 +71,7 @@ public static int ActualLength(ReadOnlySpan value) return length; } - private static int Reduce(Span bits, ReadOnlySpan modulus) + private static int Reduce(Span bits, ReadOnlySpan modulus) { // Executes a modulo operation using the divide operation. @@ -77,10 +85,118 @@ private static int Reduce(Span bits, ReadOnlySpan modulus) } [Conditional("DEBUG")] - public static void InitializeForDebug(Span bits) + public static void InitializeForDebug(Span bits) { - // Reproduce the case where the return value of `stackalloc uint` is not initialized to zero. + // Reproduce the case where the return value of `stackalloc nuint` is not initialized to zero. bits.Fill(0xCD); } + + /// + /// Performs widening addition of two limbs plus a carry-in, returning the sum and carry-out. + /// On 64-bit: uses 128-bit arithmetic. On 32-bit: uses 64-bit arithmetic. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static nuint AddWithCarry(nuint a, nuint b, nuint carryIn, out nuint carryOut) + { + if (nint.Size == 8) + { + UInt128 sum = (UInt128)(ulong)a + (ulong)b + (ulong)carryIn; + carryOut = (nuint)(ulong)(sum >> 64); + return (nuint)(ulong)sum; + } + else + { + ulong sum = (ulong)a + b + carryIn; + carryOut = (nuint)(uint)(sum >> 32); + return (nuint)(uint)sum; + } + } + + /// + /// Performs widening subtraction of two limbs with a borrow-in, returning the difference and borrow-out. + /// borrowOut is 0 (no borrow) or 1 (borrow occurred). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static nuint SubWithBorrow(nuint a, nuint b, nuint borrowIn, out nuint borrowOut) + { + if (nint.Size == 8) + { + // Use unsigned underflow detection + nuint diff1 = a - b; + nuint b1 = (diff1 > a) ? (nuint)1 : (nuint)0; + nuint diff2 = diff1 - borrowIn; + nuint b2 = (diff2 > diff1) ? (nuint)1 : (nuint)0; + borrowOut = b1 + b2; + return diff2; + } + else + { + long diff = (long)(int)a - (int)b - (int)borrowIn; + borrowOut = (nuint)(uint)(-(int)(diff >> 32)); // 0 or 1 + return (nuint)(uint)diff; + } + } + + /// + /// Performs widening multiply: a * b → (hi, lo). Used for schoolbook multiply inner loops. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static nuint MulAdd(nuint a, nuint b, nuint addend, ref nuint carry) + { + if (nint.Size == 8) + { + UInt128 product = (UInt128)(ulong)a * (ulong)b + (ulong)addend + (ulong)carry; + carry = (nuint)(ulong)(product >> 64); + return (nuint)(ulong)product; + } + else + { + ulong product = (ulong)a * b + addend + carry; + carry = (nuint)(uint)(product >> 32); + return (nuint)(uint)product; + } + } + + /// + /// Widening divide: (hi:lo) / divisor → (quotient, remainder). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remainder) + { + if (nint.Size == 8) + { + UInt128 value = ((UInt128)(ulong)hi << 64) | (ulong)lo; + UInt128 digit = value / (ulong)divisor; + remainder = (nuint)(ulong)(value - digit * (ulong)divisor); + return (nuint)(ulong)digit; + } + else + { + ulong value = ((ulong)hi << 32) | lo; + ulong digit = value / divisor; + remainder = (nuint)(uint)(value - digit * divisor); + return (nuint)(uint)digit; + } + } + + /// + /// Widening multiply of two limbs, returning just the product as (hi, lo). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static nuint BigMul(nuint a, nuint b, out nuint low) + { + if (nint.Size == 8) + { + UInt128 product = (UInt128)(ulong)a * (ulong)b; + low = (nuint)(ulong)product; + return (nuint)(ulong)(product >> 64); + } + else + { + ulong product = (ulong)a * b; + low = (nuint)(uint)product; + return (nuint)(uint)(product >> 32); + } + } } } 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 687cf52e894343..64aa035e112930 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs @@ -10,8 +10,6 @@ namespace System.Numerics { internal static class NumericsHelpers { - private const int kcbitUint = 32; - public static void GetDoubleParts(double dbl, out int sign, out int exp, out ulong man, out bool fFinite) { ulong bits = BitConverter.DoubleToUInt64Bits(dbl); @@ -98,7 +96,7 @@ public static double GetDoubleFromParts(int sign, int exp, ulong man) // Do an in-place two's complement. "Dangerous" because it causes // a mutation and needs to be used with care for immutable types. - public static void DangerousMakeTwosComplement(Span d) + public static void DangerousMakeTwosComplement(Span d) { // Given a number: // XXXXXXXXXXXY00000 @@ -108,7 +106,7 @@ public static void DangerousMakeTwosComplement(Span d) // where A = ~X and B = -Y // Trim trailing 0s (at the first in little endian array) - int i = d.IndexOfAnyExcept(0u); + int i = d.IndexOfAnyExcept((nuint)0); if ((uint)i >= (uint)d.Length) { @@ -116,7 +114,7 @@ public static void DangerousMakeTwosComplement(Span d) } // Make the first non-zero element to be two's complement - d[i] = (uint)(-(int)d[i]); + d[i] = (nuint)(-(nint)d[i]); d = d.Slice(i + 1); if (d.IsEmpty) @@ -129,7 +127,7 @@ public static void DangerousMakeTwosComplement(Span d) // Do an in-place one's complement. "Dangerous" because it causes // a mutation and needs to be used with care for immutable types. - public static void DangerousMakeOnesComplement(Span d) + public static void DangerousMakeOnesComplement(Span d) { // Given a number: // XXXXXXXXXXX @@ -139,27 +137,27 @@ public static void DangerousMakeOnesComplement(Span d) // where A = ~X int offset = 0; - ref uint start = ref MemoryMarshal.GetReference(d); + ref nuint start = ref MemoryMarshal.GetReference(d); - while (Vector512.IsHardwareAccelerated && d.Length - offset >= Vector512.Count) + while (Vector512.IsHardwareAccelerated && d.Length - offset >= Vector512.Count) { - Vector512 complement = ~Vector512.LoadUnsafe(ref start, (nuint)offset); + Vector512 complement = ~Vector512.LoadUnsafe(ref start, (nuint)offset); Vector512.StoreUnsafe(complement, ref start, (nuint)offset); - offset += Vector512.Count; + offset += Vector512.Count; } - while (Vector256.IsHardwareAccelerated && d.Length - offset >= Vector256.Count) + while (Vector256.IsHardwareAccelerated && d.Length - offset >= Vector256.Count) { - Vector256 complement = ~Vector256.LoadUnsafe(ref start, (nuint)offset); + Vector256 complement = ~Vector256.LoadUnsafe(ref start, (nuint)offset); Vector256.StoreUnsafe(complement, ref start, (nuint)offset); - offset += Vector256.Count; + offset += Vector256.Count; } - while (Vector128.IsHardwareAccelerated && d.Length - offset >= Vector128.Count) + while (Vector128.IsHardwareAccelerated && d.Length - offset >= Vector128.Count) { - Vector128 complement = ~Vector128.LoadUnsafe(ref start, (nuint)offset); + Vector128 complement = ~Vector128.LoadUnsafe(ref start, (nuint)offset); Vector128.StoreUnsafe(complement, ref start, (nuint)offset); - offset += Vector128.Count; + offset += Vector128.Count; } for (; offset < d.Length; offset++) @@ -168,17 +166,12 @@ public static void DangerousMakeOnesComplement(Span d) } } - public static ulong MakeUInt64(uint uHi, uint uLo) - { - return ((ulong)uHi << kcbitUint) | uLo; - } - - public static uint Abs(int a) + public static nuint Abs(nint a) { unchecked { - uint mask = (uint)(a >> 31); - return ((uint)a ^ mask) - mask; + nuint mask = (nuint)(a >> (nint.Size * 8 - 1)); + return ((nuint)a ^ mask) - mask; } } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/DebuggerDisplayTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/DebuggerDisplayTests.cs index 01b079f6c87418..cc17eb598157c9 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/DebuggerDisplayTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/DebuggerDisplayTests.cs @@ -10,13 +10,60 @@ namespace System.Numerics.Tests { public class DebuggerDisplayTests { + public static TheoryData DisplayData() + { + var data = new TheoryData + { + { new uint[] { 0, 0, 1 }, "18446744073709551616" }, + }; + + // On 64-bit, these values fit in <= 4 nuint limbs so DebuggerDisplay + // shows the full ToString rather than scientific notation. + if (nint.Size == 8) + { + data.Add(new uint[] { 0, 0, 0, 0, 1 }, "340282366920938463463374607431768211456"); + data.Add(new uint[] { 0, 0x12345678, 0, 0xCC00CC00, 0x80808080 }, + new BigInteger(1, new nuint[] { unchecked((nuint)0x12345678_00000000), unchecked((nuint)0xCC00CC00_00000000), 0x80808080 }).ToString()); + } + else + { + data.Add(new uint[] { 0, 0, 0, 0, 1 }, "3.40282367e+38"); + data.Add(new uint[] { 0, 0x12345678, 0, 0xCC00CC00, 0x80808080 }, "7.33616508e+47"); + } + + return data; + } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))] - [InlineData(new uint[] { 0, 0, 1 }, "18446744073709551616")] - [InlineData(new uint[] { 0, 0, 0, 0, 1 }, "3.40282367e+38")] - [InlineData(new uint[] { 0, 0x12345678, 0, 0xCC00CC00, 0x80808080 }, "7.33616508e+47")] + [MemberData(nameof(DisplayData))] [SkipOnPlatform(TestPlatforms.Browser, "DebuggerDisplayAttribute is stripped on wasm")] - public void TestDebuggerDisplay(uint[] bits, string displayString) + public void TestDebuggerDisplay(uint[] bits32, string displayString) { + // Convert uint[] test data to nuint[] for the internal constructor + nuint[] bits; + if (nint.Size == 8) + { + int nuintLen = (bits32.Length + 1) / 2; + bits = new nuint[nuintLen]; + for (int i = 0; i < bits32.Length; i += 2) + { + ulong lo = bits32[i]; + ulong hi = (i + 1 < bits32.Length) ? bits32[i + 1] : 0; + bits[i / 2] = (nuint)(lo | (hi << 32)); + } + // Trim trailing zeros + int len = bits.Length; + while (len > 0 && bits[len - 1] == 0) len--; + if (len < bits.Length) + bits = bits[..len]; + } + else + { + bits = new nuint[bits32.Length]; + for (int i = 0; i < bits32.Length; i++) + bits[i] = bits32[i]; + } + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) { BigInteger positiveValue = new BigInteger(1, bits); diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/MyBigInt.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/MyBigInt.cs index 2646d85e8df501..1490d14c7dc152 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/MyBigInt.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/MyBigInt.cs @@ -660,7 +660,7 @@ public static List ShiftRightUnsigned(List bytes1, List bytes2 if (fill == byte.MaxValue) { - while (bytes1.Count % 4 != 0) + while (bytes1.Count % nint.Size != 0) { bytes1.Add(fill); } @@ -688,7 +688,7 @@ public static List ShiftRightUnsigned(List bytes1, List bytes2 } bytes1 = temp; - if (fill == byte.MaxValue && bytes1.Count % 4 == 1) + if (fill == byte.MaxValue && bytes1.Count % nint.Size == 1) { bytes1.RemoveAt(bytes1.Count - 1); } @@ -900,7 +900,7 @@ public static List RotateLeft(List bytes1, List bytes2) if (fill == 0 && bytes1.Count > 1 && bytes1[bytes1.Count - 1] == 0) bytes1.RemoveAt(bytes1.Count - 1); - while (bytes1.Count % 4 != 0) + while (bytes1.Count % nint.Size != 0) { bytes1.Add(fill); } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/Rotate.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/Rotate.cs index 564eb23935cda9..ea5136eba80506 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/Rotate.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/Rotate.cs @@ -281,7 +281,7 @@ private static byte[] GetRandomNegByteArray(Random random, int size) private static byte[] GetRandomLengthAllOnesUIntByteArray(Random random) { int gap = random.Next(0, 128); - int byteLength = 4 + gap * 4 + 1; + int byteLength = nint.Size + gap * nint.Size + 1; byte[] array = new byte[byteLength]; array[0] = 1; array[^1] = 0xFF; @@ -290,9 +290,9 @@ private static byte[] GetRandomLengthAllOnesUIntByteArray(Random random) private static byte[] GetRandomLengthFirstUIntMaxSecondUIntMSBMaxArray(Random random) { int gap = random.Next(0, 128); - int byteLength = 4 + gap * 4 + 1; + int byteLength = nint.Size + gap * nint.Size + 1; byte[] array = new byte[byteLength]; - array[^5] = 0x80; + array[^(nint.Size + 1)] = 0x80; array[^1] = 0xFF; return array; } @@ -308,133 +308,120 @@ public class RotateLeftTest : RotateTestBase public override string opstring => "bRotateLeft"; - public static TheoryData NegativeNumber_TestData = new TheoryData + public static TheoryData NegativeNumber_TestData() { + int bpl = nint.Size * 8; // bits per limb + BigInteger neg2 = -(BigInteger.One << (3 * bpl - 1)); // -2^(3*bpl-1) + BigInteger neg1 = neg2 + 1; // -(2^(3*bpl-1) - 1) + BigInteger neg3 = neg2 + 2; // -(2^(3*bpl-1) - 2) + var data = new TheoryData { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0000)), - 1, - new BigInteger(unchecked((long)0xFFFF_FFFE_0000_0001)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0000)), - 2, - new BigInteger(unchecked((long)0xFFFF_FFFC_0000_0003)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0001)), - 1, - new BigInteger(unchecked((long)0xFFFF_FFFE_0000_0003)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0001)), - 2, - new BigInteger(unchecked((long)0xFFFF_FFFC_0000_0007)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0002)), - 1, - new BigInteger(unchecked((long)0xFFFF_FFFE_0000_0005)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0002)), - 2, - new BigInteger(unchecked((long)0xFFFF_FFFC_0000_000B)) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0000)), - 1, - new BigInteger(0x1) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0000)), - 2, - new BigInteger(0x2) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0001)), - 1, - new BigInteger(0x3) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0001)), - 2, - new BigInteger(0x6) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0002)), - 1, - new BigInteger(0x5) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0002)), - 2, - new BigInteger(0xA) - }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0000)), + 1, + new BigInteger(unchecked((long)0xFFFF_FFFE_0000_0001)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0000)), + 2, + new BigInteger(unchecked((long)0xFFFF_FFFC_0000_0003)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0001)), + 1, + new BigInteger(unchecked((long)0xFFFF_FFFE_0000_0003)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0001)), + 2, + new BigInteger(unchecked((long)0xFFFF_FFFC_0000_0007)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0002)), + 1, + new BigInteger(unchecked((long)0xFFFF_FFFE_0000_0005)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0002)), + 2, + new BigInteger(unchecked((long)0xFFFF_FFFC_0000_000B)) + }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), - 1, - new BigInteger(0x1) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), - 2, - new BigInteger(0x2) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), - 1, - new BigInteger(0x3) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), - 2, - new BigInteger(0x6) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), - 1, - new BigInteger(0x5) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), - 2, - new BigInteger(0xA) - }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0000)), + 1, + new BigInteger(0x1) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0000)), + 2, + new BigInteger(0x2) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0001)), + 1, + new BigInteger(0x3) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0001)), + 2, + new BigInteger(0x6) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0002)), + 1, + new BigInteger(0x5) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0002)), + 2, + new BigInteger(0xA) + }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("________E_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("________C_0000_0000_0000_0000_0000_0003".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("________E_0000_0000_0000_0000_0000_0003".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("________C_0000_0000_0000_0000_0000_0007".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("________E_0000_0000_0000_0000_0000_0005".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("________C_0000_0000_0000_0000_0000_000B".Replace("_", ""), NumberStyles.HexNumber) - }, - }; + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), + 1, + BigInteger.Parse("________E_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), + 2, + BigInteger.Parse("________C_0000_0000_0000_0000_0000_0003".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), + 1, + BigInteger.Parse("________E_0000_0000_0000_0000_0000_0003".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), + 2, + BigInteger.Parse("________C_0000_0000_0000_0000_0000_0007".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), + 1, + BigInteger.Parse("________E_0000_0000_0000_0000_0000_0005".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), + 2, + BigInteger.Parse("________C_0000_0000_0000_0000_0000_000B".Replace("_", ""), NumberStyles.HexNumber) + }, + }; + + // Values occupying exactly 3 limbs with MSB set (ring = 3*bpl bits) + data.Add(neg2, 1, new BigInteger(1)); + data.Add(neg2, 2, new BigInteger(2)); + data.Add(neg1, 1, new BigInteger(3)); + data.Add(neg1, 2, new BigInteger(6)); + data.Add(neg3, 1, new BigInteger(5)); + data.Add(neg3, 2, new BigInteger(10)); + + return data; + } [Theory] [MemberData(nameof(NegativeNumber_TestData))] @@ -446,21 +433,22 @@ public void NegativeNumber(BigInteger input, int rotateAmount, BigInteger expect [Fact] public void PowerOfTwo() { - for (int i = 0; i < 32; i++) + int bpl = nint.Size * 8; // bits per limb + for (int i = 0; i < bpl; i++) { foreach (int k in new int[] { 1, 2, 3, 10 }) { - BigInteger plus = BigInteger.One << (32 * k + i); - BigInteger minus = BigInteger.MinusOne << (32 * k + i); + BigInteger plus = BigInteger.One << (bpl * k + i); + BigInteger minus = BigInteger.MinusOne << (bpl * k + i); - Assert.Equal(BigInteger.One << (i == 31 ? 0 : (32 * k + i + 1)), BigInteger.RotateLeft(plus, 1)); - Assert.Equal(BigInteger.One << i, BigInteger.RotateLeft(plus, 32)); - Assert.Equal(BigInteger.One << (32 * (k - 1) + i), BigInteger.RotateLeft(plus, 32 * k)); + Assert.Equal(BigInteger.One << (i == bpl - 1 ? 0 : (bpl * k + i + 1)), BigInteger.RotateLeft(plus, 1)); + Assert.Equal(BigInteger.One << i, BigInteger.RotateLeft(plus, bpl)); + Assert.Equal(BigInteger.One << (bpl * (k - 1) + i), BigInteger.RotateLeft(plus, bpl * k)); - Assert.Equal(i == 31 ? BigInteger.One : (new BigInteger(-1 << (i + 1)) << 32 * k) + 1, + Assert.Equal(i == bpl - 1 ? BigInteger.One : (new BigInteger((nint)(-1) << (i + 1)) << bpl * k) + 1, BigInteger.RotateLeft(minus, 1)); - Assert.Equal(new BigInteger(uint.MaxValue << i), BigInteger.RotateLeft(minus, 32)); - Assert.Equal(new BigInteger(uint.MaxValue << i) << (32 * (k - 1)), BigInteger.RotateLeft(minus, 32 * k)); + Assert.Equal((BigInteger.One << bpl) - (BigInteger.One << i), BigInteger.RotateLeft(minus, bpl)); + Assert.Equal(((BigInteger.One << bpl) - (BigInteger.One << i)) << (bpl * (k - 1)), BigInteger.RotateLeft(minus, bpl * k)); } } } @@ -470,133 +458,120 @@ public class RotateRightTest : RotateTestBase { public override string opstring => "bRotateRight"; - public static TheoryData NegativeNumber_TestData = new TheoryData + public static TheoryData NegativeNumber_TestData() { + int bpl = nint.Size * 8; // bits per limb + BigInteger neg2 = -(BigInteger.One << (3 * bpl - 1)); // -2^(3*bpl-1) + BigInteger neg1 = neg2 + 1; // -(2^(3*bpl-1) - 1) + BigInteger neg3 = neg2 + 2; // -(2^(3*bpl-1) - 2) + var data = new TheoryData { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0000)), - 1, - new BigInteger(unchecked((long)0x7FFF_FFFF_8000_0000)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0000)), - 2, - new BigInteger(unchecked((long)0x3FFF_FFFF_C000_0000)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0001)), - 1, - new BigInteger(unchecked((int)0x8000_0000)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0001)), - 2, - new BigInteger(unchecked((long)0x7FFF_FFFF_C000_0000)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0002)), - 1, - new BigInteger(unchecked((long)0x7FFF_FFFF_8000_0001)) - }, - { - new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0002)), - 2, - new BigInteger(unchecked((long)0xBFFF_FFFF_C000_0000)) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0000)), - 1, - new BigInteger(unchecked((long)0x4000_0000_0000_0000)) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0000)), - 2, - new BigInteger(unchecked((long)0x2000_0000_0000_0000)) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0001)), - 1, - new BigInteger(unchecked((long)0xC000_0000_0000_0000)) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0001)), - 2, - new BigInteger(unchecked((long)0x6000_0000_0000_0000)) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0002)), - 1, - new BigInteger(unchecked((long)0x4000_0000_0000_0001)) - }, - { - new BigInteger(unchecked((long)0x8000_0000_0000_0002)), - 2, - new BigInteger(unchecked((long)0xA000_0000_0000_0000)) - }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0000)), + 1, + new BigInteger(unchecked((long)0x7FFF_FFFF_8000_0000)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0000)), + 2, + new BigInteger(unchecked((long)0x3FFF_FFFF_C000_0000)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0001)), + 1, + new BigInteger(unchecked((int)0x8000_0000)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0001)), + 2, + new BigInteger(unchecked((long)0x7FFF_FFFF_C000_0000)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0002)), + 1, + new BigInteger(unchecked((long)0x7FFF_FFFF_8000_0001)) + }, + { + new BigInteger(unchecked((long)0xFFFF_FFFF_0000_0002)), + 2, + new BigInteger(unchecked((long)0xBFFF_FFFF_C000_0000)) + }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("4000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("2000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("C000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("6000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("4000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("8000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("A000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0000)), + 1, + new BigInteger(unchecked((long)0x4000_0000_0000_0000)) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0000)), + 2, + new BigInteger(unchecked((long)0x2000_0000_0000_0000)) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0001)), + 1, + new BigInteger(unchecked((long)0xC000_0000_0000_0000)) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0001)), + 2, + new BigInteger(unchecked((long)0x6000_0000_0000_0000)) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0002)), + 1, + new BigInteger(unchecked((long)0x4000_0000_0000_0001)) + }, + { + new BigInteger(unchecked((long)0x8000_0000_0000_0002)), + 2, + new BigInteger(unchecked((long)0xA000_0000_0000_0000)) + }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("7FFF_FFFF_8000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("3FFF_FFFF_C000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("__________8000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("7FFF_FFFF_C000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), - 1, - BigInteger.Parse("7FFF_FFFF_8000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber) - }, - { - BigInteger.Parse("________F_0000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), - 2, - BigInteger.Parse("BFFF_FFFF_C000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) - }, - }; + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), + 1, + BigInteger.Parse("7FFF_FFFF_8000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber), + 2, + BigInteger.Parse("3FFF_FFFF_C000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), + 1, + BigInteger.Parse("__________8000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber), + 2, + BigInteger.Parse("7FFF_FFFF_C000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), + 1, + BigInteger.Parse("7FFF_FFFF_8000_0000_0000_0000_0000_0001".Replace("_", ""), NumberStyles.HexNumber) + }, + { + BigInteger.Parse("________F_0000_0000_0000_0000_0000_0002".Replace("_", ""), NumberStyles.HexNumber), + 2, + BigInteger.Parse("BFFF_FFFF_C000_0000_0000_0000_0000_0000".Replace("_", ""), NumberStyles.HexNumber) + }, + }; + + // Values occupying exactly 3 limbs with MSB set (ring = 3*bpl bits) + data.Add(neg2, 1, BigInteger.One << (3 * bpl - 2)); + data.Add(neg2, 2, BigInteger.One << (3 * bpl - 3)); + data.Add(neg1, 1, -(BigInteger.One << (3 * bpl - 2))); + data.Add(neg1, 2, (BigInteger)3 << (3 * bpl - 3)); + data.Add(neg3, 1, (BigInteger.One << (3 * bpl - 2)) + 1); + data.Add(neg3, 2, -((BigInteger)3 << (3 * bpl - 3))); + + return data; + } [Theory] [MemberData(nameof(NegativeNumber_TestData))] @@ -608,20 +583,21 @@ public void NegativeNumber(BigInteger input, int rotateAmount, BigInteger expect [Fact] public void PowerOfTwo() { - for (int i = 0; i < 32; i++) + int bpl = nint.Size * 8; // bits per limb + for (int i = 0; i < bpl; i++) { foreach (int k in new int[] { 1, 2, 3, 10 }) { - BigInteger plus = BigInteger.One << (32 * k + i); - BigInteger minus = BigInteger.MinusOne << (32 * k + i); + BigInteger plus = BigInteger.One << (bpl * k + i); + BigInteger minus = BigInteger.MinusOne << (bpl * k + i); - Assert.Equal(BigInteger.One << (32 * k + i - 1), BigInteger.RotateRight(plus, 1)); - Assert.Equal(BigInteger.One << (32 * (k - 1) + i), BigInteger.RotateRight(plus, 32)); - Assert.Equal(BigInteger.One << i, BigInteger.RotateRight(plus, 32 * k)); + Assert.Equal(BigInteger.One << (bpl * k + i - 1), BigInteger.RotateRight(plus, 1)); + Assert.Equal(BigInteger.One << (bpl * (k - 1) + i), BigInteger.RotateRight(plus, bpl)); + Assert.Equal(BigInteger.One << i, BigInteger.RotateRight(plus, bpl * k)); - Assert.Equal(new BigInteger(uint.MaxValue << i) << (32 * k - 1), BigInteger.RotateRight(minus, 1)); - Assert.Equal(new BigInteger(uint.MaxValue << i) << (32 * (k - 1)), BigInteger.RotateRight(minus, 32)); - Assert.Equal(new BigInteger(uint.MaxValue << i), BigInteger.RotateRight(minus, 32 * k)); + Assert.Equal(((BigInteger.One << bpl) - (BigInteger.One << i)) << (bpl * k - 1), BigInteger.RotateRight(minus, 1)); + Assert.Equal(((BigInteger.One << bpl) - (BigInteger.One << i)) << (bpl * (k - 1)), BigInteger.RotateRight(minus, bpl)); + Assert.Equal((BigInteger.One << bpl) - (BigInteger.One << i), BigInteger.RotateRight(minus, bpl * k)); } } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/op_rightshift.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/op_rightshift.cs index c725d52bc0ba4c..ab450b0592a6b2 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/op_rightshift.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/op_rightshift.cs @@ -325,7 +325,7 @@ public static void BigShiftsTest() public static void LargeNegativeBigIntegerShiftTest() { // Create a very large negative BigInteger - int bitsPerElement = 8 * sizeof(uint); + int bitsPerElement = 8 * nint.Size; int maxBitLength = ((Array.MaxLength / bitsPerElement) * bitsPerElement); BigInteger bigInt = new BigInteger(-1) << (maxBitLength - 1); Assert.Equal(maxBitLength - 1, bigInt.GetBitLength()); @@ -335,19 +335,18 @@ public static void LargeNegativeBigIntegerShiftTest() // At this point, bigInt should be a 1 followed by maxBitLength - 1 zeros. // Given this, bigInt._bits is expected to be structured as follows: // - _bits.Length == ceil(maxBitLength / bitsPerElement) - // - First (_bits.Length - 1) elements: 0x00000000 - // - Last element: 0x80000000 - // ^------ (There's the leading '1') + // - First (_bits.Length - 1) elements: 0 + // - Last element: 1 << (bitsPerElement - 1) Assert.Equal((maxBitLength + (bitsPerElement - 1)) / bitsPerElement, bigInt._bits.Length); - uint i = 0; - for (; i < (bigInt._bits.Length - 1); i++) + nuint i = 0; + for (; i < (nuint)(bigInt._bits.Length - 1); i++) { - Assert.Equal(0x00000000u, bigInt._bits[i]); + Assert.Equal((nuint)0, bigInt._bits[(int)i]); } - Assert.Equal(0x80000000u, bigInt._bits[i]); + Assert.Equal((nuint)1 << (bitsPerElement - 1), bigInt._bits[(int)i]); // Right shift the BigInteger BigInteger shiftedBigInt = bigInt >> 1; @@ -358,19 +357,18 @@ public static void LargeNegativeBigIntegerShiftTest() // At this point, shiftedBigInt should be a 1 followed by maxBitLength - 2 zeros. // Given this, shiftedBigInt._bits is expected to be structured as follows: // - _bits.Length == ceil((maxBitLength - 1) / bitsPerElement) - // - First (_bits.Length - 1) elements: 0x00000000 - // - Last element: 0x40000000 - // ^------ (the '1' is now one position to the right) + // - First (_bits.Length - 1) elements: 0 + // - Last element: 1 << (bitsPerElement - 2) Assert.Equal(((maxBitLength - 1) + (bitsPerElement - 1)) / bitsPerElement, shiftedBigInt._bits.Length); i = 0; - for (; i < (shiftedBigInt._bits.Length - 1); i++) + for (; i < (nuint)(shiftedBigInt._bits.Length - 1); i++) { - Assert.Equal(0x00000000u, shiftedBigInt._bits[i]); + Assert.Equal((nuint)0, shiftedBigInt._bits[(int)i]); } - Assert.Equal(0x40000000u, shiftedBigInt._bits[i]); + Assert.Equal((nuint)1 << (bitsPerElement - 2), shiftedBigInt._bits[(int)i]); } } @@ -381,12 +379,13 @@ public class op_UnsignedRightshiftTest : op_rightshiftTestBase [Fact] public void PowerOfTwo() { - for (int i = 0; i < 32; i++) + int bpl = nint.Size * 8; // bits per limb + for (int i = 0; i < bpl; i++) { foreach (int k in new int[] { 1, 2, 10 }) { - Assert.Equal(BigInteger.One << i, (BigInteger.One << (32 * k + i)) >>> (32 * k)); - Assert.Equal(new BigInteger(unchecked((int)(uint.MaxValue << i))), (BigInteger.MinusOne << (32 * k + i)) >>> (32 * k)); + Assert.Equal(BigInteger.One << i, (BigInteger.One << (bpl * k + i)) >>> (bpl * k)); + Assert.Equal(new BigInteger(unchecked((nint)(nuint.MaxValue << i))), (BigInteger.MinusOne << (bpl * k + i)) >>> (bpl * k)); } } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs b/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs index 5ce87ae4236c50..6531b64e3f9d10 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs @@ -209,8 +209,8 @@ public static void DivRemTest() [Fact] public static void LeadingZeroCountTest() { - Assert.Equal((BigInteger)32, BinaryIntegerHelper.LeadingZeroCount(Zero)); - Assert.Equal((BigInteger)31, BinaryIntegerHelper.LeadingZeroCount(One)); + Assert.Equal((BigInteger)(nint.Size * 8), BinaryIntegerHelper.LeadingZeroCount(Zero)); + Assert.Equal((BigInteger)(nint.Size * 8 - 1), BinaryIntegerHelper.LeadingZeroCount(One)); Assert.Equal((BigInteger)1, BinaryIntegerHelper.LeadingZeroCount(Int64MaxValue)); Assert.Equal((BigInteger)0, BinaryIntegerHelper.LeadingZeroCount(Int64MinValue)); @@ -228,7 +228,7 @@ public static void PopCountTest() Assert.Equal((BigInteger)63, BinaryIntegerHelper.PopCount(Int64MaxValue)); Assert.Equal((BigInteger)1, BinaryIntegerHelper.PopCount(Int64MinValue)); - Assert.Equal((BigInteger)32, BinaryIntegerHelper.PopCount(NegativeOne)); + Assert.Equal((BigInteger)(nint.Size * 8), BinaryIntegerHelper.PopCount(NegativeOne)); Assert.Equal((BigInteger)1, BinaryIntegerHelper.PopCount(Int64MaxValuePlusOne)); Assert.Equal((BigInteger)64, BinaryIntegerHelper.PopCount(UInt64MaxValue)); @@ -242,8 +242,8 @@ public static void RotateLeftTest() Assert.Equal((BigInteger)0x00000000, BinaryIntegerHelper.RotateLeft(Zero, 33)); Assert.Equal((BigInteger)0x00000002, BinaryIntegerHelper.RotateLeft(One, 1)); - Assert.Equal((BigInteger)0x00000001, BinaryIntegerHelper.RotateLeft(One, 32)); - Assert.Equal((BigInteger)0x00000002, BinaryIntegerHelper.RotateLeft(One, 33)); + Assert.Equal(BigInteger.One << (32 % (nint.Size * 8)), BinaryIntegerHelper.RotateLeft(One, 32)); + Assert.Equal(BigInteger.One << (33 % (nint.Size * 8)), BinaryIntegerHelper.RotateLeft(One, 33)); Assert.Equal((BigInteger)0xFFFFFFFFFFFFFFFE, BinaryIntegerHelper.RotateLeft(Int64MaxValue, 1)); Assert.Equal((BigInteger)0xFFFFFFFF7FFFFFFF, BinaryIntegerHelper.RotateLeft(Int64MaxValue, 32)); @@ -269,8 +269,8 @@ public static void RotateLeftTest() Assert.Equal((BigInteger)0x00000000, BinaryIntegerHelper.RotateLeft(Zero, -32)); Assert.Equal((BigInteger)0x00000000, BinaryIntegerHelper.RotateLeft(Zero, -33)); - Assert.Equal((BigInteger)0x80000000, BinaryIntegerHelper.RotateLeft(One, -1)); - Assert.Equal((BigInteger)0x00000001, BinaryIntegerHelper.RotateLeft(One, -32)); + Assert.Equal(BigInteger.One << (nint.Size * 8 - 1), BinaryIntegerHelper.RotateLeft(One, -1)); + Assert.Equal(BigInteger.One << ((nint.Size * 8 - 32) % (nint.Size * 8)), BinaryIntegerHelper.RotateLeft(One, -32)); Assert.Equal((BigInteger)0x80000000, BinaryIntegerHelper.RotateLeft(One, -33)); Assert.Equal((BigInteger)0xBFFFFFFFFFFFFFFF, BinaryIntegerHelper.RotateLeft(Int64MaxValue, -1)); @@ -301,8 +301,8 @@ public static void RotateRightTest() Assert.Equal((BigInteger)0x00000000, BinaryIntegerHelper.RotateRight(Zero, 32)); Assert.Equal((BigInteger)0x00000000, BinaryIntegerHelper.RotateRight(Zero, 33)); - Assert.Equal((BigInteger)0x80000000, BinaryIntegerHelper.RotateRight(One, 1)); - Assert.Equal((BigInteger)0x00000001, BinaryIntegerHelper.RotateRight(One, 32)); + Assert.Equal(BigInteger.One << (nint.Size * 8 - 1), BinaryIntegerHelper.RotateRight(One, 1)); + Assert.Equal(BigInteger.One << ((nint.Size * 8 - 32) % (nint.Size * 8)), BinaryIntegerHelper.RotateRight(One, 32)); Assert.Equal((BigInteger)0x80000000, BinaryIntegerHelper.RotateRight(One, 33)); Assert.Equal((BigInteger)0xBFFFFFFFFFFFFFFF, BinaryIntegerHelper.RotateRight(Int64MaxValue, 1)); @@ -330,8 +330,8 @@ public static void RotateRightTest() Assert.Equal((BigInteger)0x00000000, BinaryIntegerHelper.RotateRight(Zero, -33)); Assert.Equal((BigInteger)0x00000002, BinaryIntegerHelper.RotateRight(One, -1)); - Assert.Equal((BigInteger)0x00000001, BinaryIntegerHelper.RotateRight(One, -32)); - Assert.Equal((BigInteger)0x00000002, BinaryIntegerHelper.RotateRight(One, -33)); + Assert.Equal(BigInteger.One << (32 % (nint.Size * 8)), BinaryIntegerHelper.RotateRight(One, -32)); + Assert.Equal(BigInteger.One << (33 % (nint.Size * 8)), BinaryIntegerHelper.RotateRight(One, -33)); Assert.Equal((BigInteger)0xFFFFFFFFFFFFFFFE, BinaryIntegerHelper.RotateRight(Int64MaxValue, -1)); Assert.Equal((BigInteger)0xFFFFFFFF7FFFFFFF, BinaryIntegerHelper.RotateRight(Int64MaxValue, -32)); @@ -357,7 +357,7 @@ public static void RotateRightTest() [Fact] public static void TrailingZeroCountTest() { - Assert.Equal((BigInteger)32, BinaryIntegerHelper.TrailingZeroCount(Zero)); + Assert.Equal((BigInteger)(nint.Size * 8), BinaryIntegerHelper.TrailingZeroCount(Zero)); Assert.Equal((BigInteger)0, BinaryIntegerHelper.TrailingZeroCount(One)); Assert.Equal((BigInteger)0, BinaryIntegerHelper.TrailingZeroCount(Int64MaxValue)); @@ -374,21 +374,21 @@ public static void TrailingZeroCountTest() [Fact] public static void GetByteCountTest() { - Assert.Equal(4, BinaryIntegerHelper.GetByteCount(Zero)); - Assert.Equal(4, BinaryIntegerHelper.GetByteCount(One)); + Assert.Equal(nint.Size, BinaryIntegerHelper.GetByteCount(Zero)); + Assert.Equal(nint.Size, BinaryIntegerHelper.GetByteCount(One)); Assert.Equal(8, BinaryIntegerHelper.GetByteCount(Int64MaxValue)); Assert.Equal(8, BinaryIntegerHelper.GetByteCount(Int64MinValue)); - Assert.Equal(4, BinaryIntegerHelper.GetByteCount(NegativeOne)); + Assert.Equal(nint.Size, BinaryIntegerHelper.GetByteCount(NegativeOne)); Assert.Equal(8, BinaryIntegerHelper.GetByteCount(Int64MaxValuePlusOne)); Assert.Equal(8, BinaryIntegerHelper.GetByteCount(UInt64MaxValue)); Assert.Equal(16, BinaryIntegerHelper.GetByteCount(Int128MaxValue)); Assert.Equal(16, BinaryIntegerHelper.GetByteCount(Int128MinValue)); - Assert.Equal(20, BinaryIntegerHelper.GetByteCount(Int128MinValueMinusOne)); + Assert.Equal(nint.Size == 8 ? 24 : 20, BinaryIntegerHelper.GetByteCount(Int128MinValueMinusOne)); Assert.Equal(16, BinaryIntegerHelper.GetByteCount(Int128MinValuePlusOne)); - Assert.Equal(20, BinaryIntegerHelper.GetByteCount(Int128MinValueTimesTwo)); + Assert.Equal(nint.Size == 8 ? 24 : 20, BinaryIntegerHelper.GetByteCount(Int128MinValueTimesTwo)); Assert.Equal(16, BinaryIntegerHelper.GetByteCount(Int128MaxValuePlusOne)); Assert.Equal(16, BinaryIntegerHelper.GetByteCount(UInt128MaxValue)); } @@ -418,16 +418,16 @@ public static void GetShortestBitLengthTest() [Fact] public static void TryWriteBigEndianTest() { - Span destination = stackalloc byte[20]; + Span destination = stackalloc byte[24]; int bytesWritten = 0; Assert.True(BinaryIntegerHelper.TryWriteBigEndian(Zero, destination, out bytesWritten)); - Assert.Equal(4, bytesWritten); - Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x00 }, destination.Slice(0, 4).ToArray()); + Assert.Equal(nint.Size, bytesWritten); + Assert.Equal(new byte[nint.Size], destination.Slice(0, nint.Size).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteBigEndian(One, destination, out bytesWritten)); - Assert.Equal(4, bytesWritten); - Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, destination.Slice(0, 4).ToArray()); + Assert.Equal(nint.Size, bytesWritten); + Assert.Equal(nint.Size == 8 ? new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } : new byte[] { 0x00, 0x00, 0x00, 0x01 }, destination.Slice(0, nint.Size).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteBigEndian(Int64MaxValue, destination, out bytesWritten)); Assert.Equal(8, bytesWritten); @@ -438,8 +438,8 @@ public static void TryWriteBigEndianTest() Assert.Equal(new byte[] { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, destination.Slice(0, 8).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteBigEndian(NegativeOne, destination, out bytesWritten)); - Assert.Equal(4, bytesWritten); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, 4).ToArray()); + Assert.Equal(nint.Size, bytesWritten); + Assert.Equal(nint.Size == 8 ? new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } : new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, nint.Size).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteBigEndian(Int64MaxValuePlusOne, destination, out bytesWritten)); Assert.Equal(8, bytesWritten); @@ -458,16 +458,16 @@ public static void TryWriteBigEndianTest() Assert.Equal(new byte[] { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, destination.Slice(0, 16).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteBigEndian(Int128MinValueMinusOne, destination, out bytesWritten)); - Assert.Equal(20, bytesWritten); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, 20).ToArray()); + Assert.Equal(nint.Size == 8 ? 24 : 20, bytesWritten); + Assert.Equal(nint.Size == 8 ? new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } : new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, nint.Size == 8 ? 24 : 20).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteBigEndian(Int128MinValuePlusOne, destination, out bytesWritten)); Assert.Equal(16, bytesWritten); Assert.Equal(new byte[] { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }, destination.Slice(0, 16).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteBigEndian(Int128MinValueTimesTwo, destination, out bytesWritten)); - Assert.Equal(20, bytesWritten); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, destination.Slice(0, 20).ToArray()); + Assert.Equal(nint.Size == 8 ? 24 : 20, bytesWritten); + Assert.Equal(nint.Size == 8 ? new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } : new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, destination.Slice(0, nint.Size == 8 ? 24 : 20).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteBigEndian(Int128MaxValuePlusOne, destination, out bytesWritten)); Assert.Equal(16, bytesWritten); @@ -479,22 +479,22 @@ public static void TryWriteBigEndianTest() Assert.False(BinaryIntegerHelper.TryWriteBigEndian(default, Span.Empty, out bytesWritten)); Assert.Equal(0, bytesWritten); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }, destination.ToArray()); + Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, destination.ToArray()); } [Fact] public static void TryWriteLittleEndianTest() { - Span destination = stackalloc byte[20]; + Span destination = stackalloc byte[24]; int bytesWritten = 0; Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(Zero, destination, out bytesWritten)); - Assert.Equal(4, bytesWritten); - Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x00 }, destination.Slice(0, 4).ToArray()); + Assert.Equal(nint.Size, bytesWritten); + Assert.Equal(new byte[nint.Size], destination.Slice(0, nint.Size).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(One, destination, out bytesWritten)); - Assert.Equal(4, bytesWritten); - Assert.Equal(new byte[] { 0x01, 0x00, 0x00, 0x00 }, destination.Slice(0, 4).ToArray()); + Assert.Equal(nint.Size, bytesWritten); + Assert.Equal(nint.Size == 8 ? new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } : new byte[] { 0x01, 0x00, 0x00, 0x00 }, destination.Slice(0, nint.Size).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(Int64MaxValue, destination, out bytesWritten)); Assert.Equal(8, bytesWritten); @@ -505,8 +505,8 @@ public static void TryWriteLittleEndianTest() Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }, destination.Slice(0, 8).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(NegativeOne, destination, out bytesWritten)); - Assert.Equal(4, bytesWritten); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, 4).ToArray()); + Assert.Equal(nint.Size, bytesWritten); + Assert.Equal(nint.Size == 8 ? new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } : new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, nint.Size).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(Int64MaxValuePlusOne, destination, out bytesWritten)); Assert.Equal(8, bytesWritten); @@ -525,16 +525,16 @@ public static void TryWriteLittleEndianTest() Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }, destination.Slice(0, 16).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(Int128MinValueMinusOne, destination, out bytesWritten)); - Assert.Equal(20, bytesWritten); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, 20).ToArray()); + Assert.Equal(nint.Size == 8 ? 24 : 20, bytesWritten); + Assert.Equal(nint.Size == 8 ? new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } : new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, nint.Size == 8 ? 24 : 20).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(Int128MinValuePlusOne, destination, out bytesWritten)); Assert.Equal(16, bytesWritten); Assert.Equal(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }, destination.Slice(0, 16).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(Int128MinValueTimesTwo, destination, out bytesWritten)); - Assert.Equal(20, bytesWritten); - Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, 20).ToArray()); + Assert.Equal(nint.Size == 8 ? 24 : 20, bytesWritten); + Assert.Equal(nint.Size == 8 ? new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } : new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF }, destination.Slice(0, nint.Size == 8 ? 24 : 20).ToArray()); Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(Int128MaxValuePlusOne, destination, out bytesWritten)); Assert.Equal(16, bytesWritten); @@ -546,7 +546,7 @@ public static void TryWriteLittleEndianTest() Assert.False(BinaryIntegerHelper.TryWriteLittleEndian(default, Span.Empty, out bytesWritten)); Assert.Equal(0, bytesWritten); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, destination.ToArray()); + Assert.Equal(nint.Size == 8 ? new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } : new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }, destination.ToArray()); } // @@ -2650,7 +2650,7 @@ public static void op_UnsignedRightShiftTest() Assert.Equal((BigInteger)0x3FFFFFFFFFFFFFFF, ShiftOperatorsHelper.op_UnsignedRightShift(Int64MaxValue, 1)); Assert.Equal((BigInteger)0x4000000000000000, ShiftOperatorsHelper.op_UnsignedRightShift(Int64MinValue, 1)); - Assert.Equal(Int32MaxValue, ShiftOperatorsHelper.op_UnsignedRightShift(NegativeOne, 1)); + Assert.Equal((BigInteger)nint.MaxValue, ShiftOperatorsHelper.op_UnsignedRightShift(NegativeOne, 1)); Assert.Equal((BigInteger)0x4000000000000000, ShiftOperatorsHelper.op_UnsignedRightShift(Int64MaxValuePlusOne, 1)); Assert.Equal(Int64MaxValue, ShiftOperatorsHelper.op_UnsignedRightShift(UInt64MaxValue, 1)); From 7a8770c763602b6860f348e290ee9eb3f7ef54fb Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Mar 2026 17:30:13 -0400 Subject: [PATCH 03/27] Fix SubWithBorrow sign-extension bug on 32-bit and update stale comments SubWithBorrow's 32-bit path cast nuint operands through (int) before widening to long, which sign-extends values >= 0x80000000 instead of zero-extending. For example, SubWithBorrow(0xFFFFFFFF, 0, 0) would incorrectly return borrowOut=1 instead of 0. Fix: cast directly to long (zero-extension for unsigned nuint/uint). Also update FastReducer comments from '2^(32*k)' to '2^(kcbitNuint*k)' to reflect the variable limb width. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/BigIntegerCalculator.FastReducer.cs | 4 ++-- .../src/System/Numerics/BigIntegerCalculator.Utils.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 a7368e12cc79e1..9fd8cacbcfb717 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 @@ -73,7 +73,7 @@ private static int DivMul(ReadOnlySpan left, ReadOnlySpan right, S // 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 + // preceding division by 2^(kcbitNuint*k). To spare memory allocations // we write the result to an already allocated memory. if (left.Length > k) @@ -93,7 +93,7 @@ private static int SubMod(Span left, ReadOnlySpan right, ReadOnlyS { // 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 + // preceding reduction by 2^(kcbitNuint*k). Furthermore, if left is // still greater than modulus, further subtractions are used. if (left.Length > k) 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 index 1fbdc37c1f3fb4..4cf3aaf9812b3b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs @@ -131,7 +131,7 @@ internal static nuint SubWithBorrow(nuint a, nuint b, nuint borrowIn, out nuint } else { - long diff = (long)(int)a - (int)b - (int)borrowIn; + long diff = (long)a - (long)b - (long)borrowIn; borrowOut = (nuint)(uint)(-(int)(diff >> 32)); // 0 or 1 return (nuint)(uint)diff; } From 5255f857c3d15ef43861846518e700b19af5fdd9 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Mar 2026 17:47:32 -0400 Subject: [PATCH 04/27] Use branchless underflow detection in SubtractDivisor Replace branching carry propagation (if/++carry) with branchless conditional-move patterns in the division inner loop. Branch misprediction penalties dominate for large divisions where the carry is unpredictable. The branchless version uses unsigned underflow comparison (original < loWithCarry) converted to 0/1. See dotnet/runtime#41495 for the original proposal showing 2.6x improvement for 65536-bit division. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.DivRem.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) 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 fd7174f6e8f6fa..0da3ae123fdd2a 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 @@ -312,20 +312,26 @@ private static nuint SubtractDivisor(Span left, ReadOnlySpan right // Combines a subtract and a multiply operation, which is naturally // more efficient than multiplying and then subtracting... + // Uses branchless underflow detection to avoid branch misprediction + // penalties that dominate for large divisions (see issue #41495). - nuint carry = (nuint)0; + nuint carry = 0; for (int i = 0; i < right.Length; i++) { nuint hi = BigMul(right[i], q, out nuint lo); - lo += carry; - if (lo < carry) - hi++; - carry = hi; + + // Add carry to lo, propagate overflow to hi (branchless) + nuint loWithCarry = lo + carry; + hi += (loWithCarry < lo) ? (nuint)1 : 0; + + // Subtract from left, detect underflow (branchless) ref nuint leftElement = ref left[i]; - if (leftElement < lo) - ++carry; - leftElement -= lo; + nuint original = leftElement; + leftElement -= loWithCarry; + hi += (original < loWithCarry) ? (nuint)1 : 0; + + carry = hi; } return carry; From bf3328d2bf1197c2cd58a60d4c17c43ac60c3ab5 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Mar 2026 17:53:10 -0400 Subject: [PATCH 05/27] Fuse two's complement conversion with bitwise AND/OR/XOR Replace the three-buffer approach (allocate x, y, z; copy+negate x and y; operate into z) with a single-buffer approach that computes two's complement limbs on-the-fly via GetTwosComplementLimb(). This eliminates 2 temporary buffer allocations and 2-3 full data passes per operation. For positive operands (the common case), no negation is needed at all -- magnitude limbs are read directly and zero-extended. For negative operands, the two's complement is computed inline: ~magnitude + carry with carry propagation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/BigInteger.cs | 226 ++++++++++-------- 1 file changed, 124 insertions(+), 102 deletions(-) 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 1ace0ab8286eee..f78cb1598be643 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -2627,166 +2627,188 @@ public static implicit operator BigInteger(nuint value) public static BigInteger operator &(BigInteger left, BigInteger right) { if (left.IsZero || right.IsZero) - { return Zero; - } if (left._bits is null && right._bits is null) - { return left._sign & right._sign; - } - nuint xExtend = (left._sign < 0) ? nuint.MaxValue : 0; - nuint yExtend = (right._sign < 0) ? nuint.MaxValue : 0; + return BitwiseAnd(ref left, ref right); + } - nuint[]? leftBufferFromPool = null; - int size = (left._bits?.Length ?? 1) + 1; - Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - x = x.Slice(0, left.WriteTo(x)); + public static BigInteger operator |(BigInteger left, BigInteger right) + { + if (left.IsZero) + return right; + if (right.IsZero) + return left; - nuint[]? rightBufferFromPool = null; - size = (right._bits?.Length ?? 1) + 1; - Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - y = y.Slice(0, right.WriteTo(y)); + if (left._bits is null && right._bits is null) + return left._sign | right._sign; + + return BitwiseOr(ref left, ref right); + } + + public static BigInteger operator ^(BigInteger left, BigInteger right) + { + if (left._bits is null && right._bits is null) + return left._sign ^ right._sign; + + return BitwiseXor(ref left, ref right); + } + + /// + /// Computes two's complement AND directly from magnitude representation, + /// eliminating temporary buffers for the operands. + /// + private static BigInteger BitwiseAnd(ref readonly BigInteger left, ref readonly BigInteger right) + { + bool leftNeg = left._sign < 0; + bool rightNeg = right._sign < 0; + + ReadOnlySpan xBits = left._bits ?? default; + ReadOnlySpan yBits = right._bits ?? default; + int xLen = left._bits?.Length ?? 1; + int yLen = right._bits?.Length ?? 1; + nuint xInline = unchecked((nuint)left._sign); + nuint yInline = unchecked((nuint)right._sign); + + // AND result length: for positive operands, min length suffices (AND with 0 = 0). + // For negative operands (sign-extended with 1s), we need max length + 1 for sign. + int zLen = (leftNeg || rightNeg) + ? Math.Max(xLen, yLen) + 1 + : Math.Min(xLen, yLen); nuint[]? resultBufferFromPool = null; - size = Math.Max(x.Length, y.Length); - Span z = (size <= BigIntegerCalculator.StackAllocThreshold + Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); - for (int i = 0; i < z.Length; i++) + nuint xBorrow = 1, yBorrow = 1; + + for (int i = 0; i < zLen; i++) { - nuint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; - nuint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; + nuint xu = GetTwosComplementLimb(xBits, xInline, i, xLen, leftNeg, ref xBorrow); + nuint yu = GetTwosComplementLimb(yBits, yInline, i, yLen, rightNeg, ref yBorrow); z[i] = xu & yu; } - if (leftBufferFromPool != null) - ArrayPool.Shared.Return(leftBufferFromPool); - - if (rightBufferFromPool != null) - ArrayPool.Shared.Return(rightBufferFromPool); - var result = new BigInteger(z); - if (resultBufferFromPool != null) + if (resultBufferFromPool is not null) ArrayPool.Shared.Return(resultBufferFromPool); return result; } - public static BigInteger operator |(BigInteger left, BigInteger right) + /// + /// Computes two's complement OR directly from magnitude representation. + /// + private static BigInteger BitwiseOr(ref readonly BigInteger left, ref readonly BigInteger right) { - if (left.IsZero) - return right; - if (right.IsZero) - return left; - - if (left._bits is null && right._bits is null) - { - return left._sign | right._sign; - } + bool leftNeg = left._sign < 0; + bool rightNeg = right._sign < 0; - nuint xExtend = (left._sign < 0) ? nuint.MaxValue : 0; - nuint yExtend = (right._sign < 0) ? nuint.MaxValue : 0; + ReadOnlySpan xBits = left._bits ?? default; + ReadOnlySpan yBits = right._bits ?? default; + int xLen = left._bits?.Length ?? 1; + int yLen = right._bits?.Length ?? 1; + nuint xInline = unchecked((nuint)left._sign); + nuint yInline = unchecked((nuint)right._sign); - nuint[]? leftBufferFromPool = null; - int size = (left._bits?.Length ?? 1) + 1; - Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - x = x.Slice(0, left.WriteTo(x)); - - nuint[]? rightBufferFromPool = null; - size = (right._bits?.Length ?? 1) + 1; - Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - y = y.Slice(0, right.WriteTo(y)); + int zLen = Math.Max(xLen, yLen) + 1; nuint[]? resultBufferFromPool = null; - size = Math.Max(x.Length, y.Length); - Span z = (size <= BigIntegerCalculator.StackAllocThreshold + Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); + + nuint xBorrow = 1, yBorrow = 1; - for (int i = 0; i < z.Length; i++) + for (int i = 0; i < zLen; i++) { - nuint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; - nuint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; + nuint xu = GetTwosComplementLimb(xBits, xInline, i, xLen, leftNeg, ref xBorrow); + nuint yu = GetTwosComplementLimb(yBits, yInline, i, yLen, rightNeg, ref yBorrow); z[i] = xu | yu; } - if (leftBufferFromPool != null) - ArrayPool.Shared.Return(leftBufferFromPool); - - if (rightBufferFromPool != null) - ArrayPool.Shared.Return(rightBufferFromPool); - var result = new BigInteger(z); - if (resultBufferFromPool != null) + if (resultBufferFromPool is not null) ArrayPool.Shared.Return(resultBufferFromPool); return result; } - public static BigInteger operator ^(BigInteger left, BigInteger right) + /// + /// Computes two's complement XOR directly from magnitude representation. + /// + private static BigInteger BitwiseXor(ref readonly BigInteger left, ref readonly BigInteger right) { - if (left._bits is null && right._bits is null) - { - return left._sign ^ right._sign; - } + bool leftNeg = left._sign < 0; + bool rightNeg = right._sign < 0; - nuint xExtend = (left._sign < 0) ? nuint.MaxValue : 0; - nuint yExtend = (right._sign < 0) ? nuint.MaxValue : 0; + ReadOnlySpan xBits = left._bits ?? default; + ReadOnlySpan yBits = right._bits ?? default; + int xLen = left._bits?.Length ?? 1; + int yLen = right._bits?.Length ?? 1; + nuint xInline = unchecked((nuint)left._sign); + nuint yInline = unchecked((nuint)right._sign); - nuint[]? leftBufferFromPool = null; - int size = (left._bits?.Length ?? 1) + 1; - Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - x = x.Slice(0, left.WriteTo(x)); - - nuint[]? rightBufferFromPool = null; - size = (right._bits?.Length ?? 1) + 1; - Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - y = y.Slice(0, right.WriteTo(y)); + int zLen = Math.Max(xLen, yLen) + 1; nuint[]? resultBufferFromPool = null; - size = Math.Max(x.Length, y.Length); - Span z = (size <= BigIntegerCalculator.StackAllocThreshold + Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); - for (int i = 0; i < z.Length; i++) + nuint xBorrow = 1, yBorrow = 1; + + for (int i = 0; i < zLen; i++) { - nuint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; - nuint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; + nuint xu = GetTwosComplementLimb(xBits, xInline, i, xLen, leftNeg, ref xBorrow); + nuint yu = GetTwosComplementLimb(yBits, yInline, i, yLen, rightNeg, ref yBorrow); z[i] = xu ^ yu; } - if (leftBufferFromPool != null) - ArrayPool.Shared.Return(leftBufferFromPool); - - if (rightBufferFromPool != null) - ArrayPool.Shared.Return(rightBufferFromPool); - var result = new BigInteger(z); - if (resultBufferFromPool != null) + if (resultBufferFromPool is not null) ArrayPool.Shared.Return(resultBufferFromPool); return result; } + /// + /// Returns the i-th limb of a BigInteger in two's complement representation, + /// computed on-the-fly from the magnitude without allocating a temp buffer. + /// For positive values, returns magnitude limbs with zero extension. + /// For negative values, computes ~magnitude + 1 with carry propagation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlineValue, int i, int len, bool isNegative, ref nuint borrow) + { + // Get the magnitude limb (or sign-extension beyond the value) + nuint mag; + if (bits.Length > 0) + { + mag = (uint)i < (uint)bits.Length ? bits[i] : 0; + } + else + { + // Inline value: _sign holds the value directly. + // For negative inline: magnitude is Abs(_sign), stored as positive nuint. + mag = i == 0 ? (isNegative ? NumericsHelpers.Abs(unchecked((nint)inlineValue)) : inlineValue) : 0; + } + + if (!isNegative) + return (uint)i < (uint)len ? mag : 0; + + // Two's complement: ~mag + borrow (borrow starts at 1 for the +1) + nuint tc = ~mag + borrow; + borrow = (tc < ~mag || (tc == 0 && borrow != 0)) ? (nuint)1 : 0; + return (uint)i < (uint)len ? tc : nuint.MaxValue; + } + public static BigInteger operator <<(BigInteger value, int shift) { if (shift == 0) From 71968acd1be801e6acd16dcfaa12993f610e1a33 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Mar 2026 18:42:29 -0400 Subject: [PATCH 06/27] Cache PowersOf1e9 table for repeated ToString/Parse calls The PowersOf1e9 table (computed by repeated squaring of 10^9) is deterministic and expensive to compute for large numbers. Cache it in a static field so that subsequent ToString/Parse calls on similarly-sized or smaller numbers reuse the precomputed table instead of recomputing from scratch. This eliminates the ArrayPool rent/return overhead at both call sites and avoids redundant squaring work entirely on cache hits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.BigInteger.cs | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index d1a04456908fa6..bebb7488696136 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -23,6 +23,8 @@ internal static partial class Number | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier); + private static nuint[]? s_cachedPowersOf1e9; + private static ReadOnlySpan UInt32PowersOfTen { get @@ -425,14 +427,7 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, int valueDigits = (base1E9.Length - 1) * PowersOf1e9.MaxPartialDigits + FormattingHelpers.CountDigits(base1E9[^1]); int powersOf1e9BufferLength = PowersOf1e9.GetBufferSize(Math.Max(valueDigits, trailingZeroCount + 1), out int maxIndex); - nuint[]? powersOf1e9BufferFromPool = null; - Span powersOf1e9Buffer = ( - (uint)powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : powersOf1e9BufferFromPool = ArrayPool.Shared.Rent(powersOf1e9BufferLength)).Slice(0, powersOf1e9BufferLength); - powersOf1e9Buffer.Clear(); - - PowersOf1e9 powersOf1e9 = new PowersOf1e9(powersOf1e9Buffer); + PowersOf1e9 powersOf1e9 = PowersOf1e9.GetCached(powersOf1e9BufferLength); if (trailingZeroCount > 0) { @@ -457,9 +452,6 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, { Recursive(powersOf1e9, maxIndex, base1E9, bits); } - - if (powersOf1e9BufferFromPool != null) - ArrayPool.Shared.Return(powersOf1e9BufferFromPool); } static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnlySpan base1E9, Span bits) @@ -1009,22 +1001,10 @@ private static void BigIntegerToBase1E9(ReadOnlySpan bits, Span ba } PowersOf1e9.FloorBufferSize(bits.Length, out int powersOf1e9BufferLength, out int maxIndex); - nuint[]? powersOf1e9BufferFromPool = null; - Span powersOf1e9Buffer = ( - powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : powersOf1e9BufferFromPool = ArrayPool.Shared.Rent(powersOf1e9BufferLength)).Slice(0, powersOf1e9BufferLength); - powersOf1e9Buffer.Clear(); - - PowersOf1e9 powersOf1e9 = new PowersOf1e9(powersOf1e9Buffer); + PowersOf1e9 powersOf1e9 = PowersOf1e9.GetCached(powersOf1e9BufferLength); DivideAndConquer(powersOf1e9, maxIndex, bits, base1E9Buffer, out base1E9Written); - if (powersOf1e9BufferFromPool != null) - { - ArrayPool.Shared.Return(powersOf1e9BufferFromPool); - } - static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOnlySpan bits, Span base1E9Buffer, out int base1E9Written) { Debug.Assert(bits.Length == 0 || bits[^1] != 0); @@ -1122,6 +1102,40 @@ internal readonly ref struct PowersOf1e9 public const nuint TenPowMaxPartial = 1000000000; public const int MaxPartialDigits = 9; + private PowersOf1e9(nuint[] pow1E9) + { + this.pow1E9 = pow1E9; + } + + public static PowersOf1e9 GetCached(int bufferLength) + { + nuint[]? cached = s_cachedPowersOf1e9; + if (cached is not null && cached.Length >= bufferLength) + { + return new PowersOf1e9(cached); + } + + nuint[] buffer = new nuint[bufferLength]; + PowersOf1e9 result = new PowersOf1e9((Span)buffer); + + // Only cache buffers large enough to contain computed powers. + // Small buffers (≤ LeadingPowers1E9.Length) aren't populated by + // the constructor — it uses the static LeadingPowers1E9 directly. + if (buffer.Length > LeadingPowers1E9.Length && + (cached is null || buffer.Length > cached.Length)) + { + // The write is safe without explicit memory barriers because: + // 1. The array is fully initialized before being stored. + // 2. On ARM64, the .NET GC write barrier uses stlr (store-release), + // providing release semantics for reference-type stores. + // 3. Readers have a data dependency (load reference → access elements), + // providing natural acquire ordering on all architectures. + s_cachedPowersOf1e9 = buffer; + } + + return result; + } + // indexes[i] is pre-calculated length of (10^9)^i // This means that pow1E9[indexes[i-1]..indexes[i]] equals 1000000000 * (1< Date: Tue, 17 Mar 2026 18:55:35 -0400 Subject: [PATCH 07/27] Tune Burnikel-Ziegler division threshold for 64-bit limbs Raise DivideBurnikelZieglerThreshold from 32 to 64 limbs based on empirical benchmarking with nuint (64-bit) limbs. With 64-bit limbs, each schoolbook division step processes twice the data, making the schoolbook algorithm competitive to larger sizes. The old threshold caused BZ to be used at 32-63 limb divisors where it was 9-26% slower than schoolbook due to recursive overhead. The new threshold of 64 improves division performance across all tested sizes (12-26% faster for balanced divisions). Even sizes above the threshold benefit because BZ sub-problems now bottom out into schoolbook earlier, avoiding near-threshold recursive overhead. Multiply thresholds (Karatsuba=32, Toom3=256) were validated as already optimal for 64-bit limbs through the same benchmarking. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/BigIntegerCalculator.DivRem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0da3ae123fdd2a..88bbfc7988a936 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 @@ -14,7 +14,7 @@ internal static #else internal const #endif - int DivideBurnikelZieglerThreshold = 32; + int DivideBurnikelZieglerThreshold = 64; public static void Divide(ReadOnlySpan left, nuint right, Span quotient, out nuint remainder) { From 2109128749f9af33c8698d4264a15fc332ea95b9 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Mar 2026 21:40:44 -0400 Subject: [PATCH 08/27] Add property-based validation tests and fix BitwiseAnd sign bug Add comprehensive BigIntegerPropertyTests covering algebraic invariants at various sizes including algorithm threshold boundaries: - Parse/ToString roundtrip (decimal and hex) - ToByteArray roundtrip - Division invariant (q*d+r == n) - Multiply/divide roundtrip ((a*b)/b == a) - Square vs multiply consistency (a*a == Pow(a,2)) - GCD divides both operands - Add/subtract roundtrip ((a+b)-b == a) - Shift roundtrip ((a<>n == a) - Carry propagation with all-ones patterns - Power-of-two boundary arithmetic - nuint.MaxValue edge cases - ModPow basic invariants (a^0=1, a^1=a, a^2=a*a) - Bitwise identities ((a&b)|(a^b)==a|b, a^a==0, etc.) Add nuint-boundary test data to existing Add/Subtract Theories: - Values at 2^64 boundary (carry/borrow across 64-bit limb) - Multi-limb carry propagation (all-ones + 1) Fix bug in fused BitwiseAnd: when both operands are positive, zLen lacked sign extension (+1), causing the two's complement constructor to misinterpret results with high bit set as negative. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/BigInteger.cs | 6 +- .../tests/BigInteger/BigInteger.AddTests.cs | 9 + .../BigInteger/BigInteger.SubtractTests.cs | 5 + .../BigInteger/BigIntegerPropertyTests.cs | 337 ++++++++++++++++++ .../System.Runtime.Numerics.Tests.csproj | 1 + 5 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs 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 f78cb1598be643..d8ec0df7cd2454 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -2672,11 +2672,13 @@ private static BigInteger BitwiseAnd(ref readonly BigInteger left, ref readonly nuint xInline = unchecked((nuint)left._sign); nuint yInline = unchecked((nuint)right._sign); - // AND result length: for positive operands, min length suffices (AND with 0 = 0). + // AND result length: for positive operands, min length suffices (AND with 0 = 0), + // plus 1 for sign extension so the two's complement constructor doesn't + // misinterpret a high bit in the top limb as a negative sign. // For negative operands (sign-extended with 1s), we need max length + 1 for sign. int zLen = (leftNeg || rightNeg) ? Math.Max(xLen, yLen) + 1 - : Math.Min(xLen, yLen); + : Math.Min(xLen, yLen) + 1; nuint[]? resultBufferFromPool = null; Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigInteger.AddTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigInteger.AddTests.cs index 63ebafc8be12cd..ca2bec78bdd6d6 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigInteger.AddTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigInteger.AddTests.cs @@ -67,6 +67,15 @@ public static IEnumerable BinaryPlus_TestData() yield return new object[] { new BigInteger(Math.Pow(2, 32)), new BigInteger(Math.Pow(2, 32)), new BigInteger(8589934592) }; yield return new object[] { new BigInteger(Math.Pow(2, 32) + Math.Pow(2, 31)), new BigInteger(Math.Pow(2, 32) + Math.Pow(2, 31)), new BigInteger(12884901888) }; + // 64-bit nuint boundaries (carry across limb boundary on 64-bit platforms) + yield return new object[] { new BigInteger(ulong.MaxValue), BigInteger.One, BigInteger.Parse("18446744073709551616") }; + yield return new object[] { new BigInteger(ulong.MaxValue), new BigInteger(ulong.MaxValue), BigInteger.Parse("36893488147419103230") }; + yield return new object[] { BigInteger.Parse("18446744073709551616"), BigInteger.Parse("18446744073709551616"), BigInteger.Parse("36893488147419103232") }; + + // Multi-limb carry propagation (all-ones + 1 forces carry through every limb) + yield return new object[] { (BigInteger.One << 256) - 1, BigInteger.One, BigInteger.One << 256 }; + yield return new object[] { (BigInteger.One << 512) - 1, BigInteger.One, BigInteger.One << 512 }; + // Very large + very large yield return new object[] { diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigInteger.SubtractTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigInteger.SubtractTests.cs index 7d8485a9543248..9d3b8784fa1d23 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigInteger.SubtractTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigInteger.SubtractTests.cs @@ -70,6 +70,11 @@ public static IEnumerable BinaryMinus_TestData() yield return new object[] { largeNegativeBigInt1, new BigInteger(123), BigInteger.Parse("-" + LargePositiveBigIntMinusSmallNegativeBigIntString) }; yield return new object[] { largeNegativeBigInt1, new BigInteger(-123), BigInteger.Parse("-" + LargePositiveBigIntMinusSmallPositiveBigIntString) }; + // 64-bit nuint boundaries (borrow across limb boundary on 64-bit platforms) + yield return new object[] { BigInteger.Parse("18446744073709551616"), BigInteger.One, BigInteger.Parse("18446744073709551615") }; + yield return new object[] { BigInteger.One << 256, BigInteger.One, (BigInteger.One << 256) - 1 }; + yield return new object[] { BigInteger.One << 512, BigInteger.One, (BigInteger.One << 512) - 1 }; + // Very large - very large yield return new object[] { diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs new file mode 100644 index 00000000000000..4a7193bc1f0d12 --- /dev/null +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs @@ -0,0 +1,337 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using Xunit; + +namespace System.Numerics.Tests +{ + /// + /// Property-based algebraic verification tests for BigInteger. + /// These test cross-operation invariants at various sizes including + /// algorithm threshold boundaries relevant to nuint-width limbs. + /// + public class BigIntegerPropertyTests + { + // On 64-bit, nuint limbs are 8 bytes. These sizes (in bytes) are chosen to exercise: + // - Small (inline _sign), single limb, multi-limb + // - Algorithm threshold boundaries (Karatsuba=32 limbs, BZ=64 limbs, Toom3=256 limbs) + // Byte counts map to limb counts via ceil(bytes / nint.Size). + public static IEnumerable ByteSizes => new object[][] + { + new object[] { 1 }, // inline in _sign + new object[] { 4 }, // single uint, single nuint on 32-bit + new object[] { 8 }, // single nuint on 64-bit + new object[] { 9 }, // 2 limbs on 64-bit + new object[] { 16 }, // 2 nuint limbs + new object[] { 64 }, // 8 limbs on 64-bit + new object[] { 128 }, // 16 limbs + new object[] { 248 }, // just below Karatsuba threshold on 64-bit (31 limbs) + new object[] { 264 }, // just above Karatsuba threshold on 64-bit (33 limbs) + new object[] { 504 }, // just below BZ threshold on 64-bit (63 limbs) + new object[] { 520 }, // just above BZ threshold on 64-bit (65 limbs) + new object[] { 1024 }, // 128 limbs, well into Karatsuba territory + new object[] { 2040 }, // just below Toom3 threshold (255 limbs) + new object[] { 2056 }, // just above Toom3 threshold (257 limbs) + }; + + private static BigInteger MakeRandom(int byteCount, int seed) => + MakeRandom(byteCount, new Random(seed)); + + private static BigInteger MakeRandom(int byteCount, Random rng) + { + byte[] bytes = new byte[byteCount + 1]; // +1 for sign byte + rng.NextBytes(bytes); + bytes[^1] = 0; // ensure positive + if (bytes.Length > 1 && bytes[^2] == 0) + bytes[^2] = 1; // ensure non-zero high byte + return new BigInteger(bytes); + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void ParseToStringRoundtrip(int byteCount) + { + var rng = new Random(42 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger original = MakeRandom(byteCount, rng); + string decStr = original.ToString(); + Assert.Equal(original, BigInteger.Parse(decStr)); + + string hexStr = original.ToString("X"); + Assert.Equal(original, BigInteger.Parse(hexStr, NumberStyles.HexNumber)); + + // Negative + BigInteger neg = -original; + Assert.Equal(neg, BigInteger.Parse(neg.ToString())); + } + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void ToByteArrayRoundtrip(int byteCount) + { + var rng = new Random(123 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger original = MakeRandom(byteCount, rng); + byte[] bytes = original.ToByteArray(); + Assert.Equal(original, new BigInteger(bytes)); + + BigInteger neg = -original; + bytes = neg.ToByteArray(); + Assert.Equal(neg, new BigInteger(bytes)); + } + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void DivisionInvariant(int byteCount) + { + var rng = new Random(200 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger dividend = MakeRandom(byteCount, rng); + BigInteger divisor = MakeRandom(Math.Max(1, byteCount / 2), rng); + if (divisor.IsZero) divisor = BigInteger.One; + + var (quotient, remainder) = BigInteger.DivRem(dividend, divisor); + + // q * d + r == n + Assert.Equal(dividend, quotient * divisor + remainder); + + // |r| < |d| + Assert.True(BigInteger.Abs(remainder) < BigInteger.Abs(divisor)); + + // Verify with negatives too + var (q2, r2) = BigInteger.DivRem(-dividend, divisor); + Assert.Equal(-dividend, q2 * divisor + r2); + } + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void MultiplyDivideRoundtrip(int byteCount) + { + var rng = new Random(300 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger a = MakeRandom(byteCount, rng); + BigInteger b = MakeRandom(Math.Max(1, byteCount / 2), rng); + if (b.IsZero) b = BigInteger.One; + + BigInteger product = a * b; + Assert.Equal(a, product / b); + } + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void SquareVsMultiply(int byteCount) + { + var rng = new Random(400 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger a = MakeRandom(byteCount, rng); + Assert.Equal(a * a, BigInteger.Pow(a, 2)); + } + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void GcdDividesBoth(int byteCount) + { + var rng = new Random(500 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger a = MakeRandom(byteCount, rng); + BigInteger b = MakeRandom(Math.Max(1, byteCount / 2), rng); + + BigInteger gcd = BigInteger.GreatestCommonDivisor(a, b); + + if (!gcd.IsZero) + { + Assert.Equal(BigInteger.Zero, a % gcd); + Assert.Equal(BigInteger.Zero, b % gcd); + } + } + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void AddSubtractRoundtrip(int byteCount) + { + var rng = new Random(600 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger a = MakeRandom(byteCount, rng); + BigInteger b = MakeRandom(byteCount, rng); + + Assert.Equal(a, (a + b) - b); + Assert.Equal(b, (a + b) - a); + Assert.Equal(a + b, b + a); // commutativity + } + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void ShiftRoundtrip(int byteCount) + { + var rng = new Random(700 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger a = MakeRandom(byteCount, rng); + foreach (int shift in new[] { 1, 7, 8, 15, 16, 31, 32, 33, 63, 64, 65, 128 }) + { + Assert.Equal(a, (a << shift) >> shift); + } + } + } + + [Theory] + [InlineData(64)] + [InlineData(128)] + [InlineData(256)] + [InlineData(512)] + [InlineData(1024)] + [InlineData(2048)] + [InlineData(4096)] + public void CarryPropagationAllOnes(int bits) + { + BigInteger allOnes = (BigInteger.One << bits) - 1; + BigInteger powerOf2 = BigInteger.One << bits; + + // All-ones + 1 should equal the next power of 2 + Assert.Equal(powerOf2, allOnes + 1); + + // Power of 2 - 1 should equal all-ones + Assert.Equal(allOnes, powerOf2 - 1); + + // All-ones * all-ones should be (2^bits - 1)^2 + BigInteger squared = allOnes * allOnes; + Assert.Equal(allOnes, BigInteger.Pow(allOnes, 2).IsqrtCheck()); + + // Verify ToString/Parse roundtrip for all-ones pattern + Assert.Equal(allOnes, BigInteger.Parse(allOnes.ToString())); + } + + [Theory] + [InlineData(31)] + [InlineData(32)] + [InlineData(33)] + [InlineData(63)] + [InlineData(64)] + [InlineData(65)] + [InlineData(127)] + [InlineData(128)] + [InlineData(129)] + public void PowerOfTwoBoundaries(int bits) + { + BigInteger p = BigInteger.One << bits; + BigInteger pMinus1 = p - 1; + BigInteger pPlus1 = p + 1; + + // Verify basic identities + Assert.Equal(p, pMinus1 + 1); + Assert.Equal(p, pPlus1 - 1); + + // Division: (2^n) / (2^n - 1) should be 1 remainder 1 + var (q, r) = BigInteger.DivRem(p, pMinus1); + Assert.Equal(BigInteger.One, q); + Assert.Equal(BigInteger.One, r); + + // Parse/ToString roundtrip at boundary + Assert.Equal(p, BigInteger.Parse(p.ToString())); + Assert.Equal(pMinus1, BigInteger.Parse(pMinus1.ToString())); + } + + [Fact] + public void NuintMaxValueEdgeCases() + { + // Test values at nuint boundaries + BigInteger uint32Max = new BigInteger(uint.MaxValue); + BigInteger uint64Max = new BigInteger(ulong.MaxValue); + + // Arithmetic at uint boundaries + Assert.Equal(new BigInteger((long)uint.MaxValue + 1), uint32Max + 1); + Assert.Equal(uint32Max * uint32Max, BigInteger.Parse("18446744065119617025")); + + // Arithmetic at ulong boundaries + Assert.Equal(BigInteger.Parse("18446744073709551616"), uint64Max + 1); + Assert.Equal(BigInteger.Parse("340282366920938463426481119284349108225"), uint64Max * uint64Max); + + // Division at boundary + var (q, r) = BigInteger.DivRem(uint64Max * uint64Max, uint64Max); + Assert.Equal(uint64Max, q); + Assert.Equal(BigInteger.Zero, r); + } + + [Theory] + [InlineData(32)] + [InlineData(64)] + [InlineData(128)] + [InlineData(256)] + [InlineData(512)] + public void ModPowBasicInvariants(int bits) + { + var rng = new Random(800 + bits); + BigInteger b = MakeRandom(bits / 8, rng); + BigInteger modulus = MakeRandom(bits / 8, rng); + if (modulus <= 1) modulus = BigInteger.Parse("1000000007"); + + // a^1 mod m == a mod m + Assert.Equal(b % modulus, BigInteger.ModPow(b, 1, modulus)); + + // a^0 mod m == 1 (when m > 1) + Assert.Equal(BigInteger.One, BigInteger.ModPow(b, 0, modulus)); + + // (a^2) mod m == (a * a) mod m + BigInteger mp2 = BigInteger.ModPow(b, 2, modulus); + BigInteger direct = (b * b) % modulus; + Assert.Equal(direct, mp2); + } + + [Theory] + [MemberData(nameof(ByteSizes))] + public void BitwiseRoundtrip(int byteCount) + { + var rng = new Random(900 + byteCount); + for (int trial = 0; trial < 3; trial++) + { + BigInteger a = MakeRandom(byteCount, rng); + BigInteger b = MakeRandom(byteCount, rng); + + // (a & b) | (a ^ b) == a | b + Assert.Equal(a | b, (a & b) | (a ^ b)); + + // a ^ a == 0 + Assert.Equal(BigInteger.Zero, a ^ a); + + // a & a == a + Assert.Equal(a, a & a); + + // a | a == a + Assert.Equal(a, a | a); + } + } + } + + internal static class BigIntegerTestExtensions + { + /// Verify that value^2 == original, returning the sqrt. + internal static BigInteger IsqrtCheck(this BigInteger squared) + { + // Newton's method integer sqrt for verification + if (squared.IsZero) return BigInteger.Zero; + BigInteger x = BigInteger.One << ((int)squared.GetBitLength() / 2 + 1); + while (true) + { + BigInteger next = (x + squared / x) / 2; + if (next >= x) return x; + x = next; + } + } + } +} diff --git a/src/libraries/System.Runtime.Numerics/tests/System.Runtime.Numerics.Tests.csproj b/src/libraries/System.Runtime.Numerics/tests/System.Runtime.Numerics.Tests.csproj index 5d468f9ce66885..540676f2d3e1af 100644 --- a/src/libraries/System.Runtime.Numerics/tests/System.Runtime.Numerics.Tests.csproj +++ b/src/libraries/System.Runtime.Numerics/tests/System.Runtime.Numerics.Tests.csproj @@ -47,6 +47,7 @@ + From defa53d70d94cd01e850794293f036e14310e07e Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Mar 2026 22:43:33 -0400 Subject: [PATCH 09/27] Add edge-case tests for sign combos, SIMD boundaries, and coverage gaps Add BigIntegerEdgeCaseTests class with tests targeting: - All 4 sign combinations (+/+, +/-, -/+, -/-) for arithmetic and bitwise operations, exercising two's complement paths - Vector-aligned limb counts (2,3,4,5,7,8,9,15,16,17 limbs) to exercise SIMD loop tails at Vector128/256/512 boundaries - Asymmetric operand sizes (1x65, 3x33, 16x33 limbs etc.) to exercise RightSmall and mixed-algorithm paths - Toom-Cook 3 multiply with both operands >= 256 limbs - Barrett reduction in ModPow with large modulus (65 limbs) - GCD with specific operand size offsets (0,1,2,3+ limb difference) - CopySign with all inline/array and positive/negative combinations - Explicit Int32/Int64/Int128/UInt128 conversions at boundaries - Power-of-two divisors with shift equivalence verification - Threshold-straddling multiply pairs at Karatsuba/BZ/Toom3 edges Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../BigInteger/BigIntegerPropertyTests.cs | 618 +++++++++++++++++- 1 file changed, 615 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs index 4a7193bc1f0d12..37cc5cbf8d532a 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs @@ -209,9 +209,8 @@ public void CarryPropagationAllOnes(int bits) // Power of 2 - 1 should equal all-ones Assert.Equal(allOnes, powerOf2 - 1); - // All-ones * all-ones should be (2^bits - 1)^2 - BigInteger squared = allOnes * allOnes; - Assert.Equal(allOnes, BigInteger.Pow(allOnes, 2).IsqrtCheck()); + // Verify integer sqrt of allOnes^2 recovers allOnes + Assert.Equal(allOnes, (allOnes * allOnes).IsqrtCheck()); // Verify ToString/Parse roundtrip for all-ones pattern Assert.Equal(allOnes, BigInteger.Parse(allOnes.ToString())); @@ -318,6 +317,619 @@ public void BitwiseRoundtrip(int byteCount) } } + /// + /// Tests exercising sign combinations, vectorization boundaries, + /// special values, and asymmetric operand sizes. + /// + public class BigIntegerEdgeCaseTests + { + // Sizes chosen to exercise SIMD vector tail handling. + // On 64-bit: Vector128=2 limbs, Vector256=4, Vector512=8. + // Shift loops need VectorWidth+1 limbs minimum. + // Multiply: Karatsuba=32 limbs, Toom3=256 limbs. + public static IEnumerable VectorAlignedLimbCounts => new object[][] + { + new object[] { 1 }, // scalar only + new object[] { 2 }, // Vector128 width (no SIMD — needs 3 for shift) + new object[] { 3 }, // Vector128 + 1 (minimum for SIMD shift) + new object[] { 4 }, // Vector256 width + new object[] { 5 }, // Vector256 + 1 (minimum for Vector256 shift) + new object[] { 7 }, // Vector512 - 1 + new object[] { 8 }, // Vector512 width + new object[] { 9 }, // Vector512 + 1 (minimum for Vector512 shift) + new object[] { 15 }, // 2×Vector512 - 1 + new object[] { 16 }, // 2×Vector512 + new object[] { 17 }, // 2×Vector512 + 1 + new object[] { 31 }, // Karatsuba - 1 + new object[] { 32 }, // Karatsuba threshold + new object[] { 33 }, // Karatsuba + 1 + new object[] { 63 }, // BZ - 1 + new object[] { 64 }, // BZ threshold + new object[] { 65 }, // BZ + 1 + }; + + private static BigInteger MakePositive(int limbCount, Random rng) + { + int byteCount = limbCount * nint.Size; + byte[] bytes = new byte[byteCount + 1]; + rng.NextBytes(bytes); + bytes[^1] = 0; + if (bytes.Length > 1 && bytes[^2] == 0) + bytes[^2] = 1; + return new BigInteger(bytes); + } + + // --- Sign combination tests --- + + public static IEnumerable SignCombinations() + { + int[] limbCounts = [1, 3, 9, 33, 65]; + foreach (int n in limbCounts) + { + yield return new object[] { n, true, true }; + yield return new object[] { n, true, false }; + yield return new object[] { n, false, true }; + yield return new object[] { n, false, false }; + } + } + + [Theory] + [MemberData(nameof(SignCombinations))] + public void ArithmeticSignCombinations(int limbCount, bool aNeg, bool bNeg) + { + var rng = new Random(1000 + limbCount * 4 + (aNeg ? 2 : 0) + (bNeg ? 1 : 0)); + BigInteger a = MakePositive(limbCount, rng); + BigInteger b = MakePositive(Math.Max(1, limbCount / 2), rng); + if (aNeg) a = -a; + if (bNeg) b = -b; + + // Add/subtract roundtrip + Assert.Equal(a, (a + b) - b); + Assert.Equal(a + b, b + a); + + // Multiply/divide roundtrip + BigInteger product = a * b; + Assert.Equal(a, product / b); + + // Division invariant + var (q, r) = BigInteger.DivRem(a, b); + Assert.Equal(a, q * b + r); + Assert.True(BigInteger.Abs(r) < BigInteger.Abs(b)); + } + + [Theory] + [MemberData(nameof(SignCombinations))] + public void BitwiseSignCombinations(int limbCount, bool aNeg, bool bNeg) + { + var rng = new Random(2000 + limbCount * 4 + (aNeg ? 2 : 0) + (bNeg ? 1 : 0)); + BigInteger a = MakePositive(limbCount, rng); + BigInteger b = MakePositive(limbCount, rng); + if (aNeg) a = -a; + if (bNeg) b = -b; + + // De Morgan: ~(a & b) == (~a) | (~b) + Assert.Equal(~(a & b), (~a) | (~b)); + + // De Morgan: ~(a | b) == (~a) & (~b) + Assert.Equal(~(a | b), (~a) & (~b)); + + // XOR identity: (a ^ b) ^ b == a + Assert.Equal(a, (a ^ b) ^ b); + + // Self-identities + Assert.Equal(BigInteger.Zero, a ^ a); + Assert.Equal(a, a & a); + Assert.Equal(a, a | a); + } + + // --- Vectorization boundary tests --- + + [Theory] + [MemberData(nameof(VectorAlignedLimbCounts))] + public void ShiftAtVectorBoundaries(int limbCount) + { + var rng = new Random(3000 + limbCount); + BigInteger a = MakePositive(limbCount, rng); + + // Shift amounts that exercise different vector paths + foreach (int shift in new[] { 1, 63, 64, 65, 127, 128, 129, 255, 256, 512 }) + { + BigInteger shifted = a << shift; + Assert.Equal(a, shifted >> shift); + + // Negative shift + BigInteger negA = -a; + BigInteger negShifted = negA << shift; + Assert.Equal(negA, negShifted >> shift); + } + } + + [Theory] + [MemberData(nameof(VectorAlignedLimbCounts))] + public void BitwiseNotAtVectorBoundaries(int limbCount) + { + var rng = new Random(4000 + limbCount); + BigInteger a = MakePositive(limbCount, rng); + + // ~~a == a + Assert.Equal(a, ~~a); + + // ~a == -(a+1) for all integers + Assert.Equal(-(a + 1), ~a); + + // Negative too + BigInteger neg = -a; + Assert.Equal(neg, ~~neg); + Assert.Equal(-(neg + 1), ~neg); + } + + [Theory] + [MemberData(nameof(VectorAlignedLimbCounts))] + public void MultiplyAtVectorBoundaries(int limbCount) + { + var rng = new Random(5000 + limbCount); + BigInteger a = MakePositive(limbCount, rng); + BigInteger b = MakePositive(limbCount, rng); + + BigInteger product = a * b; + + // Commutativity + Assert.Equal(product, b * a); + + // Multiply/divide roundtrip + Assert.Equal(a, product / b); + Assert.Equal(b, product / a); + + // Square consistency + Assert.Equal(a * a, BigInteger.Pow(a, 2)); + } + + // --- Asymmetric operand sizes --- + + public static IEnumerable AsymmetricSizePairs => new object[][] + { + new object[] { 1, 9 }, // 1 limb × 9 limbs (Vector512 + 1) + new object[] { 1, 33 }, // 1 limb × above Karatsuba + new object[] { 1, 65 }, // 1 limb × above BZ + new object[] { 3, 33 }, // small × Karatsuba+1 + new object[] { 9, 65 }, // Vector512+1 × BZ+1 + new object[] { 16, 33 }, // half of Karatsuba × Karatsuba+1 (RightSmall path) + new object[] { 31, 64 }, // Karatsuba-1 × BZ (left>2*right triggers RightSmall) + new object[] { 33, 65 }, // both above Karatsuba, different sizes + }; + + [Theory] + [MemberData(nameof(AsymmetricSizePairs))] + public void AsymmetricMultiply(int smallLimbs, int largeLimbs) + { + var rng = new Random(6000 + smallLimbs * 100 + largeLimbs); + BigInteger small = MakePositive(smallLimbs, rng); + BigInteger large = MakePositive(largeLimbs, rng); + + BigInteger product = small * large; + Assert.Equal(product, large * small); + Assert.Equal(small, product / large); + Assert.Equal(large, product / small); + } + + [Theory] + [MemberData(nameof(AsymmetricSizePairs))] + public void AsymmetricDivision(int smallLimbs, int largeLimbs) + { + var rng = new Random(7000 + smallLimbs * 100 + largeLimbs); + BigInteger dividend = MakePositive(largeLimbs, rng); + BigInteger divisor = MakePositive(smallLimbs, rng); + + var (q, r) = BigInteger.DivRem(dividend, divisor); + Assert.Equal(dividend, q * divisor + r); + Assert.True(BigInteger.Abs(r) < BigInteger.Abs(divisor)); + + // Also with negative dividend + var (q2, r2) = BigInteger.DivRem(-dividend, divisor); + Assert.Equal(-dividend, q2 * divisor + r2); + } + + [Theory] + [MemberData(nameof(AsymmetricSizePairs))] + public void AsymmetricGcd(int smallLimbs, int largeLimbs) + { + var rng = new Random(8000 + smallLimbs * 100 + largeLimbs); + BigInteger a = MakePositive(largeLimbs, rng); + BigInteger b = MakePositive(smallLimbs, rng); + + BigInteger gcd = BigInteger.GreatestCommonDivisor(a, b); + if (!gcd.IsZero) + { + Assert.Equal(BigInteger.Zero, a % gcd); + Assert.Equal(BigInteger.Zero, b % gcd); + } + } + + // --- Special value interactions --- + + public static IEnumerable SpecialValues() + { + yield return new object[] { BigInteger.Zero, "0" }; + yield return new object[] { BigInteger.One, "1" }; + yield return new object[] { BigInteger.MinusOne, "-1" }; + yield return new object[] { new BigInteger(nint.MaxValue), "nint.MaxValue" }; + yield return new object[] { new BigInteger(nint.MaxValue) + 1, "nint.MaxValue+1" }; + yield return new object[] { new BigInteger(nint.MinValue), "nint.MinValue" }; + yield return new object[] { new BigInteger(int.MinValue), "int.MinValue" }; + yield return new object[] { new BigInteger(uint.MaxValue), "uint.MaxValue" }; + yield return new object[] { new BigInteger(ulong.MaxValue), "ulong.MaxValue" }; + } + + [Theory] + [MemberData(nameof(SpecialValues))] + public void SpecialValueArithmetic(BigInteger special, string _) + { + // Identity properties + Assert.Equal(special, special + BigInteger.Zero); + Assert.Equal(special, special - BigInteger.Zero); + Assert.Equal(special, special * BigInteger.One); + Assert.Equal(BigInteger.Zero, special * BigInteger.Zero); + Assert.Equal(-special, special * BigInteger.MinusOne); + + if (!special.IsZero) + { + Assert.Equal(BigInteger.One, special / special); + Assert.Equal(BigInteger.Zero, special % special); + Assert.Equal(BigInteger.Zero, BigInteger.Zero / special); + } + + // Parse/ToString roundtrip + Assert.Equal(special, BigInteger.Parse(special.ToString())); + } + + [Theory] + [MemberData(nameof(SpecialValues))] + public void SpecialValueBitwise(BigInteger special, string _) + { + Assert.Equal(BigInteger.Zero, special ^ special); + Assert.Equal(special, special & special); + Assert.Equal(special, special | special); + Assert.Equal(special, ~~special); + Assert.Equal(-(special + 1), ~special); + + // AND with zero = zero, OR with zero = identity + Assert.Equal(BigInteger.Zero, special & BigInteger.Zero); + Assert.Equal(special, special | BigInteger.Zero); + } + + [Theory] + [MemberData(nameof(SpecialValues))] + public void SpecialValueWithLargeOperand(BigInteger special, string _) + { + var rng = new Random(9000); + BigInteger large = MakePositive(33, rng); // above Karatsuba threshold + + // Arithmetic identities hold with mixed sizes + Assert.Equal(special + large, large + special); + Assert.Equal(special * large, large * special); + + if (!special.IsZero) + { + var (q, r) = BigInteger.DivRem(large, special); + Assert.Equal(large, q * special + r); + } + } + + // --- Threshold-straddling multiply pairs --- + + [Theory] + [InlineData(31, 32)] // one below, one at Karatsuba + [InlineData(32, 33)] // one at, one above Karatsuba + [InlineData(31, 33)] // one below, one above Karatsuba + [InlineData(16, 33)] // half × Karatsuba+1 (RightSmall path) + [InlineData(17, 33)] // just over half × Karatsuba+1 + [InlineData(63, 64)] // at BZ boundary + [InlineData(255, 256)] // at Toom3 boundary + [InlineData(256, 257)] // at/above Toom3 + public void ThresholdStraddlingMultiply(int leftLimbs, int rightLimbs) + { + var rng = new Random(10000 + leftLimbs * 1000 + rightLimbs); + BigInteger a = MakePositive(leftLimbs, rng); + BigInteger b = MakePositive(rightLimbs, rng); + + BigInteger product = a * b; + Assert.Equal(product, b * a); + Assert.Equal(a, product / b); + Assert.Equal(b, product / a); + + // Square at threshold + BigInteger aSq = a * a; + Assert.Equal(aSq, BigInteger.Pow(a, 2)); + } + + // --- Power-of-two divisors (fast paths in some implementations) --- + + [Theory] + [InlineData(1)] + [InlineData(31)] + [InlineData(32)] + [InlineData(63)] + [InlineData(64)] + [InlineData(65)] + [InlineData(128)] + [InlineData(256)] + public void PowerOfTwoDivisor(int bits) + { + var rng = new Random(11000 + bits); + BigInteger dividend = MakePositive(Math.Max(1, (bits / (nint.Size * 8)) + 2), rng); + BigInteger powerOf2 = BigInteger.One << bits; + + var (q, r) = BigInteger.DivRem(dividend, powerOf2); + Assert.Equal(dividend, q * powerOf2 + r); + Assert.True(r >= 0 && r < powerOf2); + + // Shift equivalence: dividend / 2^n == dividend >> n (for positive) + Assert.Equal(dividend >> bits, q); + } + + // --- Toom-Cook 3 body coverage (both operands >= 256 limbs) --- + + [Theory] + [InlineData(256, 256)] // Equal size at Toom3 threshold + [InlineData(256, 300)] // Right larger + [InlineData(300, 300)] // Both well above threshold + [InlineData(256, 512)] // 2x asymmetry at Toom3 scale + public void Toom3Multiply(int leftLimbs, int rightLimbs) + { + var rng = new Random(12000 + leftLimbs * 1000 + rightLimbs); + BigInteger a = MakePositive(leftLimbs, rng); + BigInteger b = MakePositive(rightLimbs, rng); + + BigInteger product = a * b; + Assert.Equal(product, b * a); + + // Square consistency at Toom3 scale + BigInteger aSq = a * a; + Assert.Equal(aSq, BigInteger.Pow(a, 2)); + } + + // --- Barrett reduction in ModPow (large modulus) --- + + [Theory] + [InlineData(8, 4)] // Small modulus (no Barrett) + [InlineData(33, 16)] // Medium modulus + [InlineData(65, 32)] // Large modulus (Barrett reduction) + public void ModPowLargeModulus(int modulusLimbs, int baseLimbs) + { + var rng = new Random(13000 + modulusLimbs); + BigInteger modulus = MakePositive(modulusLimbs, rng); + BigInteger b = MakePositive(baseLimbs, rng); + BigInteger exp = new BigInteger(65537); // Common RSA exponent + + BigInteger result = BigInteger.ModPow(b, exp, modulus); + + // Result must be in [0, modulus) + Assert.True(result >= 0); + Assert.True(result < modulus); + + // Verify against manual computation for small exponent + // a^1 mod m == a mod m (basic sanity) + BigInteger r1 = BigInteger.ModPow(b, 1, modulus); + BigInteger expected1 = b % modulus; + if (expected1 < 0) expected1 += modulus; + Assert.Equal(expected1, r1); + + // a^2 mod m == (a*a) mod m + BigInteger r2 = BigInteger.ModPow(b, 2, modulus); + BigInteger expected2 = (b * b) % modulus; + if (expected2 < 0) expected2 += modulus; + Assert.Equal(expected2, r2); + } + + // --- GCD with specific size differences --- + + [Theory] + [InlineData(10, 10)] // Same length (case 0) + [InlineData(10, 9)] // Offset by 1 (case 1) + [InlineData(10, 8)] // Offset by 2 (case 2) + [InlineData(10, 5)] // Offset by 5 (default case) + [InlineData(33, 33)] // Large, same length + [InlineData(33, 32)] // Large, offset by 1 + [InlineData(33, 31)] // Large, offset by 2 + [InlineData(65, 33)] // Very different sizes + public void GcdSizeOffsets(int aLimbs, int bLimbs) + { + var rng = new Random(14000 + aLimbs * 100 + bLimbs); + BigInteger a = MakePositive(aLimbs, rng); + BigInteger b = MakePositive(bLimbs, rng); + + BigInteger gcd = BigInteger.GreatestCommonDivisor(a, b); + + // GCD divides both + Assert.True(gcd > 0); + Assert.Equal(BigInteger.Zero, a % gcd); + Assert.Equal(BigInteger.Zero, b % gcd); + + // GCD is symmetric + Assert.Equal(gcd, BigInteger.GreatestCommonDivisor(b, a)); + + // GCD with negatives + Assert.Equal(gcd, BigInteger.GreatestCommonDivisor(-a, b)); + Assert.Equal(gcd, BigInteger.GreatestCommonDivisor(a, -b)); + } + + // --- CopySign coverage --- + + [Theory] + [InlineData(5, true, 3, true)] // inline pos, inline pos → keep pos + [InlineData(5, false, -3, false)] // inline pos, inline neg → flip to neg + [InlineData(-5, true, 3, true)] // inline neg, inline pos → flip to pos + [InlineData(-5, false, -3, false)] // inline neg, inline neg → keep neg + public void CopySignInline(long value, bool expectPositive, long sign, bool _) + { + BigInteger v = new BigInteger(value); + BigInteger s = new BigInteger(sign); + BigInteger result = BigInteger.CopySign(v, s); + Assert.Equal(BigInteger.Abs(v), BigInteger.Abs(result)); + Assert.Equal(expectPositive ? 1 : -1, result.Sign); + } + + [Fact] + public void CopySignMixed() + { + var rng = new Random(15000); + BigInteger large = MakePositive(10, rng); + + // Array value, inline sign + Assert.Equal(large, BigInteger.CopySign(large, BigInteger.One)); + Assert.Equal(-large, BigInteger.CopySign(large, BigInteger.MinusOne)); + Assert.Equal(large, BigInteger.CopySign(-large, BigInteger.One)); + Assert.Equal(-large, BigInteger.CopySign(-large, BigInteger.MinusOne)); + + // Inline value, array sign + BigInteger small = new BigInteger(42); + Assert.Equal(small, BigInteger.CopySign(small, large)); + Assert.Equal(-small, BigInteger.CopySign(small, -large)); + + // Array value, array sign + BigInteger other = MakePositive(10, rng); + Assert.Equal(large, BigInteger.CopySign(large, other)); + Assert.Equal(-large, BigInteger.CopySign(large, -other)); + + // Zero value + Assert.Equal(BigInteger.Zero, BigInteger.CopySign(BigInteger.Zero, large)); + Assert.Equal(BigInteger.Zero, BigInteger.CopySign(BigInteger.Zero, -large)); + } + + // --- Explicit conversions at boundaries --- + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(-1)] + [InlineData(int.MaxValue)] + [InlineData(int.MinValue)] + public void ExplicitInt32Conversion(int value) + { + BigInteger bi = new BigInteger(value); + Assert.Equal(value, (int)bi); + } + + [Theory] + [InlineData(0L)] + [InlineData(1L)] + [InlineData(-1L)] + [InlineData(long.MaxValue)] + [InlineData(long.MinValue)] + [InlineData((long)int.MaxValue + 1)] + [InlineData((long)int.MinValue - 1)] + public void ExplicitInt64Conversion(long value) + { + BigInteger bi = new BigInteger(value); + Assert.Equal(value, (long)bi); + } + + [Fact] + public void ExplicitInt128Conversion() + { + // Values that exercise multi-limb Int128 conversion paths + Int128 val1 = (Int128)long.MaxValue + 1; + Assert.Equal(val1, (Int128)(BigInteger)val1); + + Int128 val2 = Int128.MaxValue; + Assert.Equal(val2, (Int128)(BigInteger)val2); + + Int128 val3 = Int128.MinValue; + Assert.Equal(val3, (Int128)(BigInteger)val3); + + Int128 val4 = (Int128)ulong.MaxValue + 1; + Assert.Equal(val4, (Int128)(BigInteger)val4); + + // Negative values near boundaries + Int128 val5 = -(Int128)long.MaxValue - 2; + Assert.Equal(val5, (Int128)(BigInteger)val5); + + // Overflow + BigInteger tooLarge = (BigInteger)Int128.MaxValue + 1; + Assert.Throws(() => (Int128)tooLarge); + } + + [Fact] + public void ExplicitUInt128Conversion() + { + UInt128 val1 = (UInt128)ulong.MaxValue + 1; + Assert.Equal(val1, (UInt128)(BigInteger)val1); + + UInt128 val2 = UInt128.MaxValue; + Assert.Equal(val2, (UInt128)(BigInteger)val2); + + UInt128 val3 = UInt128.MinValue; + Assert.Equal(val3, (UInt128)(BigInteger)val3); + + // Overflow + BigInteger tooLarge = (BigInteger)UInt128.MaxValue + 1; + Assert.Throws(() => (UInt128)tooLarge); + + BigInteger negative = BigInteger.MinusOne; + Assert.Throws(() => (UInt128)negative); + } + + // --- DebuggerDisplay for large values --- + + [Fact] + public void DebuggerDisplayLargeValue() + { + BigInteger large = MakePositive(10, new Random(16000)); + BigInteger veryLarge = MakePositive(100, new Random(16001)); + + // DebuggerDisplay is accessed via DebuggerDisplayAttribute + // Verify it doesn't throw for large values + string display1 = large.ToString(); + Assert.NotNull(display1); + Assert.NotEmpty(display1); + + string display2 = veryLarge.ToString(); + Assert.NotNull(display2); + Assert.NotEmpty(display2); + + // Verify roundtrip still works at these sizes + Assert.Equal(large, BigInteger.Parse(display1)); + Assert.Equal(veryLarge, BigInteger.Parse(display2)); + } + + // --- Inline-to-array representation boundary --- + + [Fact] + public void InlineToArrayTransition() + { + BigInteger nintMax = new BigInteger(nint.MaxValue); + BigInteger nintMaxPlus1 = nintMax + 1; + BigInteger nintMin = new BigInteger(nint.MinValue); + BigInteger nintMinMinus1 = nintMin - 1; + + // Arithmetic across the boundary + Assert.Equal(nintMax, nintMaxPlus1 - 1); + Assert.Equal(nintMaxPlus1, nintMax + 1); + Assert.Equal(nintMin, nintMinMinus1 + 1); + Assert.Equal(nintMinMinus1, nintMin - 1); + + // Multiply at boundary + Assert.Equal(nintMax * nintMax, BigInteger.Pow(nintMax, 2)); + + // Parse/ToString roundtrip at boundary + Assert.Equal(nintMax, BigInteger.Parse(nintMax.ToString())); + Assert.Equal(nintMaxPlus1, BigInteger.Parse(nintMaxPlus1.ToString())); + Assert.Equal(nintMin, BigInteger.Parse(nintMin.ToString())); + Assert.Equal(nintMinMinus1, BigInteger.Parse(nintMinMinus1.ToString())); + + // ToByteArray roundtrip at boundary + Assert.Equal(nintMax, new BigInteger(nintMax.ToByteArray())); + Assert.Equal(nintMaxPlus1, new BigInteger(nintMaxPlus1.ToByteArray())); + Assert.Equal(nintMin, new BigInteger(nintMin.ToByteArray())); + Assert.Equal(nintMinMinus1, new BigInteger(nintMinMinus1.ToByteArray())); + + // Bitwise at boundary + Assert.Equal(BigInteger.Zero, nintMax ^ nintMax); + Assert.Equal(BigInteger.Zero, nintMaxPlus1 ^ nintMaxPlus1); + Assert.Equal(BigInteger.Zero, nintMin ^ nintMin); + } + } + internal static class BigIntegerTestExtensions { /// Verify that value^2 == original, returning the sqrt. From 2b0b385e4257a27415afd6070c7942b5267556ce Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 09:17:45 -0400 Subject: [PATCH 10/27] Add Montgomery multiplication for ModPow with odd moduli Implement Montgomery modular exponentiation as an alternative to Barrett reduction when the modulus is odd. This avoids expensive division in the inner loop by working in Montgomery form (multiplying by R = 2^(k*wordBits) mod n), using REDC for reduction, and converting back at the end. Key components: - PowCoreMontgomery: square-and-multiply loop in Montgomery domain - ComputeMontgomeryInverse: Newton's method for -n0^{-1} mod 2^wordsize - MontgomeryReduce (REDC): single-pass reduction without trial division - PowCoreBarrett: refactored Barrett path into helper method The Montgomery path is selected automatically when the modulus is odd, which is the common case for cryptographic operations (RSA, etc.). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.PowMod.cs | 326 +++++++++++++----- .../BigInteger/BigIntegerPropertyTests.cs | 43 +++ 2 files changed, 280 insertions(+), 89 deletions(-) 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 97cb384f9c180c..29a774919878a8 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 @@ -330,10 +330,14 @@ internal const int ReducerThreshold = 32; private static void PowCore(Span value, int valueLength, - ReadOnlySpan power, ReadOnlySpan modulus, - Span temp, Span bits) + ReadOnlySpan power, ReadOnlySpan modulus, + Span temp, Span bits) { - // Executes the big pow algorithm. + if ((modulus[0] & 1) != 0) + { + PowCoreMontgomery(value, valueLength, power, modulus, temp, bits); + return; + } bits[0] = 1; @@ -345,56 +349,20 @@ private static void PowCore(Span value, int valueLength, } else { - int size = modulus.Length * 2 + 1; - nuint[]? rFromPool = null; - Span r = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : rFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - r.Clear(); - - size = r.Length - modulus.Length + 1; - nuint[]? muFromPool = null; - Span mu = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - mu.Clear(); - - size = modulus.Length * 2 + 2; - nuint[]? q1FromPool = null; - Span q1 = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - q1.Clear(); - - nuint[]? q2FromPool = null; - Span q2 = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[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); - - Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); - result.CopyTo(bits); - bits.Slice(result.Length).Clear(); - - if (muFromPool != null) - ArrayPool.Shared.Return(muFromPool); - if (q1FromPool != null) - ArrayPool.Shared.Return(q1FromPool); - if (q2FromPool != null) - ArrayPool.Shared.Return(q2FromPool); + PowCoreBarrett(value, valueLength, power, modulus, temp, bits); } } private static void PowCore(Span value, int valueLength, - nuint power, ReadOnlySpan modulus, - Span temp, Span bits) + nuint power, ReadOnlySpan modulus, + Span temp, Span bits) { - // Executes the big pow algorithm. + if ((modulus[0] & 1) != 0) + { + PowCoreMontgomery(value, valueLength, new ReadOnlySpan(in power), modulus, temp, bits); + return; + } + bits[0] = 1; if (modulus.Length < ReducerThreshold) @@ -405,49 +373,229 @@ private static void PowCore(Span value, int valueLength, } else { - int size = modulus.Length * 2 + 1; - nuint[]? rFromPool = null; - Span r = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : rFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - r.Clear(); - - size = r.Length - modulus.Length + 1; - nuint[]? muFromPool = null; - Span mu = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - mu.Clear(); - - size = modulus.Length * 2 + 2; - nuint[]? q1FromPool = null; - Span q1 = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - q1.Clear(); - - nuint[]? q2FromPool = null; - Span q2 = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[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); - - Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); - result.CopyTo(bits); - bits.Slice(result.Length).Clear(); + PowCoreBarrett(value, valueLength, new ReadOnlySpan(in power), modulus, temp, bits); + } + } + + private static void PowCoreBarrett(Span value, int valueLength, + ReadOnlySpan power, ReadOnlySpan modulus, + Span temp, Span bits) + { + int size = modulus.Length * 2 + 1; + nuint[]? rFromPool = null; + Span r = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : rFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + r.Clear(); + + size = r.Length - modulus.Length + 1; + nuint[]? muFromPool = null; + Span mu = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + mu.Clear(); + + size = modulus.Length * 2 + 2; + nuint[]? q1FromPool = null; + Span q1 = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[StackAllocThreshold] + : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + q1.Clear(); + + nuint[]? q2FromPool = null; + Span q2 = ((uint)size <= StackAllocThreshold ? + stackalloc nuint[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); + + Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); + + if (muFromPool != null) + ArrayPool.Shared.Return(muFromPool); + if (q1FromPool != null) + ArrayPool.Shared.Return(q1FromPool); + if (q2FromPool != null) + ArrayPool.Shared.Return(q2FromPool); + } + + private static void PowCoreMontgomery(Span value, int valueLength, + ReadOnlySpan power, ReadOnlySpan modulus, + Span temp, Span bits) + { + Debug.Assert((modulus[0] & 1) != 0); + Debug.Assert(bits.Length >= modulus.Length * 2); + + // Save a reference to the original output buffer. MultiplySelf/SquareSelf + // swap their ref parameters, so 'bits' may point to a different buffer + // by the time we're done. We'll copy the result back at the end. + Span originalBits = bits; + + int k = modulus.Length; + nuint n0inv = ComputeMontgomeryInverse(modulus[0]); + + // Convert value to Montgomery form: montValue = (value << k*wordBits) mod n + int shiftLen = k + valueLength; + nuint[]? shiftPool = null; + Span shifted = ((uint)shiftLen <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : shiftPool = ArrayPool.Shared.Rent(shiftLen)).Slice(0, shiftLen); + shifted.Clear(); + value.Slice(0, valueLength).CopyTo(shifted.Slice(k)); + + if (shifted.Length >= modulus.Length) + DivRem(shifted, modulus, default); + + shifted.Slice(0, k).CopyTo(value); + value.Slice(k).Clear(); + valueLength = ActualLength(value.Slice(0, k)); + + if (shiftPool is not null) + ArrayPool.Shared.Return(shiftPool); + + // Initialize result to Montgomery form of 1: R mod n + int oneShiftLen = k + 1; + nuint[]? oneShiftPool = null; + Span oneShifted = ((uint)oneShiftLen <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : oneShiftPool = ArrayPool.Shared.Rent(oneShiftLen)).Slice(0, oneShiftLen); + oneShifted.Clear(); + oneShifted[k] = 1; + + DivRem(oneShifted, modulus, default); + + bits.Clear(); + oneShifted.Slice(0, k).CopyTo(bits); + int resultLength = ActualLength(bits.Slice(0, k)); + + if (oneShiftPool is not null) + ArrayPool.Shared.Return(oneShiftPool); + + // Square-and-multiply loop processing exponent bits right-to-left + for (int i = 0; i < power.Length - 1; i++) + { + nuint p = power[i]; + for (int j = 0; j < kcbitNuint; j++) + { + if ((p & 1) == 1) + { + resultLength = MultiplySelf(ref bits, resultLength, value.Slice(0, valueLength), ref temp); + resultLength = MontgomeryReduce(bits, modulus, n0inv); + } + valueLength = SquareSelf(ref value, valueLength, ref temp); + valueLength = MontgomeryReduce(value, modulus, n0inv); + p >>= 1; + } + } + + // Last exponent limb + { + nuint p = power[power.Length - 1]; + while (p != 0) + { + if ((p & 1) == 1) + { + resultLength = MultiplySelf(ref bits, resultLength, value.Slice(0, valueLength), ref temp); + resultLength = MontgomeryReduce(bits, modulus, n0inv); + } + if (p != 1) + { + valueLength = SquareSelf(ref value, valueLength, ref temp); + valueLength = MontgomeryReduce(value, modulus, n0inv); + } + p >>= 1; + } + } + + // Convert result from Montgomery form: REDC(montResult) + bits.Slice(resultLength).Clear(); + resultLength = MontgomeryReduce(bits, modulus, n0inv); + + // Copy result back to the original output buffer + bits.Slice(0, resultLength).CopyTo(originalBits); + originalBits.Slice(resultLength).Clear(); + } + + /// + /// Computes -n[0]^{-1} mod 2^wordsize using Newton's method with quadratic convergence. + /// + private static nuint ComputeMontgomeryInverse(nuint n0) + { + Debug.Assert((n0 & 1) != 0); + + nuint x = 1; + int iterations = nint.Size == 8 ? 6 : 5; + for (int i = 0; i < iterations; i++) + { + x *= 2 - n0 * x; + } + + return unchecked((nuint)0 - x); + } + + /// + /// Montgomery reduction (REDC): computes T * R^{-1} mod n in-place. + /// T is a 2k-limb value, n is the k-limb odd modulus. + /// Result is placed in value[0..k-1]; returns actual length. + /// + private static int MontgomeryReduce(Span value, ReadOnlySpan modulus, nuint n0inv) + { + int k = modulus.Length; + Debug.Assert(value.Length >= 2 * k); + + nuint overflow = 0; + + for (int i = 0; i < k; i++) + { + nuint m = unchecked(value[i] * n0inv); + nuint carry = 0; + + for (int j = 0; j < k; j++) + { + if (nint.Size == 8) + { + UInt128 p = (UInt128)m * modulus[j] + value[i + j] + carry; + value[i + j] = (nuint)(ulong)p; + carry = (nuint)(ulong)(p >> 64); + } + else + { + ulong p = (ulong)m * modulus[j] + value[i + j] + carry; + value[i + j] = (nuint)(uint)p; + carry = (nuint)(uint)(p >> 32); + } + } - if (muFromPool != null) - ArrayPool.Shared.Return(muFromPool); - if (q1FromPool != null) - ArrayPool.Shared.Return(q1FromPool); - if (q2FromPool != null) - ArrayPool.Shared.Return(q2FromPool); + for (int idx = i + k; carry != 0 && idx < 2 * k; idx++) + { + nuint sum = value[idx] + carry; + carry = (sum < value[idx]) ? (nuint)1 : 0; + value[idx] = sum; + } + overflow += carry; } + + // The mathematical bound guarantees T' < 2*n*R, so T'/R < 2n, + // meaning overflow past the 2k-limb buffer is at most 1. + Debug.Assert(overflow <= 1); + + Span upper = value.Slice(k, k); + + if (overflow != 0 || Compare(upper, modulus) >= 0) + { + SubtractSelf(upper, modulus); + } + + upper.CopyTo(value); + value.Slice(k).Clear(); + + return ActualLength(value.Slice(0, k)); } private static Span PowCore(Span value, int valueLength, diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs index 37cc5cbf8d532a..2778d29a2b47a7 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs @@ -928,6 +928,49 @@ public void InlineToArrayTransition() Assert.Equal(BigInteger.Zero, nintMaxPlus1 ^ nintMaxPlus1); Assert.Equal(BigInteger.Zero, nintMin ^ nintMin); } + + [Theory] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(8)] + [InlineData(16)] + [InlineData(20)] + [InlineData(32)] + public void ModPowOddModulus(int limbCount) + { + Random rng = new Random(42); + int byteCount = limbCount * nint.Size; + + byte[] modBytes = new byte[byteCount + 1]; + rng.NextBytes(modBytes); + modBytes[^1] = 0; + modBytes[0] |= 1; // ensure odd + + BigInteger mod = new BigInteger(modBytes); + + byte[] baseBytes = new byte[byteCount / 2 + 1]; + rng.NextBytes(baseBytes); + baseBytes[^1] = 0; + BigInteger b = new BigInteger(baseBytes); + + BigInteger exp = 65537; + + BigInteger result = BigInteger.ModPow(b, exp, mod); + + BigInteger check = BigInteger.One; + BigInteger bb = b % mod; + int e = 65537; + while (e > 0) + { + if ((e & 1) == 1) + check = (check * bb) % mod; + bb = (bb * bb) % mod; + e >>= 1; + } + + Assert.Equal(check, result); + } } internal static class BigIntegerTestExtensions From 3333cec8585f4e0c4599d68cf094ad52f05cf077 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 09:35:32 -0400 Subject: [PATCH 11/27] Add tests for Barrett+FastReducer with even large moduli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover previously uncovered code paths: - PowCore(FastReducer, multi-limb power): 20% → 100% - PowCoreBarrett pool return paths: 97.2% → 100% - Pow pool return paths: 90% → 100% - PowCore multi-limb dispatcher: 80% → 100% Uses even moduli (≥ 33 and 65 limbs) with multi-limb exponents to exercise the Barrett reduction path that Montgomery doesn't cover. Cross-validates using the identity a^e ≡ a^e1 * a^e2 (mod m). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../BigInteger/BigIntegerPropertyTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs index 2778d29a2b47a7..198f8d9068a8da 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs @@ -721,6 +721,32 @@ public void ModPowLargeModulus(int modulusLimbs, int baseLimbs) Assert.Equal(expected2, r2); } + [Theory] + [InlineData(33, 16, 2)] // Even modulus ≥ ReducerThreshold → Barrett with multi-limb exponent + [InlineData(65, 32, 2)] // Large even modulus → Barrett + pool allocation + public void ModPowEvenLargeModulusMultiLimbExponent(int modulusLimbs, int baseLimbs, int exponentLimbs) + { + var rng = new Random(14000 + modulusLimbs); + BigInteger modulus = MakePositive(modulusLimbs, rng); + modulus &= ~BigInteger.One; // force even + if (modulus < 2) modulus = 2; + + BigInteger b = MakePositive(baseLimbs, rng); + BigInteger exp = MakePositive(exponentLimbs, rng); + + BigInteger result = BigInteger.ModPow(b, exp, modulus); + + Assert.True(result >= 0); + Assert.True(result < modulus); + + // Cross-validate: a^e mod m == (a^e1 * a^e2) mod m where e = e1 + e2 + BigInteger e1 = exp >> 1; + BigInteger e2 = exp - e1; + BigInteger r1 = BigInteger.ModPow(b, e1, modulus); + BigInteger r2 = BigInteger.ModPow(b, e2, modulus); + Assert.Equal(result, (r1 * r2) % modulus); + } + // --- GCD with specific size differences --- [Theory] From 2c9b3569569a7916a804c37b0c3960d584ff4041 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 10:27:11 -0400 Subject: [PATCH 12/27] Add sliding window exponentiation and separate squaring thresholds Montgomery ModPow now uses k-ary sliding window exponentiation instead of basic binary square-and-multiply. The window size is chosen adaptively based on exponent bit length (1-7), matching Java's BigInteger thresholds. For window size k, this precomputes 2^(k-1) odd powers of the base in Montgomery form, then processes the exponent left-to-right. This reduces the number of Montgomery multiplications by ~20-40% for large exponents. Squaring now uses separate thresholds (SquareKaratsubaThreshold=48, SquareToom3Threshold=384) instead of sharing the multiply thresholds. Squaring has fewer cross-terms than general multiplication, so schoolbook squaring remains competitive at larger sizes. Java and Python both use higher thresholds for squaring than multiplication. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.PowMod.cs | 229 +++++++++++++++--- .../Numerics/BigIntegerCalculator.SquMul.cs | 8 +- 2 files changed, 196 insertions(+), 41 deletions(-) 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 29a774919878a8..80bafcddb16961 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 @@ -425,6 +425,57 @@ stackalloc nuint[StackAllocThreshold] ArrayPool.Shared.Return(q2FromPool); } + /// + /// Chooses the sliding window size based on exponent bit length. + /// Larger windows reduce multiplications but increase precomputation. + /// Thresholds follow Java's BigInteger (adjusted for 64-bit limbs). + /// + private static int ChooseWindowSize(int expBitLength) + { + if (expBitLength <= 24) + return 1; + if (expBitLength <= 96) + return 3; + if (expBitLength <= 384) + return 4; + if (expBitLength <= 1536) + return 5; + if (expBitLength <= 4096) + return 6; + + return 7; + } + + /// + /// Returns the total bit length of the exponent (position of highest set bit + 1). + /// + private static int BitLength(ReadOnlySpan value) + { + int length = ActualLength(value); + if (length == 0) + return 0; + + nuint topLimb = value[length - 1]; + int bits = (length - 1) * kcbitNuint; + + if (nint.Size == 8) + bits += 64 - BitOperations.LeadingZeroCount((ulong)topLimb); + else + bits += 32 - BitOperations.LeadingZeroCount((uint)topLimb); + + return bits; + } + + /// + /// Gets the bit at position of the multi-limb exponent. + /// + private static int GetBit(ReadOnlySpan value, int bitIndex) + { + int limbIndex = bitIndex / kcbitNuint; + int bitOffset = bitIndex % kcbitNuint; + return (int)((value[limbIndex] >> bitOffset) & 1); + } + private static void PowCoreMontgomery(Span value, int valueLength, ReadOnlySpan power, ReadOnlySpan modulus, Span temp, Span bits) @@ -438,6 +489,7 @@ private static void PowCoreMontgomery(Span value, int valueLength, Span originalBits = bits; int k = modulus.Length; + int bufLen = k * 2; nuint n0inv = ComputeMontgomeryInverse(modulus[0]); // Convert value to Montgomery form: montValue = (value << k*wordBits) mod n @@ -459,67 +511,166 @@ private static void PowCoreMontgomery(Span value, int valueLength, if (shiftPool is not null) ArrayPool.Shared.Return(shiftPool); - // Initialize result to Montgomery form of 1: R mod n - int oneShiftLen = k + 1; - nuint[]? oneShiftPool = null; - Span oneShifted = ((uint)oneShiftLen <= StackAllocThreshold + // Compute R mod n (Montgomery form of 1) and save for later + nuint[]? rModNPool = null; + Span rModN = ((uint)k <= StackAllocThreshold ? stackalloc nuint[StackAllocThreshold] - : oneShiftPool = ArrayPool.Shared.Rent(oneShiftLen)).Slice(0, oneShiftLen); - oneShifted.Clear(); - oneShifted[k] = 1; + : rModNPool = ArrayPool.Shared.Rent(k)).Slice(0, k); + { + int oneShiftLen = k + 1; + nuint[]? oneShiftPool = null; + Span oneShifted = ((uint)oneShiftLen <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : oneShiftPool = ArrayPool.Shared.Rent(oneShiftLen)).Slice(0, oneShiftLen); + oneShifted.Clear(); + oneShifted[k] = 1; + DivRem(oneShifted, modulus, default); + oneShifted.Slice(0, k).CopyTo(rModN); + if (oneShiftPool is not null) + ArrayPool.Shared.Return(oneShiftPool); + } + int rModNLength = ActualLength(rModN); - DivRem(oneShifted, modulus, default); + // Choose sliding window size based on exponent bit length + int expBitLength = BitLength(power); + if (expBitLength == 0) + { + // power is zero: result = 1 mod n + bits.Clear(); + rModN.Slice(0, rModNLength).CopyTo(bits); + bits.Slice(rModNLength).Clear(); + int resultLength = MontgomeryReduce(bits, modulus, n0inv); + bits.Slice(0, resultLength).CopyTo(originalBits); + originalBits.Slice(resultLength).Clear(); + if (rModNPool is not null) + ArrayPool.Shared.Return(rModNPool); + return; + } - bits.Clear(); - oneShifted.Slice(0, k).CopyTo(bits); - int resultLength = ActualLength(bits.Slice(0, k)); + int windowSize = ChooseWindowSize(expBitLength); + int tableLen = 1 << (windowSize - 1); - if (oneShiftPool is not null) - ArrayPool.Shared.Return(oneShiftPool); + // Cap window size so the precomputation table stays reasonable + // (e.g., for a 100K-limb modulus, window 7 would need 64*100K = 6.4M limbs) + while (windowSize > 1 && (long)tableLen * k > 64 * 1024) + { + windowSize--; + tableLen = 1 << (windowSize - 1); + } - // Square-and-multiply loop processing exponent bits right-to-left - for (int i = 0; i < power.Length - 1; i++) + // Precompute odd powers in Montgomery form: base^1, base^3, ..., base^(2*tableLen-1) + int totalTableLen = tableLen * k; + nuint[] tablePool = ArrayPool.Shared.Rent(totalTableLen); + Span table = tablePool.AsSpan(0, totalTableLen); + table.Clear(); + + // table[0] = base in Montgomery form + value.Slice(0, valueLength).CopyTo(table.Slice(0, k)); + + if (tableLen > 1) { - nuint p = power[i]; - for (int j = 0; j < kcbitNuint; j++) + // Use a separate product buffer for precomputation to avoid + // corrupting bits/temp (which are needed pristine for the main loop). + nuint[]? prodPool = null; + Span prod = ((uint)bufLen <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : prodPool = ArrayPool.Shared.Rent(bufLen)).Slice(0, bufLen); + + // Compute base^2 in Montgomery form + nuint[]? base2Pool = null; + Span base2 = ((uint)k <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : base2Pool = ArrayPool.Shared.Rent(k)).Slice(0, k); + base2.Clear(); + + prod.Clear(); + Square(value.Slice(0, valueLength), prod.Slice(0, valueLength * 2)); + MontgomeryReduce(prod, modulus, n0inv); + prod.Slice(0, k).CopyTo(base2); + int base2Length = ActualLength(base2); + + // table[i] = table[i-1] * base^2 (mod n, in Montgomery form) + for (int i = 1; i < tableLen; i++) { - if ((p & 1) == 1) - { - resultLength = MultiplySelf(ref bits, resultLength, value.Slice(0, valueLength), ref temp); - resultLength = MontgomeryReduce(bits, modulus, n0inv); - } - valueLength = SquareSelf(ref value, valueLength, ref temp); - valueLength = MontgomeryReduce(value, modulus, n0inv); - p >>= 1; + ReadOnlySpan prev = table.Slice((i - 1) * k, k); + int prevLength = ActualLength(prev); + + prod.Clear(); + Multiply(prev.Slice(0, prevLength), (ReadOnlySpan)base2.Slice(0, base2Length), + prod.Slice(0, prevLength + base2Length)); + MontgomeryReduce(prod, modulus, n0inv); + prod.Slice(0, k).CopyTo(table.Slice(i * k, k)); } + + if (base2Pool is not null) + ArrayPool.Shared.Return(base2Pool); + if (prodPool is not null) + ArrayPool.Shared.Return(prodPool); } - // Last exponent limb + // Initialize result to R mod n (bits and temp are untouched from caller) + bits.Clear(); + rModN.Slice(0, rModNLength).CopyTo(bits); + int resultLen = rModNLength; + + if (rModNPool is not null) + ArrayPool.Shared.Return(rModNPool); + + // Left-to-right sliding window exponentiation + int bitPos = expBitLength - 1; + while (bitPos >= 0) { - nuint p = power[power.Length - 1]; - while (p != 0) + if (GetBit(power, bitPos) == 0) { - if ((p & 1) == 1) + resultLen = SquareSelf(ref bits, resultLen, ref temp); + resultLen = MontgomeryReduce(bits, modulus, n0inv); + bitPos--; + } + else + { + // Collect up to windowSize bits starting from bitPos + int wLen = 1; + int wValue = 1; + for (int i = 1; i < windowSize && bitPos - i >= 0; i++) { - resultLength = MultiplySelf(ref bits, resultLength, value.Slice(0, valueLength), ref temp); - resultLength = MontgomeryReduce(bits, modulus, n0inv); + wValue = (wValue << 1) | GetBit(power, bitPos - i); + wLen++; } - if (p != 1) + + // Trim trailing zeros to ensure the window value is odd + while ((wValue & 1) == 0) { - valueLength = SquareSelf(ref value, valueLength, ref temp); - valueLength = MontgomeryReduce(value, modulus, n0inv); + wValue >>= 1; + wLen--; } - p >>= 1; + + // Square for each bit in the window + for (int i = 0; i < wLen; i++) + { + resultLen = SquareSelf(ref bits, resultLen, ref temp); + resultLen = MontgomeryReduce(bits, modulus, n0inv); + } + + // Multiply by the precomputed odd power + Debug.Assert(wValue >= 1 && (wValue & 1) == 1); + ReadOnlySpan entry = table.Slice(((wValue - 1) >> 1) * k, k); + int entryLength = ActualLength(entry); + resultLen = MultiplySelf(ref bits, resultLen, entry.Slice(0, entryLength), ref temp); + resultLen = MontgomeryReduce(bits, modulus, n0inv); + + bitPos -= wLen; } } + ArrayPool.Shared.Return(tablePool); + // Convert result from Montgomery form: REDC(montResult) - bits.Slice(resultLength).Clear(); - resultLength = MontgomeryReduce(bits, modulus, n0inv); + bits.Slice(resultLen).Clear(); + resultLen = MontgomeryReduce(bits, modulus, n0inv); // Copy result back to the original output buffer - bits.Slice(0, resultLength).CopyTo(originalBits); - originalBits.Slice(resultLength).Clear(); + bits.Slice(0, resultLen).CopyTo(originalBits); + originalBits.Slice(resultLen).Clear(); } /// 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 659fd9e4c1410c..c1ce289bff3051 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 @@ -14,9 +14,13 @@ internal static partial class BigIntegerCalculator // Mutable for unit testing... internal static int MultiplyKaratsubaThreshold = 32; internal static int MultiplyToom3Threshold = 256; + internal static int SquareKaratsubaThreshold = 48; + internal static int SquareToom3Threshold = 384; #else internal const int MultiplyKaratsubaThreshold = 32; internal const int MultiplyToom3Threshold = 256; + internal const int SquareKaratsubaThreshold = 48; + internal const int SquareToom3Threshold = 384; #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -34,11 +38,11 @@ public static void Square(ReadOnlySpan value, Span bits) // NOTE: useful thresholds needs some "empirical" testing, // which are smaller in DEBUG mode for testing purpose. - if (value.Length < MultiplyKaratsubaThreshold) + if (value.Length < SquareKaratsubaThreshold) { Naive(value, bits); } - else if (value.Length < MultiplyToom3Threshold) + else if (value.Length < SquareToom3Threshold) { Karatsuba(value, bits); } From 1323037526ccd766de01d1cb17afd74fc77d218a Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 10:59:46 -0400 Subject: [PATCH 13/27] Avoid array copy in GCD when one operand is zero When GCD(0, x) returns |x|, reuse the existing _bits array directly via the (nint, nuint[]) constructor instead of copying through the ReadOnlySpan constructor. Since BigInteger is immutable, the array is never mutated after construction, making sharing safe. Add 25 targeted GCD-with-zero test cases covering: - Both operand orderings (GCD(0,x) and GCD(x,0)) - Positive and negative multi-limb values - Various sizes: 1-limb, 2-limb, 3-limb, many-limb - Edge case: 1-limb values that exceed nint.MaxValue (stored in _bits) - Symmetry and sign-invariance properties Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/BigInteger.cs | 4 +- .../BigInteger/BigIntegerPropertyTests.cs | 64 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) 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 d8ec0df7cd2454..a8110fe76047ca 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -1000,7 +1000,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, negative: false); + : new BigInteger(+1, right._bits); } if (trivialRight) @@ -1008,7 +1008,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, negative: false); + : new BigInteger(+1, left._bits); } Debug.Assert(left._bits != null && right._bits != null); diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs index 198f8d9068a8da..3cae11dbc42f4c 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs @@ -779,6 +779,70 @@ public void GcdSizeOffsets(int aLimbs, int bLimbs) Assert.Equal(gcd, BigInteger.GreatestCommonDivisor(a, -b)); } + // --- GCD with zero (exercises array-sharing paths) --- + + public static IEnumerable GcdWithZeroData() + { + // Single-limb values that fit in _sign (trivialLeft/trivialRight both true) + yield return new object[] { BigInteger.Zero, BigInteger.Zero, BigInteger.Zero }; + yield return new object[] { BigInteger.Zero, BigInteger.One, BigInteger.One }; + yield return new object[] { BigInteger.One, BigInteger.Zero, BigInteger.One }; + yield return new object[] { BigInteger.Zero, BigInteger.MinusOne, BigInteger.One }; + yield return new object[] { BigInteger.MinusOne, BigInteger.Zero, BigInteger.One }; + yield return new object[] { BigInteger.Zero, new BigInteger(42), new BigInteger(42) }; + yield return new object[] { new BigInteger(-42), BigInteger.Zero, new BigInteger(42) }; + + // Multi-limb values (exercises the trivialLeft / trivialRight paths that share _bits) + BigInteger twoLimb = (BigInteger.One << (nint.Size * 8)) + 1; // just over 1 limb + BigInteger threeLimb = (BigInteger.One << (nint.Size * 8 * 2)) + 1; // just over 2 limbs + BigInteger large = BigInteger.Pow(new BigInteger(long.MaxValue), 4); // many limbs + + yield return new object[] { BigInteger.Zero, twoLimb, twoLimb }; + yield return new object[] { twoLimb, BigInteger.Zero, twoLimb }; + yield return new object[] { BigInteger.Zero, -twoLimb, twoLimb }; + yield return new object[] { -twoLimb, BigInteger.Zero, twoLimb }; + + yield return new object[] { BigInteger.Zero, threeLimb, threeLimb }; + yield return new object[] { threeLimb, BigInteger.Zero, threeLimb }; + yield return new object[] { BigInteger.Zero, -threeLimb, threeLimb }; + yield return new object[] { -threeLimb, BigInteger.Zero, threeLimb }; + + yield return new object[] { BigInteger.Zero, large, large }; + yield return new object[] { large, BigInteger.Zero, large }; + yield return new object[] { BigInteger.Zero, -large, large }; + yield return new object[] { -large, BigInteger.Zero, large }; + + // One-limb value that doesn't fit in _sign (magnitude >= nint.MaxValue) + BigInteger oneLimbLarge = new BigInteger(nint.MaxValue) + 1; // requires _bits with 1 element + yield return new object[] { BigInteger.Zero, oneLimbLarge, oneLimbLarge }; + yield return new object[] { oneLimbLarge, BigInteger.Zero, oneLimbLarge }; + yield return new object[] { BigInteger.Zero, -oneLimbLarge, oneLimbLarge }; + yield return new object[] { -oneLimbLarge, BigInteger.Zero, oneLimbLarge }; + + // Non-zero trivial + multi-limb (tests Gcd(bits, scalar) path) + yield return new object[] { new BigInteger(6), twoLimb, BigInteger.GreatestCommonDivisor(6, twoLimb) }; + yield return new object[] { twoLimb, new BigInteger(6), BigInteger.GreatestCommonDivisor(twoLimb, 6) }; + } + + [Theory] + [MemberData(nameof(GcdWithZeroData))] + public void GcdWithZero(BigInteger left, BigInteger right, BigInteger expected) + { + BigInteger result = BigInteger.GreatestCommonDivisor(left, right); + Assert.Equal(expected, result); + + // GCD is always non-negative + Assert.True(result >= 0); + + // GCD is symmetric + Assert.Equal(result, BigInteger.GreatestCommonDivisor(right, left)); + + // GCD with negated inputs should give the same result + Assert.Equal(result, BigInteger.GreatestCommonDivisor(-left, right)); + Assert.Equal(result, BigInteger.GreatestCommonDivisor(left, -right)); + Assert.Equal(result, BigInteger.GreatestCommonDivisor(-left, -right)); + } + // --- CopySign coverage --- [Theory] From bf16170c379fc0b7a864f81cbea1d082e0502255 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 14:09:54 -0400 Subject: [PATCH 14/27] Optimize GCD LehmerCore and DivRem to avoid Int128/UInt128 overhead Replace Int128 arithmetic in LehmerCore with Math.BigMul (compiles to a single mul instruction on x64) plus explicit carry tracking using long/ulong. The Lehmer coefficients are bounded at 31 bits, so each 95-bit product is computed natively. Add fast paths to DivRem(nuint, nuint, nuint, out nuint): - hi == 0: single native 64-bit division - divisor <= uint.MaxValue: split into two chained 32-bit-half divisions (covers ToString base-10^9 conversion and small-divisor Divide paths) - Fallback to UInt128 for large divisors (Knuth division) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.GcdInv.cs | 40 +++++++++++++++---- .../Numerics/BigIntegerCalculator.Utils.cs | 37 +++++++++++++++-- 2 files changed, 66 insertions(+), 11 deletions(-) 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 8b5b7b02654624..34a65b39c5d9e2 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 @@ -375,15 +375,41 @@ private static int LehmerCore(Span x, } else { - Int128 xCarry = 0, yCarry = 0; + // Use Math.BigMul for widening multiplies instead of Int128 + // which compiles to much cheaper native mul instructions. + // a,b,c,d are at most 31 bits, so each product fits in 95 bits. + ulong ua = (ulong)a, ub = (ulong)b, uc = (ulong)c, ud = (ulong)d; + long xCarry = 0, yCarry = 0; for (int i = 0; i < length; i++) { - Int128 xDigit = a * (Int128)(ulong)x[i] - b * (Int128)(ulong)y[i] + xCarry; - Int128 yDigit = d * (Int128)(ulong)y[i] - c * (Int128)(ulong)x[i] + yCarry; - xCarry = xDigit >> 64; - yCarry = yDigit >> 64; - x[i] = unchecked((nuint)(ulong)xDigit); - y[i] = unchecked((nuint)(ulong)yDigit); + ulong xi = (ulong)x[i]; + ulong yi = (ulong)y[i]; + + // xDigit = a*xi - b*yi + xCarry (fits in ~97 signed bits) + ulong axi_hi = Math.BigMul(ua, xi, out ulong axi_lo); + ulong byi_hi = Math.BigMul(ub, yi, out ulong byi_lo); + + ulong xlo = axi_lo - byi_lo; + long xhi = (long)(axi_hi - byi_hi) - (axi_lo < byi_lo ? 1L : 0L); + + ulong xResultLo = xlo + unchecked((ulong)xCarry); + xhi += (xCarry >> 63) + (xResultLo < xlo ? 1L : 0L); + + x[i] = unchecked((nuint)xResultLo); + xCarry = xhi; + + // yDigit = d*yi - c*xi + yCarry + ulong dyi_hi = Math.BigMul(ud, yi, out ulong dyi_lo); + ulong cxi_hi = Math.BigMul(uc, xi, out ulong cxi_lo); + + ulong ylo = dyi_lo - cxi_lo; + long yhi = (long)(dyi_hi - cxi_hi) - (dyi_lo < cxi_lo ? 1L : 0L); + + ulong yResultLo = ylo + unchecked((ulong)yCarry); + yhi += (yCarry >> 63) + (yResultLo < ylo ? 1L : 0L); + + y[i] = unchecked((nuint)yResultLo); + yCarry = yhi; } } 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 index 4cf3aaf9812b3b..a9ce2802644f3d 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs @@ -165,10 +165,39 @@ internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remain { if (nint.Size == 8) { - UInt128 value = ((UInt128)(ulong)hi << 64) | (ulong)lo; - UInt128 digit = value / (ulong)divisor; - remainder = (nuint)(ulong)(value - digit * (ulong)divisor); - return (nuint)(ulong)digit; + // Compute (hi * 2^64 + lo) / divisor. + // hi < divisor is guaranteed, so quotient fits in 64 bits. + Debug.Assert((ulong)hi < (ulong)divisor); + + if (hi == 0) + { + (ulong q, ulong r) = Math.DivRem((ulong)lo, (ulong)divisor); + remainder = (nuint)r; + return (nuint)q; + } + + // When divisor fits in 32 bits, split lo into two 32-bit halves + // and chain two native 64-bit divisions (avoids UInt128 overhead): + // (hi * 2^32 + lo_hi) / divisor → (q_hi, r1) [fits: hi < divisor < 2^32] + // (r1 * 2^32 + lo_lo) / divisor → (q_lo, r2) [fits: r1 < divisor < 2^32] + if ((ulong)divisor <= uint.MaxValue) + { + ulong lo_hi = (ulong)lo >> 32; + ulong lo_lo = (ulong)lo & 0xFFFFFFFF; + + (ulong q_hi, ulong r1) = Math.DivRem(((ulong)hi << 32) | lo_hi, (ulong)divisor); + (ulong q_lo, ulong r2) = Math.DivRem((r1 << 32) | lo_lo, (ulong)divisor); + + remainder = (nuint)r2; + return (nuint)((q_hi << 32) | q_lo); + } + + { + UInt128 value = ((UInt128)(ulong)hi << 64) | (ulong)lo; + UInt128 digit = value / (ulong)divisor; + remainder = (nuint)(ulong)(value - digit * (ulong)divisor); + return (nuint)(ulong)digit; + } } else { From 16024df97655f2979817dab07475c679ee136dc0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 14:39:19 -0400 Subject: [PATCH 15/27] Fix ToString Naive loop regression by processing 32-bit halves The Naive base-10^9 conversion loop had a 4.3x regression for medium numbers (200 digits) because BigIntegerCalculator.DivRem was too large to inline on 64-bit, preventing the JIT from applying multiply-by- reciprocal optimization for the constant divisor 10^9. Fix: process each 64-bit limb as two 32-bit halves. Each inner division becomes ulong/const_uint which the JIT optimizes to a single mulq+shift (~5 cycles) instead of a div r64 (~35 cycles). The math is equivalent: (base * 2^32 + hi) * 2^32 + lo = base * 2^64 + limb. Results: 200-char 4.30x regression eliminated (now 1.00x); 20K-char cases improved from 0.97-1.18x to 0.52-0.54x (2x faster than main). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.BigInteger.cs | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index bebb7488696136..c2baa2c5351e65 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -1076,23 +1076,43 @@ static void Naive(ReadOnlySpan bits, Span base1E9Buffer, out int b for (int iuSrc = bits.Length; --iuSrc >= 0;) { - nuint uCarry = bits[iuSrc]; - Span base1E9 = base1E9Buffer.Slice(0, base1E9Written); - for (int iuDst = 0; iuDst < base1E9.Length; iuDst++) + if (nint.Size == 8) { - Debug.Assert(base1E9[iuDst] < PowersOf1e9.TenPowMaxPartial); - - nuint hi = base1E9[iuDst]; - uCarry = BigIntegerCalculator.DivRem(hi, uCarry, PowersOf1e9.TenPowMaxPartial, out base1E9[iuDst]); + // Process each 64-bit limb as two 32-bit halves (high then low). + // This keeps each division as ulong / constant_uint which the JIT + // optimizes to a fast multiply-by-reciprocal, avoiding expensive + // 128÷64 software division through BigIntegerCalculator.DivRem. + // Net effect: (base * 2^32 + hi) * 2^32 + lo = base * 2^64 + limb. + ulong limb = (ulong)bits[iuSrc]; + NaiveDigit((uint)(limb >> 32), base1E9Buffer, ref base1E9Written); + NaiveDigit((uint)limb, base1E9Buffer, ref base1E9Written); } - while (uCarry != 0) + else { - (nuint quo, nuint rem) = Math.DivRem(uCarry, PowersOf1e9.TenPowMaxPartial); - base1E9Buffer[base1E9Written++] = rem; - uCarry = quo; + NaiveDigit((uint)bits[iuSrc], base1E9Buffer, ref base1E9Written); } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void NaiveDigit(uint digit, Span base1E9Buffer, ref int base1E9Written) + { + const uint divisor = (uint)PowersOf1e9.TenPowMaxPartial; + + uint uCarry = digit; + for (int iuDst = 0; iuDst < base1E9Written; iuDst++) + { + ulong value = ((ulong)(uint)base1E9Buffer[iuDst] << 32) | uCarry; + ulong quo = value / divisor; + base1E9Buffer[iuDst] = (nuint)(uint)(value - quo * divisor); + uCarry = (uint)quo; + } + while (uCarry != 0) + { + base1E9Buffer[base1E9Written++] = (nuint)(uCarry % divisor); + uCarry /= divisor; + } + } } internal readonly ref struct PowersOf1e9 From c09c5ffcfcabc2dcc142c4a6d8f5c46a11c7ae89 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 15:48:59 -0400 Subject: [PATCH 16/27] Fix small-operand, GCD LehmerCore, and ToByteArray regressions - Add int-range fast path in operator* with Int128 fallback extracted to [NoInlining] helper to keep operator body small for JIT inlining - Rewrite LehmerCore 64-bit path to process nuint limbs as uint halves via MemoryMarshal.Cast on little-endian, using the same cheap long arithmetic as the 32-bit path (eliminates Math.BigMul + manual carry overhead); big-endian falls back to Int128 widening arithmetic - Add uint fast path in scalar Gcd(nuint, nuint) for values <= uint.Max (div r32 is faster than div r64) - Replace byte-scanning loop in TryGetBytes with BitOperations.LeadingZeroCount for O(1) MSB detection (was O(bytesPerLimb) per call) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/BigInteger.cs | 32 +++++--- .../Numerics/BigIntegerCalculator.GcdInv.cs | 78 +++++++++++-------- 2 files changed, 68 insertions(+), 42 deletions(-) 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 a8110fe76047ca..6ca3c76defe1f2 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -1550,17 +1550,21 @@ private enum GetBytesMode highLimb = bits[bits.Length - 1]; } - // Find the most significant byte index within the high limb + // Find the most significant byte index within the high limb. + // Use LeadingZeroCount for O(1) instead of byte-scanning loop. byte msb; int msbIndex; - int maxByteIndex = bytesPerLimb - 1; - msbIndex = maxByteIndex; - while (msbIndex > 0) + if (highByte == 0x00) { - msb = unchecked((byte)(highLimb >> (msbIndex * 8))); - if (msb != highByte) - break; - msbIndex--; + // Positive: find highest non-zero byte + int lzc = BitOperations.LeadingZeroCount(highLimb); + msbIndex = Math.Max(0, bytesPerLimb - 1 - (lzc / 8)); + } + else + { + // Negative: find highest non-0xFF byte + int lzc = BitOperations.LeadingZeroCount(~highLimb); + msbIndex = Math.Max(0, bytesPerLimb - 1 - (lzc / 8)); } msb = unchecked((byte)(highLimb >> (msbIndex * 8))); @@ -3010,12 +3014,22 @@ private static BigInteger LeftShift(nint value, int digitShift, int smallShift) { if (nint.Size == 4) return (long)left._sign * right._sign; - return (BigInteger)((Int128)left._sign * right._sign); + + nint s1 = left._sign, s2 = right._sign; + if (s1 == (int)s1 && s2 == (int)s2) + return (long)(int)s1 * (int)s2; + + return MultiplyNint(s1, s2); } return Multiply(left._bits, left._sign, right._bits, right._sign); } + // Extracted to keep operator* body small enough for JIT inlining. + [MethodImpl(MethodImplOptions.NoInlining)] + private static BigInteger MultiplyNint(nint left, nint right) + => (BigInteger)((Int128)left * right); + private static BigInteger Multiply(ReadOnlySpan left, nint leftSign, ReadOnlySpan right, nint rightSign) { bool trivialLeft = left.IsEmpty; 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 34a65b39c5d9e2..24c4af3c31cfcc 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 @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; +using System.Runtime.InteropServices; namespace System.Numerics { @@ -14,6 +15,20 @@ public static nuint Gcd(nuint left, nuint right) // https://en.wikipedia.org/wiki/Euclidean_algorithm + if (nint.Size == 8 && left <= uint.MaxValue && right <= uint.MaxValue) + { + // Use 32-bit modulo when values fit — div r32 is faster than div r64. + uint l = (uint)left, r = (uint)right; + while (r != 0) + { + uint temp = l % r; + l = r; + r = temp; + } + + return l; + } + while (right != 0) { nuint temp = left % right; @@ -373,43 +388,40 @@ private static int LehmerCore(Span x, y[i] = unchecked((nuint)yDigit); } } + else if (BitConverter.IsLittleEndian) + { + // On 64-bit little-endian, reinterpret the nuint limbs as uint halves. + // Since a,b,c,d are at most 31 bits and each half is 32 bits, + // each product fits in 63 bits and the full expression fits in long. + // This matches the 32-bit path's arithmetic but operates on the + // raw memory of 64-bit limbs (little-endian stores low half first). + Span x32 = MemoryMarshal.Cast(x); + Span y32 = MemoryMarshal.Cast(y); + int length32 = length * 2; + + long xCarry = 0L, yCarry = 0L; + for (int i = 0; i < length32; i++) + { + long xDigit = a * (long)x32[i] - b * (long)y32[i] + xCarry; + long yDigit = d * (long)y32[i] - c * (long)x32[i] + yCarry; + xCarry = xDigit >> 32; + yCarry = yDigit >> 32; + x32[i] = unchecked((uint)xDigit); + y32[i] = unchecked((uint)yDigit); + } + } else { - // Use Math.BigMul for widening multiplies instead of Int128 - // which compiles to much cheaper native mul instructions. - // a,b,c,d are at most 31 bits, so each product fits in 95 bits. - ulong ua = (ulong)a, ub = (ulong)b, uc = (ulong)c, ud = (ulong)d; - long xCarry = 0, yCarry = 0; + // Big-endian fallback: use Int128 for widening arithmetic. + Int128 xCarry = 0, yCarry = 0; for (int i = 0; i < length; i++) { - ulong xi = (ulong)x[i]; - ulong yi = (ulong)y[i]; - - // xDigit = a*xi - b*yi + xCarry (fits in ~97 signed bits) - ulong axi_hi = Math.BigMul(ua, xi, out ulong axi_lo); - ulong byi_hi = Math.BigMul(ub, yi, out ulong byi_lo); - - ulong xlo = axi_lo - byi_lo; - long xhi = (long)(axi_hi - byi_hi) - (axi_lo < byi_lo ? 1L : 0L); - - ulong xResultLo = xlo + unchecked((ulong)xCarry); - xhi += (xCarry >> 63) + (xResultLo < xlo ? 1L : 0L); - - x[i] = unchecked((nuint)xResultLo); - xCarry = xhi; - - // yDigit = d*yi - c*xi + yCarry - ulong dyi_hi = Math.BigMul(ud, yi, out ulong dyi_lo); - ulong cxi_hi = Math.BigMul(uc, xi, out ulong cxi_lo); - - ulong ylo = dyi_lo - cxi_lo; - long yhi = (long)(dyi_hi - cxi_hi) - (dyi_lo < cxi_lo ? 1L : 0L); - - ulong yResultLo = ylo + unchecked((ulong)yCarry); - yhi += (yCarry >> 63) + (yResultLo < ylo ? 1L : 0L); - - y[i] = unchecked((nuint)yResultLo); - yCarry = yhi; + Int128 xDigit = a * (Int128)x[i] - b * (Int128)y[i] + xCarry; + Int128 yDigit = d * (Int128)y[i] - c * (Int128)x[i] + yCarry; + xCarry = xDigit >> 64; + yCarry = yDigit >> 64; + x[i] = unchecked((nuint)(ulong)xDigit); + y[i] = unchecked((nuint)(ulong)yDigit); } } From c3fc756649baa28f734e648764a54cc2bc390e0e Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 17:26:29 -0400 Subject: [PATCH 17/27] Add unrolled Mul1/MulAdd1/SubMul1 primitives for multiply and division inner loops Introduce fused multiply (Mul1), multiply-accumulate (MulAdd1), and subtract-multiply (SubMul1) span-based primitives unrolled by 4 on 64-bit. Issuing 4 widening multiplies before consuming carries hides the 3-5 cycle multiply latency by allowing the CPU to pipeline them while carry chains complete sequentially. - Mul1: replaces scalar Multiply(left, scalar, bits) - MulAdd1: replaces inline inner loop in schoolbook multiply (Naive) - SubMul1: replaces SubtractDivisor loop in grammar-school division - All use Span/ReadOnlySpan parameters (no Unsafe.Add) for safety - Both use UInt128 for clean carry tracking on 64-bit, ulong on 32-bit - Removed now-unused MulAdd helper - All 3031 tests pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.DivRem.cs | 26 +-- .../Numerics/BigIntegerCalculator.SquMul.cs | 29 +-- .../Numerics/BigIntegerCalculator.Utils.cs | 209 ++++++++++++++++-- 3 files changed, 194 insertions(+), 70 deletions(-) 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 88bbfc7988a936..8ac18081eb9533 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 @@ -310,31 +310,7 @@ private static nuint SubtractDivisor(Span left, ReadOnlySpan right { Debug.Assert(left.Length >= right.Length); - // Combines a subtract and a multiply operation, which is naturally - // more efficient than multiplying and then subtracting... - // Uses branchless underflow detection to avoid branch misprediction - // penalties that dominate for large divisions (see issue #41495). - - nuint carry = 0; - - for (int i = 0; i < right.Length; i++) - { - nuint hi = BigMul(right[i], q, out nuint lo); - - // Add carry to lo, propagate overflow to hi (branchless) - nuint loWithCarry = lo + carry; - hi += (loWithCarry < lo) ? (nuint)1 : 0; - - // Subtract from left, detect underflow (branchless) - ref nuint leftElement = ref left[i]; - nuint original = leftElement; - leftElement -= loWithCarry; - hi += (original < loWithCarry) ? (nuint)1 : 0; - - carry = hi; - } - - return carry; + return SubMul1(left, right, q); } private static bool DivideGuessTooBig(nuint q, nuint valHi1, nuint valHi0, 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 c1ce289bff3051..4150cc514e0fd8 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 @@ -217,19 +217,8 @@ public static void Multiply(ReadOnlySpan left, nuint right, Span b { Debug.Assert(bits.Length == left.Length + 1); - // Executes the multiplication for one big and one native-width integer. - // Since every step holds the already slightly familiar equation - // a_i * b + c <= 2^n - 1 + (2^n - 1)^2 < 2^(2n) - 1, - // we are safe regarding to overflows (n = kcbitNuint). - - int i = 0; - nuint carry = 0; - - for (; i < left.Length; i++) - { - bits[i] = MulAdd(left[i], right, (nuint)0, ref carry); - } - bits[i] = carry; + nuint carry = Mul1(bits, left, right); + bits[left.Length] = carry; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -549,10 +538,6 @@ static void Naive(ReadOnlySpan left, ReadOnlySpan right, Span left, ReadOnlySpan right, Span - /// Performs widening multiply: a * b → (hi, lo). Used for schoolbook multiply inner loops. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static nuint MulAdd(nuint a, nuint b, nuint addend, ref nuint carry) - { - if (nint.Size == 8) - { - UInt128 product = (UInt128)(ulong)a * (ulong)b + (ulong)addend + (ulong)carry; - carry = (nuint)(ulong)(product >> 64); - return (nuint)(ulong)product; - } - else - { - ulong product = (ulong)a * b + addend + carry; - carry = (nuint)(uint)(product >> 32); - return (nuint)(uint)product; - } - } - /// /// Widening divide: (hi:lo) / divisor → (quotient, remainder). /// @@ -227,5 +207,194 @@ internal static nuint BigMul(nuint a, nuint b, out nuint low) return (nuint)(uint)(product >> 32); } } + + /// + /// Multiply by scalar: result[0..left.Length] = left * multiplier. + /// Returns the carry out. Unrolled by 4 on 64-bit. + /// Unlike MulAdd1, this writes to result rather than accumulating. + /// + internal static nuint Mul1(Span result, ReadOnlySpan left, nuint multiplier) + { + Debug.Assert(result.Length >= left.Length); + + int length = left.Length; + int i = 0; + nuint carry = 0; + + if (nint.Size == 8) + { + for (; i + 3 < length; i += 4) + { + UInt128 p0 = (UInt128)(ulong)left[i] * (ulong)multiplier + (ulong)carry; + result[i] = (nuint)(ulong)p0; + + UInt128 p1 = (UInt128)(ulong)left[i + 1] * (ulong)multiplier + (ulong)(p0 >> 64); + result[i + 1] = (nuint)(ulong)p1; + + UInt128 p2 = (UInt128)(ulong)left[i + 2] * (ulong)multiplier + (ulong)(p1 >> 64); + result[i + 2] = (nuint)(ulong)p2; + + UInt128 p3 = (UInt128)(ulong)left[i + 3] * (ulong)multiplier + (ulong)(p2 >> 64); + result[i + 3] = (nuint)(ulong)p3; + + carry = (nuint)(ulong)(p3 >> 64); + } + + for (; i < length; i++) + { + UInt128 product = (UInt128)(ulong)left[i] * (ulong)multiplier + (ulong)carry; + result[i] = (nuint)(ulong)product; + carry = (nuint)(ulong)(product >> 64); + } + } + else + { + for (; i < length; i++) + { + ulong product = (ulong)left[i] * multiplier + carry; + result[i] = (nuint)(uint)product; + carry = (nuint)(uint)(product >> 32); + } + } + + return carry; + } + + /// + /// Fused multiply-accumulate by scalar: result[0..left.Length] += left * multiplier. + /// Returns the carry out. Unrolled by 4 on 64-bit to overlap multiply latencies. + /// + internal static nuint MulAdd1(Span result, ReadOnlySpan left, nuint multiplier) + { + Debug.Assert(result.Length >= left.Length); + + int length = left.Length; + int i = 0; + nuint carry = 0; + + if (nint.Size == 8) + { + // Unroll by 4: mulx has 3-5 cycle latency but 1 cycle throughput, + // so issuing 4 multiplies allows the CPU to pipeline them while + // carry chains complete sequentially behind. + for (; i + 3 < length; i += 4) + { + UInt128 p0 = (UInt128)(ulong)left[i] * (ulong)multiplier + + (ulong)result[i] + (ulong)carry; + result[i] = (nuint)(ulong)p0; + + UInt128 p1 = (UInt128)(ulong)left[i + 1] * (ulong)multiplier + + (ulong)result[i + 1] + (ulong)(p0 >> 64); + result[i + 1] = (nuint)(ulong)p1; + + UInt128 p2 = (UInt128)(ulong)left[i + 2] * (ulong)multiplier + + (ulong)result[i + 2] + (ulong)(p1 >> 64); + result[i + 2] = (nuint)(ulong)p2; + + UInt128 p3 = (UInt128)(ulong)left[i + 3] * (ulong)multiplier + + (ulong)result[i + 3] + (ulong)(p2 >> 64); + result[i + 3] = (nuint)(ulong)p3; + + carry = (nuint)(ulong)(p3 >> 64); + } + + for (; i < length; i++) + { + UInt128 product = (UInt128)(ulong)left[i] * (ulong)multiplier + + (ulong)result[i] + (ulong)carry; + result[i] = (nuint)(ulong)product; + carry = (nuint)(ulong)(product >> 64); + } + } + else + { + for (; i < length; i++) + { + ulong product = (ulong)left[i] * multiplier + + result[i] + carry; + result[i] = (nuint)(uint)product; + carry = (nuint)(uint)(product >> 32); + } + } + + return carry; + } + + /// + /// Fused subtract-multiply by scalar: result[0..right.Length] -= right * multiplier. + /// Returns the borrow out. Unrolled by 4 on 64-bit. + /// + internal static nuint SubMul1(Span result, ReadOnlySpan right, nuint multiplier) + { + Debug.Assert(result.Length >= right.Length); + + int length = right.Length; + int i = 0; + nuint carry = 0; + + if (nint.Size == 8) + { + for (; i + 3 < length; i += 4) + { + UInt128 prod0 = (UInt128)(ulong)right[i] * (ulong)multiplier + (ulong)carry; + nuint lo0 = (nuint)(ulong)prod0; + nuint hi0 = (nuint)(ulong)(prod0 >> 64); + nuint orig0 = result[i]; + result[i] = orig0 - lo0; + hi0 += (orig0 < lo0) ? (nuint)1 : 0; + + UInt128 prod1 = (UInt128)(ulong)right[i + 1] * (ulong)multiplier + (ulong)hi0; + nuint lo1 = (nuint)(ulong)prod1; + nuint hi1 = (nuint)(ulong)(prod1 >> 64); + nuint orig1 = result[i + 1]; + result[i + 1] = orig1 - lo1; + hi1 += (orig1 < lo1) ? (nuint)1 : 0; + + UInt128 prod2 = (UInt128)(ulong)right[i + 2] * (ulong)multiplier + (ulong)hi1; + nuint lo2 = (nuint)(ulong)prod2; + nuint hi2 = (nuint)(ulong)(prod2 >> 64); + nuint orig2 = result[i + 2]; + result[i + 2] = orig2 - lo2; + hi2 += (orig2 < lo2) ? (nuint)1 : 0; + + UInt128 prod3 = (UInt128)(ulong)right[i + 3] * (ulong)multiplier + (ulong)hi2; + nuint lo3 = (nuint)(ulong)prod3; + nuint hi3 = (nuint)(ulong)(prod3 >> 64); + nuint orig3 = result[i + 3]; + result[i + 3] = orig3 - lo3; + hi3 += (orig3 < lo3) ? (nuint)1 : 0; + + carry = hi3; + } + + for (; i < length; i++) + { + UInt128 product = (UInt128)(ulong)right[i] * (ulong)multiplier + (ulong)carry; + nuint lo = (nuint)(ulong)product; + nuint hi = (nuint)(ulong)(product >> 64); + nuint orig = result[i]; + result[i] = orig - lo; + hi += (orig < lo) ? (nuint)1 : 0; + carry = hi; + } + } + else + { + for (; i < length; i++) + { + ulong product = (ulong)right[i] * multiplier + carry; + uint lo = (uint)product; + uint hi = (uint)(product >> 32); + + uint orig = (uint)result[i]; + result[i] = (nuint)(orig - lo); + hi += (orig < lo) ? 1u : 0; + + carry = (nuint)hi; + } + } + + return carry; + } } } From 52dc799a6e529f03a80d357c0c18341832e1d5ba Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 21:04:59 -0400 Subject: [PATCH 18/27] Replace Unsafe.Add with span indexing using bounds hints for JIT elision Convert all Unsafe.Add-based inner loops in AddSub.cs and SquMul.cs to use direct span indexing (span[i]) with upfront bounds-proving accesses that enable the JIT to elide bounds checks. Key changes: - AddSub.cs: Convert Add, AddSelf, Subtract, SubtractSelf and their tail helpers from Unsafe.Add to span[i] with for-loops + bounds hints. Remove unused ref nuint resultPtr parameters from tail helpers. Remove using System.Runtime.InteropServices. - SquMul.cs: Convert Naive square inner loop and SubtractCore from Unsafe.Add/MemoryMarshal.GetReference to span[i] with bounds hints. The pattern uses upfront span accesses to prove cross-span length relationships to the JIT, enabling bounds check elision in standard for loops. This avoids do...while loops which prevent JIT range analysis. Benchmarks confirm no regression vs the previous Unsafe.Add version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.AddSub.cs | 109 +++++++----------- .../Numerics/BigIntegerCalculator.SquMul.cs | 48 ++++---- 2 files changed, 64 insertions(+), 93 deletions(-) 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 163eb004ce51a3..b9bf2cbd42a0b4 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 @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace System.Numerics { @@ -21,7 +20,7 @@ public static void Add(ReadOnlySpan left, nuint right, Span bits) Debug.Assert(left.Length >= 1); Debug.Assert(bits.Length == left.Length + 1); - Add(left, bits, ref MemoryMarshal.GetReference(bits), startIndex: 0, initialCarry: right); + Add(left, bits, startIndex: 0, initialCarry: right); } public static void Add(ReadOnlySpan left, ReadOnlySpan right, Span bits) @@ -30,30 +29,19 @@ public static void Add(ReadOnlySpan left, ReadOnlySpan right, Span Debug.Assert(left.Length >= right.Length); Debug.Assert(bits.Length == left.Length + 1); - // Switching to managed references helps eliminating - // index bounds check for all buffers. - ref nuint resultPtr = ref MemoryMarshal.GetReference(bits); - ref nuint rightPtr = ref MemoryMarshal.GetReference(right); - ref nuint leftPtr = ref MemoryMarshal.GetReference(left); + // Establish cross-span length relationships so the JIT can + // elide bounds checks for left[i] and bits[i] in the loop. + _ = left[right.Length - 1]; + _ = bits[right.Length]; - int i = 0; nuint carry = 0; - // 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(base - 1) + 1 = 2*base - 1, our carry c - // has always the value 1 or 0; hence, we're safe here. - - do + for (int i = 0; i < right.Length; i++) { - Unsafe.Add(ref resultPtr, i) = AddWithCarry( - Unsafe.Add(ref leftPtr, i), - Unsafe.Add(ref rightPtr, i), - carry, out carry); - i++; - } while (i < right.Length); - - Add(left, bits, ref resultPtr, startIndex: i, initialCarry: carry); + bits[i] = AddWithCarry(left[i], right[i], carry, out carry); + } + + Add(left, bits, startIndex: right.Length, initialCarry: carry); } public static void AddSelf(Span left, ReadOnlySpan right) @@ -63,18 +51,14 @@ public static void AddSelf(Span left, ReadOnlySpan right) int i = 0; nuint carry = 0; - // Switching to managed references helps eliminating - // index bounds check... - ref nuint 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. + if (right.Length != 0) + { + _ = left[right.Length - 1]; + } for (; i < right.Length; i++) { - Unsafe.Add(ref leftPtr, i) = AddWithCarry( - Unsafe.Add(ref leftPtr, i), right[i], carry, out carry); + left[i] = AddWithCarry(left[i], right[i], carry, out carry); } for (; carry != 0 && i < left.Length; i++) { @@ -92,7 +76,7 @@ public static void Subtract(ReadOnlySpan left, nuint right, Span b Debug.Assert(left[0] >= right || left.Length >= 2); Debug.Assert(bits.Length == left.Length); - Subtract(left, bits, ref MemoryMarshal.GetReference(bits), startIndex: 0, initialBorrow: right); + Subtract(left, bits, startIndex: 0, initialBorrow: right); } public static void Subtract(ReadOnlySpan left, ReadOnlySpan right, Span bits) @@ -102,27 +86,17 @@ public static void Subtract(ReadOnlySpan left, ReadOnlySpan right, Debug.Assert(CompareActual(left, right) >= 0); Debug.Assert(bits.Length == left.Length); - // Switching to managed references helps eliminating - // index bounds check for all buffers. - ref nuint resultPtr = ref MemoryMarshal.GetReference(bits); - ref nuint rightPtr = ref MemoryMarshal.GetReference(right); - ref nuint leftPtr = ref MemoryMarshal.GetReference(left); + _ = left[right.Length - 1]; + _ = bits[right.Length - 1]; - int i = 0; nuint borrow = 0; - // Executes the "grammar-school" algorithm for computing z = a - b. - - do + for (int i = 0; i < right.Length; i++) { - Unsafe.Add(ref resultPtr, i) = SubWithBorrow( - Unsafe.Add(ref leftPtr, i), - Unsafe.Add(ref rightPtr, i), - borrow, out borrow); - i++; - } while (i < right.Length); - - Subtract(left, bits, ref resultPtr, startIndex: i, initialBorrow: borrow); + bits[i] = SubWithBorrow(left[i], right[i], borrow, out borrow); + } + + Subtract(left, bits, startIndex: right.Length, initialBorrow: borrow); } public static void SubtractSelf(Span left, ReadOnlySpan right) @@ -135,18 +109,14 @@ public static void SubtractSelf(Span left, ReadOnlySpan right) int i = 0; nuint borrow = 0; - // Switching to managed references helps eliminating - // index bounds check... - ref nuint 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. + if (right.Length != 0) + { + _ = left[right.Length - 1]; + } for (; i < right.Length; i++) { - Unsafe.Add(ref leftPtr, i) = SubWithBorrow( - Unsafe.Add(ref leftPtr, i), right[i], borrow, out borrow); + left[i] = SubWithBorrow(left[i], right[i], borrow, out borrow); } for (; borrow != 0 && i < left.Length; i++) { @@ -160,23 +130,25 @@ public static void SubtractSelf(Span left, ReadOnlySpan right) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Add(ReadOnlySpan left, Span bits, ref nuint resultPtr, int startIndex, nuint initialCarry) + private static void Add(ReadOnlySpan left, Span bits, int startIndex, nuint initialCarry) { // Executes the addition for one big and one single-limb integer. int i = startIndex; nuint carry = initialCarry; + _ = bits[left.Length]; + if (left.Length <= CopyToThreshold) { for (; i < left.Length; i++) { nuint sum = left[i] + carry; carry = (sum < carry) ? (nuint)1 : (nuint)0; - Unsafe.Add(ref resultPtr, i) = sum; + bits[i] = sum; } - Unsafe.Add(ref resultPtr, left.Length) = carry; + bits[left.Length] = carry; } else { @@ -184,7 +156,7 @@ private static void Add(ReadOnlySpan left, Span bits, ref nuint re { nuint sum = left[i] + carry; carry = (sum < carry) ? (nuint)1 : (nuint)0; - Unsafe.Add(ref resultPtr, i) = sum; + bits[i] = sum; i++; // Once carry is set to 0 it can not be 1 anymore. @@ -195,7 +167,7 @@ private static void Add(ReadOnlySpan left, Span bits, ref nuint re } } - Unsafe.Add(ref resultPtr, left.Length) = carry; + bits[left.Length] = carry; if (i < left.Length) { @@ -205,13 +177,18 @@ private static void Add(ReadOnlySpan left, Span bits, ref nuint re } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Subtract(ReadOnlySpan left, Span bits, ref nuint resultPtr, int startIndex, nuint initialBorrow) + private static void Subtract(ReadOnlySpan left, Span bits, int startIndex, nuint initialBorrow) { // Executes the subtraction for one big and one single-limb integer. int i = startIndex; nuint borrow = initialBorrow; + if (left.Length != 0) + { + _ = bits[left.Length - 1]; + } + if (left.Length <= CopyToThreshold) { for (; i < left.Length; i++) @@ -219,7 +196,7 @@ private static void Subtract(ReadOnlySpan left, Span bits, ref nui nuint val = left[i]; nuint diff = val - borrow; borrow = (diff > val) ? (nuint)1 : (nuint)0; - Unsafe.Add(ref resultPtr, i) = diff; + bits[i] = diff; } } else @@ -229,7 +206,7 @@ private static void Subtract(ReadOnlySpan left, Span bits, ref nui nuint val = left[i]; nuint diff = val - borrow; borrow = (diff > val) ? (nuint)1 : (nuint)0; - Unsafe.Add(ref resultPtr, i) = diff; + bits[i] = diff; i++; // Once borrow is set to 0 it can not be 1 anymore. 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 4150cc514e0fd8..b59915d40fd6f6 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 @@ -156,10 +156,6 @@ static void Naive(ReadOnlySpan value, Span bits) Debug.Assert(bits.Length == value.Length + value.Length); Debug.Assert(bits.Trim((nuint)0).IsEmpty); - // Switching to managed references helps eliminating - // index bounds check... - ref nuint 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 @@ -173,41 +169,38 @@ static void Naive(ReadOnlySpan value, Span bits) // extra shifts. if (nint.Size == 8) { - // On 64-bit, carry needs 65 bits (one more than the limb width), - // so we use UInt128 for the carry accumulator. for (int i = 0; i < value.Length; i++) { UInt128 carry = 0; nuint v = value[i]; for (int j = 0; j < i; j++) { - UInt128 digit1 = (UInt128)(ulong)Unsafe.Add(ref resultPtr, i + j) + carry; + UInt128 digit1 = (UInt128)(ulong)bits[i + j] + carry; UInt128 digit2 = (UInt128)(ulong)value[j] * (ulong)v; - Unsafe.Add(ref resultPtr, i + j) = (nuint)(ulong)(digit1 + (digit2 << 1)); + bits[i + j] = (nuint)(ulong)(digit1 + (digit2 << 1)); carry = (digit2 + (digit1 >> 1)) >> 63; } UInt128 digits = (UInt128)(ulong)v * (ulong)v + carry; - Unsafe.Add(ref resultPtr, i + i) = (nuint)(ulong)digits; - Unsafe.Add(ref resultPtr, i + i + 1) = (nuint)(ulong)(digits >> 64); + bits[i + i] = (nuint)(ulong)digits; + bits[i + i + 1] = (nuint)(ulong)(digits >> 64); } } else { - // On 32-bit, carry needs 33 bits, so ulong suffices. for (int i = 0; i < value.Length; i++) { ulong carry = 0; nuint v = value[i]; for (int j = 0; j < i; j++) { - ulong digit1 = Unsafe.Add(ref resultPtr, i + j) + carry; + ulong digit1 = bits[i + j] + carry; ulong digit2 = (ulong)value[j] * v; - Unsafe.Add(ref resultPtr, i + j) = (nuint)(uint)(digit1 + (digit2 << 1)); + bits[i + j] = (nuint)(uint)(digit1 + (digit2 << 1)); carry = (digit2 + (digit1 >> 1)) >> 31; } ulong digits = (ulong)v * v + carry; - Unsafe.Add(ref resultPtr, i + i) = (nuint)(uint)digits; - Unsafe.Add(ref resultPtr, i + i + 1) = (nuint)(uint)(digits >> 32); + bits[i + i] = (nuint)(uint)digits; + bits[i + i + 1] = (nuint)(uint)(digits >> 32); } } } @@ -883,10 +876,11 @@ private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan r int i = 0; - // Switching to managed references helps eliminating - // index bounds check... - ref nuint leftPtr = ref MemoryMarshal.GetReference(left); - ref nuint corePtr = ref MemoryMarshal.GetReference(core); + if (right.Length != 0) + { + _ = left[right.Length - 1]; + _ = core[left.Length - 1]; + } if (nint.Size == 8) { @@ -894,15 +888,15 @@ private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan r for (; i < right.Length; i++) { - Int128 digit = (Int128)(ulong)Unsafe.Add(ref corePtr, i) + carry - (ulong)Unsafe.Add(ref leftPtr, i) - (ulong)right[i]; - Unsafe.Add(ref corePtr, i) = (nuint)(ulong)digit; + Int128 digit = (Int128)(ulong)core[i] + carry - (ulong)left[i] - (ulong)right[i]; + core[i] = (nuint)(ulong)digit; carry = digit >> 64; } for (; i < left.Length; i++) { - Int128 digit = (Int128)(ulong)Unsafe.Add(ref corePtr, i) + carry - (ulong)left[i]; - Unsafe.Add(ref corePtr, i) = (nuint)(ulong)digit; + Int128 digit = (Int128)(ulong)core[i] + carry - (ulong)left[i]; + core[i] = (nuint)(ulong)digit; carry = digit >> 64; } @@ -919,15 +913,15 @@ private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan r for (; i < right.Length; i++) { - long digit = ((long)(uint)Unsafe.Add(ref corePtr, i) + carry) - (uint)Unsafe.Add(ref leftPtr, i) - (uint)right[i]; - Unsafe.Add(ref corePtr, i) = (nuint)(uint)digit; + long digit = ((long)(uint)core[i] + carry) - (uint)left[i] - (uint)right[i]; + core[i] = (nuint)(uint)digit; carry = digit >> 32; } for (; i < left.Length; i++) { - long digit = ((long)(uint)Unsafe.Add(ref corePtr, i) + carry) - (uint)left[i]; - Unsafe.Add(ref corePtr, i) = (nuint)(uint)digit; + long digit = ((long)(uint)core[i] + carry) - (uint)left[i]; + core[i] = (nuint)(uint)digit; carry = digit >> 32; } From db7bb90fa355d8db613447860ce639f2c9f661eb Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 21:49:50 -0400 Subject: [PATCH 19/27] Use hardware intrinsics for BigMul, DivRem, and AddWithCarry primitives - BigMul: Call Math.BigMul directly instead of going through UInt128 multiplication. Math.BigMul uses Bmi2.X64.MultiplyNoFlags on x86-64 and ArmBase.Arm64.MultiplyHigh on ARM64. - DivRem: Use X86Base.X64.DivRem for 128-by-64 division with large divisors (>uint.MaxValue), replacing the expensive UInt128 software division path. Falls back to UInt128 on non-x86 platforms. - AddWithCarry: Replace UInt128-based carry detection with native overflow detection pattern (sum < a), avoiding 128-bit arithmetic for a simple carry-out computation. Benchmarks show: Add 64K 12% faster, Divide 64K 6% faster, Add 1K regression eliminated (1.034 -> 0.994). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.Utils.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) 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 index db0cc01c184df2..46ba4e226ae73b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs @@ -3,6 +3,8 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.X86; +using X86Base = System.Runtime.Intrinsics.X86.X86Base; namespace System.Numerics { @@ -100,9 +102,12 @@ internal static nuint AddWithCarry(nuint a, nuint b, nuint carryIn, out nuint ca { if (nint.Size == 8) { - UInt128 sum = (UInt128)(ulong)a + (ulong)b + (ulong)carryIn; - carryOut = (nuint)(ulong)(sum >> 64); - return (nuint)(ulong)sum; + nuint sum1 = a + b; + nuint c1 = (sum1 < a) ? (nuint)1 : (nuint)0; + nuint sum2 = sum1 + carryIn; + nuint c2 = (sum2 < sum1) ? (nuint)1 : (nuint)0; + carryOut = c1 + c2; + return sum2; } else { @@ -173,6 +178,15 @@ internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remain } { +#pragma warning disable SYSLIB5004 // X86Base.DivRem is experimental + if (X86Base.X64.IsSupported) + { + (ulong q, ulong r) = X86Base.X64.DivRem((ulong)lo, (ulong)hi, (ulong)divisor); + remainder = (nuint)r; + return (nuint)q; + } +#pragma warning restore SYSLIB5004 + UInt128 value = ((UInt128)(ulong)hi << 64) | (ulong)lo; UInt128 digit = value / (ulong)divisor; remainder = (nuint)(ulong)(value - digit * (ulong)divisor); @@ -196,9 +210,9 @@ internal static nuint BigMul(nuint a, nuint b, out nuint low) { if (nint.Size == 8) { - UInt128 product = (UInt128)(ulong)a * (ulong)b; - low = (nuint)(ulong)product; - return (nuint)(ulong)(product >> 64); + ulong hi = Math.BigMul((ulong)a, (ulong)b, out ulong lo); + low = (nuint)lo; + return (nuint)hi; } else { From 2990b90752b6a79481d58f66437b94b4ff94942f Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 18 Mar 2026 23:12:22 -0400 Subject: [PATCH 20/27] Revert _sign from nint to int to eliminate small-value regressions The nuint limb conversion changed _sign from int to nint, which caused significant regressions for small-value operations (Multiply 16-bit 1.78x, Divide 16-bit 1.67x) due to 64-bit imul/idiv being slower than 32-bit equivalents. Reverting _sign to int while keeping nuint[] _bits preserves all large-number speedups while restoring small-value performance. Key changes: - Field declaration: nint _sign -> int _sign - s_bnMinInt simplified (no nint.Size branching) - All constructors updated for int _sign with nuint[] _bits - Removed MultiplyNint helper (no longer needed) - All operators simplified (removed nint.Size==8 branches for _sign) - Fixed decimal constructor canonicalization: use _bits[0] <= int.MaxValue instead of (int)_bits[0] > 0 to avoid truncation with 64-bit limbs - NumericsHelpers.Abs updated for int parameter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.BigInteger.cs | 14 +- .../src/System/Numerics/BigInteger.cs | 291 ++++++++---------- .../src/System/Numerics/NumericsHelpers.cs | 4 +- 3 files changed, 144 insertions(+), 165 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index c2baa2c5351e65..fb0cac365b5309 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -221,21 +221,21 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0) + nint signedLeading = unchecked((nint)leading); + if ((nint)(leading ^ signBits) >= 0 && int.MinValue < signedLeading && signedLeading <= int.MaxValue) { - // Small value that fits in nint. - // Delegate to the constructor for int.MinValue handling. - result = new BigInteger((nint)leading, null); + // Small value that fits in int _sign. + result = new BigInteger((int)signedLeading, null); return ParsingStatus.OK; } else if (leading != 0) { - // The sign of result differs with leading digit. - // Require to store in _bits. + // The sign of result differs with leading digit, or value + // doesn't fit in int _sign. Require to store in _bits. // Positive: sign=1, bits=[leading] // Negative: sign=-1, bits=[(leading ^ -1) + 1]=[-leading] - result = new BigInteger((nint)signBits | 1, [(leading ^ signBits) - signBits]); + result = new BigInteger(unchecked((int)signBits) | 1, [(leading ^ signBits) - signBits]); return ParsingStatus.OK; } else 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 6ca3c76defe1f2..7ac831181b3273 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -36,28 +36,26 @@ public readonly struct BigInteger // roll their own type that utilizes native memory and other specialized techniques. internal static int MaxLength => Array.MaxLength / (nint.Size * 8); - // For values nint.MinValue < n <= nint.MaxValue, the value is stored in sign + // 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 - internal readonly nint _sign; // Do not rename (binary serialization) + internal readonly int _sign; // Do not rename (binary serialization) internal readonly nuint[]? _bits; // Do not rename (binary serialization) - // We have to make a choice of how to represent nint.MinValue. This is the one - // value that fits in an nint, but whose negation does not fit in an nint. + // We have to make a choice of how to represent int.MinValue. This is the one + // value that fits in an int, but whose negation does not fit in an int. // We choose to use a large representation, so we're symmetric with respect to negation. - private static readonly BigInteger s_bnMinInt = nint.Size == 8 - ? new BigInteger(-1, new nuint[] { unchecked((nuint)long.MinValue) }) - : new BigInteger(-1, new nuint[] { kuMaskHighBit }); + private static readonly BigInteger s_bnMinInt = new BigInteger(-1, new nuint[] { kuMaskHighBit }); private static readonly BigInteger s_bnOneInt = new BigInteger(1); private static readonly BigInteger s_bnZeroInt = new BigInteger(0); private static readonly BigInteger s_bnMinusOneInt = new BigInteger(-1); public BigInteger(int value) { - if (nint.Size == 4 && value == int.MinValue) + if (value == int.MinValue) this = s_bnMinInt; else { - _sign = (nint)value; + _sign = value; _bits = null; } AssertValid(); @@ -66,9 +64,9 @@ public BigInteger(int value) [CLSCompliant(false)] public BigInteger(uint value) { - if (nint.Size == 8 || value <= int.MaxValue) + if (value <= int.MaxValue) { - _sign = (nint)value; + _sign = (int)value; _bits = null; } else @@ -81,21 +79,9 @@ public BigInteger(uint value) public BigInteger(long value) { - if (nint.Size == 8) - { - if (value == long.MinValue) - { - this = s_bnMinInt; - } - else - { - _sign = (nint)value; - _bits = null; - } - } - else if (int.MinValue < value && value <= int.MaxValue) + if (int.MinValue < value && value <= int.MaxValue) { - _sign = (nint)(int)value; + _sign = (int)value; _bits = null; } else if (value == int.MinValue) @@ -116,7 +102,11 @@ public BigInteger(long value) _sign = +1; } - if (x <= uint.MaxValue) + if (nint.Size == 8) + { + _bits = [(nuint)x]; + } + else if (x <= uint.MaxValue) { _bits = [(nuint)(uint)x]; } @@ -134,35 +124,28 @@ public BigInteger(long value) [CLSCompliant(false)] public BigInteger(ulong value) { - if (nint.Size == 8) + if (value <= int.MaxValue) { - if (value <= (ulong)long.MaxValue) - { - _sign = (nint)value; - _bits = null; - } - else - { - _sign = +1; - _bits = [(nuint)value]; - } - } - else if (value <= int.MaxValue) - { - _sign = (nint)(int)value; + _sign = (int)value; _bits = null; } - else if (value <= uint.MaxValue) - { - _sign = +1; - _bits = [(nuint)(uint)value]; - } else { _sign = +1; - _bits = new nuint[2]; - _bits[0] = unchecked((nuint)(uint)value); - _bits[1] = (nuint)(uint)(value >> kcbitUint); + if (nint.Size == 8) + { + _bits = [(nuint)value]; + } + else if (value <= uint.MaxValue) + { + _bits = [(nuint)(uint)value]; + } + else + { + _bits = new nuint[2]; + _bits[0] = unchecked((nuint)(uint)value); + _bits[1] = (nuint)(uint)(value >> kcbitUint); + } } AssertValid(); @@ -279,7 +262,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 = (nint)bits[0]; + _sign = bits[0]; _sign *= ((bits[3] & signMask) != 0) ? -1 : +1; _bits = null; } @@ -314,10 +297,10 @@ public BigInteger(decimal value) _sign = ((bits[3] & signMask) != 0) ? -1 : +1; - // Canonicalize: single-limb values that fit in nint should be stored inline - if (_bits is { Length: 1 } && (nint)_bits[0] >= 0) + // Canonicalize: single-limb values that fit in int should be stored inline + if (_bits is { Length: 1 } && _bits[0] <= int.MaxValue) { - _sign = _sign < 0 ? -(nint)_bits[0] : (nint)_bits[0]; + _sign = _sign < 0 ? -(int)_bits[0] : (int)_bits[0]; _bits = null; } } @@ -386,9 +369,9 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE return; } - if (byteCount <= nint.Size) + if (byteCount <= 4) { - _sign = isNegative ? (nint)(-1) : 0; + _sign = isNegative ? -1 : 0; if (isBigEndian) { @@ -408,11 +391,11 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE _bits = null; if (_sign < 0 && !isNegative) { - // nint overflow: unsigned value overflows into the nint sign bit - _bits = new nuint[1] { unchecked((nuint)_sign) }; + // int overflow: unsigned value overflows into the int sign bit + _bits = new nuint[1] { unchecked((nuint)(uint)_sign) }; _sign = +1; } - if (_sign == nint.MinValue) + if (_sign == int.MinValue) { this = s_bnMinInt; } @@ -497,7 +480,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE while (len >= 0 && val[len] == 0) len--; len++; - nuint nintMinMagnitude = unchecked((nuint)nint.MinValue); + nuint intMinMagnitude = unchecked((nuint)(uint)int.MinValue); if (len == 1) { @@ -506,14 +489,14 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE this = s_bnMinusOneInt; return; } - else if (val[0] == nintMinMagnitude) // abs(nint.MinValue) + else if (val[0] == intMinMagnitude) // abs(int.MinValue) { this = s_bnMinInt; return; } - else if (val[0] < nintMinMagnitude) // fits in nint as negative + else if (val[0] < intMinMagnitude) // fits in int as negative { - _sign = -(nint)val[0]; + _sign = -(int)val[0]; _bits = null; AssertValid(); return; @@ -547,7 +530,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE /// /// the sign field /// the bits field - internal BigInteger(nint sign, nuint[]? bits) + internal BigInteger(int sign, nuint[]? bits) { // Runtime check is converted to assertions because only one call from TryParseBigIntegerHexOrBinaryNumberStyle may fail the length check. // Validation in TryParseBigIntegerHexOrBinaryNumberStyle is also added in the accompanying PR. @@ -577,34 +560,26 @@ internal BigInteger(ReadOnlySpan value, bool negative) ThrowHelper.ThrowOverflowException(); } - nuint nintMinMagnitude = unchecked((nuint)nint.MinValue); + nuint intMinMagnitude = unchecked((nuint)(uint)int.MinValue); if (value.Length == 0) { this = default; } - else if (value.Length == 1) + else if (value.Length == 1 && value[0] < intMinMagnitude) { - if (value[0] < nintMinMagnitude) - { - _sign = negative ? -(nint)value[0] : (nint)value[0]; - _bits = null; - } - else if (negative && value[0] == nintMinMagnitude) - { - // Although nint.MinValue fits in _sign, we represent this case differently for negate - this = s_bnMinInt; - } - else - { - _sign = negative ? -1 : +1; - _bits = [value[0]]; - } + _sign = negative ? -(int)value[0] : (int)value[0]; + _bits = null; + } + else if (value.Length == 1 && negative && value[0] == intMinMagnitude) + { + // Although int.MinValue fits in _sign, we represent this case differently for negate + this = s_bnMinInt; } else { _sign = negative ? -1 : +1; - _bits = value.ToArray(); + _bits = value.Length == 1 ? [value[0]] : value.ToArray(); } AssertValid(); } @@ -642,7 +617,7 @@ private BigInteger(Span value) ThrowHelper.ThrowOverflowException(); } - nuint nintMinMagnitude = unchecked((nuint)nint.MinValue); + nuint intMinMagnitude = unchecked((nuint)(uint)int.MinValue); if (value.Length == 0) { @@ -658,25 +633,46 @@ private BigInteger(Span value) // -1 this = s_bnMinusOneInt; } - else if (value[0] == nintMinMagnitude) + else if (nint.Size == 4 && value[0] == intMinMagnitude) { - // nint.MinValue + // int.MinValue this = s_bnMinInt; } else { - _sign = unchecked((nint)value[0]); - _bits = null; + // Single-limb negative twos-complement: convert to magnitude and + // check if it fits in int _sign. + NumericsHelpers.DangerousMakeTwosComplement(value); + nuint magnitude = value[0]; + + if (magnitude < intMinMagnitude) + { + _sign = -(int)magnitude; + _bits = null; + } + else if (nint.Size == 4) + { + // On 32-bit, magnitude > int.MaxValue always needs _bits + _sign = -1; + _bits = [magnitude]; + } + else + { + // On 64-bit, check if multi-uint magnitude fits in one nuint + _sign = -1; + int trimLen = value.LastIndexOfAnyExcept((nuint)0) + 1; + _bits = trimLen == 1 ? [magnitude] : value[..trimLen].ToArray(); + } } } - else if (value[0] >= nintMinMagnitude) + else if (value[0] >= intMinMagnitude) { _sign = +1; _bits = [value[0]]; } else { - _sign = unchecked((nint)value[0]); + _sign = (int)value[0]; _bits = null; } } @@ -733,7 +729,7 @@ public bool IsPowerOfTwo public int Sign { - get { AssertValid(); return (int)((_sign >> (nint.Size * 8 - 1)) - (-_sign >> (nint.Size * 8 - 1))); } + get { AssertValid(); return (_sign >> 31) - (-_sign >> 31); } } public static BigInteger Parse(string value) @@ -805,7 +801,7 @@ public static int Compare(BigInteger left, BigInteger right) public static BigInteger Abs(BigInteger value) { value.AssertValid(); - return new BigInteger(unchecked((nint)NumericsHelpers.Abs(value._sign)), value._bits); + return new BigInteger((int)NumericsHelpers.Abs(value._sign), value._bits); } public static BigInteger Add(BigInteger left, BigInteger right) @@ -844,7 +840,7 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big if (trivialDividend && trivialDivisor) { BigInteger quotient; - (nint q, nint r) = Math.DivRem(dividend._sign, divisor._sign); + (int q, int r) = Math.DivRem(dividend._sign, divisor._sign); quotient = q; remainder = r; return quotient; @@ -1197,7 +1193,7 @@ public override int GetHashCode() AssertValid(); if (_bits is null) - return (int)_sign ^ (int)((long)_sign >> 32); + return _sign; HashCode hash = default; hash.AddBytes(MemoryMarshal.AsBytes(_bits.AsSpan())); @@ -1217,7 +1213,7 @@ public bool Equals(long other) AssertValid(); if (_bits == null) - return (long)_sign == other; + return _sign == other; int cu; int maxLimbs = 8 / nint.Size; @@ -1283,7 +1279,7 @@ public int CompareTo(long other) int cu; int maxLimbs = 8 / nint.Size; if (((long)_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) - return (int)_sign; + return _sign; ulong uu = other < 0 ? (ulong)-other : (ulong)other; ulong uuTmp; @@ -1296,7 +1292,7 @@ public int CompareTo(long other) { uuTmp = cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : (uint)_bits[0]; } - return (int)_sign * uuTmp.CompareTo(uu); + return _sign * uuTmp.CompareTo(uu); } [CLSCompliant(false)] @@ -1307,7 +1303,7 @@ public int CompareTo(ulong other) if (_sign < 0) return -1; if (_bits == null) - return ((ulong)_sign).CompareTo(other); + return ((ulong)(uint)_sign).CompareTo(other); int cu = _bits.Length; int maxLimbs = 8 / nint.Size; @@ -1479,7 +1475,7 @@ private enum GetBytesMode Debug.Assert(mode == GetBytesMode.AllocateArray || mode == GetBytesMode.Count || mode == GetBytesMode.Span, $"Unexpected mode {mode}."); Debug.Assert(mode == GetBytesMode.Span || destination.IsEmpty, $"If we're not in span mode, we shouldn't have been passed a destination."); - nint sign = _sign; + int sign = _sign; if (sign == 0) { switch (mode) @@ -1830,7 +1826,7 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS return Number.TryFormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider), MemoryMarshal.Cast(utf8Destination), out bytesWritten); } - private static BigInteger Add(ReadOnlySpan leftBits, nint leftSign, ReadOnlySpan rightBits, nint rightSign) + private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) { bool trivialLeft = leftBits.IsEmpty; bool trivialRight = rightBits.IsEmpty; @@ -1901,14 +1897,14 @@ private static BigInteger Add(ReadOnlySpan leftBits, nint leftSign, ReadO right.AssertValid(); if (left._bits == null && right._bits == null) - return (Int128)left._sign - right._sign; + return (long)left._sign - right._sign; if (left._sign < 0 != right._sign < 0) return Add(left._bits, left._sign, right._bits, -1 * right._sign); return Subtract(left._bits, left._sign, right._bits, right._sign); } - private static BigInteger Subtract(ReadOnlySpan leftBits, nint leftSign, ReadOnlySpan rightBits, nint rightSign) + private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) { bool trivialLeft = leftBits.IsEmpty; bool trivialRight = rightBits.IsEmpty; @@ -2034,11 +2030,11 @@ public static explicit operator double(BigInteger value) { value.AssertValid(); - nint sign = value._sign; + int sign = value._sign; nuint[]? bits = value._bits; if (bits == null) - return (double)(long)sign; + return (double)sign; int length = bits.Length; int bitsPerLimb = nint.Size * 8; @@ -2079,7 +2075,7 @@ public static explicit operator double(BigInteger value) man = (h << 32 + z) | (m << z) | (l >> 32 - z); } - return NumericsHelpers.GetDoubleFromParts((int)sign, exp, man); + return NumericsHelpers.GetDoubleFromParts(sign, exp, man); } /// Explicitly converts a big integer to a value. @@ -2108,7 +2104,7 @@ public static explicit operator int(BigInteger value) value.AssertValid(); if (value._bits == null) { - return checked((int)value._sign); // nint to int; may overflow on 64-bit + return value._sign; } if (value._bits.Length > 1) { @@ -2132,7 +2128,7 @@ public static explicit operator long(BigInteger value) value.AssertValid(); if (value._bits == null) { - return (long)value._sign; + return value._sign; } int len = value._bits.Length; @@ -2174,7 +2170,7 @@ public static explicit operator Int128(BigInteger value) if (value._bits is null) { - return (long)value._sign; + return value._sign; } int len = value._bits.Length; @@ -2444,16 +2440,16 @@ public static implicit operator BigInteger(long value) /// converted to a big integer. public static implicit operator BigInteger(Int128 value) { - nint sign; + int sign; nuint[]? bits; - if ((nint.MinValue < value) && (value <= nint.MaxValue)) + if ((int.MinValue < value) && (value <= int.MaxValue)) { - if (value == nint.MinValue) + if (value == int.MinValue) { return s_bnMinInt; } - sign = (nint)(long)value; + sign = (int)(long)value; bits = null; } else @@ -2523,11 +2519,7 @@ public static implicit operator BigInteger(Int128 value) /// converted to a big integer. public static implicit operator BigInteger(nint value) { - if (value == nint.MinValue) - { - return s_bnMinInt; - } - return new BigInteger(value, null); + return new BigInteger((long)value); } [CLSCompliant(false)] @@ -2560,12 +2552,12 @@ public static implicit operator BigInteger(ulong value) [CLSCompliant(false)] public static implicit operator BigInteger(UInt128 value) { - nint sign = +1; + int sign = +1; nuint[]? bits; - if (value <= (ulong)nint.MaxValue) + if (value <= (ulong)int.MaxValue) { - sign = (nint)(ulong)value; + sign = (int)(ulong)value; bits = null; } else if (nint.Size == 8) @@ -2618,9 +2610,9 @@ public static implicit operator BigInteger(UInt128 value) [CLSCompliant(false)] public static implicit operator BigInteger(nuint value) { - if (value <= (nuint)nint.MaxValue) + if (value <= (nuint)int.MaxValue) { - return new BigInteger((nint)value, null); + return new BigInteger((int)value, null); } else { @@ -2803,7 +2795,7 @@ private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlin { // Inline value: _sign holds the value directly. // For negative inline: magnitude is Abs(_sign), stored as positive nuint. - mag = i == 0 ? (isNegative ? NumericsHelpers.Abs(unchecked((nint)inlineValue)) : inlineValue) : 0; + mag = i == 0 ? (isNegative ? NumericsHelpers.Abs(unchecked((int)inlineValue)) : inlineValue) : 0; } if (!isNegative) @@ -2864,7 +2856,7 @@ private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlin return new BigInteger(value._sign, z); } - private static BigInteger LeftShift(nint value, int digitShift, int smallShift) + private static BigInteger LeftShift(int value, int digitShift, int smallShift) { if (value == 0) return s_bnZeroInt; @@ -2881,8 +2873,8 @@ private static BigInteger LeftShift(nint value, int digitShift, int smallShift) if (over == 0) { - if (digitShift == 0 && (nint)r >= 0) - return new BigInteger(value << smallShift, null); + if (digitShift == 0 && r <= (nuint)int.MaxValue) + return new BigInteger(value >= 0 ? (int)r : -(int)r, null); rgu = new nuint[digitShift + 1]; } @@ -2912,12 +2904,12 @@ private static BigInteger LeftShift(nint value, int digitShift, int smallShift) if (value._bits is null) { - if (digitShift != 0) + if (digitShift != 0 || smallShift >= 32) { - // If the shift length exceeds the bit width, non-negative values result + // If the shift length exceeds the int bit width, non-negative values result // in 0, and negative values result in -1. This behavior can be implemented - // using a (kcbitNuint - 1)-bit right shift on an nint type. - smallShift = BigIntegerCalculator.kcbitNuint - 1; + // using a 31-bit right shift on an int type. + smallShift = 31; } return new BigInteger(value._sign >> smallShift, null); @@ -2930,7 +2922,7 @@ private static BigInteger LeftShift(nint value, int digitShift, int smallShift) int zLength = bits.Length - digitShift + 1; if (zLength <= 1) - return new BigInteger(value._sign >> (BigIntegerCalculator.kcbitNuint - 1), null); + return new BigInteger(value._sign >> 31, null); nuint[]? zFromPool = null; Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold @@ -2995,9 +2987,7 @@ private static BigInteger LeftShift(nint value, int digitShift, int smallShift) if (left._bits == null && right._bits == null) { - if (nint.Size == 4) - return (long)left._sign + right._sign; - return (BigInteger)((Int128)left._sign + right._sign); + return (long)left._sign + right._sign; } if (left._sign < 0 != right._sign < 0) @@ -3012,25 +3002,13 @@ private static BigInteger LeftShift(nint value, int digitShift, int smallShift) if (left._bits == null && right._bits == null) { - if (nint.Size == 4) - return (long)left._sign * right._sign; - - nint s1 = left._sign, s2 = right._sign; - if (s1 == (int)s1 && s2 == (int)s2) - return (long)(int)s1 * (int)s2; - - return MultiplyNint(s1, s2); + return (long)left._sign * right._sign; } return Multiply(left._bits, left._sign, right._bits, right._sign); } - // Extracted to keep operator* body small enough for JIT inlining. - [MethodImpl(MethodImplOptions.NoInlining)] - private static BigInteger MultiplyNint(nint left, nint right) - => (BigInteger)((Int128)left * right); - - private static BigInteger Multiply(ReadOnlySpan left, nint leftSign, ReadOnlySpan right, nint rightSign) + private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOnlySpan right, int rightSign) { bool trivialLeft = left.IsEmpty; bool trivialRight = right.IsEmpty; @@ -3383,7 +3361,7 @@ public long GetBitLength() nuint highValue; int bitsArrayLength; - nint sign = _sign; + int sign = _sign; nuint[]? bits = _bits; if (bits == null) @@ -3432,7 +3410,7 @@ private void AssertValid() // _bits must contain at least 1 element or be null Debug.Assert(_bits.Length > 0); // Wasted space: _bits[0] could have been packed into _sign - Debug.Assert(_bits.Length > 1 || (nint)_bits[0] < 0); + Debug.Assert(_bits.Length > 1 || _bits[0] > (nuint)int.MaxValue); // Wasted space: leading zeros could have been truncated Debug.Assert(_bits[_bits.Length - 1] != 0); // Arrays larger than this can't fit into a Span @@ -3440,8 +3418,8 @@ private void AssertValid() } else { - // nint.MinValue should not be stored in the _sign field - Debug.Assert(_sign > nint.MinValue); + // int.MinValue should not be stored in the _sign field + Debug.Assert(_sign > int.MinValue); } } @@ -3702,15 +3680,15 @@ int IBinaryInteger.GetShortestBitLength() if (bits is null) { - nint value = _sign; + int value = _sign; if (value >= 0) { - return (nint.Size * 8) - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)(nuint)value) : BitOperations.LeadingZeroCount((uint)(nuint)value)); + return 32 - BitOperations.LeadingZeroCount((uint)value); } else { - return (nint.Size * 8) + 1 - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)(nuint)~value) : BitOperations.LeadingZeroCount((uint)(nuint)~value)); + return 33 - BitOperations.LeadingZeroCount(~(uint)value); } } @@ -5468,7 +5446,9 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va goto Excess; } - return new BigInteger(value._sign >>> smallShift, null); + // Sign-extend _sign from int to nint before unsigned right shift + // to preserve nint-width semantics consistent with nuint-limb storage. + return new BigInteger((nint)value._sign >>> smallShift); } ReadOnlySpan bits = value._bits; @@ -5569,12 +5549,11 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va return result; Excess: // Return -1 if the value is negative; otherwise, return 0. - return new BigInteger(value._sign >> (BigIntegerCalculator.kcbitNuint - 1), null); + return new BigInteger(value._sign >> 31, null); } // // ISignedNumber - // /// static BigInteger ISignedNumber.NegativeOne => MinusOne; 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 64aa035e112930..f86ac24f431550 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs @@ -166,11 +166,11 @@ public static void DangerousMakeOnesComplement(Span d) } } - public static nuint Abs(nint a) + public static nuint Abs(int a) { unchecked { - nuint mask = (nuint)(a >> (nint.Size * 8 - 1)); + nuint mask = (nuint)(a >> 31); return ((nuint)a ^ mask) - mask; } } From 60a0281a597fe8cc14b9e28d8e33c772a57b37bd Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 19 Mar 2026 09:09:36 -0400 Subject: [PATCH 21/27] Use ulong arithmetic in PowCore when modulus fits in uint On 64-bit platforms, PowCore unconditionally used UInt128 arithmetic for the square-and-multiply loop, even when all values fit in uint. UInt128 multiply+modulus is significantly more expensive than ulong. Now checks modulus <= uint.MaxValue and uses the cheaper ulong path, eliminating the ModPow 16-bit regression (1.27x -> 1.02x) while preserving all large-number speedups. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.PowMod.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) 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 80bafcddb16961..6bb147ad85bb9d 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 @@ -160,12 +160,22 @@ private static nuint PowCore(nuint value, ReadOnlySpan power, nuint modul // The single-limb modulus pow algorithm for all but // the last power limb using square-and-multiply. + // When modulus fits in uint, all intermediate values also fit in uint + // (since every step takes % modulus), so we can use cheaper ulong arithmetic. + bool useUlong = nint.Size == 4 || modulus <= uint.MaxValue; + for (int i = 0; i < power.Length - 1; i++) { nuint p = power[i]; for (int j = 0; j < kcbitNuint; j++) { - if (nint.Size == 8) + if (useUlong) + { + if ((p & 1) == 1) + result = (nuint)(uint)(((ulong)result * value) % modulus); + value = (nuint)(uint)(((ulong)value * value) % modulus); + } + else { if ((p & 1) == 1) { @@ -177,12 +187,6 @@ private static nuint PowCore(nuint value, ReadOnlySpan power, nuint modul value = (nuint)(ulong)(sq % (ulong)modulus); } } - else - { - if ((p & 1) == 1) - result = (nuint)(uint)(((ulong)result * value) % modulus); - value = (nuint)(uint)(((ulong)value * value) % modulus); - } p >>= 1; } } @@ -195,9 +199,20 @@ private static nuint PowCore(nuint value, nuint power, nuint modulus, nuint resu // The single-limb modulus pow algorithm for the last or // the only power limb using square-and-multiply. + // When modulus fits in uint, all intermediate values also fit in uint + // (since every step takes % modulus), so we can use cheaper ulong arithmetic. + bool useUlong = nint.Size == 4 || modulus <= uint.MaxValue; + while (power != 0) { - if (nint.Size == 8) + if (useUlong) + { + if ((power & 1) == 1) + result = (nuint)(uint)(((ulong)result * value) % modulus); + if (power != 1) + value = (nuint)(uint)(((ulong)value * value) % modulus); + } + else { if ((power & 1) == 1) { @@ -210,13 +225,6 @@ private static nuint PowCore(nuint value, nuint power, nuint modulus, nuint resu value = (nuint)(ulong)(sq % (ulong)modulus); } } - else - { - if ((power & 1) == 1) - result = (nuint)(uint)(((ulong)result * value) % modulus); - if (power != 1) - value = (nuint)(uint)(((ulong)value * value) % modulus); - } power >>= 1; } From 3dbb09e3fb3ad521558257c77977596a652087f3 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 19 Mar 2026 09:57:34 -0400 Subject: [PATCH 22/27] Clean up formatting / style --- .../src/System/Number.Formatting.Common.cs | 22 +- .../Common/src/System/Number.NumberBuffer.cs | 4 +- .../src/System/Number.Parsing.Common.cs | 2 +- .../src/System/Number.BigInteger.cs | 211 +- .../src/System/Number.Polyfill.cs | 6 +- .../src/System/Numerics/BigInteger.cs | 1944 +++++++---------- .../Numerics/BigIntegerCalculator.AddSub.cs | 20 +- .../Numerics/BigIntegerCalculator.DivRem.cs | 156 +- .../BigIntegerCalculator.FastReducer.cs | 18 +- .../Numerics/BigIntegerCalculator.GcdInv.cs | 82 +- .../Numerics/BigIntegerCalculator.PowMod.cs | 200 +- .../Numerics/BigIntegerCalculator.ShiftRot.cs | 37 +- .../Numerics/BigIntegerCalculator.SquMul.cs | 96 +- .../Numerics/BigIntegerCalculator.Utils.cs | 91 +- .../src/System/Numerics/Complex.cs | 28 +- .../src/System/Numerics/NumericsHelpers.cs | 24 +- 16 files changed, 1401 insertions(+), 1540 deletions(-) diff --git a/src/libraries/Common/src/System/Number.Formatting.Common.cs b/src/libraries/Common/src/System/Number.Formatting.Common.cs index 37734e001ad949..9885bb7c733831 100644 --- a/src/libraries/Common/src/System/Number.Formatting.Common.cs +++ b/src/libraries/Common/src/System/Number.Formatting.Common.cs @@ -141,7 +141,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig internal static unsafe void NumberToString(ref ValueListBuilder vlb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); number.CheckConsistency(); bool isCorrectlyRounded = (number.Kind == NumberBufferKind.FloatingPoint); @@ -290,7 +290,7 @@ internal static unsafe void NumberToString(ref ValueListBuilder vl internal static unsafe void NumberToStringFormat(ref ValueListBuilder vlb, ref NumberBuffer number, ReadOnlySpan format, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); number.CheckConsistency(); @@ -684,7 +684,7 @@ internal static unsafe void NumberToStringFormat(ref ValueListBuilder(ref ValueListBuilder(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); string fmt = number.IsNegative ? s_negCurrencyFormats[info.CurrencyNegativePattern] : @@ -747,7 +747,7 @@ private static unsafe void FormatFixed( int nMaxDigits, int[]? groupDigits, ReadOnlySpan sDecimal, ReadOnlySpan sGroup) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); int digPos = number.Scale; byte* dig = number.DigitsPtr; @@ -862,7 +862,7 @@ private static unsafe void FormatFixed( [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe void AppendUnknownChar(ref ValueListBuilder vlb, char ch) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); if (sizeof(TChar) == sizeof(char) || char.IsAscii(ch)) { @@ -883,7 +883,7 @@ static void AppendNonAsciiBytes(ref ValueListBuilder vlb, char ch) private static unsafe void FormatNumber(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); string fmt = number.IsNegative ? s_negNumberFormats[info.NumberNegativePattern] : @@ -910,7 +910,7 @@ private static unsafe void FormatNumber(ref ValueListBuilder vlb, private static unsafe void FormatScientific(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); byte* dig = number.DigitsPtr; @@ -932,7 +932,7 @@ private static unsafe void FormatScientific(ref ValueListBuilder v private static unsafe void FormatExponent(ref ValueListBuilder vlb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); vlb.Append(TChar.CastFrom(expChar)); @@ -956,7 +956,7 @@ private static unsafe void FormatExponent(ref ValueListBuilder vlb private static unsafe void FormatGeneral(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool suppressScientific) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); int digPos = number.Scale; bool scientific = false; @@ -1010,7 +1010,7 @@ private static unsafe void FormatGeneral(ref ValueListBuilder vlb, private static unsafe void FormatPercent(ref ValueListBuilder vlb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { - Debug.Assert(sizeof(TChar) == sizeof(char) || sizeof(TChar) == sizeof(byte)); + Debug.Assert(sizeof(TChar) is sizeof(char) or sizeof(byte)); string fmt = number.IsNegative ? s_negPercentFormats[info.PercentNegativePattern] : diff --git a/src/libraries/Common/src/System/Number.NumberBuffer.cs b/src/libraries/Common/src/System/Number.NumberBuffer.cs index 44c1d47d96dcab..9350f15677d5c0 100644 --- a/src/libraries/Common/src/System/Number.NumberBuffer.cs +++ b/src/libraries/Common/src/System/Number.NumberBuffer.cs @@ -62,7 +62,7 @@ public NumberBuffer(NumberBufferKind kind, Span digits) public void CheckConsistency() { #if DEBUG - Debug.Assert((Kind == NumberBufferKind.Integer) || (Kind == NumberBufferKind.Decimal) || (Kind == NumberBufferKind.FloatingPoint)); + Debug.Assert(Kind is NumberBufferKind.Integer or NumberBufferKind.Decimal or NumberBufferKind.FloatingPoint); Debug.Assert(Digits[0] != '0', "Leading zeros should never be stored in a Number"); int numDigits; @@ -89,7 +89,7 @@ public void CheckConsistency() // public override string ToString() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); sb.Append('['); sb.Append('"'); diff --git a/src/libraries/Common/src/System/Number.Parsing.Common.cs b/src/libraries/Common/src/System/Number.Parsing.Common.cs index 96661652f59ef7..cc3bc73c3d1236 100644 --- a/src/libraries/Common/src/System/Number.Parsing.Common.cs +++ b/src/libraries/Common/src/System/Number.Parsing.Common.cs @@ -317,7 +317,7 @@ internal enum ParsingStatus Overflow } - private static bool IsSpaceReplacingChar(uint c) => (c == '\u00a0') || (c == '\u202f'); + private static bool IsSpaceReplacingChar(uint c) => c is '\u00a0' or '\u202f'; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint NormalizeSpaceReplacingChar(uint c) => IsSpaceReplacingChar(c) ? '\u0020' : c; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index fb0cac365b5309..e9d9d2e2a9316b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -30,7 +30,10 @@ private static ReadOnlySpan UInt32PowersOfTen get { if (nint.Size == 8) + { return MemoryMarshal.Cast(UInt64PowersOfTen); + } + return MemoryMarshal.Cast(UInt32PowersOfTenCore); } } @@ -39,11 +42,9 @@ private static ReadOnlySpan UInt32PowersOfTen private static ReadOnlySpan UInt64PowersOfTen => [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; [DoesNotReturn] - internal static void ThrowOverflowOrFormatException(ParsingStatus status) => throw GetException(status); - - private static Exception GetException(ParsingStatus status) + internal static void ThrowOverflowOrFormatException(ParsingStatus status) { - return status == ParsingStatus.Failed + throw status == ParsingStatus.Failed ? new FormatException(SR.Overflow_ParseBigInteger) : new OverflowException(SR.Overflow_ParseBigInteger); } @@ -56,6 +57,7 @@ internal static bool TryValidateParseStyleInteger(NumberStyles style, [NotNullWh e = new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); return false; } + if ((style & NumberStyles.AllowHexSpecifier) != 0) { // Check for hex number if ((style & ~NumberStyles.HexNumber) != 0) @@ -64,6 +66,7 @@ internal static bool TryValidateParseStyleInteger(NumberStyles style, [NotNullWh return false; } } + e = null; return true; } @@ -114,7 +117,7 @@ internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpa fixed (byte* ptr = buffer) // NumberBuffer expects pinned span { - NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, buffer); + NumberBuffer number = new(NumberBufferKind.Integer, buffer); if (!TryStringToNumber(value, style, ref number, info)) { @@ -164,7 +167,9 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0; whiteIndex--) { if (!IsWhite(TChar.CastToUInt32(value[whiteIndex]))) + { break; + } } value = value[..(whiteIndex + 1)]; @@ -215,13 +222,14 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0 && int.MinValue < signedLeading && signedLeading <= int.MaxValue) { // Small value that fits in int _sign. @@ -235,18 +243,18 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle.Shared.Rent(base1E9Length)).Slice(0, base1E9Length); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : base1E9FromPool = ArrayPool.Shared.Rent(base1E9Length)).Slice(0, base1E9Length); int di = base1E9Length; ReadOnlySpan leadingDigits = intDigits[..(intDigits.Length % PowersOf1e9.MaxPartialDigits)]; @@ -389,16 +400,18 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big base1E9[di] = partialVal; intDigits = intDigits.Slice(PowersOf1e9.MaxPartialDigits); } + Debug.Assert(intDigits.Length == 0); } - double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.kcbitNuint; // log_{2^kcbitNuint}(10) + // Estimate limb count needed for the decimal value. + double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.BitsPerLimb; // log_{2^BitsPerLimb}(10) int resultLength = checked((int)(digitRatio * number.Scale) + 1 + 2); nuint[]? resultBufferFromPool = null; Span resultBuffer = ( resultLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(resultLength)).Slice(0, resultLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(resultLength)).Slice(0, resultLength); resultBuffer.Clear(); int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); @@ -416,9 +429,14 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big result = new BigInteger(resultBuffer, number.IsNegative); if (base1E9FromPool != null) + { ArrayPool.Shared.Return(base1E9FromPool); + } + if (resultBufferFromPool != null) + { ArrayPool.Shared.Return(resultBufferFromPool); + } return ParsingStatus.OK; @@ -431,13 +449,13 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, if (trailingZeroCount > 0) { - double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.kcbitNuint; + double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.BitsPerLimb; int leadingLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * base1E9.Length) + 3); nuint[]? leadingFromPool = null; Span leading = ( leadingLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : leadingFromPool = ArrayPool.Shared.Rent(leadingLength)).Slice(0, leadingLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : leadingFromPool = ArrayPool.Shared.Rent(leadingLength)).Slice(0, leadingLength); leading.Clear(); Recursive(powersOf1e9, maxIndex, base1E9, leading); @@ -446,7 +464,9 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, powersOf1e9.MultiplyPowerOfTen(leading, trailingZeroCount, bits); if (leadingFromPool != null) + { ArrayPool.Shared.Return(leadingFromPool); + } } else { @@ -471,18 +491,19 @@ static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnly { multiplier1E9Length = 1 << (--powersOf1e9Index); } + ReadOnlySpan multiplier = powersOf1e9.GetSpan(powersOf1e9Index); int multiplierTrailingZeroCount = PowersOf1e9.OmittedLength(powersOf1e9Index); Debug.Assert(multiplier1E9Length < base1E9.Length && base1E9.Length <= multiplier1E9Length * 2); - double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.kcbitNuint; + double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.BitsPerLimb; int bufferLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * multiplier1E9Length) + 1 + 2); nuint[]? bufferFromPool = null; scoped Span buffer = ( bufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bufferFromPool = ArrayPool.Shared.Rent(bufferLength)).Slice(0, bufferLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bufferFromPool = ArrayPool.Shared.Rent(bufferLength)).Slice(0, bufferLength); buffer.Clear(); Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[multiplier1E9Length..], buffer); @@ -498,7 +519,9 @@ static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnly BigIntegerCalculator.AddSelf(bits, buffer.Slice(0, BigIntegerCalculator.ActualLength(buffer))); if (bufferFromPool != null) + { ArrayPool.Shared.Return(bufferFromPool); + } } static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) @@ -508,6 +531,7 @@ static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Spa // number is 0. return; } + int resultLength = NaiveBase1E9ToBits(base1E9, bits); int trailingPartialCount = Math.DivRem(trailingZeroCount, PowersOf1e9.MaxPartialDigits, out int remainingTrailingZeroCount); @@ -516,7 +540,9 @@ static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Spa nuint carry = MultiplyAdd(bits.Slice(0, resultLength), PowersOf1e9.TenPowMaxPartial, 0); Debug.Assert(bits[resultLength] == 0); if (carry != 0) + { bits[resultLength++] = carry; + } } if (remainingTrailingZeroCount != 0) @@ -525,14 +551,18 @@ static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Spa nuint carry = MultiplyAdd(bits.Slice(0, resultLength), multiplier, 0); Debug.Assert(bits[resultLength] == 0); if (carry != 0) + { bits[resultLength++] = carry; + } } } static int NaiveBase1E9ToBits(ReadOnlySpan base1E9, Span bits) { if (base1E9.Length == 0) + { return 0; + } int resultLength = 1; bits[0] = base1E9[^1]; @@ -541,8 +571,11 @@ static int NaiveBase1E9ToBits(ReadOnlySpan base1E9, Span bits) nuint carry = MultiplyAdd(bits.Slice(0, resultLength), PowersOf1e9.TenPowMaxPartial, base1E9[i]); Debug.Assert(bits[resultLength] == 0); if (carry != 0) + { bits[resultLength++] = carry; + } } + return resultLength; } @@ -564,10 +597,11 @@ static nuint MultiplyAdd(Span bits, nuint multiplier, nuint addValue) for (int i = 0; i < bits.Length; i++) { ulong p = (ulong)multiplier * bits[i] + carry; - bits[i] = (nuint)(uint)p; - carry = (nuint)(uint)(p >> 32); + bits[i] = (uint)p; + carry = (uint)(p >> 32); } } + return carry; } } @@ -575,7 +609,7 @@ static nuint MultiplyAdd(Span bits, nuint multiplier, nuint addValue) private static string? FormatBigIntegerToHex(bool targetSpan, BigInteger value, char format, int digits, NumberFormatInfo info, Span destination, out int charsWritten, out bool spanSuccess) where TChar : unmanaged, IUtfChar { - Debug.Assert(format == 'x' || format == 'X'); + Debug.Assert(format is 'x' or 'X'); // Get the bytes that make up the BigInteger. byte[]? arrayToReturnToPool = null; @@ -586,6 +620,7 @@ static nuint MultiplyAdd(Span bits, nuint multiplier, nuint addValue) bool success = value.TryWriteBytes(bits, out bytesWrittenOrNeeded); Debug.Assert(success); } + bits = bits.Slice(0, bytesWrittenOrNeeded); var sb = new ValueStringBuilder(stackalloc TChar[128]); // each byte is typically two chars @@ -669,6 +704,7 @@ static nuint MultiplyAdd(Span bits, nuint multiplier, nuint addValue) bool success = value.TryWriteBytes(bytes, out _); Debug.Assert(success); } + bytes = bytes.Slice(0, bytesWrittenOrNeeded); Debug.Assert(!bytes.IsEmpty); @@ -774,20 +810,20 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan 0 ? $"D{digits}" : "D"; } @@ -803,6 +839,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan, Span>(destination), out charsWritten, formatSpan, info); } + return null; } else @@ -814,21 +851,21 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan(BigInteger value, ReadOnlySpan sNegative = value.Sign < 0 ? info.NegativeSignTChar() : default; @@ -866,9 +903,11 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan bits, Span base1E9Buffer, out int base1E9Written) @@ -1053,7 +1092,9 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn out int lowerWritten); if (lowerFromPool != null) + { ArrayPool.Shared.Return(lowerFromPool); + } Debug.Assert(lower1E9Length >= lowerWritten); @@ -1065,7 +1106,9 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn out base1E9Written); if (upperFromPool != null) + { ArrayPool.Shared.Return(upperFromPool); + } base1E9Written += lower1E9Length; } @@ -1083,7 +1126,7 @@ static void Naive(ReadOnlySpan bits, Span base1E9Buffer, out int b // optimizes to a fast multiply-by-reciprocal, avoiding expensive // 128÷64 software division through BigIntegerCalculator.DivRem. // Net effect: (base * 2^32 + hi) * 2^32 + lo = base * 2^64 + limb. - ulong limb = (ulong)bits[iuSrc]; + ulong limb = bits[iuSrc]; NaiveDigit((uint)(limb >> 32), base1E9Buffer, ref base1E9Written); NaiveDigit((uint)limb, base1E9Buffer, ref base1E9Written); } @@ -1097,27 +1140,28 @@ static void Naive(ReadOnlySpan bits, Span base1E9Buffer, out int b [MethodImpl(MethodImplOptions.AggressiveInlining)] static void NaiveDigit(uint digit, Span base1E9Buffer, ref int base1E9Written) { - const uint divisor = (uint)PowersOf1e9.TenPowMaxPartial; + const uint Divisor = (uint)PowersOf1e9.TenPowMaxPartial; uint uCarry = digit; for (int iuDst = 0; iuDst < base1E9Written; iuDst++) { ulong value = ((ulong)(uint)base1E9Buffer[iuDst] << 32) | uCarry; - ulong quo = value / divisor; - base1E9Buffer[iuDst] = (nuint)(uint)(value - quo * divisor); + ulong quo = value / Divisor; + base1E9Buffer[iuDst] = (uint)(value - quo * Divisor); uCarry = (uint)quo; } + while (uCarry != 0) { - base1E9Buffer[base1E9Written++] = (nuint)(uCarry % divisor); - uCarry /= divisor; + base1E9Buffer[base1E9Written++] = uCarry % Divisor; + uCarry /= Divisor; } } } internal readonly ref struct PowersOf1e9 { - // Holds 1000000000^(1<<Holds 1000000000^(1<<<n). private readonly ReadOnlySpan pow1E9; public const nuint TenPowMaxPartial = 1000000000; public const int MaxPartialDigits = 9; @@ -1136,7 +1180,7 @@ public static PowersOf1e9 GetCached(int bufferLength) } nuint[] buffer = new nuint[bufferLength]; - PowersOf1e9 result = new PowersOf1e9((Span)buffer); + PowersOf1e9 result = new((Span)buffer); // Only cache buffers large enough to contain computed powers. // Small buffers (≤ LeadingPowers1E9.Length) aren't populated by @@ -1156,19 +1200,10 @@ public static PowersOf1e9 GetCached(int bufferLength) return result; } - // indexes[i] is pre-calculated length of (10^9)^i - // This means that pow1E9[indexes[i-1]..indexes[i]] equals 1000000000 * (1< + /// Pre-calculated cumulative lengths into . + /// pow1E9[Indexes[i-1]..Indexes[i]] equals 1000000000^(1<<i). + /// private static ReadOnlySpan Indexes => nint.Size == 8 ? Indexes64 : Indexes32; private static ReadOnlySpan Indexes32 => @@ -1243,11 +1278,10 @@ public static PowersOf1e9 GetCached(int bufferLength) 701198819, ]; - // The PowersOf1e9 structure holds 1000000000^(1<< + /// Pre-computed leading powers of 10^9 for small exponents. Entries up to + /// 1000000000^(1<<5) are stored directly because their low limb is never zero. + /// private static ReadOnlySpan LeadingPowers1E9 => nint.Size == 8 ? MemoryMarshal.Cast(LeadingPowers1E9_64) : MemoryMarshal.Cast(LeadingPowers1E9_32); @@ -1349,6 +1383,7 @@ public PowersOf1e9(Span pow1E9) this.pow1E9 = LeadingPowers1E9; return; } + LeadingPowers1E9.CopyTo(pow1E9.Slice(0, LeadingPowers1E9.Length)); this.pow1E9 = pow1E9; @@ -1358,13 +1393,16 @@ public PowersOf1e9(Span pow1E9) { Debug.Assert(2 * src.Length - (Indexes[i + 1] - Indexes[i]) is 0 or 1); if (pow1E9.Length - toExclusive < (src.Length << 1)) + { break; + } + Span dst = pow1E9.Slice(toExclusive, src.Length << 1); BigIntegerCalculator.Square(src, dst); - // When 9*(1<<(i-1)) is not evenly divisible by kcbitNuint, the stored + // When 9*(1<<(i-1)) is not evenly divisible by BitsPerLimb, the stored // power at index i-1 carries a residual factor of 2^r. Squaring doubles - // that residual; if 2r >= kcbitNuint the result has extra trailing zero + // that residual; if 2r >= BitsPerLimb the result has extra trailing zero // limbs that must be stripped to yield the correct stored representation. int shift = OmittedLength(i) - 2 * OmittedLength(i - 1); if (shift > 0) @@ -1387,7 +1425,9 @@ public static int GetBufferSize(int digits, out int maxIndex) int index = maxIndex + 1; int bufferSize; if ((uint)index < (uint)Indexes.Length) + { bufferSize = Indexes[index]; + } else { maxIndex = Indexes.Length - 2; @@ -1399,7 +1439,7 @@ public static int GetBufferSize(int digits, out int maxIndex) public ReadOnlySpan GetSpan(int index) { - // Returns 1E9^(1<> (kcbitNuint*(9*(1<> (BitsPerLimb*(9*(1< GetSpan(int index) public static int OmittedLength(int index) { - // Returns 9*(1< left, int trailingZeroCount, Span powersOfTen = ( bits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : powersOfTenFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : powersOfTenFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); scoped Span powersOfTen2 = bits; int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); @@ -1504,7 +1545,9 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, BigIntegerCalculator.Multiply(left, powersOfTen, bits2); if (powersOfTenFromPool != null) + { ArrayPool.Shared.Return(powersOfTenFromPool); + } if (remainingTrailingZeroCount > 0) { @@ -1524,8 +1567,8 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, for (int i = 0; i < bits2.Length; i++) { ulong p = (ulong)multiplier * bits2[i] + carry; - bits2[i] = (nuint)(uint)p; - carry = (nuint)(uint)(p >> 32); + bits2[i] = (uint)p; + carry = (uint)(p >> 32); } } @@ -1571,13 +1614,11 @@ static virtual bool TryParseSingleBlock(ReadOnlySpan input, out nuint res static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) { Debug.Assert(destination.Length * TParser.DigitsPerBlock == input.Length); - ref TChar lastWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(input), input.Length - TParser.DigitsPerBlock); for (int i = 0; i < destination.Length; i++) { - if (!TParser.TryParseSingleBlock( - MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Subtract(ref lastWholeBlockStart, i * TParser.DigitsPerBlock), TParser.DigitsPerBlock), - out destination[i])) + int blockStart = input.Length - (i + 1) * TParser.DigitsPerBlock; + if (!TParser.TryParseSingleBlock(input.Slice(blockStart, TParser.DigitsPerBlock), out destination[i])) { return false; } @@ -1594,7 +1635,7 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span d public static NumberStyles BlockNumberStyle => NumberStyles.AllowHexSpecifier; - // A valid ASCII hex digit is positive (0-7) if it starts with 00110 + /// Returns all-zero bits if is a valid hex digit ('0'-'7'), or all-one bits otherwise. public static nuint GetSignBitsIfValid(uint ch) => (nuint)(nint)((ch & 0b_1111_1000) == 0b_0011_0000 ? 0 : -1); [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1627,7 +1668,7 @@ public static bool TryParseWholeBlocks(ReadOnlySpan input, Span de public static NumberStyles BlockNumberStyle => NumberStyles.AllowBinarySpecifier; - // Taking the LSB is enough for distinguishing 0/1 + /// Returns all-zero bits if is '0', or all-one bits if '1' (using LSB sign extension). public static nuint GetSignBitsIfValid(uint ch) => (nuint)(nint)(((int)ch << 31) >> 31); } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs index 519cba467cf6ba..15996a2e38c2a2 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -109,8 +109,8 @@ internal static OperationStatus DecodeFromUtfChar(ReadOnlySpan spa where TChar : unmanaged, IUtfChar { return (typeof(TChar) == typeof(Utf8Char)) - ? Rune.DecodeFromUtf8(Unsafe.BitCast, ReadOnlySpan>(span), out result, out elemsConsumed) - : Rune.DecodeFromUtf16(Unsafe.BitCast, ReadOnlySpan>(span), out result, out elemsConsumed); + ? Rune.DecodeFromUtf8(Unsafe.BitCast, ReadOnlySpan>(span), out result, out elemsConsumed) + : Rune.DecodeFromUtf16(Unsafe.BitCast, ReadOnlySpan>(span), out result, out elemsConsumed); } [MethodImpl(MethodImplOptions.AggressiveInlining)] 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 7ac831181b3273..25605931731178 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -22,42 +22,49 @@ public readonly struct BigInteger IBinaryInteger, ISignedNumber { - internal const uint kuMaskHighBit = unchecked((uint)int.MinValue); - internal const int kcbitUint = 32; - internal const int kcbitUlong = 64; + internal const uint UInt32HighBit = 0x80000000; + internal const int BitsPerUInt32 = 32; + internal const int BitsPerUInt64 = 64; internal const int DecimalScaleFactorMask = 0x00FF0000; - // Various APIs only allow up to int.MaxValue bits, so we will restrict ourselves - // to fit within this given our underlying storage representation and the maximum - // array length. This gives us just shy of 256MB as the largest allocation size. - // - // Such a value allows for almost 646,456,974 digits, which is more than large enough - // for typical scenarios. If user code requires more than this, they should likely - // roll their own type that utilizes native memory and other specialized techniques. + /// Splits a shift by int.MinValue into two shifts to avoid negation overflow (-int.MinValue overflows int). + private const int MinIntSplitShift = int.MaxValue - BitsPerUInt32 + 1; + + /// + /// Maximum number of limbs in a . Restricts allocations to ~256MB, + /// supporting almost 646,456,974 digits. + /// internal static int MaxLength => Array.MaxLength / (nint.Size * 8); - // 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 + /// + /// For values int.MinValue < n <= int.MaxValue, the value is stored in + /// and is . + /// For all other values, is +1 or -1 and the magnitude is in . + /// internal readonly int _sign; // Do not rename (binary serialization) internal readonly nuint[]? _bits; // Do not rename (binary serialization) - // We have to make a choice of how to represent int.MinValue. This is the one - // value that fits in an int, but whose negation does not fit in an int. - // We choose to use a large representation, so we're symmetric with respect to negation. - private static readonly BigInteger s_bnMinInt = new BigInteger(-1, new nuint[] { kuMaskHighBit }); - private static readonly BigInteger s_bnOneInt = new BigInteger(1); - private static readonly BigInteger s_bnZeroInt = new BigInteger(0); - private static readonly BigInteger s_bnMinusOneInt = new BigInteger(-1); + /// + /// Cached representation of as a BigInteger. Uses the large + /// representation (sign=-1, bits=[0x80000000]) so that negation is symmetric. + /// + private static readonly BigInteger s_int32MinValue = new(-1, [UInt32HighBit]); + private static readonly BigInteger s_one = new(1); + private static readonly BigInteger s_zero = new(0); + private static readonly BigInteger s_minusOne = new(-1); public BigInteger(int value) { if (value == int.MinValue) - this = s_bnMinInt; + { + this = s_int32MinValue; + } else { _sign = value; _bits = null; } + AssertValid(); } @@ -72,28 +79,29 @@ public BigInteger(uint value) else { _sign = +1; - _bits = [(nuint)value]; + _bits = [value]; } + AssertValid(); } public BigInteger(long value) { - if (int.MinValue < value && value <= int.MaxValue) + if (value is > int.MinValue and <= int.MaxValue) { _sign = (int)value; _bits = null; } else if (value == int.MinValue) { - this = s_bnMinInt; + this = s_int32MinValue; } else { ulong x; if (value < 0) { - x = unchecked((ulong)-value); + x = (ulong)-value; _sign = -1; } else @@ -102,20 +110,10 @@ public BigInteger(long value) _sign = +1; } - if (nint.Size == 8) - { - _bits = [(nuint)x]; - } - else if (x <= uint.MaxValue) - { - _bits = [(nuint)(uint)x]; - } - else - { - _bits = new nuint[2]; - _bits[0] = unchecked((nuint)(uint)x); - _bits[1] = (nuint)(uint)(x >> kcbitUint); - } + _bits = + nint.Size == 8 ? [(nuint)x] : + x <= uint.MaxValue ? [((uint)x)] : + [(uint)x, (uint)(x >> BitsPerUInt32)]; } AssertValid(); @@ -132,20 +130,10 @@ public BigInteger(ulong value) else { _sign = +1; - if (nint.Size == 8) - { - _bits = [(nuint)value]; - } - else if (value <= uint.MaxValue) - { - _bits = [(nuint)(uint)value]; - } - else - { - _bits = new nuint[2]; - _bits[0] = unchecked((nuint)(uint)value); - _bits[1] = (nuint)(uint)(value >> kcbitUint); - } + _bits = + nint.Size == 8 ? [(nuint)value] : + value <= uint.MaxValue ? [((uint)value)] : + [(uint)value, (uint)(value >> BitsPerUInt32)]; } AssertValid(); @@ -159,23 +147,14 @@ public BigInteger(double value) { if (!double.IsFinite(value)) { - if (double.IsInfinity(value)) - { - throw new OverflowException(SR.Overflow_BigIntInfinity); - } - else // NaN - { - throw new OverflowException(SR.Overflow_NotANumber); - } + throw new OverflowException(double.IsInfinity(value) ? SR.Overflow_BigIntInfinity : SR.Overflow_NotANumber); } _sign = 0; _bits = null; - int sign, exp; - ulong man; - NumericsHelpers.GetDoubleParts(value, out sign, out exp, out man, out _); - Debug.Assert(sign == +1 || sign == -1); + NumericsHelpers.GetDoubleParts(value, out int sign, out int exp, out ulong man, out _); + Debug.Assert(sign is +1 or -1); if (man == 0) { @@ -188,20 +167,27 @@ public BigInteger(double value) if (exp <= 0) { - if (exp <= -kcbitUlong) + if (exp <= -BitsPerUInt64) { this = Zero; return; } + this = man >> -exp; if (sign < 0) + { _sign = -_sign; + } } else if (exp <= 11) { + // 53-bit mantissa shifted left by at most 11 fits in 64 bits (53 + 11 = 64), + // so the result fits in a single inline value without needing _bits. this = man << exp; if (sign < 0) + { _sign = -_sign; + } } else { @@ -225,17 +211,22 @@ public BigInteger(double value) _bits = new nuint[cu + 1]; _bits[cu] = (nuint)(man >> cbit); if (cbit > 0) + { _bits[cu - 1] = (nuint)(man << (64 - cbit)); + } } else { // 32-bit: mantissa (64 bits) spans 2-3 nuint limbs _bits = new nuint[cu + 2]; - _bits[cu + 1] = (nuint)(uint)(man >> (cbit + kcbitUint)); - _bits[cu] = unchecked((nuint)(uint)(man >> cbit)); + _bits[cu + 1] = (uint)(man >> (cbit + BitsPerUInt32)); + _bits[cu] = (uint)(man >> cbit); if (cbit > 0) - _bits[cu - 1] = unchecked((nuint)(uint)man) << (kcbitUint - cbit); + { + _bits[cu - 1] = (nuint)(uint)man << (BitsPerUInt32 - cbit); + } } + _sign = sign; } @@ -250,20 +241,19 @@ 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--; + const int SignMask = int.MinValue; + int size = bits[..3].LastIndexOfAnyExcept(0) + 1; + if (size == 0) { - this = s_bnZeroInt; + this = s_zero; } else if (size == 1 && bits[0] > 0) { // 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] & signMask) != 0) ? -1 : +1; + _sign *= ((bits[3] & SignMask) != 0) ? -1 : +1; _bits = null; } else @@ -273,29 +263,31 @@ public BigInteger(decimal value) // 64-bit: pack up to 3 uint-sized values into 1-2 nuint limbs int nuintSize = (size + 1) / 2; _bits = new nuint[nuintSize]; - unchecked + _bits[0] = (uint)bits[0]; + if (size > 1) { - _bits[0] = (nuint)(uint)bits[0]; - if (size > 1) - _bits[0] |= (nuint)(uint)bits[1] << 32; + _bits[0] |= (nuint)(uint)bits[1] << 32; if (size > 2) - _bits[nuintSize - 1] = (nuint)(uint)bits[2]; + { + _bits[nuintSize - 1] = (uint)bits[2]; + } } } else { _bits = new nuint[size]; - unchecked + _bits[0] = (uint)bits[0]; + if (size > 1) { - _bits[0] = (nuint)(uint)bits[0]; - if (size > 1) - _bits[1] = (nuint)(uint)bits[1]; + _bits[1] = (uint)bits[1]; if (size > 2) - _bits[2] = (nuint)(uint)bits[2]; + { + _bits[2] = (uint)bits[2]; + } } } - _sign = ((bits[3] & signMask) != 0) ? -1 : +1; + _sign = ((bits[3] & SignMask) != 0) ? -1 : +1; // Canonicalize: single-limb values that fit in int should be stored inline if (_bits is { Length: 1 } && _bits[0] <= int.MaxValue) @@ -304,6 +296,7 @@ public BigInteger(decimal value) _bits = null; } } + AssertValid(); } @@ -332,26 +325,13 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE // Try to conserve space as much as possible by checking for wasted leading byte[] entries if (isBigEndian) { - int offset = 1; - - while (offset < byteCount && value[offset] == 0) - { - offset++; - } - - value = value.Slice(offset); + int offset = value.Slice(1).IndexOfAnyExcept((byte)0); + value = value.Slice(offset < 0 ? byteCount : offset + 1); byteCount = value.Length; } else { - byteCount -= 2; - - while (byteCount >= 0 && value[byteCount] == 0) - { - byteCount--; - } - - byteCount++; + byteCount = value[..^1].LastIndexOfAnyExcept((byte)0) + 1; } } } @@ -392,18 +372,18 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE if (_sign < 0 && !isNegative) { // int overflow: unsigned value overflows into the int sign bit - _bits = new nuint[1] { unchecked((nuint)(uint)_sign) }; + _bits = [(uint)_sign]; _sign = +1; } + if (_sign == int.MinValue) { - this = s_bnMinInt; + this = s_int32MinValue; } } else { - int bytesPerLimb = nint.Size; - int wholeLimbCount = Math.DivRem(byteCount, bytesPerLimb, out int unalignedBytes); + int wholeLimbCount = Math.DivRem(byteCount, nint.Size, out int unalignedBytes); nuint[] val = new nuint[wholeLimbCount + (unalignedBytes == 0 ? 0 : 1)]; // Copy the bytes to the nuint array, apart from those which represent the @@ -425,24 +405,15 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE { // The bytes parameter is in little-endian byte order. // We can just copy the bytes directly into the nuint array. - - value.Slice(0, wholeLimbCount * bytesPerLimb).CopyTo(MemoryMarshal.AsBytes(val.AsSpan())); + value.Slice(0, wholeLimbCount * nint.Size).CopyTo(MemoryMarshal.AsBytes(val.AsSpan())); } // In both of the above cases on big-endian architecture, we need to perform // an endianness swap on the resulting limbs. if (!BitConverter.IsLittleEndian) { - if (nint.Size == 8) - { - Span ulongSpan = MemoryMarshal.Cast(val.AsSpan(0, wholeLimbCount)); - BinaryPrimitives.ReverseEndianness((ReadOnlySpan)ulongSpan, ulongSpan); - } - else - { - Span uintSpan = MemoryMarshal.Cast(val.AsSpan(0, wholeLimbCount)); - BinaryPrimitives.ReverseEndianness((ReadOnlySpan)uintSpan, uintSpan); - } + Span limbSpan = val.AsSpan(0, wholeLimbCount); + BinaryPrimitives.ReverseEndianness(limbSpan, limbSpan); } // Copy the last limb specially if it's not aligned @@ -476,22 +447,20 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE NumericsHelpers.DangerousMakeTwosComplement(val); // Mutates val // Pack _bits to remove any wasted space after the twos complement - int len = val.Length - 1; - while (len >= 0 && val[len] == 0) len--; - len++; + int len = val.AsSpan().LastIndexOfAnyExcept((nuint)0) + 1; - nuint intMinMagnitude = unchecked((nuint)(uint)int.MinValue); + nuint intMinMagnitude = UInt32HighBit; if (len == 1) { if (val[0] == 1) // abs(-1) { - this = s_bnMinusOneInt; + this = s_minusOne; return; } else if (val[0] == intMinMagnitude) // abs(int.MinValue) { - this = s_bnMinInt; + this = s_int32MinValue; return; } else if (val[0] < intMinMagnitude) // fits in int as negative @@ -506,8 +475,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE if (len != val.Length) { _sign = -1; - _bits = new nuint[len]; - Array.Copy(val, _bits, len); + _bits = val.AsSpan(0, len).ToArray(); } else { @@ -521,6 +489,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE _bits = val; } } + AssertValid(); } @@ -560,7 +529,7 @@ internal BigInteger(ReadOnlySpan value, bool negative) ThrowHelper.ThrowOverflowException(); } - nuint intMinMagnitude = unchecked((nuint)(uint)int.MinValue); + nuint intMinMagnitude = UInt32HighBit; if (value.Length == 0) { @@ -574,13 +543,14 @@ internal BigInteger(ReadOnlySpan value, bool negative) else if (value.Length == 1 && negative && value[0] == intMinMagnitude) { // Although int.MinValue fits in _sign, we represent this case differently for negate - this = s_bnMinInt; + this = s_int32MinValue; } else { _sign = negative ? -1 : +1; - _bits = value.Length == 1 ? [value[0]] : value.ToArray(); + _bits = value.ToArray(); } + AssertValid(); } @@ -603,6 +573,7 @@ private BigInteger(Span value) // We need to preserve the sign bit length++; } + Debug.Assert((nint)value[length - 1] < 0); } else @@ -610,6 +581,7 @@ private BigInteger(Span value) isNegative = false; length = value.LastIndexOfAnyExcept((nuint)0) + 1; } + value = value[..length]; if (value.Length > MaxLength) @@ -617,12 +589,12 @@ private BigInteger(Span value) ThrowHelper.ThrowOverflowException(); } - nuint intMinMagnitude = unchecked((nuint)(uint)int.MinValue); + nuint intMinMagnitude = UInt32HighBit; if (value.Length == 0) { // 0 - this = s_bnZeroInt; + this = s_zero; } else if (value.Length == 1) { @@ -631,12 +603,12 @@ private BigInteger(Span value) if (value[0] == nuint.MaxValue) { // -1 - this = s_bnMinusOneInt; + this = s_minusOne; } else if (nint.Size == 4 && value[0] == intMinMagnitude) { // int.MinValue - this = s_bnMinInt; + this = s_int32MinValue; } else { @@ -692,45 +664,45 @@ private BigInteger(Span value) { _sign = +1; } + _bits = value.ToArray(); } + AssertValid(); } - public static BigInteger Zero { get { return s_bnZeroInt; } } + public static BigInteger Zero => s_zero; - public static BigInteger One { get { return s_bnOneInt; } } + public static BigInteger One => s_one; - public static BigInteger MinusOne { get { return s_bnMinusOneInt; } } + public static BigInteger MinusOne => s_minusOne; public bool IsPowerOfTwo { get { - AssertValid(); - - if (_bits == null) + if (_bits is null) + { return BitOperations.IsPow2(_sign); + } if (_sign != 1) + { return false; + } int iu = _bits.Length - 1; - return BitOperations.IsPow2(_bits[iu]) && !_bits.AsSpan(0, iu).ContainsAnyExcept((nuint)0); } } - public bool IsZero { get { AssertValid(); return _sign == 0; } } + public bool IsZero => _sign == 0; - public bool IsOne { get { AssertValid(); return _sign == 1 && _bits == null; } } + public bool IsOne => _sign == 1 && _bits is null; - public bool IsEven { get { AssertValid(); return _bits == null ? (_sign & 1) == 0 : (_bits[0] & 1) == 0; } } + public bool IsEven => _bits is null ? (_sign & 1) == 0 : (_bits[0] & 1) == 0; - public int Sign - { - get { AssertValid(); return (_sign >> 31) - (-_sign >> 31); } - } + public int Sign => (_sign >> 31) - (-_sign >> 31); public static BigInteger Parse(string value) { @@ -800,7 +772,6 @@ public static int Compare(BigInteger left, BigInteger right) public static BigInteger Abs(BigInteger value) { - value.AssertValid(); return new BigInteger((int)NumericsHelpers.Abs(value._sign), value._bits); } @@ -831,11 +802,8 @@ public static BigInteger Remainder(BigInteger dividend, BigInteger divisor) public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out BigInteger remainder) { - dividend.AssertValid(); - divisor.AssertValid(); - - bool trivialDividend = dividend._bits == null; - bool trivialDivisor = divisor._bits == null; + bool trivialDividend = dividend._bits is null; + bool trivialDivisor = divisor._bits is null; if (trivialDividend && trivialDivisor) { @@ -848,70 +816,73 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big if (trivialDividend) { - // The divisor is non-trivial - // and therefore the bigger one + // The divisor is non-trivial and therefore the bigger one. remainder = dividend; - return s_bnZeroInt; + return s_zero; } - Debug.Assert(dividend._bits != null); + Debug.Assert(dividend._bits is not null); if (trivialDivisor) { - nuint rest; - nuint[]? bitsFromPool = null; int size = dividend._bits.Length; Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[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); + BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient, out nuint rest); remainder = dividend._sign < 0 ? -1 * (long)rest : (long)rest; return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); } finally { - if (bitsFromPool != null) + if (bitsFromPool is not null) + { ArrayPool.Shared.Return(bitsFromPool); + } } } - Debug.Assert(divisor._bits != null); + Debug.Assert(divisor._bits is not null); if (dividend._bits.Length < divisor._bits.Length) { remainder = dividend; - return s_bnZeroInt; + return s_zero; } else { nuint[]? remainderFromPool = null; int size = dividend._bits.Length; Span rest = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : remainderFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : remainderFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); nuint[]? quotientFromPool = null; size = dividend._bits.Length - divisor._bits.Length + 1; Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[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)); + remainder = new(rest, dividend._sign < 0); + BigInteger result = new(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - if (remainderFromPool != null) + if (remainderFromPool is not null) + { ArrayPool.Shared.Return(remainderFromPool); + } - if (quotientFromPool != null) + if (quotientFromPool is not null) + { ArrayPool.Shared.Return(quotientFromPool); + } return result; } @@ -930,13 +901,24 @@ public static double Log(BigInteger value) public static double Log(BigInteger value, double baseValue) { if (value._sign < 0 || baseValue == 1.0D) + { return double.NaN; + } + if (baseValue == double.PositiveInfinity) + { return value.IsOne ? 0.0D : double.NaN; + } + if (baseValue == 0.0D && !value.IsOne) + { return double.NaN; - if (value._bits == null) - return Math.Log((double)value._sign, baseValue); + } + + if (value._bits is null) + { + return Math.Log(value._sign, baseValue); + } ulong h, m, l; int c; @@ -945,8 +927,8 @@ public static double Log(BigInteger value, double baseValue) if (nint.Size == 8) { - h = (ulong)value._bits[value._bits.Length - 1]; - m = value._bits.Length > 1 ? (ulong)value._bits[value._bits.Length - 2] : 0; + h = value._bits[value._bits.Length - 1]; + m = value._bits.Length > 1 ? value._bits[value._bits.Length - 2] : 0; c = BitOperations.LeadingZeroCount(h); b = (long)value._bits.Length * 64 - c; @@ -980,11 +962,8 @@ public static double Log10(BigInteger value) public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right) { - left.AssertValid(); - right.AssertValid(); - - bool trivialLeft = left._bits == null; - bool trivialRight = right._bits == null; + bool trivialLeft = left._bits is null; + bool trivialRight = right._bits is null; if (trivialLeft && trivialRight) { @@ -993,7 +972,7 @@ public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right if (trivialLeft) { - Debug.Assert(right._bits != null); + Debug.Assert(right._bits is not null); return left._sign != 0 ? BigIntegerCalculator.Gcd(right._bits, NumericsHelpers.Abs(left._sign)) : new BigInteger(+1, right._bits); @@ -1001,22 +980,17 @@ public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right if (trivialRight) { - Debug.Assert(left._bits != null); + Debug.Assert(left._bits is not null); return right._sign != 0 ? BigIntegerCalculator.Gcd(left._bits, NumericsHelpers.Abs(right._sign)) : new BigInteger(+1, left._bits); } - Debug.Assert(left._bits != null && right._bits != null); + Debug.Assert(left._bits is not null && right._bits is not null); - if (BigIntegerCalculator.Compare(left._bits, right._bits) < 0) - { - return GreatestCommonDivisor(right._bits, left._bits); - } - else - { - return GreatestCommonDivisor(left._bits, right._bits); - } + return BigIntegerCalculator.Compare(left._bits, right._bits) < 0 + ? GreatestCommonDivisor(right._bits, left._bits) + : GreatestCommonDivisor(left._bits, right._bits); } private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, ReadOnlySpan rightBits) @@ -1035,8 +1009,8 @@ private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, Re else if (nint.Size == 4 && rightBits.Length == 2) { Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); BigIntegerCalculator.Remainder(leftBits, rightBits, bits); @@ -1048,53 +1022,47 @@ private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, Re else { Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); BigIntegerCalculator.Gcd(leftBits, rightBits, bits); result = new BigInteger(bits, negative: false); } - if (bitsFromPool != null) + if (bitsFromPool is not null) + { ArrayPool.Shared.Return(bitsFromPool); + } return result; } public static BigInteger Max(BigInteger left, BigInteger right) { - if (left.CompareTo(right) < 0) - return right; - return left; + return left.CompareTo(right) < 0 ? right : left; } public static BigInteger Min(BigInteger left, BigInteger right) { - if (left.CompareTo(right) <= 0) - return left; - return right; + return left.CompareTo(right) <= 0 ? left : right; } public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigInteger modulus) { ArgumentOutOfRangeException.ThrowIfNegative(exponent.Sign, nameof(exponent)); - value.AssertValid(); - exponent.AssertValid(); - modulus.AssertValid(); - - bool trivialValue = value._bits == null; - bool trivialExponent = exponent._bits == null; - bool trivialModulus = modulus._bits == null; + bool trivialValue = value._bits is null; + bool trivialExponent = exponent._bits is null; + bool trivialModulus = modulus._bits is null; BigInteger result; if (trivialModulus) { nuint bitsResult = trivialValue && trivialExponent ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) : - trivialValue ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), exponent._bits!, NumericsHelpers.Abs(modulus._sign)) : - trivialExponent ? BigIntegerCalculator.Pow(value._bits!, NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) : - BigIntegerCalculator.Pow(value._bits!, exponent._bits!, NumericsHelpers.Abs(modulus._sign)); + trivialValue ? BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), exponent._bits!, NumericsHelpers.Abs(modulus._sign)) : + trivialExponent ? BigIntegerCalculator.Pow(value._bits!, NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) : + BigIntegerCalculator.Pow(value._bits!, exponent._bits!, NumericsHelpers.Abs(modulus._sign)); result = value._sign < 0 && !exponent.IsEven ? -1 * (long)bitsResult : (long)bitsResult; } @@ -1103,8 +1071,8 @@ public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigIntege int size = (modulus._bits?.Length ?? 1) << 1; nuint[]? bitsFromPool = null; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); bits.Clear(); if (trivialValue) { @@ -1128,8 +1096,10 @@ public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigIntege result = new BigInteger(bits, value._sign < 0 && !exponent.IsEven); - if (bitsFromPool != null) + if (bitsFromPool is not null) + { ArrayPool.Shared.Return(bitsFromPool); + } } return result; @@ -1139,14 +1109,17 @@ public static BigInteger Pow(BigInteger value, int exponent) { ArgumentOutOfRangeException.ThrowIfNegative(exponent); - value.AssertValid(); - if (exponent == 0) - return s_bnOneInt; + { + return s_one; + } + if (exponent == 1) + { return value; + } - bool trivialValue = value._bits == null; + bool trivialValue = value._bits is null; nuint power = NumericsHelpers.Abs(exponent); nuint[]? bitsFromPool = null; @@ -1155,16 +1128,24 @@ public static BigInteger Pow(BigInteger value, int exponent) if (trivialValue) { if (value._sign == 1) + { return value; + } + if (value._sign == -1) - return (exponent & 1) != 0 ? value : s_bnOneInt; + { + return (exponent & 1) != 0 ? value : s_one; + } + if (value._sign == 0) + { return value; + } int size = BigIntegerCalculator.PowBound(power, 1); Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); bits.Clear(); BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), power, bits); @@ -1174,26 +1155,28 @@ public static BigInteger Pow(BigInteger value, int exponent) { int size = BigIntegerCalculator.PowBound(power, value._bits!.Length); Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[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); } - if (bitsFromPool != null) + if (bitsFromPool is not null) + { ArrayPool.Shared.Return(bitsFromPool); + } return result; } public override int GetHashCode() { - AssertValid(); - if (_bits is null) + { return _sign; + } HashCode hash = default; hash.AddBytes(MemoryMarshal.AsBytes(_bits.AsSpan())); @@ -1203,130 +1186,117 @@ public override int GetHashCode() public override bool Equals([NotNullWhen(true)] object? obj) { - AssertValid(); - return obj is BigInteger other && Equals(other); } public bool Equals(long other) { - AssertValid(); - - if (_bits == null) + if (_bits is null) + { return _sign == other; + } int cu; int maxLimbs = 8 / nint.Size; - if (((long)_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) + if ((_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) + { return false; + } ulong uu = other < 0 ? (ulong)-other : (ulong)other; - if (nint.Size == 8) - { - return (ulong)_bits[0] == uu; - } - else - { - if (cu == 1) - return (uint)_bits[0] == uu; - return ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == uu; - } + return + nint.Size == 8 ? _bits[0] == uu : + cu == 1 ? (uint)_bits[0] == uu : + ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == uu; } [CLSCompliant(false)] public bool Equals(ulong other) { - AssertValid(); - if (_sign < 0) + { return false; - if (_bits == null) + } + + if (_bits is null) + { return (ulong)_sign == other; + } int cu = _bits.Length; int maxLimbs = 8 / nint.Size; if (cu > maxLimbs) - return false; - - if (nint.Size == 8) - { - return (ulong)_bits[0] == other; - } - else { - if (cu == 1) - return (uint)_bits[0] == other; - return ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == other; + return false; } + + return + nint.Size == 8 ? _bits[0] == other : + cu == 1 ? (uint)_bits[0] == other : + ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == other; } public bool Equals(BigInteger other) { - AssertValid(); - other.AssertValid(); - return _sign == other._sign && _bits.AsSpan().SequenceEqual(other._bits); } public int CompareTo(long other) { - AssertValid(); - - if (_bits == null) + if (_bits is null) + { return ((long)_sign).CompareTo(other); + } int cu; int maxLimbs = 8 / nint.Size; - if (((long)_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) + if ((_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) + { return _sign; + } ulong uu = other < 0 ? (ulong)-other : (ulong)other; ulong uuTmp; - if (nint.Size == 8) - { - uuTmp = (ulong)_bits[0]; - } - else - { - uuTmp = cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : (uint)_bits[0]; - } + uuTmp = + nint.Size == 8 ? _bits[0] : + cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : + (uint)_bits[0]; + return _sign * uuTmp.CompareTo(uu); } [CLSCompliant(false)] public int CompareTo(ulong other) { - AssertValid(); - if (_sign < 0) + { return -1; - if (_bits == null) + } + + if (_bits is null) + { return ((ulong)(uint)_sign).CompareTo(other); + } int cu = _bits.Length; int maxLimbs = 8 / nint.Size; if (cu > maxLimbs) - return +1; - - ulong uuTmp; - if (nint.Size == 8) - { - uuTmp = (ulong)_bits[0]; - } - else { - uuTmp = cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : (uint)_bits[0]; + return +1; } + + ulong uuTmp = + nint.Size == 8 ? _bits[0] : + cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : + (uint)_bits[0]; + return uuTmp.CompareTo(other); } public int CompareTo(BigInteger other) { - AssertValid(); - other.AssertValid(); - if ((_sign ^ other._sign) < 0) { // Different signs, so the comparison is easy. @@ -1334,15 +1304,19 @@ public int CompareTo(BigInteger other) } // Same signs - if (_bits == null) + if (_bits is null) { - if (other._bits == null) - return _sign < other._sign ? -1 : _sign > other._sign ? +1 : 0; - return -(int)other._sign; + return + other._bits is not null ? -other._sign : + _sign < other._sign ? -1 : + _sign > other._sign ? +1 : + 0; } - if (other._bits == null) - return (int)_sign; + if (other._bits is null) + { + return _sign; + } int bitsResult = BigIntegerCalculator.Compare(_bits, other._bits); return _sign < 0 ? -bitsResult : bitsResult; @@ -1350,11 +1324,10 @@ public int CompareTo(BigInteger other) public int CompareTo(object? obj) { - if (obj == null) - return 1; - if (obj is not BigInteger bigInt) + return + obj is null ? 1 : + obj is BigInteger bigInt ? CompareTo(bigInt) : throw new ArgumentException(SR.Argument_MustBeBigInt, nameof(obj)); - return CompareTo(bigInt); } /// @@ -1421,28 +1394,29 @@ public byte[] ToByteArray(bool isUnsigned = false, bool isBigEndian = false) public bool TryWriteBytes(Span destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false) { bytesWritten = 0; - if (TryGetBytes(GetBytesMode.Span, destination, isUnsigned, isBigEndian, ref bytesWritten) == null) + if (TryGetBytes(GetBytesMode.Span, destination, isUnsigned, isBigEndian, ref bytesWritten) is null) { bytesWritten = 0; return false; } + return true; } internal bool TryWriteOrCountBytes(Span destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false) { bytesWritten = 0; - return TryGetBytes(GetBytesMode.Span, destination, isUnsigned, isBigEndian, ref bytesWritten) != null; + return TryGetBytes(GetBytesMode.Span, destination, isUnsigned, isBigEndian, ref bytesWritten) is not null; } /// Gets the number of bytes that will be output by and . /// The number of bytes. public int GetByteCount(bool isUnsigned = false) { - int count = 0; // Big or Little Endian doesn't matter for the byte count. + int count = 0; const bool IsBigEndian = false; - TryGetBytes(GetBytesMode.Count, default(Span), isUnsigned, IsBigEndian, ref count); + TryGetBytes(GetBytesMode.Count, default, isUnsigned, IsBigEndian, ref count); return count; } @@ -1472,7 +1446,7 @@ private enum GetBytesMode /// If is true and is negative. private byte[]? TryGetBytes(GetBytesMode mode, Span destination, bool isUnsigned, bool isBigEndian, ref int bytesWritten) { - Debug.Assert(mode == GetBytesMode.AllocateArray || mode == GetBytesMode.Count || mode == GetBytesMode.Span, $"Unexpected mode {mode}."); + Debug.Assert(mode is GetBytesMode.AllocateArray or GetBytesMode.Count or GetBytesMode.Span, $"Unexpected mode {mode}."); Debug.Assert(mode == GetBytesMode.Span || destination.IsEmpty, $"If we're not in span mode, we shouldn't have been passed a destination."); int sign = _sign; @@ -1481,10 +1455,12 @@ private enum GetBytesMode switch (mode) { case GetBytesMode.AllocateArray: - return new byte[] { 0 }; + return [0]; + case GetBytesMode.Count: bytesWritten = 1; return null; + default: // case GetBytesMode.Span: bytesWritten = 1; if (destination.Length != 0) @@ -1492,6 +1468,7 @@ private enum GetBytesMode destination[0] = 0; return Array.Empty(); } + return null; } } @@ -1506,10 +1483,10 @@ private enum GetBytesMode int nonZeroLimbIndex = 0; nuint highLimb; nuint[]? bits = _bits; - if (bits == null) + if (bits is null) { highByte = (byte)((sign < 0) ? 0xff : 0x00); - highLimb = unchecked((nuint)sign); + highLimb = (nuint)sign; } else if (sign == -1) { @@ -1526,10 +1503,7 @@ private enum GetBytesMode // would be encoded as _bits = null and _sign = 0. Debug.Assert(bits.Length > 0); Debug.Assert(bits[bits.Length - 1] != 0); - while (bits[nonZeroLimbIndex] == 0) - { - nonZeroLimbIndex++; - } + nonZeroLimbIndex = ((ReadOnlySpan)bits).IndexOfAnyExcept((nuint)0); highLimb = ~bits[bits.Length - 1]; if (bits.Length - 1 == nonZeroLimbIndex) @@ -1562,12 +1536,13 @@ private enum GetBytesMode int lzc = BitOperations.LeadingZeroCount(~highLimb); msbIndex = Math.Max(0, bytesPerLimb - 1 - (lzc / 8)); } - msb = unchecked((byte)(highLimb >> (msbIndex * 8))); + + msb = (byte)(highLimb >> (msbIndex * 8)); // Ensure high bit is 0 if positive, 1 if negative bool needExtraByte = (msb & 0x80) != (highByte & 0x80) && !isUnsigned; int length = msbIndex + 1 + (needExtraByte ? 1 : 0); - if (bits != null) + if (bits is not null) { length = checked(bytesPerLimb * (bits.Length - 1) + length); } @@ -1578,15 +1553,18 @@ private enum GetBytesMode case GetBytesMode.AllocateArray: destination = array = new byte[length]; break; + case GetBytesMode.Count: bytesWritten = length; return null; + default: // case GetBytesMode.Span: bytesWritten = length; if (destination.Length < length) { return null; } + array = Array.Empty(); break; } @@ -1594,7 +1572,7 @@ private enum GetBytesMode int curByte = isBigEndian ? length : 0; int increment = isBigEndian ? -1 : 1; - if (bits != null) + if (bits is not null) { if (BitConverter.IsLittleEndian && sign > 0) { @@ -1624,24 +1602,18 @@ private enum GetBytesMode limb = ~limb; if (i <= nonZeroLimbIndex) { - limb = unchecked(limb + 1); + limb++; } } if (isBigEndian) { curByte -= bytesPerLimb; - if (nint.Size == 8) - BinaryPrimitives.WriteUInt64BigEndian(destination.Slice(curByte), (ulong)limb); - else - BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(curByte), (uint)limb); + BinaryPrimitives.WriteUIntPtrBigEndian(destination.Slice(curByte), limb); } else { - if (nint.Size == 8) - BinaryPrimitives.WriteUInt64LittleEndian(destination.Slice(curByte), (ulong)limb); - else - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(curByte), (uint)limb); + BinaryPrimitives.WriteUIntPtrLittleEndian(destination.Slice(curByte), limb); curByte += bytesPerLimb; } } @@ -1653,18 +1625,20 @@ private enum GetBytesMode curByte--; } - Debug.Assert(msbIndex >= 0 && msbIndex < bytesPerLimb); // Write significant bytes of the high limb + Debug.Assert(msbIndex >= 0 && msbIndex < bytesPerLimb); for (int byteIdx = 0; byteIdx <= msbIndex; byteIdx++) { - destination[curByte] = unchecked((byte)(highLimb >> (byteIdx * 8))); + destination[curByte] = (byte)(highLimb >> (byteIdx * 8)); if (byteIdx < msbIndex) + { curByte += increment; + } } // Assert we're big endian, or little endian consistency holds. - Debug.Assert(isBigEndian || (!needExtraByte && curByte == length - 1) || (needExtraByte && curByte == length - 2)); // Assert we're little endian, or big endian consistency holds. + Debug.Assert(isBigEndian || (!needExtraByte && curByte == length - 1) || (needExtraByte && curByte == length - 2)); Debug.Assert(!isBigEndian || (!needExtraByte && curByte == 0) || (needExtraByte && curByte == 1)); if (needExtraByte) @@ -1690,7 +1664,7 @@ private int WriteTo(Span buffer) if (_bits is null) { - buffer[0] = unchecked((nuint)_sign); + buffer[0] = (nuint)_sign; highLimb = (_sign < 0) ? nuint.MaxValue : 0; } else @@ -1703,15 +1677,13 @@ private int WriteTo(Span buffer) highLimb = nuint.MaxValue; } else + { highLimb = 0; + } } // Find highest significant limb and ensure high bit is 0 if positive, 1 if negative - int msb = buffer.Length - 2; - while (msb > 0 && buffer[msb] == highLimb) - { - msb--; - } + int msb = Math.Max(0, buffer[..^1].LastIndexOfAnyExcept(highLimb)); // Ensure high bit is 0 if positive, 1 if negative nuint highBitMask = (nuint)1 << (nint.Size * 8 - 1); @@ -1770,25 +1742,26 @@ private string DebuggerDisplay // Represent L as `k * 10^i`, then `x = L * 2^n = k * 10^(i + (n * log10(2)))` // Let `m = n * log10(2)`, the final result would be `x = (k * 10^(m - [m])) * 10^(i+[m])` - const double log10Of2 = 0.3010299956639812; // Log10(2) + const double Log10Of2 = 0.3010299956639812; // Log10(2) int bitsPerLimb = nint.Size * 8; ulong highBits; if (nint.Size == 8) { - highBits = (ulong)_bits[^1]; + highBits = _bits[^1]; } else { - highBits = ((ulong)_bits[^1] << kcbitUint) + (uint)_bits[^2]; + highBits = ((ulong)_bits[^1] << BitsPerUInt32) + (uint)_bits[^2]; } + double lowBitsCount = _bits.Length - (nint.Size == 8 ? 1 : 2); - double exponentLow = lowBitsCount * bitsPerLimb * log10Of2; + double exponentLow = lowBitsCount * bitsPerLimb * Log10Of2; // Max possible length of _bits is int.MaxValue of bytes, // thus max possible value of BigInteger is 2^(8*Array.MaxLength)-1 which is larger than 10^(2^33) // Use long to avoid potential overflow long exponent = (long)exponentLow; - double significand = (double)highBits * Math.Pow(10, exponentLow - exponent); + double significand = highBits * Math.Pow(10, exponentLow - exponent); // scale significand to [1, 10) double log10 = Math.Log10(significand); @@ -1842,8 +1815,8 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOn int size = rightBits.Length + 1; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Add(rightBits, NumericsHelpers.Abs(leftSign), bits); result = new BigInteger(bits, leftSign < 0); @@ -1854,8 +1827,8 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOn int size = leftBits.Length + 1; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Add(leftBits, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, leftSign < 0); @@ -1866,8 +1839,8 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOn int size = rightBits.Length + 1; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Add(rightBits, leftBits, bits); result = new BigInteger(bits, leftSign < 0); @@ -1878,30 +1851,31 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOn int size = leftBits.Length + 1; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Add(leftBits, rightBits, bits); result = new BigInteger(bits, leftSign < 0); } - if (bitsFromPool != null) + if (bitsFromPool is not null) + { ArrayPool.Shared.Return(bitsFromPool); + } return result; } public static BigInteger operator -(BigInteger left, BigInteger right) { - left.AssertValid(); - right.AssertValid(); - - if (left._bits == null && right._bits == null) + if (left._bits is null && right._bits is null) + { return (long)left._sign - right._sign; + } - if (left._sign < 0 != right._sign < 0) - return Add(left._bits, left._sign, right._bits, -1 * right._sign); - return Subtract(left._bits, left._sign, right._bits, right._sign); + return left._sign < 0 != right._sign < 0 + ? Add(left._bits, left._sign, right._bits, -1 * right._sign) + : Subtract(left._bits, left._sign, right._bits, right._sign); } private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) @@ -1920,8 +1894,8 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, R int size = rightBits.Length; Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Subtract(rightBits, NumericsHelpers.Abs(leftSign), bits); result = new BigInteger(bits, leftSign >= 0); @@ -1932,8 +1906,8 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, R int size = leftBits.Length; Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Subtract(leftBits, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, leftSign < 0); @@ -1942,8 +1916,8 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, R { int size = rightBits.Length; Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Subtract(rightBits, leftBits, bits); result = new BigInteger(bits, leftSign >= 0); @@ -1954,15 +1928,17 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, R int size = leftBits.Length; Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Subtract(leftBits, rightBits, bits); result = new BigInteger(bits, leftSign < 0); } - if (bitsFromPool != null) + if (bitsFromPool is not null) + { ArrayPool.Shared.Return(bitsFromPool); + } return result; } @@ -1971,24 +1947,19 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, R // Explicit Conversions From BigInteger // - public static explicit operator byte(BigInteger value) - { - return checked((byte)((int)value)); - } + public static explicit operator byte(BigInteger value) => checked((byte)((int)value)); /// Explicitly converts a big integer to a value. /// The value to convert. /// converted to value. - public static explicit operator char(BigInteger value) - { - return checked((char)((int)value)); - } + public static explicit operator char(BigInteger value) => checked((char)((int)value)); public static explicit operator decimal(BigInteger value) { - value.AssertValid(); - if (value._bits == null) - return (long)value._sign; + if (value._bits is null) + { + return value._sign; + } int length = value._bits.Length; @@ -1999,28 +1970,25 @@ public static explicit operator decimal(BigInteger value) // 64-bit: at most 2 nuint limbs for 96 bits if (length > 2) throw new OverflowException(SR.Overflow_Decimal); - unchecked + lo = (int)(uint)value._bits[0]; + mi = (int)(uint)(value._bits[0] >> 32); + if (length > 1) { - lo = (int)(uint)value._bits[0]; - mi = (int)(uint)(value._bits[0] >> 32); - if (length > 1) + if (value._bits[1] > uint.MaxValue) { - if (value._bits[1] > uint.MaxValue) - throw new OverflowException(SR.Overflow_Decimal); - hi = (int)(uint)value._bits[1]; + throw new OverflowException(SR.Overflow_Decimal); } + + hi = (int)(uint)value._bits[1]; } } else { if (length > 3) throw new OverflowException(SR.Overflow_Decimal); - unchecked - { - if (length > 2) hi = (int)(uint)value._bits[2]; - if (length > 1) mi = (int)(uint)value._bits[1]; - if (length > 0) lo = (int)(uint)value._bits[0]; - } + if (length > 2) hi = (int)(uint)value._bits[2]; + if (length > 1) mi = (int)(uint)value._bits[1]; + if (length > 0) lo = (int)(uint)value._bits[0]; } return new decimal(lo, mi, hi, value._sign < 0, 0); @@ -2028,13 +1996,13 @@ public static explicit operator decimal(BigInteger value) public static explicit operator double(BigInteger value) { - value.AssertValid(); - int sign = value._sign; nuint[]? bits = value._bits; - if (bits == null) - return (double)sign; + if (bits is null) + { + return sign; + } int length = bits.Length; int bitsPerLimb = nint.Size * 8; @@ -2045,10 +2013,7 @@ public static explicit operator double(BigInteger value) if (length > infinityLength) { - if (sign == 1) - return double.PositiveInfinity; - else - return double.NegativeInfinity; + return sign == 1 ? double.PositiveInfinity : double.NegativeInfinity; } ulong h, m, l; @@ -2057,8 +2022,8 @@ public static explicit operator double(BigInteger value) if (nint.Size == 8) { - h = (ulong)bits[length - 1]; - m = length > 1 ? (ulong)bits[length - 2] : 0; + h = bits[length - 1]; + m = length > 1 ? bits[length - 2] : 0; z = BitOperations.LeadingZeroCount(h); exp = (length - 1) * 64 - z; @@ -2081,52 +2046,45 @@ public static explicit operator double(BigInteger value) /// Explicitly converts a big integer to a value. /// The value to convert. /// converted to value. - public static explicit operator Half(BigInteger value) - { - return (Half)(double)value; - } + public static explicit operator Half(BigInteger value) => (Half)(double)value; /// Explicitly converts a big integer to a value. /// The value to convert. /// converted to value. - public static explicit operator BFloat16(BigInteger value) - { - return (BFloat16)(double)value; - } + public static explicit operator BFloat16(BigInteger value) => (BFloat16)(double)value; - public static explicit operator short(BigInteger value) - { - return checked((short)((int)value)); - } + public static explicit operator short(BigInteger value) => checked((short)((int)value)); public static explicit operator int(BigInteger value) { - value.AssertValid(); - if (value._bits == null) + if (value._bits is null) { return value._sign; } + if (value._bits.Length > 1) { // More than one limb throw new OverflowException(SR.Overflow_Int32); } + if (value._sign > 0) { return checked((int)value._bits[0]); } - if (value._bits[0] > kuMaskHighBit) + + if (value._bits[0] > UInt32HighBit) { // Value > Int32.MinValue throw new OverflowException(SR.Overflow_Int32); } - return unchecked(-(int)value._bits[0]); + + return -(int)value._bits[0]; } public static explicit operator long(BigInteger value) { - value.AssertValid(); - if (value._bits == null) + if (value._bits is null) { return value._sign; } @@ -2138,26 +2096,18 @@ public static explicit operator long(BigInteger value) throw new OverflowException(SR.Overflow_Int64); } - ulong uu; - if (nint.Size == 8) - { - uu = (ulong)value._bits[0]; - } - else if (len > 1) - { - uu = ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]); - } - else - { - uu = (uint)value._bits[0]; - } + ulong uu = + nint.Size == 8 ? value._bits[0] : + len > 1 ? ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) : + (uint)value._bits[0]; - long ll = value._sign > 0 ? unchecked((long)uu) : unchecked(-(long)uu); + long ll = value._sign > 0 ? (long)uu : -(long)uu; if ((ll > 0 && value._sign > 0) || (ll < 0 && value._sign < 0)) { // Signs match, no overflow return ll; } + throw new OverflowException(SR.Overflow_Int64); } @@ -2166,8 +2116,6 @@ public static explicit operator long(BigInteger value) /// converted to value. public static explicit operator Int128(BigInteger value) { - value.AssertValid(); - if (value._bits is null) { return value._sign; @@ -2185,14 +2133,7 @@ public static explicit operator Int128(BigInteger value) if (nint.Size == 8) { - if (len > 1) - { - uu = new UInt128((ulong)value._bits[1], (ulong)value._bits[0]); - } - else - { - uu = (ulong)value._bits[0]; - } + uu = len > 1 ? new UInt128(value._bits[1], value._bits[0]) : (UInt128)(ulong)value._bits[0]; } else if (len > 2) { @@ -2201,80 +2142,56 @@ public static explicit operator Int128(BigInteger value) ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) ); } - else if (len > 1) - { - uu = ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]); - } else { - uu = (uint)value._bits[0]; + uu = len > 1 + ? (UInt128)((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) + : (UInt128)(uint)value._bits[0]; } - Int128 ll = (value._sign > 0) ? unchecked((Int128)uu) : unchecked(-(Int128)uu); + Int128 ll = (value._sign > 0) ? (Int128)uu : -(Int128)uu; if (((ll > 0) && (value._sign > 0)) || ((ll < 0) && (value._sign < 0))) { // Signs match, no overflow return ll; } + throw new OverflowException(SR.Overflow_Int128); } /// Explicitly converts a big integer to a value. /// The value to convert. /// converted to value. - public static explicit operator nint(BigInteger value) - { - if (Environment.Is64BitProcess) - { - return (nint)(long)value; - } - else - { - return (int)value; - } - } + public static explicit operator nint(BigInteger value) => Environment.Is64BitProcess ? (nint)(long)value : (int)value; [CLSCompliant(false)] - public static explicit operator sbyte(BigInteger value) - { - return checked((sbyte)((int)value)); - } + public static explicit operator sbyte(BigInteger value) => checked((sbyte)((int)value)); - public static explicit operator float(BigInteger value) - { - return (float)((double)value); - } + public static explicit operator float(BigInteger value) => (float)((double)value); [CLSCompliant(false)] - public static explicit operator ushort(BigInteger value) - { - return checked((ushort)((int)value)); - } + public static explicit operator ushort(BigInteger value) => checked((ushort)((int)value)); [CLSCompliant(false)] public static explicit operator uint(BigInteger value) { - value.AssertValid(); - if (value._bits == null) + if (value._bits is null) { return checked((uint)value._sign); } - else if (value._bits.Length > 1 || value._sign < 0) - { - throw new OverflowException(SR.Overflow_UInt32); - } else { - return checked((uint)value._bits[0]); + return value._bits.Length <= 1 && value._sign >= 0 + ? checked((uint)value._bits[0]) + : throw new OverflowException(SR.Overflow_UInt32); } } [CLSCompliant(false)] public static explicit operator ulong(BigInteger value) { - value.AssertValid(); - if (value._bits == null) + if (value._bits is null) { return checked((ulong)value._sign); } @@ -2286,15 +2203,10 @@ public static explicit operator ulong(BigInteger value) throw new OverflowException(SR.Overflow_UInt64); } - if (nint.Size == 8) - { - return (ulong)value._bits[0]; - } - else if (len > 1) - { - return ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]); - } - return (uint)value._bits[0]; + return + nint.Size == 8 ? value._bits[0] : + len > 1 ? ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) : + (uint)value._bits[0]; } /// Explicitly converts a big integer to a value. @@ -2303,8 +2215,6 @@ public static explicit operator ulong(BigInteger value) [CLSCompliant(false)] public static explicit operator UInt128(BigInteger value) { - value.AssertValid(); - if (value._bits is null) { return checked((UInt128)value._sign); @@ -2320,11 +2230,9 @@ public static explicit operator UInt128(BigInteger value) if (nint.Size == 8) { - if (len > 1) - { - return new UInt128((ulong)value._bits[1], (ulong)value._bits[0]); - } - return (ulong)value._bits[0]; + return len > 1 + ? new UInt128(value._bits[1], value._bits[0]) + : (UInt128)(ulong)value._bits[0]; } else if (len > 2) { @@ -2337,6 +2245,7 @@ public static explicit operator UInt128(BigInteger value) { return ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]); } + return (uint)value._bits[0]; } @@ -2344,47 +2253,25 @@ public static explicit operator UInt128(BigInteger value) /// The value to convert. /// converted to value. [CLSCompliant(false)] - public static explicit operator nuint(BigInteger value) - { - if (Environment.Is64BitProcess) - { - return (nuint)(ulong)value; - } - else - { - return (uint)value; - } - } + public static explicit operator nuint(BigInteger value) => Environment.Is64BitProcess ? (nuint)(ulong)value : (uint)value; // // Explicit Conversions To BigInteger // - public static explicit operator BigInteger(decimal value) - { - return new BigInteger(value); - } + public static explicit operator BigInteger(decimal value) => new BigInteger(value); - public static explicit operator BigInteger(double value) - { - return new BigInteger(value); - } + public static explicit operator BigInteger(double value) => new BigInteger(value); /// Explicitly converts a value to a big integer. /// The value to convert. /// converted to a big integer. - public static explicit operator BigInteger(Half value) - { - return new BigInteger((float)value); - } + public static explicit operator BigInteger(Half value) => new BigInteger((float)value); /// Explicitly converts a value to a big integer. /// The value to convert. /// converted to a big integer. - public static explicit operator BigInteger(BFloat16 value) - { - return new BigInteger((float)value); - } + public static explicit operator BigInteger(BFloat16 value) => new BigInteger((float)value); /// Explicitly converts a value to a big integer. /// The value to convert. @@ -2395,45 +2282,28 @@ public static explicit operator BigInteger(Complex value) { ThrowHelper.ThrowOverflowException(); } + return (BigInteger)value.Real; } - public static explicit operator BigInteger(float value) - { - return new BigInteger(value); - } + public static explicit operator BigInteger(float value) => new BigInteger(value); // // Implicit Conversions To BigInteger // - public static implicit operator BigInteger(byte value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(byte value) => new BigInteger(value); /// Implicitly converts a value to a big integer. /// The value to convert. /// converted to a big integer. - public static implicit operator BigInteger(char value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(char value) => new BigInteger(value); - public static implicit operator BigInteger(short value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(short value) => new BigInteger(value); - public static implicit operator BigInteger(int value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(int value) => new BigInteger(value); - public static implicit operator BigInteger(long value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(long value) => new BigInteger(value); /// Implicitly converts a value to a big integer. /// The value to convert. @@ -2447,8 +2317,9 @@ public static implicit operator BigInteger(Int128 value) { if (value == int.MinValue) { - return s_bnMinInt; + return s_int32MinValue; } + sign = (int)(long)value; bits = null; } @@ -2457,7 +2328,7 @@ public static implicit operator BigInteger(Int128 value) UInt128 x; if (value < 0) { - x = unchecked((UInt128)(-value)); + x = (UInt128)(-value); sign = -1; } else @@ -2468,45 +2339,33 @@ public static implicit operator BigInteger(Int128 value) if (nint.Size == 8) { - if (x <= ulong.MaxValue) - { - bits = new nuint[1]; - bits[0] = (nuint)(ulong)x; - } - else - { - bits = new nuint[2]; - bits[0] = (nuint)(ulong)x; - bits[1] = (nuint)(ulong)(x >> 64); - } + bits = x <= ulong.MaxValue + ? [(nuint)(ulong)x] + : [(nuint)(ulong)x, (nuint)(ulong)(x >> 64)]; } else { if (x <= uint.MaxValue) { - bits = new nuint[1]; - bits[0] = (nuint)(uint)(x >> (kcbitUint * 0)); + bits = [(uint)(x >> (BitsPerUInt32 * 0))]; } else if (x <= ulong.MaxValue) { - bits = new nuint[2]; - bits[0] = (nuint)(uint)(x >> (kcbitUint * 0)); - bits[1] = (nuint)(uint)(x >> (kcbitUint * 1)); + bits = [(uint)(x >> (BitsPerUInt32 * 0)), + (uint)(x >> (BitsPerUInt32 * 1))]; } else if (x <= new UInt128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) { - bits = new nuint[3]; - bits[0] = (nuint)(uint)(x >> (kcbitUint * 0)); - bits[1] = (nuint)(uint)(x >> (kcbitUint * 1)); - bits[2] = (nuint)(uint)(x >> (kcbitUint * 2)); + bits = [(uint)(x >> (BitsPerUInt32 * 0)), + (uint)(x >> (BitsPerUInt32 * 1)), + (uint)(x >> (BitsPerUInt32 * 2))]; } else { - bits = new nuint[4]; - bits[0] = (nuint)(uint)(x >> (kcbitUint * 0)); - bits[1] = (nuint)(uint)(x >> (kcbitUint * 1)); - bits[2] = (nuint)(uint)(x >> (kcbitUint * 2)); - bits[3] = (nuint)(uint)(x >> (kcbitUint * 3)); + bits = [(uint)(x >> (BitsPerUInt32 * 0)), + (uint)(x >> (BitsPerUInt32 * 1)), + (uint)(x >> (BitsPerUInt32 * 2)), + (uint)(x >> (BitsPerUInt32 * 3))]; } } } @@ -2517,34 +2376,19 @@ public static implicit operator BigInteger(Int128 value) /// Implicitly converts a value to a big integer. /// The value to convert. /// converted to a big integer. - public static implicit operator BigInteger(nint value) - { - return new BigInteger((long)value); - } + public static implicit operator BigInteger(nint value) => new BigInteger(value); [CLSCompliant(false)] - public static implicit operator BigInteger(sbyte value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(sbyte value) => new BigInteger(value); [CLSCompliant(false)] - public static implicit operator BigInteger(ushort value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(ushort value) => new BigInteger(value); [CLSCompliant(false)] - public static implicit operator BigInteger(uint value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(uint value) => new BigInteger(value); [CLSCompliant(false)] - public static implicit operator BigInteger(ulong value) - { - return new BigInteger(value); - } + public static implicit operator BigInteger(ulong value) => new BigInteger(value); /// Implicitly converts a value to a big integer. /// The value to convert. @@ -2564,41 +2408,35 @@ public static implicit operator BigInteger(UInt128 value) { if (value <= ulong.MaxValue) { - bits = new nuint[1]; - bits[0] = (nuint)(ulong)value; + bits = [(nuint)(ulong)value]; } else { - bits = new nuint[2]; - bits[0] = (nuint)(ulong)value; - bits[1] = (nuint)(ulong)(value >> 64); + bits = [(nuint)(ulong)value, + (nuint)(ulong)(value >> 64)]; } } else if (value <= uint.MaxValue) { - bits = new nuint[1]; - bits[0] = (nuint)(uint)(value >> (kcbitUint * 0)); + bits = [(uint)(value >> (BitsPerUInt32 * 0))]; } else if (value <= ulong.MaxValue) { - bits = new nuint[2]; - bits[0] = (nuint)(uint)(value >> (kcbitUint * 0)); - bits[1] = (nuint)(uint)(value >> (kcbitUint * 1)); + bits = [(uint)(value >> (BitsPerUInt32 * 0)), + (uint)(value >> (BitsPerUInt32 * 1))]; } else if (value <= new UInt128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) { - bits = new nuint[3]; - bits[0] = (nuint)(uint)(value >> (kcbitUint * 0)); - bits[1] = (nuint)(uint)(value >> (kcbitUint * 1)); - bits[2] = (nuint)(uint)(value >> (kcbitUint * 2)); + bits = [(uint)(value >> (BitsPerUInt32 * 0)), + (uint)(value >> (BitsPerUInt32 * 1)), + (uint)(value >> (BitsPerUInt32 * 2))]; } else { - bits = new nuint[4]; - bits[0] = (nuint)(uint)(value >> (kcbitUint * 0)); - bits[1] = (nuint)(uint)(value >> (kcbitUint * 1)); - bits[2] = (nuint)(uint)(value >> (kcbitUint * 2)); - bits[3] = (nuint)(uint)(value >> (kcbitUint * 3)); + bits = [(uint)(value >> (BitsPerUInt32 * 0)), + (uint)(value >> (BitsPerUInt32 * 1)), + (uint)(value >> (BitsPerUInt32 * 2)), + (uint)(value >> (BitsPerUInt32 * 3))]; } return new BigInteger(sign, bits); @@ -2608,49 +2446,32 @@ public static implicit operator BigInteger(UInt128 value) /// The value to convert. /// converted to a big integer. [CLSCompliant(false)] - public static implicit operator BigInteger(nuint value) - { - if (value <= (nuint)int.MaxValue) - { - return new BigInteger((int)value, null); - } - else - { - return new BigInteger(+1, new nuint[] { value }); - } - } + public static implicit operator BigInteger(nuint value) => value <= int.MaxValue ? new BigInteger((int)value, null) : new BigInteger(+1, [value]); - public static BigInteger operator &(BigInteger left, BigInteger right) - { - if (left.IsZero || right.IsZero) - return Zero; - - if (left._bits is null && right._bits is null) - return left._sign & right._sign; - - return BitwiseAnd(ref left, ref right); - } + public static BigInteger operator &(BigInteger left, BigInteger right) => left.IsZero || right.IsZero ? Zero : + left._bits is null && right._bits is null ? (BigInteger)(left._sign & right._sign) : + BitwiseAnd(ref left, ref right); public static BigInteger operator |(BigInteger left, BigInteger right) { if (left.IsZero) + { return right; + } + if (right.IsZero) + { return left; + } - if (left._bits is null && right._bits is null) - return left._sign | right._sign; - - return BitwiseOr(ref left, ref right); + return left._bits is null && right._bits is null + ? (BigInteger)(left._sign | right._sign) + : BitwiseOr(ref left, ref right); } - public static BigInteger operator ^(BigInteger left, BigInteger right) - { - if (left._bits is null && right._bits is null) - return left._sign ^ right._sign; - - return BitwiseXor(ref left, ref right); - } + public static BigInteger operator ^(BigInteger left, BigInteger right) => left._bits is null && right._bits is null + ? (BigInteger)(left._sign ^ right._sign) + : BitwiseXor(ref left, ref right); /// /// Computes two's complement AND directly from magnitude representation, @@ -2665,8 +2486,8 @@ private static BigInteger BitwiseAnd(ref readonly BigInteger left, ref readonly ReadOnlySpan yBits = right._bits ?? default; int xLen = left._bits?.Length ?? 1; int yLen = right._bits?.Length ?? 1; - nuint xInline = unchecked((nuint)left._sign); - nuint yInline = unchecked((nuint)right._sign); + nuint xInline = (nuint)left._sign; + nuint yInline = (nuint)right._sign; // AND result length: for positive operands, min length suffices (AND with 0 = 0), // plus 1 for sign extension so the two's complement constructor doesn't @@ -2678,9 +2499,11 @@ private static BigInteger BitwiseAnd(ref readonly BigInteger left, ref readonly nuint[]? resultBufferFromPool = null; Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); + // Borrow initialized to 1: two's complement is ~x + 1, so the first + // limb gets the +1 via borrow, then it propagates through subsequent limbs. nuint xBorrow = 1, yBorrow = 1; for (int i = 0; i < zLen; i++) @@ -2690,10 +2513,12 @@ private static BigInteger BitwiseAnd(ref readonly BigInteger left, ref readonly z[i] = xu & yu; } - var result = new BigInteger(z); + BigInteger result = new(z); if (resultBufferFromPool is not null) + { ArrayPool.Shared.Return(resultBufferFromPool); + } return result; } @@ -2710,15 +2535,15 @@ private static BigInteger BitwiseOr(ref readonly BigInteger left, ref readonly B ReadOnlySpan yBits = right._bits ?? default; int xLen = left._bits?.Length ?? 1; int yLen = right._bits?.Length ?? 1; - nuint xInline = unchecked((nuint)left._sign); - nuint yInline = unchecked((nuint)right._sign); + nuint xInline = (nuint)left._sign; + nuint yInline = (nuint)right._sign; int zLen = Math.Max(xLen, yLen) + 1; nuint[]? resultBufferFromPool = null; Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); nuint xBorrow = 1, yBorrow = 1; @@ -2729,10 +2554,12 @@ private static BigInteger BitwiseOr(ref readonly BigInteger left, ref readonly B z[i] = xu | yu; } - var result = new BigInteger(z); + BigInteger result = new(z); if (resultBufferFromPool is not null) + { ArrayPool.Shared.Return(resultBufferFromPool); + } return result; } @@ -2749,15 +2576,15 @@ private static BigInteger BitwiseXor(ref readonly BigInteger left, ref readonly ReadOnlySpan yBits = right._bits ?? default; int xLen = left._bits?.Length ?? 1; int yLen = right._bits?.Length ?? 1; - nuint xInline = unchecked((nuint)left._sign); - nuint yInline = unchecked((nuint)right._sign); + nuint xInline = (nuint)left._sign; + nuint yInline = (nuint)right._sign; int zLen = Math.Max(xLen, yLen) + 1; nuint[]? resultBufferFromPool = null; Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); nuint xBorrow = 1, yBorrow = 1; @@ -2768,10 +2595,12 @@ private static BigInteger BitwiseXor(ref readonly BigInteger left, ref readonly z[i] = xu ^ yu; } - var result = new BigInteger(z); + BigInteger result = new(z); if (resultBufferFromPool is not null) + { ArrayPool.Shared.Return(resultBufferFromPool); + } return result; } @@ -2795,11 +2624,13 @@ private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlin { // Inline value: _sign holds the value directly. // For negative inline: magnitude is Abs(_sign), stored as positive nuint. - mag = i == 0 ? (isNegative ? NumericsHelpers.Abs(unchecked((int)inlineValue)) : inlineValue) : 0; + mag = i == 0 ? (isNegative ? NumericsHelpers.Abs((int)inlineValue) : inlineValue) : 0; } if (!isNegative) + { return (uint)i < (uint)len ? mag : 0; + } // Two's complement: ~mag + borrow (borrow starts at 1 for the +1) nuint tc = ~mag + borrow; @@ -2810,18 +2641,26 @@ private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlin public static BigInteger operator <<(BigInteger value, int shift) { if (shift == 0) + { return value; + } if (shift == int.MinValue) - return value >> unchecked(int.MinValue - kcbitUint) >> kcbitUint; + { + return value >> MinIntSplitShift >> BitsPerUInt32; + } if (shift < 0) + { return value >> -shift; + } - (int digitShift, int smallShift) = Math.DivRem(shift, BigIntegerCalculator.kcbitNuint); + (int digitShift, int smallShift) = Math.DivRem(shift, BigIntegerCalculator.BitsPerLimb); if (value._bits is null) + { return LeftShift(value._sign, digitShift, smallShift); + } ReadOnlySpan bits = value._bits; @@ -2831,7 +2670,7 @@ private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlin nuint over = smallShift == 0 ? 0 - : bits[^1] >> (BigIntegerCalculator.kcbitNuint - smallShift); + : bits[^1] >> (BigIntegerCalculator.BitsPerLimb - smallShift); nuint[] z; int zLength = bits.Length + digitShift; @@ -2856,25 +2695,29 @@ private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlin return new BigInteger(value._sign, z); } + private static BigInteger LeftShift(int value, int digitShift, int smallShift) { if (value == 0) - return s_bnZeroInt; + { + return s_zero; + } nuint m = NumericsHelpers.Abs(value); nuint r = m << smallShift; - nuint over = - smallShift == 0 + nuint over = smallShift == 0 ? 0 - : m >> (BigIntegerCalculator.kcbitNuint - smallShift); + : m >> (BigIntegerCalculator.BitsPerLimb - smallShift); nuint[] rgu; if (over == 0) { - if (digitShift == 0 && r <= (nuint)int.MaxValue) + if (digitShift == 0 && r <= int.MaxValue) + { return new BigInteger(value >= 0 ? (int)r : -(int)r, null); + } rgu = new nuint[digitShift + 1]; } @@ -2892,15 +2735,21 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) public static BigInteger operator >>(BigInteger value, int shift) { if (shift == 0) + { return value; + } if (shift == int.MinValue) - return value << kcbitUint << unchecked(int.MinValue - kcbitUint); + { + return value << BitsPerUInt32 << MinIntSplitShift; + } if (shift < 0) + { return value << -shift; + } - (int digitShift, int smallShift) = Math.DivRem(shift, BigIntegerCalculator.kcbitNuint); + (int digitShift, int smallShift) = Math.DivRem(shift, BigIntegerCalculator.BitsPerLimb); if (value._bits is null) { @@ -2922,12 +2771,14 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) int zLength = bits.Length - digitShift + 1; if (zLength <= 1) + { return new BigInteger(value._sign >> 31, null); + } nuint[]? zFromPool = null; Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); zd[^1] = 0; bits.Slice(digitShift).CopyTo(zd); @@ -2945,68 +2796,42 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) zd.Slice(0, leastSignificant).Clear(); } - BigInteger result = new BigInteger(zd, neg); + BigInteger result = new(zd, neg); - if (zFromPool != null) + if (zFromPool is not null) + { ArrayPool.Shared.Return(zFromPool); + } return result; } - public static BigInteger operator ~(BigInteger value) - { - return -(value + One); - } + public static BigInteger operator ~(BigInteger value) => -(value + One); - public static BigInteger operator -(BigInteger value) - { - value.AssertValid(); - return new BigInteger(-value._sign, value._bits); - } + public static BigInteger operator -(BigInteger value) => new BigInteger(-value._sign, value._bits); - public static BigInteger operator +(BigInteger value) - { - value.AssertValid(); - return value; - } + public static BigInteger operator +(BigInteger value) => value; - public static BigInteger operator ++(BigInteger value) - { - return value + One; - } + public static BigInteger operator ++(BigInteger value) => value + One; - public static BigInteger operator --(BigInteger value) - { - return value - One; - } + public static BigInteger operator --(BigInteger value) => value - One; public static BigInteger operator +(BigInteger left, BigInteger right) { - left.AssertValid(); - right.AssertValid(); - - if (left._bits == null && right._bits == null) + if (left._bits is null && right._bits is null) { return (long)left._sign + right._sign; } - if (left._sign < 0 != right._sign < 0) - return Subtract(left._bits, left._sign, right._bits, -1 * right._sign); - return Add(left._bits, left._sign, right._bits, right._sign); + return left._sign < 0 != right._sign < 0 + ? Subtract(left._bits, left._sign, right._bits, -1 * right._sign) + : Add(left._bits, left._sign, right._bits, right._sign); } - public static BigInteger operator *(BigInteger left, BigInteger right) - { - left.AssertValid(); - right.AssertValid(); - - if (left._bits == null && right._bits == null) - { - return (long)left._sign * right._sign; - } - - return Multiply(left._bits, left._sign, right._bits, right._sign); - } + public static BigInteger operator *(BigInteger left, BigInteger right) => + left._bits is null && right._bits is null + ? (BigInteger)((long)left._sign * right._sign) + : Multiply(left._bits, left._sign, right._bits, right._sign); private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOnlySpan right, int rightSign) { @@ -3024,8 +2849,8 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO int size = right.Length + 1; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[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)); @@ -3036,8 +2861,8 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO int size = left.Length + 1; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); BigIntegerCalculator.Multiply(left, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); @@ -3046,8 +2871,8 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO { int size = left.Length + right.Length; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); bits.Clear(); BigIntegerCalculator.Square(left, bits); @@ -3059,27 +2884,26 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO int size = left.Length + right.Length; Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[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) + if (bitsFromPool is not null) + { ArrayPool.Shared.Return(bitsFromPool); + } return result; } public static BigInteger operator /(BigInteger dividend, BigInteger divisor) { - dividend.AssertValid(); - divisor.AssertValid(); - - bool trivialDividend = dividend._bits == null; - bool trivialDivisor = divisor._bits == null; + bool trivialDividend = dividend._bits is null; + bool trivialDivisor = divisor._bits is null; if (trivialDividend && trivialDivisor) { @@ -3090,19 +2914,19 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO { // The divisor is non-trivial // and therefore the bigger one - return s_bnZeroInt; + return s_zero; } nuint[]? quotientFromPool = null; if (trivialDivisor) { - Debug.Assert(dividend._bits != null); + Debug.Assert(dividend._bits is not null); int size = dividend._bits.Length; Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); try { @@ -3112,29 +2936,33 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO } finally { - if (quotientFromPool != null) + if (quotientFromPool is not null) + { ArrayPool.Shared.Return(quotientFromPool); + } } } - Debug.Assert(dividend._bits != null && divisor._bits != null); + Debug.Assert(dividend._bits is not null && divisor._bits is not null); if (dividend._bits.Length < divisor._bits.Length) { - return s_bnZeroInt; + return s_zero; } else { int size = dividend._bits.Length - divisor._bits.Length + 1; Span quotient = ((uint)size < BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[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)); + BigInteger result = new(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - if (quotientFromPool != null) + if (quotientFromPool is not null) + { ArrayPool.Shared.Return(quotientFromPool); + } return result; } @@ -3142,11 +2970,8 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO public static BigInteger operator %(BigInteger dividend, BigInteger divisor) { - dividend.AssertValid(); - divisor.AssertValid(); - - bool trivialDividend = dividend._bits == null; - bool trivialDivisor = divisor._bits == null; + bool trivialDividend = dividend._bits is null; + bool trivialDivisor = divisor._bits is null; if (trivialDividend && trivialDivisor) { @@ -3162,12 +2987,12 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO if (trivialDivisor) { - Debug.Assert(dividend._bits != null); + Debug.Assert(dividend._bits is not null); nuint remainder = BigIntegerCalculator.Remainder(dividend._bits, NumericsHelpers.Abs(divisor._sign)); return dividend._sign < 0 ? -1 * (long)remainder : (long)remainder; } - Debug.Assert(dividend._bits != null && divisor._bits != null); + Debug.Assert(dividend._bits is not null && divisor._bits is not null); if (dividend._bits.Length < divisor._bits.Length) { @@ -3177,178 +3002,91 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO nuint[]? bitsFromPool = null; int size = dividend._bits.Length; Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + ? stackalloc nuint[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); + BigInteger result = new(bits, dividend._sign < 0); - if (bitsFromPool != null) + if (bitsFromPool is not null) + { ArrayPool.Shared.Return(bitsFromPool); + } return result; } - public static bool operator <(BigInteger left, BigInteger right) - { - return left.CompareTo(right) < 0; - } + public static bool operator <(BigInteger left, BigInteger right) => left.CompareTo(right) < 0; - public static bool operator <=(BigInteger left, BigInteger right) - { - return left.CompareTo(right) <= 0; - } + public static bool operator <=(BigInteger left, BigInteger right) => left.CompareTo(right) <= 0; - public static bool operator >(BigInteger left, BigInteger right) - { - return left.CompareTo(right) > 0; - } - public static bool operator >=(BigInteger left, BigInteger right) - { - return left.CompareTo(right) >= 0; - } + public static bool operator >(BigInteger left, BigInteger right) => left.CompareTo(right) > 0; - public static bool operator ==(BigInteger left, BigInteger right) - { - return left.Equals(right); - } + public static bool operator >=(BigInteger left, BigInteger right) => left.CompareTo(right) >= 0; - public static bool operator !=(BigInteger left, BigInteger right) - { - return !left.Equals(right); - } + public static bool operator ==(BigInteger left, BigInteger right) => left.Equals(right); - public static bool operator <(BigInteger left, long right) - { - return left.CompareTo(right) < 0; - } + public static bool operator !=(BigInteger left, BigInteger right) => !left.Equals(right); - public static bool operator <=(BigInteger left, long right) - { - return left.CompareTo(right) <= 0; - } + public static bool operator <(BigInteger left, long right) => left.CompareTo(right) < 0; - public static bool operator >(BigInteger left, long right) - { - return left.CompareTo(right) > 0; - } + public static bool operator <=(BigInteger left, long right) => left.CompareTo(right) <= 0; - public static bool operator >=(BigInteger left, long right) - { - return left.CompareTo(right) >= 0; - } + public static bool operator >(BigInteger left, long right) => left.CompareTo(right) > 0; - public static bool operator ==(BigInteger left, long right) - { - return left.Equals(right); - } + public static bool operator >=(BigInteger left, long right) => left.CompareTo(right) >= 0; - public static bool operator !=(BigInteger left, long right) - { - return !left.Equals(right); - } + public static bool operator ==(BigInteger left, long right) => left.Equals(right); - public static bool operator <(long left, BigInteger right) - { - return right.CompareTo(left) > 0; - } + public static bool operator !=(BigInteger left, long right) => !left.Equals(right); - public static bool operator <=(long left, BigInteger right) - { - return right.CompareTo(left) >= 0; - } + public static bool operator <(long left, BigInteger right) => right.CompareTo(left) > 0; - public static bool operator >(long left, BigInteger right) - { - return right.CompareTo(left) < 0; - } + public static bool operator <=(long left, BigInteger right) => right.CompareTo(left) >= 0; - public static bool operator >=(long left, BigInteger right) - { - return right.CompareTo(left) <= 0; - } + public static bool operator >(long left, BigInteger right) => right.CompareTo(left) < 0; - public static bool operator ==(long left, BigInteger right) - { - return right.Equals(left); - } + public static bool operator >=(long left, BigInteger right) => right.CompareTo(left) <= 0; - public static bool operator !=(long left, BigInteger right) - { - return !right.Equals(left); - } + public static bool operator ==(long left, BigInteger right) => right.Equals(left); + + public static bool operator !=(long left, BigInteger right) => !right.Equals(left); [CLSCompliant(false)] - public static bool operator <(BigInteger left, ulong right) - { - return left.CompareTo(right) < 0; - } + public static bool operator <(BigInteger left, ulong right) => left.CompareTo(right) < 0; [CLSCompliant(false)] - public static bool operator <=(BigInteger left, ulong right) - { - return left.CompareTo(right) <= 0; - } + public static bool operator <=(BigInteger left, ulong right) => left.CompareTo(right) <= 0; [CLSCompliant(false)] - public static bool operator >(BigInteger left, ulong right) - { - return left.CompareTo(right) > 0; - } + public static bool operator >(BigInteger left, ulong right) => left.CompareTo(right) > 0; [CLSCompliant(false)] - public static bool operator >=(BigInteger left, ulong right) - { - return left.CompareTo(right) >= 0; - } + public static bool operator >=(BigInteger left, ulong right) => left.CompareTo(right) >= 0; [CLSCompliant(false)] - public static bool operator ==(BigInteger left, ulong right) - { - return left.Equals(right); - } + public static bool operator ==(BigInteger left, ulong right) => left.Equals(right); [CLSCompliant(false)] - public static bool operator !=(BigInteger left, ulong right) - { - return !left.Equals(right); - } + public static bool operator !=(BigInteger left, ulong right) => !left.Equals(right); [CLSCompliant(false)] - public static bool operator <(ulong left, BigInteger right) - { - return right.CompareTo(left) > 0; - } + public static bool operator <(ulong left, BigInteger right) => right.CompareTo(left) > 0; [CLSCompliant(false)] - public static bool operator <=(ulong left, BigInteger right) - { - return right.CompareTo(left) >= 0; - } + public static bool operator <=(ulong left, BigInteger right) => right.CompareTo(left) >= 0; [CLSCompliant(false)] - public static bool operator >(ulong left, BigInteger right) - { - return right.CompareTo(left) < 0; - } + public static bool operator >(ulong left, BigInteger right) => right.CompareTo(left) < 0; [CLSCompliant(false)] - public static bool operator >=(ulong left, BigInteger right) - { - return right.CompareTo(left) <= 0; - } + public static bool operator >=(ulong left, BigInteger right) => right.CompareTo(left) <= 0; [CLSCompliant(false)] - public static bool operator ==(ulong left, BigInteger right) - { - return right.Equals(left); - } + public static bool operator ==(ulong left, BigInteger right) => right.Equals(left); [CLSCompliant(false)] - public static bool operator !=(ulong left, BigInteger right) - { - return !right.Equals(left); - } + public static bool operator !=(ulong left, BigInteger right) => !right.Equals(left); /// /// Gets the number of bits required for shortest two's complement representation of the current instance without the sign bit. @@ -3357,14 +3095,12 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO /// This method returns 0 iff the value of current object is equal to or . For positive integers the return value is equal to the ordinary binary representation string length. public long GetBitLength() { - AssertValid(); - nuint highValue; int bitsArrayLength; int sign = _sign; nuint[]? bits = _bits; - if (bits == null) + if (bits is null) { bitsArrayLength = 1; highValue = (nuint)(sign < 0 ? -sign : sign); @@ -3375,42 +3111,37 @@ public long GetBitLength() highValue = bits[bitsArrayLength - 1]; } - long bitLength = (long)bitsArrayLength * BigIntegerCalculator.kcbitNuint - - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)highValue) : BitOperations.LeadingZeroCount((uint)highValue)); + long bitLength = (long)bitsArrayLength * BigIntegerCalculator.BitsPerLimb - + BitOperations.LeadingZeroCount(highValue); if (sign >= 0) + { return bitLength; + } // When negative and IsPowerOfTwo, the answer is (bitLength - 1) // Check highValue if ((highValue & (highValue - 1)) != 0) - return bitLength; - - // Check the rest of the bits (if present) - for (int i = bitsArrayLength - 2; i >= 0; i--) { - // bits array is always non-null when bitsArrayLength >= 2 - if (bits![i] == 0) - continue; - return bitLength; } - return bitLength - 1; + // Check the rest of the bits (if present) + return bits.AsSpan(0, bitsArrayLength - 1).ContainsAnyExcept((nuint)0) ? bitLength : bitLength - 1; } [Conditional("DEBUG")] private void AssertValid() { - if (_bits != null) + if (_bits is not null) { // _sign must be +1 or -1 when _bits is non-null - Debug.Assert(_sign == 1 || _sign == -1); + Debug.Assert(_sign is 1 or -1); // _bits must contain at least 1 element or be null Debug.Assert(_bits.Length > 0); // Wasted space: _bits[0] could have been packed into _sign - Debug.Assert(_bits.Length > 1 || _bits[0] > (nuint)int.MaxValue); + Debug.Assert(_bits.Length > 1 || _bits[0] > int.MaxValue); // Wasted space: leading zeros could have been truncated Debug.Assert(_bits[_bits.Length - 1] != 0); // Arrays larger than this can't fit into a Span @@ -3444,33 +3175,25 @@ public static (BigInteger Quotient, BigInteger Remainder) DivRem(BigInteger left /// public static BigInteger LeadingZeroCount(BigInteger value) { - value.AssertValid(); - if (value._bits is null) { - return nint.Size == 8 - ? long.LeadingZeroCount((long)value._sign) - : int.LeadingZeroCount((int)value._sign); + return nint.LeadingZeroCount(value._sign); } - // When the value is positive, we just need to get the lzcnt of the most significant bits - // Otherwise, we're negative and the most significant bit is always set. + // When the value is positive, we just need to get the lzcnt of the most significant bits. + // When negative, two's complement has infinite sign-extension of 1-bits, so LZC is always 0. return (value._sign >= 0) - ? (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)value._bits[^1]) : BitOperations.LeadingZeroCount((uint)value._bits[^1])) + ? BitOperations.LeadingZeroCount(value._bits[^1]) : 0; } /// public static BigInteger PopCount(BigInteger value) { - value.AssertValid(); - if (value._bits is null) { - return nint.Size == 8 - ? long.PopCount((long)value._sign) - : int.PopCount((int)value._sign); + return nint.PopCount(value._sign); } ulong result = 0; @@ -3482,9 +3205,7 @@ public static BigInteger PopCount(BigInteger value) for (int i = 0; i < value._bits.Length; i++) { nuint part = value._bits[i]; - result += nint.Size == 8 - ? (ulong)BitOperations.PopCount((ulong)part) - : (ulong)BitOperations.PopCount((uint)part); + result += (ulong)BitOperations.PopCount(part); } } else @@ -3500,9 +3221,7 @@ public static BigInteger PopCount(BigInteger value) // Simply process bits, adding the carry while the previous value is zero part = ~value._bits[i] + 1; - result += nint.Size == 8 - ? (ulong)BitOperations.PopCount((ulong)part) - : (ulong)BitOperations.PopCount((uint)part); + result += (ulong)BitOperations.PopCount(part); i++; } @@ -3513,9 +3232,7 @@ public static BigInteger PopCount(BigInteger value) // Then process the remaining bits only utilizing the one's complement part = ~value._bits[i]; - result += nint.Size == 8 - ? (ulong)BitOperations.PopCount((ulong)part) - : (ulong)BitOperations.PopCount((uint)part); + result += (ulong)BitOperations.PopCount(part); i++; } } @@ -3526,21 +3243,19 @@ public static BigInteger PopCount(BigInteger value) /// public static BigInteger RotateLeft(BigInteger value, int rotateAmount) { - value.AssertValid(); - if (rotateAmount == 0) + { return value; + } bool neg = value._sign < 0; if (value._bits is null) { - nuint rs = nint.Size == 8 - ? (nuint)BitOperations.RotateLeft((ulong)(nuint)value._sign, rotateAmount) - : (nuint)BitOperations.RotateLeft((uint)value._sign, rotateAmount); + nuint rs = BitOperations.RotateLeft((nuint)value._sign, rotateAmount); return neg - ? new BigInteger((nint)rs) - : new BigInteger(rs); + ? new BigInteger((nint)rs) + : new BigInteger(rs); } return Rotate(value._bits, neg, rotateAmount); @@ -3549,21 +3264,19 @@ public static BigInteger RotateLeft(BigInteger value, int rotateAmount) /// public static BigInteger RotateRight(BigInteger value, int rotateAmount) { - value.AssertValid(); - if (rotateAmount == 0) + { return value; + } bool neg = value._sign < 0; if (value._bits is null) { - nuint rs = nint.Size == 8 - ? (nuint)BitOperations.RotateRight((ulong)(nuint)value._sign, rotateAmount) - : (nuint)BitOperations.RotateRight((uint)value._sign, rotateAmount); + nuint rs = BitOperations.RotateRight((nuint)value._sign, rotateAmount); return neg - ? new BigInteger((nint)rs) - : new BigInteger(rs); + ? new BigInteger((nint)rs) + : new BigInteger(rs); } return Rotate(value._bits, neg, -(long)rotateAmount); @@ -3578,12 +3291,12 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long r int leadingZeroCount = negative ? bits.IndexOfAnyExcept((nuint)0) : 0; if (negative && (nint)bits[^1] < 0 - && (leadingZeroCount != bits.Length - 1 || bits[^1] != ((nuint)1 << (BigIntegerCalculator.kcbitNuint - 1)))) + && (leadingZeroCount != bits.Length - 1 || bits[^1] != ((nuint)1 << (BigIntegerCalculator.BitsPerLimb - 1)))) { - // For a shift of N x kcbitNuint bit, + // For a shift of N x BitsPerLimb bit, // We check for a special case where its sign bit could be outside the nuint array after 2's complement conversion. // For example given [nuint.MaxValue, nuint.MaxValue, nuint.MaxValue], its 2's complement is [0x01, 0x00, 0x00] - // After a kcbitNuint bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back. + // After a BitsPerLimb bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back. // The expected result is [0x00, 0x00, nuint.MaxValue] (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 ++zLength; @@ -3591,8 +3304,8 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long r nuint[]? zFromPool = null; Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); zd[^1] = 0; bits.CopyTo(zd); @@ -3618,10 +3331,12 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long r negative = false; } - var result = new BigInteger(zd, negative); + BigInteger result = new(zd, negative); - if (zFromPool != null) + if (zFromPool is not null) + { ArrayPool.Shared.Return(zFromPool); + } return result; } @@ -3629,13 +3344,9 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long r /// public static BigInteger TrailingZeroCount(BigInteger value) { - value.AssertValid(); - if (value._bits is null) { - return nint.Size == 8 - ? long.TrailingZeroCount((long)value._sign) - : int.TrailingZeroCount((int)value._sign); + return nint.TrailingZeroCount(value._sign); } ulong result = 0; @@ -3651,9 +3362,7 @@ public static BigInteger TrailingZeroCount(BigInteger value) result += (uint)(nint.Size * 8); } - result += nint.Size == 8 - ? (ulong)BitOperations.TrailingZeroCount((ulong)part) - : (ulong)BitOperations.TrailingZeroCount((uint)part); + result += (ulong)BitOperations.TrailingZeroCount(part); return result; } @@ -3675,28 +3384,20 @@ static bool IBinaryInteger.TryReadLittleEndian(ReadOnlySpan so /// int IBinaryInteger.GetShortestBitLength() { - AssertValid(); nuint[]? bits = _bits; if (bits is null) { int value = _sign; - if (value >= 0) - { - return 32 - BitOperations.LeadingZeroCount((uint)value); - } - else - { - return 33 - BitOperations.LeadingZeroCount(~(uint)value); - } + return value >= 0 ? 32 - BitOperations.LeadingZeroCount((uint)value) : 33 - BitOperations.LeadingZeroCount(~(uint)value); } - int result = (bits.Length - 1) * BigIntegerCalculator.kcbitNuint; + int result = (bits.Length - 1) * BigIntegerCalculator.BitsPerLimb; if (_sign >= 0) { - result += (nint.Size * 8) - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)bits[^1]) : BitOperations.LeadingZeroCount((uint)bits[^1])); + result += (nint.Size * 8) - BitOperations.LeadingZeroCount(bits[^1]); } else { @@ -3706,16 +3407,12 @@ int IBinaryInteger.GetShortestBitLength() // bytes are not zero. This ensures we get the correct two's complement // part for the computation. - for (int index = 0; index < bits.Length - 1; index++) + if (bits.AsSpan(0, bits.Length - 1).ContainsAnyExcept((nuint)0)) { - if (bits[index] != 0) - { - part -= 1; - break; - } + part -= 1; } - result += (nint.Size * 8) + 1 - (nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)~part) : BitOperations.LeadingZeroCount((uint)~part)); + result += (nint.Size * 8) + 1 - BitOperations.LeadingZeroCount(~part); } return result; @@ -3727,7 +3424,6 @@ int IBinaryInteger.GetShortestBitLength() /// bool IBinaryInteger.TryWriteBigEndian(Span destination, out int bytesWritten) { - AssertValid(); nuint[]? bits = _bits; int byteCount = GetGenericMathByteCount(); @@ -3736,37 +3432,18 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in { if (bits is null) { - nint value = _sign; - if (BitConverter.IsLittleEndian) - { - if (nint.Size == 8) - value = (nint)BinaryPrimitives.ReverseEndianness((long)value); - else - value = (nint)BinaryPrimitives.ReverseEndianness((int)value); - } - Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value); + BinaryPrimitives.WriteIntPtrBigEndian(destination, _sign); } else if (_sign >= 0) { // When the value is positive, we simply need to copy all bits as big endian - ref byte startAddress = ref MemoryMarshal.GetReference(destination); - ref byte address = ref Unsafe.Add(ref startAddress, (bits.Length - 1) * nint.Size); + Span dest = destination; - for (int i = 0; i < bits.Length; i++) + for (int i = bits.Length - 1; i >= 0; i--) { - nuint part = bits[i]; - - if (BitConverter.IsLittleEndian) - { - if (nint.Size == 8) - part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); - else - part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); - } - - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Subtract(ref address, nint.Size); + BinaryPrimitives.WriteUIntPtrBigEndian(dest, bits[i]); + dest = dest.Slice(nint.Size); } } else @@ -3774,61 +3451,41 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in // When the value is negative, we need to copy the two's complement representation // We'll do this "inline" to avoid needing to unnecessarily allocate. - ref byte startAddress = ref MemoryMarshal.GetReference(destination); - ref byte address = ref Unsafe.Add(ref startAddress, byteCount - nint.Size); + bool needsSignExtension = byteCount > bits.Length * nint.Size; + Span dest = destination; - int i = 0; - nuint part; - - do + if (needsSignExtension) { - // first do complement and +1 as long as carry is needed - part = ~bits[i] + 1; - - if (BitConverter.IsLittleEndian) - { - if (nint.Size == 8) - part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); - else - part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); - } - - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Subtract(ref address, nint.Size); - - i++; + // We need one extra part to represent the sign as the most + // significant bit of the two's complement value was 0. + BinaryPrimitives.WriteUIntPtrBigEndian(dest, nuint.MaxValue); + dest = dest.Slice(nint.Size); } - while ((part == 0) && (i < bits.Length)); - while (i < bits.Length) + // Find first non-zero limb to determine carry boundary + int firstNonZero = ((ReadOnlySpan)bits).IndexOfAnyExcept((nuint)0); + Debug.Assert(firstNonZero >= 0); + + // Write from highest limb to lowest (forward in big-endian dest) + for (int i = bits.Length - 1; i >= 0; i--) { - // now ones complement is sufficient - part = ~bits[i]; + nuint part; - if (BitConverter.IsLittleEndian) + if (i > firstNonZero) { - if (nint.Size == 8) - part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); - else - part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); + part = ~bits[i]; + } + else if (i == firstNonZero) + { + part = ~bits[i] + 1; + } + else + { + part = 0; } - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Subtract(ref address, nint.Size); - - i++; - } - - if (Unsafe.AreSame(ref address, ref startAddress)) - { - // We need one extra part to represent the sign as the most - // significant bit of the two's complement value was 0. - Unsafe.WriteUnaligned(ref address, nuint.MaxValue); - } - else - { - // Otherwise we should have been precisely one part behind address - Debug.Assert(Unsafe.AreSame(ref startAddress, ref Unsafe.Add(ref address, nint.Size))); + BinaryPrimitives.WriteUIntPtrBigEndian(dest, part); + dest = dest.Slice(nint.Size); } } @@ -3845,7 +3502,6 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in /// bool IBinaryInteger.TryWriteLittleEndian(Span destination, out int bytesWritten) { - AssertValid(); nuint[]? bits = _bits; int byteCount = GetGenericMathByteCount(); @@ -3854,36 +3510,18 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out { if (bits is null) { - nint value = _sign; - if (!BitConverter.IsLittleEndian) - { - if (nint.Size == 8) - value = (nint)BinaryPrimitives.ReverseEndianness((long)value); - else - value = (nint)BinaryPrimitives.ReverseEndianness((int)value); - } - Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value); + BinaryPrimitives.WriteIntPtrLittleEndian(destination, _sign); } else if (_sign >= 0) { // When the value is positive, we simply need to copy all bits as little endian - ref byte address = ref MemoryMarshal.GetReference(destination); + Span dest = destination; for (int i = 0; i < bits.Length; i++) { - nuint part = bits[i]; - - if (!BitConverter.IsLittleEndian) - { - if (nint.Size == 8) - part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); - else - part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); - } - - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, nint.Size); + BinaryPrimitives.WriteUIntPtrLittleEndian(dest, bits[i]); + dest = dest.Slice(nint.Size); } } else @@ -3891,61 +3529,39 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out // When the value is negative, we need to copy the two's complement representation // We'll do this "inline" to avoid needing to unnecessarily allocate. - ref byte address = ref MemoryMarshal.GetReference(destination); - ref byte lastAddress = ref Unsafe.Add(ref address, byteCount - nint.Size); + bool needsSignExtension = byteCount > bits.Length * nint.Size; + Span dest = destination; - int i = 0; - nuint part; + // Find first non-zero limb to determine carry boundary + int firstNonZero = ((ReadOnlySpan)bits).IndexOfAnyExcept((nuint)0); + Debug.Assert(firstNonZero >= 0); - do + for (int i = 0; i < bits.Length; i++) { - // first do complement and +1 as long as carry is needed - part = ~bits[i] + 1; + nuint part; - if (!BitConverter.IsLittleEndian) + if (i < firstNonZero) { - if (nint.Size == 8) - part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); - else - part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); + part = 0; } - - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, nint.Size); - - i++; - } - while ((part == 0) && (i < bits.Length)); - - while (i < bits.Length) - { - // now ones complement is sufficient - part = ~bits[i]; - - if (!BitConverter.IsLittleEndian) + else if (i == firstNonZero) { - if (nint.Size == 8) - part = (nuint)BinaryPrimitives.ReverseEndianness((ulong)part); - else - part = (nuint)BinaryPrimitives.ReverseEndianness((uint)part); + part = ~bits[i] + 1; + } + else + { + part = ~bits[i]; } - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, nint.Size); - - i++; + BinaryPrimitives.WriteUIntPtrLittleEndian(dest, part); + dest = dest.Slice(nint.Size); } - if (Unsafe.AreSame(ref address, ref lastAddress)) + if (needsSignExtension) { // We need one extra part to represent the sign as the most // significant bit of the two's complement value was 0. - Unsafe.WriteUnaligned(ref address, nuint.MaxValue); - } - else - { - // Otherwise we should have been precisely one part ahead address - Debug.Assert(Unsafe.AreSame(ref lastAddress, ref Unsafe.Subtract(ref address, nint.Size))); + BinaryPrimitives.WriteUIntPtrLittleEndian(dest, nuint.MaxValue); } } @@ -3961,7 +3577,6 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out private int GetGenericMathByteCount() { - AssertValid(); nuint[]? bits = _bits; if (bits is null) @@ -3979,13 +3594,9 @@ private int GetGenericMathByteCount() // bytes are not zero. This ensures we get the correct two's complement // part for the computation. - for (int index = 0; index < bits.Length - 1; index++) + if (bits.AsSpan(0, bits.Length - 1).ContainsAnyExcept((nuint)0)) { - if (bits[index] != 0) - { - part -= 1; - break; - } + part -= 1; } if ((nint)part >= 0) @@ -4012,25 +3623,14 @@ private int GetGenericMathByteCount() /// public static BigInteger Log2(BigInteger value) { - value.AssertValid(); - if (IsNegative(value)) { ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - if (value._bits is null) - { - return (BigIntegerCalculator.kcbitNuint - 1) ^ - (nint.Size == 8 - ? BitOperations.LeadingZeroCount((ulong)((nuint)value._sign | 1)) - : BitOperations.LeadingZeroCount((uint)((nuint)value._sign | 1))); - } - - return ((long)value._bits.Length * BigIntegerCalculator.kcbitNuint - 1) ^ - (nint.Size == 8 - ? BitOperations.LeadingZeroCount((ulong)value._bits[^1]) - : BitOperations.LeadingZeroCount((uint)value._bits[^1])); + return value._bits is null + ? (BigInteger)((BigIntegerCalculator.BitsPerLimb - 1) ^ BitOperations.LeadingZeroCount((nuint)value._sign | 1)) + : (BigInteger)(((long)value._bits.Length * BigIntegerCalculator.BitsPerLimb - 1) ^ BitOperations.LeadingZeroCount(value._bits[^1])); } // @@ -4047,11 +3647,6 @@ public static BigInteger Log2(BigInteger value) /// public static BigInteger Clamp(BigInteger value, BigInteger min, BigInteger max) { - value.AssertValid(); - - min.AssertValid(); - max.AssertValid(); - if (min > max) { ThrowMinMaxException(min, max); @@ -4078,9 +3673,6 @@ static void ThrowMinMaxException(T min, T max) /// public static BigInteger CopySign(BigInteger value, BigInteger sign) { - value.AssertValid(); - sign.AssertValid(); - nint currentSign = value._sign; if (value._bits is null) @@ -4107,14 +3699,9 @@ public static BigInteger CopySign(BigInteger value, BigInteger sign) /// static int INumber.Sign(BigInteger value) { - value.AssertValid(); - - if (value._bits is null) - { - return value._sign > 0 ? 1 : (value._sign < 0 ? -1 : 0); - } - - return (int)value._sign; + return value._bits is null + ? value._sign > 0 ? 1 + : (value._sign < 0 ? -1 : 0) : value._sign; } // @@ -4190,13 +3777,9 @@ public static BigInteger CreateTruncating(TOther value) /// public static bool IsEvenInteger(BigInteger value) { - value.AssertValid(); - - if (value._bits is null) - { - return (value._sign & 1) == 0; - } - return (value._bits[0] & 1) == 0; + return value._bits is null + ? (value._sign & 1) == 0 + : (value._bits[0] & 1) == 0; } /// @@ -4217,7 +3800,6 @@ public static bool IsEvenInteger(BigInteger value) /// public static bool IsNegative(BigInteger value) { - value.AssertValid(); return value._sign < 0; } @@ -4230,19 +3812,14 @@ public static bool IsNegative(BigInteger value) /// public static bool IsOddInteger(BigInteger value) { - value.AssertValid(); - - if (value._bits is null) - { - return (value._sign & 1) != 0; - } - return (value._bits[0] & 1) != 0; + return value._bits is null + ? (value._sign & 1) != 0 + : (value._bits[0] & 1) != 0; } /// public static bool IsPositive(BigInteger value) { - value.AssertValid(); return value._sign >= 0; } @@ -4258,16 +3835,12 @@ public static bool IsPositive(BigInteger value) /// static bool INumberBase.IsZero(BigInteger value) { - value.AssertValid(); return value._sign == 0; } /// public static BigInteger MaxMagnitude(BigInteger x, BigInteger y) { - x.AssertValid(); - y.AssertValid(); - int compareResult = Abs(x).CompareTo(Abs(y)); return compareResult > 0 || (compareResult == 0 && IsPositive(x)) ? x : y; } @@ -4278,9 +3851,6 @@ public static BigInteger MaxMagnitude(BigInteger x, BigInteger y) /// public static BigInteger MinMagnitude(BigInteger x, BigInteger y) { - x.AssertValid(); - y.AssertValid(); - int compareResult = Abs(x).CompareTo(Abs(y)); return compareResult < 0 || (compareResult == 0 && IsNegative(x)) ? x : y; } @@ -4876,7 +4446,7 @@ static bool INumberBase.TryConvertToSaturating(BigInteger va else { actualResult = (value._sign >= int.MaxValue) ? int.MaxValue : - (value._sign <= int.MinValue) ? int.MinValue : (int)value._sign; + (value._sign <= int.MinValue) ? int.MinValue : value._sign; } result = (TOther)(object)actualResult; @@ -5085,7 +4655,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - actualResult = (int)value._sign; + actualResult = value._sign; } result = (TOther)(object)actualResult; @@ -5101,7 +4671,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (nint.Size == 8) { - bits = (ulong)value._bits[0]; + bits = value._bits[0]; } else { @@ -5109,11 +4679,11 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits.Length >= 2) { - bits = (ulong)value._bits[1]; + bits = value._bits[1]; bits <<= 32; } - bits |= (ulong)value._bits[0]; + bits |= value._bits[0]; } if (IsNegative(value)) @@ -5125,7 +4695,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - actualResult = (long)value._sign; + actualResult = value._sign; } result = (TOther)(object)actualResult; @@ -5142,36 +4712,36 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (nint.Size == 8) { - lowerBits = (ulong)value._bits[0]; + lowerBits = value._bits[0]; if (value._bits.Length >= 2) { - upperBits = (ulong)value._bits[1]; + upperBits = value._bits[1]; } } else { if (value._bits.Length >= 4) { - upperBits = (ulong)value._bits[3]; + upperBits = value._bits[3]; upperBits <<= 32; } if (value._bits.Length >= 3) { - upperBits |= (ulong)value._bits[2]; + upperBits |= value._bits[2]; } if (value._bits.Length >= 2) { - lowerBits = (ulong)value._bits[1]; + lowerBits = value._bits[1]; lowerBits <<= 32; } - lowerBits |= (ulong)value._bits[0]; + lowerBits |= value._bits[0]; } - UInt128 bits = new UInt128(upperBits, lowerBits); + UInt128 bits = new(upperBits, lowerBits); if (IsNegative(value)) { @@ -5295,7 +4865,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (nint.Size == 8) { - bits = (ulong)value._bits[0]; + bits = value._bits[0]; } else { @@ -5303,11 +4873,11 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (value._bits.Length >= 2) { - bits = (ulong)value._bits[1]; + bits = value._bits[1]; bits <<= 32; } - bits |= (ulong)value._bits[0]; + bits |= value._bits[0]; } if (IsNegative(value)) @@ -5336,36 +4906,36 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (nint.Size == 8) { - lowerBits = (ulong)value._bits[0]; + lowerBits = value._bits[0]; if (value._bits.Length >= 2) { - upperBits = (ulong)value._bits[1]; + upperBits = value._bits[1]; } } else { if (value._bits.Length >= 4) { - upperBits = (ulong)value._bits[3]; + upperBits = value._bits[3]; upperBits <<= 32; } if (value._bits.Length >= 3) { - upperBits |= (ulong)value._bits[2]; + upperBits |= value._bits[2]; } if (value._bits.Length >= 2) { - lowerBits = (ulong)value._bits[1]; + lowerBits = value._bits[1]; lowerBits <<= 32; } - lowerBits |= (ulong)value._bits[0]; + lowerBits |= value._bits[0]; } - UInt128 bits = new UInt128(upperBits, lowerBits); + UInt128 bits = new(upperBits, lowerBits); if (IsNegative(value)) { @@ -5426,18 +4996,22 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va /// public static BigInteger operator >>>(BigInteger value, int shiftAmount) { - value.AssertValid(); - if (shiftAmount == 0) + { return value; + } if (shiftAmount == int.MinValue) - return value << kcbitUint << unchecked(int.MinValue - kcbitUint); + { + return value << BitsPerUInt32 << MinIntSplitShift; + } if (shiftAmount < 0) + { return value << -shiftAmount; + } - (int digitShift, int smallShift) = Math.DivRem(shiftAmount, BigIntegerCalculator.kcbitNuint); + (int digitShift, int smallShift) = Math.DivRem(shiftAmount, BigIntegerCalculator.BitsPerLimb); if (value._bits is null) { @@ -5468,25 +5042,29 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va if (neg && (nint)bits[^1] < 0) { - // For a shift of N x kcbitNuint bit, + // For a shift of N x BitsPerLimb bit, // We check for a special case where its sign bit could be outside the nuint array after 2's complement conversion. // For example given [nuint.MaxValue, nuint.MaxValue, nuint.MaxValue], its 2's complement is [0x01, 0x00, 0x00] - // After a kcbitNuint bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back. + // After a BitsPerLimb bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back. // The expected result is [0x00, 0x00, nuint.MaxValue] (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 ++zLength; - nuint signBit = (nuint)1 << (BigIntegerCalculator.kcbitNuint - 1); + nuint signBit = (nuint)1 << (BigIntegerCalculator.BitsPerLimb - 1); if (bits[^1] == signBit && negLeadingZeroCount == bits.Length - 1) { // When bits are [0, ..., 0, signBit], special handling is required. // Since the bit length remains unchanged in two's complement, the result must be computed directly. --zLength; if (zLength <= 0) - return s_bnMinusOneInt; + { + return s_minusOne; + } if (zLength == 1) + { return new BigInteger(nint.MinValue >>> smallShift); + } nuint[] rgu = new nuint[zLength]; rgu[^1] = signBit >>> smallShift; @@ -5500,8 +5078,8 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va nuint[]? zFromPool = null; Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); zd[^1] = 0; bits.Slice(digitShift).CopyTo(zd); @@ -5531,7 +5109,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va BigInteger result; if (zd.IsEmpty) { - result = neg ? s_bnMinusOneInt : default; + result = neg ? s_minusOne : default; } else if (neg && (nint)zd[^1] < 0) { @@ -5543,8 +5121,10 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va result = new BigInteger(zd, false); } - if (zFromPool != null) + if (zFromPool is not null) + { ArrayPool.Shared.Return(zFromPool); + } return result; Excess: 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 b9bf2cbd42a0b4..f2da580d475fc0 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 @@ -8,6 +8,12 @@ namespace System.Numerics { internal static partial class BigIntegerCalculator { + /// + /// Specifies the minimum number of elements required to trigger a copy operation using an optimized path. + /// + /// + /// This threshold is determined based on benchmarking and may be adjusted in the future to balance the overhead of copying versus the benefits of reduced loop iterations. + /// private const int CopyToThreshold = 8; private static void CopyTail(ReadOnlySpan source, Span dest, int start) @@ -60,10 +66,11 @@ public static void AddSelf(Span left, ReadOnlySpan right) { left[i] = AddWithCarry(left[i], right[i], carry, out carry); } + for (; carry != 0 && i < left.Length; i++) { nuint sum = left[i] + carry; - carry = (sum < carry) ? (nuint)1 : (nuint)0; + carry = (sum < carry) ? 1 : (nuint)0; left[i] = sum; } @@ -118,11 +125,12 @@ public static void SubtractSelf(Span left, ReadOnlySpan right) { left[i] = SubWithBorrow(left[i], right[i], borrow, out borrow); } + for (; borrow != 0 && i < left.Length; i++) { nuint val = left[i]; left[i] = val - borrow; - borrow = (val < borrow) ? (nuint)1 : (nuint)0; + borrow = (val < borrow) ? 1 : (nuint)0; } // Assertion failing per https://github.com/dotnet/runtime/issues/97780 @@ -144,7 +152,7 @@ private static void Add(ReadOnlySpan left, Span bits, int startInd for (; i < left.Length; i++) { nuint sum = left[i] + carry; - carry = (sum < carry) ? (nuint)1 : (nuint)0; + carry = (sum < carry) ? 1 : (nuint)0; bits[i] = sum; } @@ -155,7 +163,7 @@ private static void Add(ReadOnlySpan left, Span bits, int startInd for (; i < left.Length;) { nuint sum = left[i] + carry; - carry = (sum < carry) ? (nuint)1 : (nuint)0; + carry = (sum < carry) ? 1 : (nuint)0; bits[i] = sum; i++; @@ -195,7 +203,7 @@ private static void Subtract(ReadOnlySpan left, Span bits, int sta { nuint val = left[i]; nuint diff = val - borrow; - borrow = (diff > val) ? (nuint)1 : (nuint)0; + borrow = (diff > val) ? 1 : (nuint)0; bits[i] = diff; } } @@ -205,7 +213,7 @@ private static void Subtract(ReadOnlySpan left, Span bits, int sta { nuint val = left[i]; nuint diff = val - borrow; - borrow = (diff > val) ? (nuint)1 : (nuint)0; + borrow = (diff > val) ? 1 : (nuint)0; bits[i] = diff; i++; 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 8ac18081eb9533..a7b3afb13675ca 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 @@ -8,18 +8,18 @@ namespace System.Numerics { internal static partial class BigIntegerCalculator { + internal #if DEBUG - // Mutable for unit testing... - internal static + static // Mutable for unit testing... #else - internal const + const #endif int DivideBurnikelZieglerThreshold = 64; public static void Divide(ReadOnlySpan left, nuint right, Span quotient, out nuint remainder) { InitializeForDebug(quotient); - nuint carry = (nuint)0; + nuint carry = 0; DivideCore(left, right, quotient, ref carry); remainder = carry; } @@ -27,7 +27,7 @@ public static void Divide(ReadOnlySpan left, nuint right, Span quo public static void Divide(ReadOnlySpan left, nuint right, Span quotient) { InitializeForDebug(quotient); - nuint carry = (nuint)0; + nuint carry = 0; DivideCore(left, right, quotient, ref carry); } @@ -53,7 +53,7 @@ public static nuint Remainder(ReadOnlySpan left, nuint right) Debug.Assert(left.Length >= 1); // Same as above, but only computing the remainder. - nuint carry = (nuint)0; + nuint carry = 0; for (int i = left.Length - 1; i >= 0; i--) { DivRem(carry, left[i], right, out carry); @@ -99,15 +99,17 @@ public static void Divide(ReadOnlySpan left, ReadOnlySpan right, S // 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 nuint[StackAllocThreshold] - : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); + Span leftCopy = (left.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); left.CopyTo(leftCopy); DivideGrammarSchool(leftCopy, right, quotient); if (leftCopyFromPool != null) + { ArrayPool.Shared.Return(leftCopyFromPool); + } } else { @@ -135,14 +137,16 @@ public static void Remainder(ReadOnlySpan left, ReadOnlySpan right int quotientLength = left.Length - right.Length + 1; nuint[]? quotientFromPool = null; - Span quotient = (quotientLength <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); + Span quotient = (quotientLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : quotientFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); DivideBurnikelZiegler(left, right, quotient, remainder); if (quotientFromPool != null) + { ArrayPool.Shared.Return(quotientFromPool); + } } } @@ -172,9 +176,9 @@ private static void DivRem(Span left, ReadOnlySpan right, Span leftCopy = (left.Length <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); + Span leftCopy = (left.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); left.CopyTo(leftCopy); nuint[]? quotientActualFromPool = null; @@ -184,9 +188,9 @@ stackalloc nuint[StackAllocThreshold] { int quotientLength = left.Length - right.Length + 1; - quotientActual = (quotientLength <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : quotientActualFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); + quotientActual = (quotientLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : quotientActualFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); } else { @@ -196,9 +200,14 @@ stackalloc nuint[StackAllocThreshold] DivideBurnikelZiegler(leftCopy, right, quotientActual, left); if (quotientActualFromPool != null) + { ArrayPool.Shared.Return(quotientActualFromPool); + } + if (leftCopyFromPool != null) + { ArrayPool.Shared.Return(leftCopyFromPool); + } } } @@ -218,18 +227,18 @@ private static void DivideGrammarSchool(Span left, ReadOnlySpan ri // will be more precise. Additionally we'll get r = a % b. nuint divHi = right[right.Length - 1]; - nuint divLo = right.Length > 1 ? right[right.Length - 2] : (nuint)0; + nuint divLo = right.Length > 1 ? right[right.Length - 2] : 0; // We measure the leading zeros of the divisor int shift = nint.Size == 8 ? BitOperations.LeadingZeroCount((ulong)divHi) : BitOperations.LeadingZeroCount((uint)divHi); - int backShift = kcbitNuint - shift; + int backShift = BitsPerLimb - shift; // And, we make sure the most significant bit is set if (shift > 0) { - nuint divNx = right.Length > 2 ? right[right.Length - 3] : (nuint)0; + nuint divNx = right.Length > 2 ? right[right.Length - 3] : 0; divHi = (divHi << shift) | (divLo >> backShift); divLo = (divLo << shift) | (divNx >> backShift); @@ -240,16 +249,16 @@ private static void DivideGrammarSchool(Span left, ReadOnlySpan ri for (int i = left.Length; i >= right.Length; i--) { int n = i - right.Length; - nuint t = (uint)i < (uint)left.Length ? left[i] : (nuint)0; + nuint t = (uint)i < (uint)left.Length ? left[i] : 0; nuint valHi1 = t; nuint valHi0 = left[i - 1]; - nuint valLo = i > 1 ? left[i - 2] : (nuint)0; + nuint valLo = i > 1 ? left[i - 2] : 0; // We shifted the divisor, we shift the dividend too if (shift > 0) { - nuint valNx = i > 2 ? left[i - 3] : (nuint)0; + nuint valNx = i > 2 ? left[i - 3] : 0; valHi1 = (valHi1 << shift) | (valHi0 >> backShift); valHi0 = (valHi0 << shift) | (valLo >> backShift); @@ -262,7 +271,9 @@ private static void DivideGrammarSchool(Span left, ReadOnlySpan ri // Our first guess may be a little bit to big while (DivideGuessTooBig(digit, valHi1, valHi0, valLo, divHi, divLo)) + { --digit; + } if (digit > 0) { @@ -282,10 +293,14 @@ private static void DivideGrammarSchool(Span left, ReadOnlySpan ri // We have the digit! if ((uint)n < (uint)quotient.Length) + { quotient[n] = digit; + } if ((uint)i < (uint)left.Length) + { left[i] = 0; + } } } @@ -295,7 +310,7 @@ private static nuint AddDivisor(Span left, ReadOnlySpan right) // Repairs the dividend, if the last subtract was too much - nuint carry = (nuint)0; + nuint carry = 0; for (int i = 0; i < right.Length; i++) { @@ -326,7 +341,9 @@ private static bool DivideGuessTooBig(nuint q, nuint valHi1, nuint valHi0, chkHiLo += chkLoHi; if (chkHiLo < chkLoHi) + { chkHiHi++; + } return (chkHiHi > valHi1) || ((chkHiHi == valHi1) && ((chkHiLo > valHi0) || ((chkHiLo == valHi0) && (chkLoLo > valLo)))); @@ -338,18 +355,14 @@ private static void DivideBurnikelZiegler(ReadOnlySpan left, ReadOnlySpan 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 - || remainder.Length == 0); + Debug.Assert(remainder.Length == left.Length || remainder.Length == 0); // Executes the Burnikel-Ziegler algorithm for computing q = a / b. - // // Burnikel, C., Ziegler, J.: Fast recursive division. Research Report MPI-I-98-1-022, MPI Saarbrücken, 1998 - // Fast recursive division: Algorithm 3 int n; { - // m = min{1< right.Length} int m = (int)BitOperations.RoundUpToPowerOf2((uint)right.Length / (uint)DivideBurnikelZieglerThreshold + 1); int j = (right.Length + m - 1) / m; // Ceil(right.Length/m) @@ -363,9 +376,9 @@ private static void DivideBurnikelZiegler(ReadOnlySpan left, ReadOnlySpan nuint[]? bFromPool = null; - Span b = (n <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : bFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); + Span b = (n <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : bFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); int aLength = left.Length + sigmaDigit; @@ -376,18 +389,19 @@ stackalloc nuint[StackAllocThreshold] ? BitOperations.LeadingZeroCount((ulong)left[^1]) : BitOperations.LeadingZeroCount((uint)left[^1]); if (leftLzc <= sigmaSmall) + { ++aLength; + } nuint[]? aFromPool = null; - Span a = (aLength <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : aFromPool = ArrayPool.Shared.Rent(aLength)).Slice(0, aLength); + Span a = (aLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : aFromPool = ArrayPool.Shared.Rent(aLength)).Slice(0, aLength); - // 4. normalize static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, Span bits) { - Debug.Assert((uint)sigmaSmall <= kcbitNuint); + Debug.Assert((uint)sigmaSmall <= BitsPerLimb); Debug.Assert(src.Length + sigmaDigit <= bits.Length); bits.Slice(0, sigmaDigit).Clear(); @@ -398,8 +412,8 @@ static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, S if (sigmaSmall != 0) { // Left shift - int carryShift = kcbitNuint - sigmaSmall; - nuint carry = (nuint)0; + int carryShift = BitsPerLimb - sigmaSmall; + nuint carry = 0; for (int i = 0; i < bits.Length; i++) { @@ -420,14 +434,14 @@ static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, S Debug.Assert(t < a.Length || (t == a.Length && (nint)a[^1] >= 0)); nuint[]? rFromPool = null; - Span r = ((n + 1) <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : rFromPool = ArrayPool.Shared.Rent(n + 1)).Slice(0, n + 1); + Span r = ((n + 1) <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : rFromPool = ArrayPool.Shared.Rent(n + 1)).Slice(0, n + 1); nuint[]? zFromPool = null; - Span z = (2 * n <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(2 * n)).Slice(0, 2 * n); + Span z = (2 * n <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : zFromPool = ArrayPool.Shared.Rent(2 * n)).Slice(0, 2 * n); a.Slice((t - 2) * n).CopyTo(z); z.Slice(a.Length - (t - 2) * n).Clear(); @@ -435,9 +449,9 @@ stackalloc nuint[StackAllocThreshold] if (quotientUpper.Length < n) { nuint[]? qFromPool = null; - Span q = (n <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : qFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); + Span q = (n <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : qFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); BurnikelZieglerD2n1n(z, b, q, r); @@ -445,7 +459,9 @@ stackalloc nuint[StackAllocThreshold] q.Slice(0, quotientUpper.Length).CopyTo(quotientUpper); if (qFromPool != null) + { ArrayPool.Shared.Return(qFromPool); + } } else { @@ -461,11 +477,19 @@ stackalloc nuint[StackAllocThreshold] } if (zFromPool != null) + { ArrayPool.Shared.Return(zFromPool); + } + if (bFromPool != null) + { ArrayPool.Shared.Return(bFromPool); + } + if (aFromPool != null) + { ArrayPool.Shared.Return(aFromPool); + } Debug.Assert(r[^1] == 0); Debug.Assert(!r.Slice(0, sigmaDigit).ContainsAnyExcept((nuint)0)); @@ -477,10 +501,10 @@ stackalloc nuint[StackAllocThreshold] if (sigmaSmall != 0) { // Right shift - Debug.Assert((uint)sigmaSmall <= kcbitNuint); + Debug.Assert((uint)sigmaSmall <= BitsPerLimb); - int carryShift = kcbitNuint - sigmaSmall; - nuint carry = (nuint)0; + int carryShift = BitsPerLimb - sigmaSmall; + nuint carry = 0; for (int i = rt.Length - 1; i >= 0; i--) { @@ -497,7 +521,9 @@ stackalloc nuint[StackAllocThreshold] } if (rFromPool != null) + { ArrayPool.Shared.Return(rFromPool); + } } private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) @@ -534,7 +560,7 @@ private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySp } else { - carry = (nuint)0; + carry = 0; quotient.Slice(left.Length).Clear(); DivideCore(left, right[0], quotient, ref carry); } @@ -548,9 +574,9 @@ private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySp else { nuint[]? r1FromPool = null; - Span r1 = (left.Length <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : r1FromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); + Span r1 = (left.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : r1FromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); left.CopyTo(r1); int quotientLength = Math.Min(left.Length - right.Length + 1, quotient.Length); @@ -570,7 +596,9 @@ stackalloc nuint[StackAllocThreshold] } if (r1FromPool != null) + { ArrayPool.Shared.Return(r1FromPool); + } } } @@ -592,15 +620,17 @@ private static void BurnikelZieglerD2n1n(ReadOnlySpan left, ReadOnlySpan< int halfN = right.Length >> 1; nuint[]? r1FromPool = null; - Span r1 = ((right.Length + 1) <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : r1FromPool = ArrayPool.Shared.Rent(right.Length + 1)).Slice(0, right.Length + 1); + Span r1 = ((right.Length + 1) <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : r1FromPool = ArrayPool.Shared.Rent(right.Length + 1)).Slice(0, right.Length + 1); BurnikelZieglerD3n2n(left.Slice(right.Length), left.Slice(halfN, halfN), right, quotient.Slice(halfN), r1); BurnikelZieglerD3n2n(r1.Slice(0, right.Length), left.Slice(0, halfN), right, quotient.Slice(0, halfN), remainder); if (r1FromPool != null) + { ArrayPool.Shared.Return(r1FromPool); + } } private static void BurnikelZieglerD3n2n(ReadOnlySpan left12, ReadOnlySpan left3, ReadOnlySpan right, Span quotient, Span remainder) @@ -621,9 +651,9 @@ private static void BurnikelZieglerD3n2n(ReadOnlySpan left12, ReadOnlySpa ReadOnlySpan b2 = right.Slice(0, n); Span r1 = remainder.Slice(n); nuint[]? dFromPool = null; - Span d = (right.Length <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : dFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); + Span d = (right.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : dFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); if (CompareActual(a1, b1) < 0) { @@ -663,7 +693,9 @@ stackalloc nuint[StackAllocThreshold] SubtractSelf(rr, d); if (dFromPool != null) + { ArrayPool.Shared.Return(dFromPool); + } static void MultiplyActual(ReadOnlySpan left, ReadOnlySpan right, Span bits) { 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 9fd8cacbcfb717..61dfc66049ba63 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 @@ -28,10 +28,11 @@ public FastReducer(ReadOnlySpan modulus, Span r, Span mu, S Debug.Assert(q1.Length == modulus.Length * 2 + 2); Debug.Assert(q2.Length == modulus.Length * 2 + 2); - // Let r = 4^k, with 2^k > m + // Barrett reduction: precompute mu = floor(4^k / m), where k = modulus.Length. + // Start by setting r = 4^k (a 1 in the highest position of a 2k+1 limb number). r[r.Length - 1] = 1; - // Let mu = 4^k / m + // Compute mu = floor(r / m) DivRem(r, modulus, mu); _modulus = modulus; @@ -47,7 +48,9 @@ public int Reduce(Span value) // Trivial: value is shorter if (value.Length < _modulus.Length) + { return value.Length; + } // Let q1 = v/2^(k-1) * mu _q1.Clear(); @@ -58,7 +61,7 @@ public int Reduce(Span value) int l2 = DivMul(_q1.Slice(0, l1), _modulus, _q2, _modulus.Length + 1); // Let v = (v - q2) % 2^(k+1) - i*m - var length = SubMod(value, _q2.Slice(0, l2), _modulus, _modulus.Length + 1); + int length = SubMod(value, _q2.Slice(0, l2), _modulus, _modulus.Length + 1); value = value.Slice(length); value.Clear(); @@ -73,7 +76,7 @@ private static int DivMul(ReadOnlySpan left, ReadOnlySpan right, S // Executes the multiplication algorithm for left and right, // but skips the first k limbs of left, which is equivalent to - // preceding division by 2^(kcbitNuint*k). To spare memory allocations + // preceding division by 2^(BitsPerLimb*k). To spare memory allocations // we write the result to an already allocated memory. if (left.Length > k) @@ -93,13 +96,18 @@ private static int SubMod(Span left, ReadOnlySpan right, ReadOnlyS { // Executes the subtraction algorithm for left and right, // but considers only the first k limbs, which is equivalent to - // preceding reduction by 2^(kcbitNuint*k). Furthermore, if left is + // preceding reduction by 2^(BitsPerLimb*k). Furthermore, if left is // still greater than modulus, further subtractions are used. if (left.Length > k) + { left = left.Slice(0, k); + } + if (right.Length > k) + { right = right.Slice(0, k); + } SubtractSelf(left, right); left = left.Slice(0, ActualLength(left)); 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 24c4af3c31cfcc..75488963f18142 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 @@ -12,7 +12,6 @@ internal static partial class BigIntegerCalculator public static nuint Gcd(nuint left, nuint right) { // Executes the classic Euclidean algorithm. - // https://en.wikipedia.org/wiki/Euclidean_algorithm if (nint.Size == 8 && left <= uint.MaxValue && right <= uint.MaxValue) @@ -51,7 +50,9 @@ public static ulong Gcd(ulong left, ulong right) } if (right != 0) + { return Gcd((uint)right, (uint)(left % right)); + } return left; } @@ -64,9 +65,8 @@ public static nuint Gcd(ReadOnlySpan left, nuint right) // A common divisor cannot be greater than right; // we compute the remainder and continue above... - nuint temp = Remainder(left, right); - - return Gcd(right, temp); + nuint remainder = Remainder(left, right); + return Gcd(right, remainder); } public static void Gcd(ReadOnlySpan left, ReadOnlySpan right, Span result) @@ -79,15 +79,17 @@ public static void Gcd(ReadOnlySpan left, ReadOnlySpan right, Span left.CopyTo(result); nuint[]? rightCopyFromPool = null; - Span rightCopy = (right.Length <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : rightCopyFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); + Span rightCopy = (right.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : rightCopyFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); right.CopyTo(rightCopy); Gcd(result, rightCopy); if (rightCopyFromPool != null) + { ArrayPool.Shared.Return(rightCopyFromPool); + } } private static void Gcd(Span left, Span right) @@ -107,16 +109,18 @@ private static void Gcd(Span left, Span right) while (right.Length > (nint.Size == 4 ? 2 : 1)) { - ulong x, y; - ExtractDigits(left, right, out x, out y); + ExtractDigits(left, right, out ulong x, out ulong y); uint a = 1U, b = 0U; uint c = 0U, d = 1U; int iteration = 0; - // Lehmer's guessing + // Lehmer's guessing: use top digits to compute a 2x2 matrix (a,b,c,d) + // that approximates several GCD steps. Stop when the quotient or + // matrix entries would overflow, or when the Jebelean termination + // condition (t < s || t + r > y - c) indicates the guess may be wrong. while (y != 0) { ulong q, r, s, t; @@ -125,16 +129,18 @@ private static void Gcd(Span left, Span right) q = x / y; if (q > 0xFFFFFFFF) + { break; + } r = a + q * c; s = b + q * d; t = x - q * y; - if (r > 0x7FFFFFFF || s > 0x7FFFFFFF) - break; - if (t < s || t + r > y - c) + if (r > 0x7FFFFFFF || s > 0x7FFFFFFF || t < s || t + r > y - c) + { break; + } a = (uint)r; b = (uint)s; @@ -142,22 +148,26 @@ private static void Gcd(Span left, Span right) ++iteration; if (x == b) + { break; + } // Even iteration q = y / x; if (q > 0xFFFFFFFF) + { break; + } r = d + q * b; s = c + q * a; t = y - q * x; - if (r > 0x7FFFFFFF || s > 0x7FFFFFFF) - break; - if (t < s || t + r > x - b) + if (r > 0x7FFFFFFF || s > 0x7FFFFFFF || t < s || t + r > x - b) + { break; + } d = (uint)r; c = (uint)s; @@ -165,7 +175,9 @@ private static void Gcd(Span left, Span right) ++iteration; if (y == c) + { break; + } } if (b == 0) @@ -180,7 +192,7 @@ private static void Gcd(Span left, Span right) else { // Lehmer's step - var count = LehmerCore(left, right, a, b, c, d); + int count = LehmerCore(left, right, a, b, c, d); left = left.Slice(0, Refresh(left, count)); right = right.Slice(0, Refresh(right, count)); @@ -203,8 +215,8 @@ private static void Gcd(Span left, Span right) if (nint.Size == 4) { - x = (ulong)right[0]; - y = (ulong)left[0]; + x = right[0]; + y = left[0]; if (right.Length > 1) { @@ -214,8 +226,8 @@ private static void Gcd(Span left, Span right) } else { - x = (ulong)right[0]; - y = (ulong)left[0]; + x = right[0]; + y = left[0]; } left = left.Slice(0, Overwrite(left, Gcd(x, y))); @@ -237,7 +249,7 @@ private static int Overwrite(Span buffer, ulong value) buffer.Slice(2).Clear(); } - nuint lo = unchecked((nuint)value); + nuint lo = (nuint)value; nuint hi = (nuint)(value >> 32); buffer[1] = hi; @@ -318,8 +330,8 @@ private static void ExtractDigits(ReadOnlySpan xBuffer, Debug.Assert(yBuffer.Length >= 2); Debug.Assert(xBuffer.Length >= yBuffer.Length); - ulong xh = (ulong)xBuffer[xBuffer.Length - 1]; - ulong xl = (ulong)xBuffer[xBuffer.Length - 2]; + ulong xh = xBuffer[xBuffer.Length - 1]; + ulong xl = xBuffer[xBuffer.Length - 2]; ulong yh, yl; @@ -327,13 +339,13 @@ private static void ExtractDigits(ReadOnlySpan xBuffer, switch (xBuffer.Length - yBuffer.Length) { case 0: - yh = (ulong)yBuffer[yBuffer.Length - 1]; - yl = (ulong)yBuffer[yBuffer.Length - 2]; + yh = yBuffer[yBuffer.Length - 1]; + yl = yBuffer[yBuffer.Length - 2]; break; case 1: yh = 0UL; - yl = (ulong)yBuffer[yBuffer.Length - 1]; + yl = yBuffer[yBuffer.Length - 1]; break; default: @@ -384,8 +396,8 @@ private static int LehmerCore(Span x, long yDigit = d * (long)y[i] - c * (long)x[i] + yCarry; xCarry = xDigit >> 32; yCarry = yDigit >> 32; - x[i] = unchecked((nuint)xDigit); - y[i] = unchecked((nuint)yDigit); + x[i] = (nuint)xDigit; + y[i] = (nuint)yDigit; } } else if (BitConverter.IsLittleEndian) @@ -402,12 +414,12 @@ private static int LehmerCore(Span x, long xCarry = 0L, yCarry = 0L; for (int i = 0; i < length32; i++) { - long xDigit = a * (long)x32[i] - b * (long)y32[i] + xCarry; - long yDigit = d * (long)y32[i] - c * (long)x32[i] + yCarry; + long xDigit = a * x32[i] - b * y32[i] + xCarry; + long yDigit = d * y32[i] - c * x32[i] + yCarry; xCarry = xDigit >> 32; yCarry = yDigit >> 32; - x32[i] = unchecked((uint)xDigit); - y32[i] = unchecked((uint)yDigit); + x32[i] = (uint)xDigit; + y32[i] = (uint)yDigit; } } else @@ -420,8 +432,8 @@ private static int LehmerCore(Span x, Int128 yDigit = d * (Int128)y[i] - c * (Int128)x[i] + yCarry; xCarry = xDigit >> 64; yCarry = yDigit >> 64; - x[i] = unchecked((nuint)(ulong)xDigit); - y[i] = unchecked((nuint)(ulong)yDigit); + x[i] = (nuint)(ulong)xDigit; + y[i] = (nuint)(ulong)yDigit; } } 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 6bb147ad85bb9d..624eb1eaf2a90e 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 @@ -10,7 +10,6 @@ internal static partial class BigIntegerCalculator { // Executes different exponentiation algorithms, which are // based on the classic square-and-multiply method. - // https://en.wikipedia.org/wiki/Exponentiation_by_squaring public static void Pow(nuint value, nuint power, Span bits) @@ -23,15 +22,15 @@ public static void Pow(ReadOnlySpan value, nuint power, Span bits) Debug.Assert(bits.Length == PowBound(power, value.Length)); nuint[]? tempFromPool = null; - Span temp = (bits.Length <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + Span temp = (bits.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); temp.Clear(); nuint[]? valueCopyFromPool = null; - Span valueCopy = (bits.Length <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : valueCopyFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + Span valueCopy = (bits.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); value.CopyTo(valueCopy); valueCopy.Slice(value.Length).Clear(); @@ -40,9 +39,14 @@ stackalloc nuint[StackAllocThreshold] bits.Slice(result.Length).Clear(); if (tempFromPool != null) + { ArrayPool.Shared.Return(tempFromPool); + } + if (valueCopyFromPool != null) + { ArrayPool.Shared.Return(valueCopyFromPool); + } } private static Span PowCore(Span value, int valueLength, Span temp, nuint power, Span result) @@ -58,9 +62,15 @@ private static Span PowCore(Span value, int valueLength, Span>= 1; } @@ -74,12 +84,13 @@ private static int MultiplySelf(ref Span left, int leftLength, ReadOnlySp int resultLength = leftLength + right.Length; Multiply(left.Slice(0, leftLength), right, temp.Slice(0, resultLength)); - left.Clear(); + //switch buffers Span t = left; left = temp; temp = t; + return ActualLength(left.Slice(0, resultLength)); } @@ -91,12 +102,13 @@ private static int SquareSelf(ref Span value, int valueLength, ref Span t = value; value = temp; temp = t; + return ActualLength(value.Slice(0, resultLength)); } @@ -111,10 +123,16 @@ public static int PowBound(nuint power, int valueLength) checked { if ((power & 1) == 1) + { resultLength += valueLength; + } + if (power != 1) + { valueLength += valueLength; + } } + power >>= 1; } @@ -167,13 +185,16 @@ private static nuint PowCore(nuint value, ReadOnlySpan power, nuint modul for (int i = 0; i < power.Length - 1; i++) { nuint p = power[i]; - for (int j = 0; j < kcbitNuint; j++) + for (int j = 0; j < BitsPerLimb; j++) { if (useUlong) { if ((p & 1) == 1) - result = (nuint)(uint)(((ulong)result * value) % modulus); - value = (nuint)(uint)(((ulong)value * value) % modulus); + { + result = (uint)(((ulong)result * value) % modulus); + } + + value = (uint)(((ulong)value * value) % modulus); } else { @@ -182,11 +203,13 @@ private static nuint PowCore(nuint value, ReadOnlySpan power, nuint modul UInt128 prod = (UInt128)(ulong)result * (ulong)value; result = (nuint)(ulong)(prod % (ulong)modulus); } + { UInt128 sq = (UInt128)(ulong)value * (ulong)value; value = (nuint)(ulong)(sq % (ulong)modulus); } } + p >>= 1; } } @@ -208,9 +231,14 @@ private static nuint PowCore(nuint value, nuint power, nuint modulus, nuint resu if (useUlong) { if ((power & 1) == 1) - result = (nuint)(uint)(((ulong)result * value) % modulus); + { + result = (uint)(((ulong)result * value) % modulus); + } + if (power != 1) - value = (nuint)(uint)(((ulong)value * value) % modulus); + { + value = (uint)(((ulong)value * value) % modulus); + } } else { @@ -219,12 +247,14 @@ private static nuint PowCore(nuint value, nuint power, nuint modulus, nuint resu UInt128 prod = (UInt128)(ulong)result * (ulong)value; result = (nuint)(ulong)(prod % (ulong)modulus); } + if (power != 1) { UInt128 sq = (UInt128)(ulong)value * (ulong)value; value = (nuint)(ulong)(sq % (ulong)modulus); } } + power >>= 1; } @@ -248,9 +278,9 @@ public static void Pow(ReadOnlySpan value, nuint power, nuint[]? valueCopyFromPool = null; int size = Math.Max(value.Length, bits.Length); - Span valueCopy = (size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span valueCopy = (size <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); // smallish optimization here: // subsequent operations will copy the elements to the beginning of the buffer, @@ -267,17 +297,22 @@ stackalloc nuint[StackAllocThreshold] } nuint[]? tempFromPool = null; - Span temp = (bits.Length <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + Span temp = (bits.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); temp.Clear(); PowCore(valueCopy, ActualLength(valueCopy), power, modulus, temp, bits); if (valueCopyFromPool != null) + { ArrayPool.Shared.Return(valueCopyFromPool); + } + if (tempFromPool != null) + { ArrayPool.Shared.Return(tempFromPool); + } } public static void Pow(nuint value, ReadOnlySpan power, @@ -297,9 +332,9 @@ public static void Pow(ReadOnlySpan value, ReadOnlySpan power, int size = Math.Max(value.Length, bits.Length); nuint[]? valueCopyFromPool = null; - Span valueCopy = (size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span valueCopy = (size <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); // smallish optimization here: // subsequent operations will copy the elements to the beginning of the buffer, @@ -316,24 +351,29 @@ stackalloc nuint[StackAllocThreshold] } nuint[]? tempFromPool = null; - Span temp = (bits.Length <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + Span temp = (bits.Length <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); temp.Clear(); PowCore(valueCopy, ActualLength(valueCopy), power, modulus, temp, bits); if (valueCopyFromPool != null) + { ArrayPool.Shared.Return(valueCopyFromPool); + } + if (tempFromPool != null) + { ArrayPool.Shared.Return(tempFromPool); + } } + internal #if DEBUG - // Mutable for unit testing... - internal static + static // Mutable for unit testing... #else - internal const + const #endif int ReducerThreshold = 32; @@ -391,46 +431,56 @@ private static void PowCoreBarrett(Span value, int valueLength, { int size = modulus.Length * 2 + 1; nuint[]? rFromPool = null; - Span r = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : rFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span r = ((uint)size <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : rFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); r.Clear(); size = r.Length - modulus.Length + 1; nuint[]? muFromPool = null; - Span mu = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span mu = ((uint)size <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); mu.Clear(); size = modulus.Length * 2 + 2; nuint[]? q1FromPool = null; - Span q1 = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span q1 = ((uint)size <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); q1.Clear(); nuint[]? q2FromPool = null; - Span q2 = ((uint)size <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : q2FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span q2 = ((uint)size <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : q2FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); q2.Clear(); - FastReducer reducer = new FastReducer(modulus, r, mu, q1, q2); + FastReducer reducer = new(modulus, r, mu, q1, q2); if (rFromPool != null) + { ArrayPool.Shared.Return(rFromPool); + } Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); result.CopyTo(bits); bits.Slice(result.Length).Clear(); if (muFromPool != null) + { ArrayPool.Shared.Return(muFromPool); + } + if (q1FromPool != null) + { ArrayPool.Shared.Return(q1FromPool); + } + if (q2FromPool != null) + { ArrayPool.Shared.Return(q2FromPool); + } } /// @@ -441,15 +491,29 @@ stackalloc nuint[StackAllocThreshold] private static int ChooseWindowSize(int expBitLength) { if (expBitLength <= 24) + { return 1; + } + if (expBitLength <= 96) + { return 3; + } + if (expBitLength <= 384) + { return 4; + } + if (expBitLength <= 1536) + { return 5; + } + if (expBitLength <= 4096) + { return 6; + } return 7; } @@ -461,15 +525,21 @@ private static int BitLength(ReadOnlySpan value) { int length = ActualLength(value); if (length == 0) + { return 0; + } nuint topLimb = value[length - 1]; - int bits = (length - 1) * kcbitNuint; + int bits = (length - 1) * BitsPerLimb; if (nint.Size == 8) + { bits += 64 - BitOperations.LeadingZeroCount((ulong)topLimb); + } else + { bits += 32 - BitOperations.LeadingZeroCount((uint)topLimb); + } return bits; } @@ -479,8 +549,8 @@ private static int BitLength(ReadOnlySpan value) /// private static int GetBit(ReadOnlySpan value, int bitIndex) { - int limbIndex = bitIndex / kcbitNuint; - int bitOffset = bitIndex % kcbitNuint; + int limbIndex = bitIndex / BitsPerLimb; + int bitOffset = bitIndex % BitsPerLimb; return (int)((value[limbIndex] >> bitOffset) & 1); } @@ -510,14 +580,18 @@ private static void PowCoreMontgomery(Span value, int valueLength, value.Slice(0, valueLength).CopyTo(shifted.Slice(k)); if (shifted.Length >= modulus.Length) + { DivRem(shifted, modulus, default); + } shifted.Slice(0, k).CopyTo(value); value.Slice(k).Clear(); valueLength = ActualLength(value.Slice(0, k)); if (shiftPool is not null) + { ArrayPool.Shared.Return(shiftPool); + } // Compute R mod n (Montgomery form of 1) and save for later nuint[]? rModNPool = null; @@ -535,8 +609,11 @@ private static void PowCoreMontgomery(Span value, int valueLength, DivRem(oneShifted, modulus, default); oneShifted.Slice(0, k).CopyTo(rModN); if (oneShiftPool is not null) + { ArrayPool.Shared.Return(oneShiftPool); + } } + int rModNLength = ActualLength(rModN); // Choose sliding window size based on exponent bit length @@ -551,7 +628,10 @@ private static void PowCoreMontgomery(Span value, int valueLength, bits.Slice(0, resultLength).CopyTo(originalBits); originalBits.Slice(resultLength).Clear(); if (rModNPool is not null) + { ArrayPool.Shared.Return(rModNPool); + } + return; } @@ -604,16 +684,21 @@ private static void PowCoreMontgomery(Span value, int valueLength, int prevLength = ActualLength(prev); prod.Clear(); - Multiply(prev.Slice(0, prevLength), (ReadOnlySpan)base2.Slice(0, base2Length), + Multiply(prev.Slice(0, prevLength), base2.Slice(0, base2Length), prod.Slice(0, prevLength + base2Length)); MontgomeryReduce(prod, modulus, n0inv); prod.Slice(0, k).CopyTo(table.Slice(i * k, k)); } if (base2Pool is not null) + { ArrayPool.Shared.Return(base2Pool); + } + if (prodPool is not null) + { ArrayPool.Shared.Return(prodPool); + } } // Initialize result to R mod n (bits and temp are untouched from caller) @@ -622,7 +707,9 @@ private static void PowCoreMontgomery(Span value, int valueLength, int resultLen = rModNLength; if (rModNPool is not null) + { ArrayPool.Shared.Return(rModNPool); + } // Left-to-right sliding window exponentiation int bitPos = expBitLength - 1; @@ -688,6 +775,8 @@ private static nuint ComputeMontgomeryInverse(nuint n0) { Debug.Assert((n0 & 1) != 0); + // Newton's method has quadratic convergence: each iteration doubles + // the number of correct bits. Need ceil(log2(BitsPerLimb)) iterations. nuint x = 1; int iterations = nint.Size == 8 ? 6 : 5; for (int i = 0; i < iterations; i++) @@ -695,7 +784,7 @@ private static nuint ComputeMontgomeryInverse(nuint n0) x *= 2 - n0 * x; } - return unchecked((nuint)0 - x); + return 0 - x; } /// @@ -712,7 +801,7 @@ private static int MontgomeryReduce(Span value, ReadOnlySpan modul for (int i = 0; i < k; i++) { - nuint m = unchecked(value[i] * n0inv); + nuint m = value[i] * n0inv; nuint carry = 0; for (int j = 0; j < k; j++) @@ -726,8 +815,8 @@ private static int MontgomeryReduce(Span value, ReadOnlySpan modul else { ulong p = (ulong)m * modulus[j] + value[i + j] + carry; - value[i + j] = (nuint)(uint)p; - carry = (nuint)(uint)(p >> 32); + value[i + j] = (uint)p; + carry = (uint)(p >> 32); } } @@ -737,6 +826,7 @@ private static int MontgomeryReduce(Span value, ReadOnlySpan modul carry = (sum < value[idx]) ? (nuint)1 : 0; value[idx] = sum; } + overflow += carry; } @@ -771,13 +861,14 @@ private static Span PowCore(Span value, int valueLength, for (int i = 0; i < power.Length - 1; i++) { nuint p = power[i]; - for (int j = 0; j < kcbitNuint; j++) + for (int j = 0; j < BitsPerLimb; j++) { if ((p & 1) == 1) { resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); resultLength = Reduce(result.Slice(0, resultLength), modulus); } + valueLength = SquareSelf(ref value, valueLength, ref temp); valueLength = Reduce(value.Slice(0, valueLength), modulus); p >>= 1; @@ -805,11 +896,13 @@ private static Span PowCore(Span value, int valueLength, resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); resultLength = Reduce(result.Slice(0, resultLength), modulus); } + if (power != 1) { valueLength = SquareSelf(ref value, valueLength, ref temp); valueLength = Reduce(value.Slice(0, valueLength), modulus); } + power >>= 1; } @@ -830,13 +923,14 @@ private static Span PowCore(Span value, int valueLength, for (int i = 0; i < power.Length - 1; i++) { nuint p = power[i]; - for (int j = 0; j < kcbitNuint; j++) + for (int j = 0; j < BitsPerLimb; j++) { if ((p & 1) == 1) { resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); resultLength = reducer.Reduce(result.Slice(0, resultLength)); } + valueLength = SquareSelf(ref value, valueLength, ref temp); valueLength = reducer.Reduce(value.Slice(0, valueLength)); p >>= 1; @@ -864,11 +958,13 @@ private static Span PowCore(Span value, int valueLength, resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); resultLength = reducer.Reduce(result.Slice(0, resultLength)); } + if (power != 1) { valueLength = SquareSelf(ref value, valueLength, ref temp); valueLength = reducer.Reduce(value.Slice(0, valueLength)); } + power >>= 1; } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs index b634c501f1f1e2..ac23b451fe80f4 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs @@ -14,7 +14,7 @@ public static void RotateLeft(Span bits, long rotateLeftAmount) { Debug.Assert(Math.Abs(rotateLeftAmount) <= 0x80000000); - int digitShiftMax = (int)((long)0x80000000 / kcbitNuint); + int digitShiftMax = (int)(0x80000000 / BitsPerLimb); int digitShift = digitShiftMax; int smallShift = 0; @@ -22,14 +22,18 @@ public static void RotateLeft(Span bits, long rotateLeftAmount) if (rotateLeftAmount < 0) { if (rotateLeftAmount != -0x80000000) - (digitShift, smallShift) = Math.DivRem(-(int)rotateLeftAmount, kcbitNuint); + { + (digitShift, smallShift) = Math.DivRem(-(int)rotateLeftAmount, BitsPerLimb); + } RotateRight(bits, digitShift % bits.Length, smallShift); } else { if (rotateLeftAmount != 0x80000000) - (digitShift, smallShift) = Math.DivRem((int)rotateLeftAmount, kcbitNuint); + { + (digitShift, smallShift) = Math.DivRem((int)rotateLeftAmount, BitsPerLimb); + } RotateLeft(bits, digitShift % bits.Length, smallShift); } @@ -43,7 +47,9 @@ public static void RotateLeft(Span bits, int digitShift, int smallShift) bits[0] |= carry; if (digitShift == 0) + { return; + } SwapUpperAndLower(bits, bits.Length - digitShift); } @@ -56,7 +62,9 @@ public static void RotateRight(Span bits, int digitShift, int smallShift) bits[^1] |= carry; if (digitShift == 0) + { return; + } SwapUpperAndLower(bits, digitShift); } @@ -75,9 +83,9 @@ private static void SwapUpperAndLower(Span bits, int lowerLength) int tmpLength = Math.Min(lowerLength, upperLength); nuint[]? tmpFromPool = null; - Span tmp = ((uint)tmpLength <= StackAllocThreshold ? - stackalloc nuint[StackAllocThreshold] - : tmpFromPool = ArrayPool.Shared.Rent(tmpLength)).Slice(0, tmpLength); + Span tmp = ((uint)tmpLength <= StackAllocThreshold + ? stackalloc nuint[StackAllocThreshold] + : tmpFromPool = ArrayPool.Shared.Rent(tmpLength)).Slice(0, tmpLength); if (upperLength < lowerLength) { @@ -93,18 +101,22 @@ stackalloc nuint[StackAllocThreshold] } if (tmpFromPool != null) + { ArrayPool.Shared.Return(tmpFromPool); + } } public static void LeftShiftSelf(Span bits, int shift, out nuint carry) { - Debug.Assert((uint)shift < kcbitNuint); + Debug.Assert((uint)shift < BitsPerLimb); carry = 0; if (shift == 0 || bits.IsEmpty) + { return; + } - int back = kcbitNuint - shift; + int back = BitsPerLimb - shift; if (Vector128.IsHardwareAccelerated) { @@ -113,6 +125,8 @@ public static void LeftShiftSelf(Span bits, int shift, out nuint carry) ref nuint start = ref MemoryMarshal.GetReference(bits); int offset = bits.Length; + // Each vector load needs one extra element below it to source the carry bits + // that shift in from the lower limbs, hence the +1 minimum. while (Vector512.IsHardwareAccelerated && offset >= Vector512.Count + 1) { Vector512 current = Vector512.LoadUnsafe(ref start, (nuint)(offset - Vector512.Count)) << shift; @@ -165,15 +179,18 @@ public static void LeftShiftSelf(Span bits, int shift, out nuint carry) } } } + public static void RightShiftSelf(Span bits, int shift, out nuint carry) { - Debug.Assert((uint)shift < kcbitNuint); + Debug.Assert((uint)shift < BitsPerLimb); carry = 0; if (shift == 0 || bits.IsEmpty) + { return; + } - int back = kcbitNuint - shift; + int back = BitsPerLimb - shift; if (Vector128.IsHardwareAccelerated) { 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 b59915d40fd6f6..51474f4c267982 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 @@ -140,7 +140,9 @@ static void Karatsuba(ReadOnlySpan value, Span bits) Square(fold, core); if (foldFromPool != null) + { ArrayPool.Shared.Return(foldFromPool); + } SubtractCore(bitsHigh, bitsLow, core); @@ -148,7 +150,9 @@ static void Karatsuba(ReadOnlySpan value, Span bits) AddSelf(bits.Slice(n), core); if (coreFromPool != null) + { ArrayPool.Shared.Return(coreFromPool); + } } static void Naive(ReadOnlySpan value, Span bits) @@ -164,7 +168,7 @@ static void Naive(ReadOnlySpan value, Span bits) // ATTENTION: an ordinary multiplication is safe, because // z_i+j + a_j * a_i + c <= 2(2^n - 1) + (2^n - 1)^2 = - // = 2^(2n) - 1, where n = kcbitNuint. But here we would need + // = 2^(2n) - 1, where n = BitsPerLimb. But here we would need // one extra bit... Hence, we split these operation and do some // extra shifts. if (nint.Size == 8) @@ -178,8 +182,12 @@ static void Naive(ReadOnlySpan value, Span bits) UInt128 digit1 = (UInt128)(ulong)bits[i + j] + carry; UInt128 digit2 = (UInt128)(ulong)value[j] * (ulong)v; bits[i + j] = (nuint)(ulong)(digit1 + (digit2 << 1)); + // We need digit1 + 2*digit2, but that could overflow UInt128. + // Instead, compute (digit2 + digit1/2) >> 63 which gives the + // same carry without needing an extra bit of precision. carry = (digit2 + (digit1 >> 1)) >> 63; } + UInt128 digits = (UInt128)(ulong)v * (ulong)v + carry; bits[i + i] = (nuint)(ulong)digits; bits[i + i + 1] = (nuint)(ulong)(digits >> 64); @@ -195,12 +203,13 @@ static void Naive(ReadOnlySpan value, Span bits) { ulong digit1 = bits[i + j] + carry; ulong digit2 = (ulong)value[j] * v; - bits[i + j] = (nuint)(uint)(digit1 + (digit2 << 1)); + bits[i + j] = (uint)(digit1 + (digit2 << 1)); carry = (digit2 + (digit1 >> 1)) >> 31; } + ulong digits = (ulong)v * v + carry; - bits[i + i] = (nuint)(uint)digits; - bits[i + i + 1] = (nuint)(uint)(digits >> 32); + bits[i + i] = (uint)digits; + bits[i + i + 1] = (uint)(digits >> 32); } } } @@ -242,13 +251,21 @@ public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, // which are smaller in DEBUG mode for testing purpose. if (right.Length < MultiplyKaratsubaThreshold) + { Naive(left, right, bits); + } else if ((left.Length + 1) >> 1 is int n && right.Length <= n) + { RightSmall(left, right, bits, n); + } else if (right.Length < MultiplyToom3Threshold) + { Karatsuba(left, right, bits, n); + } else + { Toom3(left, right, bits); + } static void Toom3(ReadOnlySpan left, ReadOnlySpan right, Span bits) { @@ -336,9 +353,13 @@ static void Toom25(ReadOnlySpan left, ReadOnlySpan right, Span left, ReadOnlySpan right, Span< Multiply(leftFold, rightFold, core); if (leftFoldFromPool != null) + { ArrayPool.Shared.Return(leftFoldFromPool); + } if (rightFoldFromPool != null) + { ArrayPool.Shared.Return(rightFoldFromPool); + } // ... compute z_1 = z_a * z_b - z_0 - z_2 = a_0 * b_1 + a_1 * b_0 SubtractCore(bitsLow, bitsHigh, core); @@ -485,7 +510,9 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span< AddSelf(bits.Slice(n), core.TrimEnd((nuint)0)); if (coreFromPool != null) + { ArrayPool.Shared.Return(coreFromPool); + } } static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span bits, int n) @@ -524,7 +551,9 @@ static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span AddSelf(bitsHigh, carry); if (carryFromPool != null) + { ArrayPool.Shared.Return(carryFromPool); + } } static void Naive(ReadOnlySpan left, ReadOnlySpan right, Span bits) @@ -536,7 +565,7 @@ static void Naive(ReadOnlySpan left, ReadOnlySpan right, Span value, int n, Span buff // Calculate p(-2) = (p(-1) + m_2)*2 { - Debug.Assert(pm2[^1] < ((nuint)1 << (kcbitNuint - 1))); + Debug.Assert(pm2[^1] < ((nuint)1 << (BitsPerLimb - 1))); LeftShiftOne(pm2); } @@ -669,8 +698,8 @@ public void MultiplyOther(in Toom3Data right, int n, Span bits, Span r0 = bits.Slice(0, p0.Length + q0.Length); Span rInf = !qInf.IsEmpty - ? bits.Slice(4 * n, pInf.Length + qInf.Length) - : default; + ? bits.Slice(4 * n, pInf.Length + qInf.Length) + : default; Span r1 = buffer.Slice(0, p1.Length + q1.Length); Span rm1 = buffer.Slice(rLength, pm1.Length + qm1.Length); @@ -696,6 +725,7 @@ public void MultiplyOther(in Toom3Data right, int n, Span bits, Span bits, Span buffer) { Debug.Assert(!buffer.ContainsAnyExcept((nuint)0)); @@ -806,7 +836,9 @@ private static void Toom3CalcResult( AddSelf(bits.Slice(2 * n), z2.TrimEnd((nuint)0)); if (bits.Length >= 3 * n) + { AddSelf(bits.Slice(3 * n), z3.TrimEnd((nuint)0)); + } } } @@ -815,20 +847,22 @@ private static void DivideThreeSelf(Span bits) nuint oneThird, twoThirds; if (nint.Size == 8) { - oneThird = unchecked((nuint)0x5555_5555_5555_5555); - twoThirds = unchecked((nuint)0xAAAA_AAAA_AAAA_AAAA); + ulong oneThird64 = 0x5555_5555_5555_5555; + ulong twoThirds64 = 0xAAAA_AAAA_AAAA_AAAA; + oneThird = (nuint)oneThird64; + twoThirds = (nuint)twoThirds64; } else { - oneThird = (nuint)0x5555_5555; - twoThirds = (nuint)0xAAAA_AAAA; + oneThird = 0x5555_5555; + twoThirds = 0xAAAA_AAAA; } nuint carry = 0; for (int i = bits.Length - 1; i >= 0; i--) { - nuint quo = bits[i] / (nuint)3; - nuint rem = bits[i] - quo * (nuint)3; + nuint quo = bits[i] / 3; + nuint rem = bits[i] - quo * 3; Debug.Assert(carry < 3); @@ -851,9 +885,13 @@ private static void DivideThreeSelf(Span bits) else { if (--rem < 3) + { ++quo; + } else + { rem = 2; + } bits[i] = twoThirds + quo; carry = rem; @@ -862,6 +900,7 @@ private static void DivideThreeSelf(Span bits) Debug.Assert(carry == 0); } + private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan right, Span core) { Debug.Assert(left.Length >= right.Length); @@ -913,22 +952,22 @@ private static void SubtractCore(ReadOnlySpan left, ReadOnlySpan r for (; i < right.Length; i++) { - long digit = ((long)(uint)core[i] + carry) - (uint)left[i] - (uint)right[i]; - core[i] = (nuint)(uint)digit; + long digit = ((uint)core[i] + carry) - (uint)left[i] - (uint)right[i]; + core[i] = (uint)digit; carry = digit >> 32; } for (; i < left.Length; i++) { - long digit = ((long)(uint)core[i] + carry) - (uint)left[i]; - core[i] = (nuint)(uint)digit; + long digit = ((uint)core[i] + carry) - (uint)left[i]; + core[i] = (uint)digit; carry = digit >> 32; } for (; carry != 0 && i < core.Length; i++) { long digit = (uint)core[i] + carry; - core[i] = (nuint)(uint)digit; + core[i] = (uint)digit; carry = digit >> 32; } } @@ -940,11 +979,17 @@ private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan= right.Length); if (rightSign == 0) + { return; + } else if (rightSign > 0) + { AddSelf(left, ref leftSign, right); + } else + { SubtractSelf(left, ref leftSign, right); + } } private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan right) @@ -986,6 +1031,7 @@ private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan left, ref int leftSign, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); @@ -1030,17 +1076,18 @@ private static void LeftShiftOne(Span bits) for (int i = 0; i < bits.Length; i++) { nuint value = carry | bits[i] << 1; - carry = bits[i] >> (kcbitNuint - 1); + carry = bits[i] >> (BitsPerLimb - 1); bits[i] = value; } } + private static void RightShiftOne(Span bits) { nuint carry = 0; for (int i = bits.Length - 1; i >= 0; i--) { nuint value = carry | bits[i] >> 1; - carry = bits[i] << (kcbitNuint - 1); + carry = bits[i] << (BitsPerLimb - 1); bits[i] = value; } } @@ -1049,11 +1096,16 @@ private static void MakeTwosComplement(Span d) { int i = d.IndexOfAnyExcept((nuint)0); if ((uint)i >= (uint)d.Length) + { return; - d[i] = (nuint)0 - d[i]; + } + + d[i] = 0 - d[i]; d = d.Slice(i + 1); for (int j = 0; j < d.Length; j++) + { d[j] = ~d[j]; + } } } } 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 index 46ba4e226ae73b..ccb848cf0f24e9 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs @@ -10,16 +10,16 @@ namespace System.Numerics { internal static partial class BigIntegerCalculator { + internal #if DEBUG - // Mutable for unit testing... - internal static + static // Mutable for unit testing... #else - internal const + const #endif int StackAllocThreshold = 64; - // Number of bits per native-width limb: 32 on 32-bit, 64 on 64-bit. - internal static int kcbitNuint + /// Number of bits per native-width limb: 32 on 32-bit, 64 on 64-bit. + internal static int BitsPerLimb { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => nint.Size * 8; @@ -31,13 +31,18 @@ public static int Compare(ReadOnlySpan left, ReadOnlySpan right) Debug.Assert(left.Length >= right.Length || right.Slice(left.Length).ContainsAnyExcept((nuint)0)); if (left.Length != right.Length) + { return left.Length < right.Length ? -1 : 1; + } int iv = left.Length; while (--iv >= 0 && left[iv] == right[iv]) ; if (iv < 0) + { return 0; + } + return left[iv] < right[iv] ? -1 : 1; } @@ -48,16 +53,23 @@ private static int CompareActual(ReadOnlySpan left, ReadOnlySpan r if (left.Length < right.Length) { if (ActualLength(right.Slice(left.Length)) > 0) + { return -1; + } + right = right.Slice(0, left.Length); } else { if (ActualLength(left.Slice(right.Length)) > 0) + { return +1; + } + left = left.Slice(0, right.Length); } } + return Compare(left, right); } @@ -69,7 +81,10 @@ public static int ActualLength(ReadOnlySpan value) int length = value.Length; while (length > 0 && value[length - 1] == 0) + { --length; + } + return length; } @@ -83,6 +98,7 @@ private static int Reduce(Span bits, ReadOnlySpan modulus) return ActualLength(bits.Slice(0, modulus.Length)); } + return bits.Length; } @@ -103,17 +119,17 @@ internal static nuint AddWithCarry(nuint a, nuint b, nuint carryIn, out nuint ca if (nint.Size == 8) { nuint sum1 = a + b; - nuint c1 = (sum1 < a) ? (nuint)1 : (nuint)0; + nuint c1 = (sum1 < a) ? 1 : (nuint)0; nuint sum2 = sum1 + carryIn; - nuint c2 = (sum2 < sum1) ? (nuint)1 : (nuint)0; + nuint c2 = (sum2 < sum1) ? 1 : (nuint)0; carryOut = c1 + c2; return sum2; } else { ulong sum = (ulong)a + b + carryIn; - carryOut = (nuint)(uint)(sum >> 32); - return (nuint)(uint)sum; + carryOut = (uint)(sum >> 32); + return (uint)sum; } } @@ -128,17 +144,17 @@ internal static nuint SubWithBorrow(nuint a, nuint b, nuint borrowIn, out nuint { // Use unsigned underflow detection nuint diff1 = a - b; - nuint b1 = (diff1 > a) ? (nuint)1 : (nuint)0; + nuint b1 = (diff1 > a) ? 1 : (nuint)0; nuint diff2 = diff1 - borrowIn; - nuint b2 = (diff2 > diff1) ? (nuint)1 : (nuint)0; + nuint b2 = (diff2 > diff1) ? 1 : (nuint)0; borrowOut = b1 + b2; return diff2; } else { long diff = (long)a - (long)b - (long)borrowIn; - borrowOut = (nuint)(uint)(-(int)(diff >> 32)); // 0 or 1 - return (nuint)(uint)diff; + borrowOut = (uint)(-(int)(diff >> 32)); // 0 or 1 + return (uint)diff; } } @@ -151,12 +167,12 @@ internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remain if (nint.Size == 8) { // Compute (hi * 2^64 + lo) / divisor. - // hi < divisor is guaranteed, so quotient fits in 64 bits. - Debug.Assert((ulong)hi < (ulong)divisor); + // hi < divisor is guaranteed by callers, so quotient fits in 64 bits. + Debug.Assert(hi < (ulong)divisor || divisor == 0); if (hi == 0) { - (ulong q, ulong r) = Math.DivRem((ulong)lo, (ulong)divisor); + (ulong q, ulong r) = Math.DivRem(lo, (ulong)divisor); remainder = (nuint)r; return (nuint)q; } @@ -170,8 +186,8 @@ internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remain ulong lo_hi = (ulong)lo >> 32; ulong lo_lo = (ulong)lo & 0xFFFFFFFF; - (ulong q_hi, ulong r1) = Math.DivRem(((ulong)hi << 32) | lo_hi, (ulong)divisor); - (ulong q_lo, ulong r2) = Math.DivRem((r1 << 32) | lo_lo, (ulong)divisor); + (ulong q_hi, ulong r1) = Math.DivRem(((ulong)hi << 32) | lo_hi, divisor); + (ulong q_lo, ulong r2) = Math.DivRem((r1 << 32) | lo_lo, divisor); remainder = (nuint)r2; return (nuint)((q_hi << 32) | q_lo); @@ -181,7 +197,7 @@ internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remain #pragma warning disable SYSLIB5004 // X86Base.DivRem is experimental if (X86Base.X64.IsSupported) { - (ulong q, ulong r) = X86Base.X64.DivRem((ulong)lo, (ulong)hi, (ulong)divisor); + (ulong q, ulong r) = X86Base.X64.DivRem(lo, hi, divisor); remainder = (nuint)r; return (nuint)q; } @@ -197,8 +213,8 @@ internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remain { ulong value = ((ulong)hi << 32) | lo; ulong digit = value / divisor; - remainder = (nuint)(uint)(value - digit * divisor); - return (nuint)(uint)digit; + remainder = (uint)(value - digit * divisor); + return (uint)digit; } } @@ -210,15 +226,15 @@ internal static nuint BigMul(nuint a, nuint b, out nuint low) { if (nint.Size == 8) { - ulong hi = Math.BigMul((ulong)a, (ulong)b, out ulong lo); + ulong hi = Math.BigMul(a, b, out ulong lo); low = (nuint)lo; return (nuint)hi; } else { ulong product = (ulong)a * b; - low = (nuint)(uint)product; - return (nuint)(uint)(product >> 32); + low = (uint)product; + return (uint)(product >> 32); } } @@ -266,8 +282,8 @@ internal static nuint Mul1(Span result, ReadOnlySpan left, nuint m for (; i < length; i++) { ulong product = (ulong)left[i] * multiplier + carry; - result[i] = (nuint)(uint)product; - carry = (nuint)(uint)(product >> 32); + result[i] = (uint)product; + carry = (uint)(product >> 32); } } @@ -293,20 +309,16 @@ internal static nuint MulAdd1(Span result, ReadOnlySpan left, nuin // carry chains complete sequentially behind. for (; i + 3 < length; i += 4) { - UInt128 p0 = (UInt128)(ulong)left[i] * (ulong)multiplier - + (ulong)result[i] + (ulong)carry; + UInt128 p0 = (UInt128)(ulong)left[i] * (ulong)multiplier + (ulong)result[i] + (ulong)carry; result[i] = (nuint)(ulong)p0; - UInt128 p1 = (UInt128)(ulong)left[i + 1] * (ulong)multiplier - + (ulong)result[i + 1] + (ulong)(p0 >> 64); + UInt128 p1 = (UInt128)(ulong)left[i + 1] * (ulong)multiplier + (ulong)result[i + 1] + (ulong)(p0 >> 64); result[i + 1] = (nuint)(ulong)p1; - UInt128 p2 = (UInt128)(ulong)left[i + 2] * (ulong)multiplier - + (ulong)result[i + 2] + (ulong)(p1 >> 64); + UInt128 p2 = (UInt128)(ulong)left[i + 2] * (ulong)multiplier + (ulong)result[i + 2] + (ulong)(p1 >> 64); result[i + 2] = (nuint)(ulong)p2; - UInt128 p3 = (UInt128)(ulong)left[i + 3] * (ulong)multiplier - + (ulong)result[i + 3] + (ulong)(p2 >> 64); + UInt128 p3 = (UInt128)(ulong)left[i + 3] * (ulong)multiplier + (ulong)result[i + 3] + (ulong)(p2 >> 64); result[i + 3] = (nuint)(ulong)p3; carry = (nuint)(ulong)(p3 >> 64); @@ -314,8 +326,7 @@ internal static nuint MulAdd1(Span result, ReadOnlySpan left, nuin for (; i < length; i++) { - UInt128 product = (UInt128)(ulong)left[i] * (ulong)multiplier - + (ulong)result[i] + (ulong)carry; + UInt128 product = (UInt128)(ulong)left[i] * (ulong)multiplier + (ulong)result[i] + (ulong)carry; result[i] = (nuint)(ulong)product; carry = (nuint)(ulong)(product >> 64); } @@ -326,8 +337,8 @@ internal static nuint MulAdd1(Span result, ReadOnlySpan left, nuin { ulong product = (ulong)left[i] * multiplier + result[i] + carry; - result[i] = (nuint)(uint)product; - carry = (nuint)(uint)(product >> 32); + result[i] = (uint)product; + carry = (uint)(product >> 32); } } @@ -401,10 +412,10 @@ internal static nuint SubMul1(Span result, ReadOnlySpan right, nui uint hi = (uint)(product >> 32); uint orig = (uint)result[i]; - result[i] = (nuint)(orig - lo); + result[i] = orig - lo; hi += (orig < lo) ? 1u : 0; - carry = (nuint)hi; + carry = hi; } } 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 bcbef6b29a4d5b..71e6d335271783 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -32,11 +32,11 @@ public readonly struct Complex | NumberStyles.AllowThousands | NumberStyles.AllowExponent | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier); - public static readonly Complex Zero = new Complex(0.0, 0.0); - public static readonly Complex One = new Complex(1.0, 0.0); - public static readonly Complex ImaginaryOne = new Complex(0.0, 1.0); - public static readonly Complex NaN = new Complex(double.NaN, double.NaN); - public static readonly Complex Infinity = new Complex(double.PositiveInfinity, double.PositiveInfinity); + public static readonly Complex Zero = new(0.0, 0.0); + public static readonly Complex One = new(1.0, 0.0); + public static readonly Complex ImaginaryOne = new(0.0, 1.0); + public static readonly Complex NaN = new(double.NaN, double.NaN); + public static readonly Complex Infinity = new(double.PositiveInfinity, double.PositiveInfinity); private const double InverseOfLog10 = 0.43429448190325; // 1 / Log(10) @@ -395,8 +395,7 @@ public static Complex Sinh(Complex value) public static Complex Asin(Complex value) { - double b, bPrime, v; - Asin_Internal(Math.Abs(value.Real), Math.Abs(value.Imaginary), out b, out bPrime, out v); + Asin_Internal(Math.Abs(value.Real), Math.Abs(value.Imaginary), out double b, out double bPrime, out double v); double u; if (bPrime < 0.0) @@ -428,8 +427,7 @@ public static Complex Cosh(Complex value) public static Complex Acos(Complex value) { - double b, bPrime, v; - Asin_Internal(Math.Abs(value.Real), Math.Abs(value.Imaginary), out b, out bPrime, out v); + Asin_Internal(Math.Abs(value.Real), Math.Abs(value.Imaginary), out double b, out double bPrime, out double v); double u; if (bPrime < 0.0) @@ -483,7 +481,7 @@ public static Complex Tanh(Complex value) public static Complex Atan(Complex value) { - Complex two = new Complex(2.0, 0.0); + Complex two = new(2.0, 0.0); return (ImaginaryOne / two) * (Log(One - ImaginaryOne * value) - Log(One + ImaginaryOne * value)); } @@ -908,7 +906,7 @@ public static implicit operator Complex(nuint value) // /// - static Complex IAdditiveIdentity.AdditiveIdentity => new Complex(0.0, 0.0); + static Complex IAdditiveIdentity.AdditiveIdentity => new(0.0, 0.0); // // IDecrementOperators @@ -929,20 +927,20 @@ public static implicit operator Complex(nuint value) // /// - static Complex IMultiplicativeIdentity.MultiplicativeIdentity => new Complex(1.0, 0.0); + static Complex IMultiplicativeIdentity.MultiplicativeIdentity => new(1.0, 0.0); // // INumberBase // /// - static Complex INumberBase.One => new Complex(1.0, 0.0); + static Complex INumberBase.One => new(1.0, 0.0); /// static int INumberBase.Radix => 2; /// - static Complex INumberBase.Zero => new Complex(0.0, 0.0); + static Complex INumberBase.Zero => new(0.0, 0.0); /// static Complex INumberBase.Abs(Complex value) => Abs(value); @@ -2253,7 +2251,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I // /// - static Complex ISignedNumber.NegativeOne => new Complex(-1.0, 0.0); + static Complex ISignedNumber.NegativeOne => new(-1.0, 0.0); // // ISpanFormattable 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 f86ac24f431550..e5340e96c71858 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs @@ -22,7 +22,9 @@ public static void GetDoubleParts(double dbl, out int sign, out int exp, out ulo // Denormalized number. fFinite = true; if (man != 0) + { exp = -1074; + } } else if (exp == 0x7FF) { @@ -51,9 +53,14 @@ public static double GetDoubleFromParts(int sign, int exp, ulong man) // Normalize so that 0x0010 0000 0000 0000 is the highest bit set. int cbitShift = BitOperations.LeadingZeroCount(man) - 11; if (cbitShift < 0) + { man >>= -cbitShift; + } else + { man <<= cbitShift; + } + exp -= cbitShift; Debug.Assert((man & 0xFFF0000000000000) == 0x0010000000000000); @@ -89,13 +96,14 @@ public static double GetDoubleFromParts(int sign, int exp, ulong man) } if (sign < 0) + { bits |= 0x8000000000000000; + } return BitConverter.UInt64BitsToDouble(bits); } - // Do an in-place two's complement. "Dangerous" because it causes - // a mutation and needs to be used with care for immutable types. + /// Performs an in-place two's complement. Use with care for immutable types. public static void DangerousMakeTwosComplement(Span d) { // Given a number: @@ -125,8 +133,7 @@ public static void DangerousMakeTwosComplement(Span d) DangerousMakeOnesComplement(d); } - // Do an in-place one's complement. "Dangerous" because it causes - // a mutation and needs to be used with care for immutable types. + /// Performs an in-place one's complement. Use with care for immutable types. public static void DangerousMakeOnesComplement(Span d) { // Given a number: @@ -166,13 +173,12 @@ public static void DangerousMakeOnesComplement(Span d) } } + /// Branchless abs: arithmetic right shift produces 0 (positive) or -1 (negative) mask, + /// then XOR-and-subtract flips negative values without branching. public static nuint Abs(int a) { - unchecked - { - nuint mask = (nuint)(a >> 31); - return ((nuint)a ^ mask) - mask; - } + nuint mask = (nuint)(a >> 31); + return ((nuint)a ^ mask) - mask; } } } From d480a312b306e8f9db803f5403acbc42482f7a57 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 19 Mar 2026 17:39:45 -0400 Subject: [PATCH 23/27] Fix SubtractSelf callers to restore borrow==0 postcondition - Restore Debug.Assert(borrow == 0) in SubtractSelf, removing the commented-out asserts from #97780. - Fix FastReducer.SubMod: Barrett's quotient can overshoot by up to 2, so add modulus before subtracting when right > left. - Fix MontgomeryReduce: when overflow != 0 the buffer value can be less than modulus; inline the subtraction so borrow cancels the overflow. - Fix Toom-3/Karatsuba signed subtraction: compute right - left directly instead of SubtractSelf + MakeTwosComplement. Remove now-unused MakeTwosComplement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Numerics/BigIntegerCalculator.AddSub.cs | 6 +--- .../BigIntegerCalculator.FastReducer.cs | 8 +++++ .../Numerics/BigIntegerCalculator.PowMod.cs | 10 +++++- .../Numerics/BigIntegerCalculator.SquMul.cs | 35 +++++++++---------- 4 files changed, 34 insertions(+), 25 deletions(-) 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 f2da580d475fc0..6d17a92413b541 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 @@ -110,9 +110,6 @@ public static void SubtractSelf(Span left, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); - // Assertion failing per https://github.com/dotnet/runtime/issues/97780 - // Debug.Assert(CompareActual(left, right) >= 0); - int i = 0; nuint borrow = 0; @@ -133,8 +130,7 @@ public static void SubtractSelf(Span left, ReadOnlySpan right) borrow = (val < borrow) ? 1 : (nuint)0; } - // Assertion failing per https://github.com/dotnet/runtime/issues/97780 - //Debug.Assert(borrow == 0); + Debug.Assert(borrow == 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] 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 61dfc66049ba63..573b7c9694fe0c 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 @@ -109,6 +109,14 @@ private static int SubMod(Span left, ReadOnlySpan right, ReadOnlyS right = right.Slice(0, k); } + // Barrett's quotient estimate may overshoot by up to 2, so right + // can exceed left by up to 2*modulus. Compensate by adding modulus + // until left >= right before subtracting. + while (CompareActual(left, right) < 0) + { + AddSelf(left, modulus); + } + SubtractSelf(left, right); left = left.Slice(0, ActualLength(left)); 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 624eb1eaf2a90e..edf58491ceec0e 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 @@ -838,7 +838,15 @@ private static int MontgomeryReduce(Span value, ReadOnlySpan modul if (overflow != 0 || Compare(upper, modulus) >= 0) { - SubtractSelf(upper, modulus); + // When overflow != 0, the true value is 2^(BitsPerLimb*k) + upper, + // which always exceeds modulus. The subtraction borrow cancels the overflow. + nuint borrow = 0; + for (int j = 0; j < k; j++) + { + upper[j] = SubWithBorrow(upper[j], modulus[j], borrow, out borrow); + } + + Debug.Assert(overflow >= borrow); } upper.CopyTo(value); 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 51474f4c267982..8f16d54bca9f76 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 @@ -1025,9 +1025,15 @@ private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan left: compute right - left directly left = left.Slice(0, right.Length); - SubtractSelf(left, right); - MakeTwosComplement(left); + nuint borrow = 0; + for (int j = 0; j < left.Length; j++) + { + left[j] = SubWithBorrow(right[j], left[j], borrow, out borrow); + } + + Debug.Assert(borrow == 0); } } } @@ -1063,9 +1069,15 @@ private static void SubtractSelf(Span left, ref int leftSign, ReadOnlySpa } else { + // right > left: compute right - left directly left = left.Slice(0, right.Length); - SubtractSelf(left, right); - MakeTwosComplement(left); + nuint borrow = 0; + for (int j = 0; j < left.Length; j++) + { + left[j] = SubWithBorrow(right[j], left[j], borrow, out borrow); + } + + Debug.Assert(borrow == 0); } } } @@ -1092,20 +1104,5 @@ private static void RightShiftOne(Span bits) } } - private static void MakeTwosComplement(Span d) - { - int i = d.IndexOfAnyExcept((nuint)0); - if ((uint)i >= (uint)d.Length) - { - return; - } - - d[i] = 0 - d[i]; - d = d.Slice(i + 1); - for (int j = 0; j < d.Length; j++) - { - d[j] = ~d[j]; - } - } } } From 69bdfb5158451b02b0c1223277598d5466c60260 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 19 Mar 2026 23:42:31 -0400 Subject: [PATCH 24/27] Address PR feedback --- .../src/System.Runtime.Numerics.csproj | 1 + .../src/System/Number.BigInteger.cs | 35 +- .../src/System/Numerics/BigInteger.cs | 540 +++++++++--------- .../Numerics/BigIntegerCalculator.Bitwise.cs | 98 ++++ .../Numerics/BigIntegerCalculator.DivRem.cs | 4 +- .../BigIntegerCalculator.FastReducer.cs | 2 +- .../Numerics/BigIntegerCalculator.GcdInv.cs | 28 +- .../Numerics/BigIntegerCalculator.PowMod.cs | 6 +- .../Numerics/BigIntegerCalculator.Utils.cs | 6 +- .../tests/BigIntegerTests.GenericMath.cs | 8 +- 10 files changed, 424 insertions(+), 304 deletions(-) create mode 100644 src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Bitwise.cs 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 41e3804b045e03..efc9713bdb6861 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -10,6 +10,7 @@ + diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index e9d9d2e2a9316b..d690c03d7ff332 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -25,18 +25,9 @@ internal static partial class Number private static nuint[]? s_cachedPowersOf1e9; - private static ReadOnlySpan UInt32PowersOfTen - { - get - { - if (nint.Size == 8) - { - return MemoryMarshal.Cast(UInt64PowersOfTen); - } - - return MemoryMarshal.Cast(UInt32PowersOfTenCore); - } - } + private static ReadOnlySpan UInt32PowersOfTen => nint.Size == 8 + ? MemoryMarshal.Cast(UInt64PowersOfTen) + : MemoryMarshal.Cast(UInt32PowersOfTenCore); private static ReadOnlySpan UInt32PowersOfTenCore => [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; private static ReadOnlySpan UInt64PowersOfTen => [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; @@ -727,13 +718,17 @@ static nuint MultiplyAdd(Span bits, nuint multiplier, nuint addValue) Debug.Assert(digits < Array.MaxLength); int charsIncludeDigits = Math.Max(digits, charsForBits); - try { scoped ValueStringBuilder sb; if (targetSpan) { if (charsIncludeDigits > destination.Length) { + if (arrayToReturnToPool is not null) + { + ArrayPool.Shared.Return(arrayToReturnToPool); + } + charsWritten = 0; spanSuccess = false; return null; @@ -765,6 +760,11 @@ static nuint MultiplyAdd(Span bits, nuint multiplier, nuint addValue) Debug.Assert(sb.Length == charsIncludeDigits); + if (arrayToReturnToPool is not null) + { + ArrayPool.Shared.Return(arrayToReturnToPool); + } + if (targetSpan) { charsWritten = charsIncludeDigits; @@ -776,13 +776,6 @@ static nuint MultiplyAdd(Span bits, nuint multiplier, nuint addValue) spanSuccess = false; return sb.ToString(); } - finally - { - if (arrayToReturnToPool is not null) - { - ArrayPool.Shared.Return(arrayToReturnToPool); - } - } static void AppendByte(ref ValueStringBuilder sb, byte b, int startHighBit = 7) { @@ -1192,7 +1185,7 @@ public static PowersOf1e9 GetCached(int bufferLength) // 1. The array is fully initialized before being stored. // 2. On ARM64, the .NET GC write barrier uses stlr (store-release), // providing release semantics for reference-type stores. - // 3. Readers have a data dependency (load reference → access elements), + // 3. Readers have a data dependency (load reference -> access elements), // providing natural acquire ordering on all architectures. s_cachedPowersOf1e9 = buffer; } 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 25605931731178..28a2728cdf35aa 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -34,13 +34,20 @@ public readonly struct BigInteger /// Maximum number of limbs in a . Restricts allocations to ~256MB, /// supporting almost 646,456,974 digits. /// - internal static int MaxLength => Array.MaxLength / (nint.Size * 8); + internal static int MaxLength => Array.MaxLength / BigIntegerCalculator.BitsPerLimb; /// /// For values int.MinValue < n <= int.MaxValue, the value is stored in /// and is . /// For all other values, is +1 or -1 and the magnitude is in . /// + /// + /// This field is rather than by design. + /// Using would allow values up to to be stored + /// inline on 64-bit, avoiding an array allocation. However, that would regress the common + /// case of values in the range by requiring wider comparisons and branches + /// everywhere is used. + /// internal readonly int _sign; // Do not rename (binary serialization) internal readonly nuint[]? _bits; // Do not rename (binary serialization) @@ -110,10 +117,14 @@ public BigInteger(long value) _sign = +1; } - _bits = - nint.Size == 8 ? [(nuint)x] : - x <= uint.MaxValue ? [((uint)x)] : - [(uint)x, (uint)(x >> BitsPerUInt32)]; + if (nint.Size == 8) + { + _bits = [(nuint)x]; + } + else + { + _bits = x <= uint.MaxValue ? [((uint)x)] : [(uint)x, (uint)(x >> BitsPerUInt32)]; + } } AssertValid(); @@ -130,10 +141,14 @@ public BigInteger(ulong value) else { _sign = +1; - _bits = - nint.Size == 8 ? [(nuint)value] : - value <= uint.MaxValue ? [((uint)value)] : - [(uint)value, (uint)(value >> BitsPerUInt32)]; + if (nint.Size == 8) + { + _bits = [(nuint)value]; + } + else + { + _bits = value <= uint.MaxValue ? [((uint)value)] : [(uint)value, (uint)(value >> BitsPerUInt32)]; + } } AssertValid(); @@ -196,7 +211,7 @@ public BigInteger(double value) man <<= 11; exp -= 11; - int bitsPerLimb = nint.Size * 8; + int bitsPerLimb = BigIntegerCalculator.BitsPerLimb; // Compute cu and cbit so that exp == bitsPerLimb * cu - cbit and 0 <= cbit < bitsPerLimb. int cu = (exp - 1) / bitsPerLimb + 1; @@ -242,7 +257,11 @@ public BigInteger(decimal value) Debug.Assert(bits.Length == 4 && (bits[3] & DecimalScaleFactorMask) == 0); const int SignMask = int.MinValue; - int size = bits[..3].LastIndexOfAnyExcept(0) + 1; + int size = + bits[2] != 0 ? 3 : + bits[1] != 0 ? 2 : + bits[0] != 0 ? 1 : + 0; if (size == 0) { @@ -269,7 +288,7 @@ public BigInteger(decimal value) _bits[0] |= (nuint)(uint)bits[1] << 32; if (size > 2) { - _bits[nuintSize - 1] = (uint)bits[2]; + _bits[1] = (uint)bits[2]; } } } @@ -290,7 +309,7 @@ public BigInteger(decimal value) _sign = ((bits[3] & SignMask) != 0) ? -1 : +1; // Canonicalize: single-limb values that fit in int should be stored inline - if (_bits is { Length: 1 } && _bits[0] <= int.MaxValue) + if (_bits.Length is 1 && _bits[0] <= int.MaxValue) { _sign = _sign < 0 ? -(int)_bits[0] : (int)_bits[0]; _bits = null; @@ -449,8 +468,6 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE // Pack _bits to remove any wasted space after the twos complement int len = val.AsSpan().LastIndexOfAnyExcept((nuint)0) + 1; - nuint intMinMagnitude = UInt32HighBit; - if (len == 1) { if (val[0] == 1) // abs(-1) @@ -458,12 +475,12 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE this = s_minusOne; return; } - else if (val[0] == intMinMagnitude) // abs(int.MinValue) + else if (val[0] == UInt32HighBit) // abs(int.MinValue) { this = s_int32MinValue; return; } - else if (val[0] < intMinMagnitude) // fits in int as negative + else if (val[0] < UInt32HighBit) // fits in int as negative { _sign = -(int)val[0]; _bits = null; @@ -529,18 +546,16 @@ internal BigInteger(ReadOnlySpan value, bool negative) ThrowHelper.ThrowOverflowException(); } - nuint intMinMagnitude = UInt32HighBit; - if (value.Length == 0) { this = default; } - else if (value.Length == 1 && value[0] < intMinMagnitude) + else if (value.Length == 1 && value[0] < UInt32HighBit) { _sign = negative ? -(int)value[0] : (int)value[0]; _bits = null; } - else if (value.Length == 1 && negative && value[0] == intMinMagnitude) + else if (value.Length == 1 && negative && value[0] == UInt32HighBit) { // Although int.MinValue fits in _sign, we represent this case differently for negate this = s_int32MinValue; @@ -589,8 +604,6 @@ private BigInteger(Span value) ThrowHelper.ThrowOverflowException(); } - nuint intMinMagnitude = UInt32HighBit; - if (value.Length == 0) { // 0 @@ -605,7 +618,7 @@ private BigInteger(Span value) // -1 this = s_minusOne; } - else if (nint.Size == 4 && value[0] == intMinMagnitude) + else if (nint.Size == 4 && value[0] == UInt32HighBit) { // int.MinValue this = s_int32MinValue; @@ -617,27 +630,27 @@ private BigInteger(Span value) NumericsHelpers.DangerousMakeTwosComplement(value); nuint magnitude = value[0]; - if (magnitude < intMinMagnitude) + if (magnitude < UInt32HighBit) { _sign = -(int)magnitude; _bits = null; } - else if (nint.Size == 4) + else if (nint.Size == 8) { - // On 32-bit, magnitude > int.MaxValue always needs _bits + // On 64-bit, check if multi-uint magnitude fits in one nuint _sign = -1; - _bits = [magnitude]; + int trimLen = value.LastIndexOfAnyExcept((nuint)0) + 1; + _bits = trimLen == 1 ? [magnitude] : value[..trimLen].ToArray(); } else { - // On 64-bit, check if multi-uint magnitude fits in one nuint + // On 32-bit, magnitude > int.MaxValue always needs _bits _sign = -1; - int trimLen = value.LastIndexOfAnyExcept((nuint)0) + 1; - _bits = trimLen == 1 ? [magnitude] : value[..trimLen].ToArray(); + _bits = [magnitude]; } } } - else if (value[0] >= intMinMagnitude) + else if (value[0] >= UInt32HighBit) { _sign = +1; _bits = [value[0]]; @@ -831,21 +844,16 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - try - { - // may throw DivideByZeroException - BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient, out nuint rest); + // may throw DivideByZeroException + BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient, out nuint rest); - remainder = dividend._sign < 0 ? -1 * (long)rest : (long)rest; - return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - } - finally + if (bitsFromPool is not null) { - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); - } + ArrayPool.Shared.Return(bitsFromPool); } + + remainder = dividend._sign < 0 ? -(long)rest : (long)rest; + return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); } Debug.Assert(divisor._bits is not null); @@ -927,8 +935,8 @@ public static double Log(BigInteger value, double baseValue) if (nint.Size == 8) { - h = value._bits[value._bits.Length - 1]; - m = value._bits.Length > 1 ? value._bits[value._bits.Length - 2] : 0; + h = value._bits[^1]; + m = value._bits.Length > 1 ? value._bits[^2] : 0; c = BitOperations.LeadingZeroCount(h); b = (long)value._bits.Length * 64 - c; @@ -938,9 +946,9 @@ public static double Log(BigInteger value, double baseValue) } else { - h = (uint)value._bits[value._bits.Length - 1]; - m = value._bits.Length > 1 ? (uint)value._bits[value._bits.Length - 2] : 0; - l = value._bits.Length > 2 ? (uint)value._bits[value._bits.Length - 3] : 0; + h = (uint)value._bits[^1]; + m = value._bits.Length > 1 ? (uint)value._bits[^2] : 0; + l = value._bits.Length > 2 ? (uint)value._bits[^3] : 0; // Measure the exact bit count c = BitOperations.LeadingZeroCount((uint)h); @@ -1064,7 +1072,7 @@ 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)); - result = value._sign < 0 && !exponent.IsEven ? -1 * (long)bitsResult : (long)bitsResult; + result = value._sign < 0 && !exponent.IsEven ? -(long)bitsResult : (long)bitsResult; } else { @@ -1197,7 +1205,7 @@ public bool Equals(long other) } int cu; - int maxLimbs = 8 / nint.Size; + int maxLimbs = sizeof(long) / nint.Size; if ((_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) { return false; @@ -1205,10 +1213,16 @@ public bool Equals(long other) ulong uu = other < 0 ? (ulong)-other : (ulong)other; - return - nint.Size == 8 ? _bits[0] == uu : - cu == 1 ? (uint)_bits[0] == uu : - ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == uu; + if (nint.Size == 8) + { + return _bits[0] == uu; + } + else + { + return cu == 1 + ? (uint)_bits[0] == uu + : ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == uu; + } } [CLSCompliant(false)] @@ -1225,16 +1239,22 @@ public bool Equals(ulong other) } int cu = _bits.Length; - int maxLimbs = 8 / nint.Size; + int maxLimbs = sizeof(long) / nint.Size; if (cu > maxLimbs) { return false; } - return - nint.Size == 8 ? _bits[0] == other : - cu == 1 ? (uint)_bits[0] == other : - ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == other; + if (nint.Size == 8) + { + return _bits[0] == other; + } + else + { + return cu == 1 + ? (uint)_bits[0] == other + : ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) == other; + } } public bool Equals(BigInteger other) @@ -1250,7 +1270,7 @@ public int CompareTo(long other) } int cu; - int maxLimbs = 8 / nint.Size; + int maxLimbs = sizeof(long) / nint.Size; if ((_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) { return _sign; @@ -1259,10 +1279,16 @@ public int CompareTo(long other) ulong uu = other < 0 ? (ulong)-other : (ulong)other; ulong uuTmp; - uuTmp = - nint.Size == 8 ? _bits[0] : - cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : - (uint)_bits[0]; + if (nint.Size == 8) + { + uuTmp = _bits[0]; + } + else + { + uuTmp = cu == 2 + ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) + : (uint)_bits[0]; + } return _sign * uuTmp.CompareTo(uu); } @@ -1281,16 +1307,24 @@ public int CompareTo(ulong other) } int cu = _bits.Length; - int maxLimbs = 8 / nint.Size; + int maxLimbs = sizeof(long) / nint.Size; if (cu > maxLimbs) { return +1; } - ulong uuTmp = - nint.Size == 8 ? _bits[0] : - cu == 2 ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) : - (uint)_bits[0]; + ulong uuTmp; + + if (nint.Size == 8) + { + uuTmp = _bits[0]; + } + else + { + uuTmp = cu == 2 + ? ((ulong)(uint)_bits[1] << 32 | (uint)_bits[0]) + : (uint)_bits[0]; + } return uuTmp.CompareTo(other); } @@ -1502,10 +1536,10 @@ private enum GetBytesMode // because a bits array of all zeros would represent 0, and this case // would be encoded as _bits = null and _sign = 0. Debug.Assert(bits.Length > 0); - Debug.Assert(bits[bits.Length - 1] != 0); + Debug.Assert(bits[^1] != 0); nonZeroLimbIndex = ((ReadOnlySpan)bits).IndexOfAnyExcept((nuint)0); - highLimb = ~bits[bits.Length - 1]; + highLimb = ~bits[^1]; if (bits.Length - 1 == nonZeroLimbIndex) { // This will not overflow because highLimb is less than or equal to nuint.MaxValue - 1. @@ -1517,7 +1551,7 @@ private enum GetBytesMode { Debug.Assert(sign == 1); highByte = 0x00; - highLimb = bits[bits.Length - 1]; + highLimb = bits[^1]; } // Find the most significant byte index within the high limb. @@ -1686,7 +1720,7 @@ private int WriteTo(Span buffer) int msb = Math.Max(0, buffer[..^1].LastIndexOfAnyExcept(highLimb)); // Ensure high bit is 0 if positive, 1 if negative - nuint highBitMask = (nuint)1 << (nint.Size * 8 - 1); + nuint highBitMask = (nuint)1 << (BigIntegerCalculator.BitsPerLimb - 1); bool needExtraLimb = (buffer[msb] & highBitMask) != (highLimb & highBitMask); int count; @@ -1694,7 +1728,7 @@ private int WriteTo(Span buffer) { count = msb + 2; buffer = buffer.Slice(0, count); - buffer[buffer.Length - 1] = highLimb; + buffer[^1] = highLimb; } else { @@ -1743,18 +1777,20 @@ private string DebuggerDisplay // Let `m = n * log10(2)`, the final result would be `x = (k * 10^(m - [m])) * 10^(i+[m])` const double Log10Of2 = 0.3010299956639812; // Log10(2) - int bitsPerLimb = nint.Size * 8; + int bitsPerLimb = BigIntegerCalculator.BitsPerLimb; ulong highBits; + double lowBitsCount; if (nint.Size == 8) { highBits = _bits[^1]; + lowBitsCount = _bits.Length - 1; } else { highBits = ((ulong)_bits[^1] << BitsPerUInt32) + (uint)_bits[^2]; + lowBitsCount = _bits.Length - 2; } - double lowBitsCount = _bits.Length - (nint.Size == 8 ? 1 : 2); double exponentLow = lowBitsCount * bitsPerLimb * Log10Of2; // Max possible length of _bits is int.MaxValue of bytes, @@ -1874,7 +1910,7 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOn } return left._sign < 0 != right._sign < 0 - ? Add(left._bits, left._sign, right._bits, -1 * right._sign) + ? Add(left._bits, left._sign, right._bits, -right._sign) : Subtract(left._bits, left._sign, right._bits, right._sign); } @@ -1961,37 +1997,7 @@ public static explicit operator decimal(BigInteger value) return value._sign; } - int length = value._bits.Length; - - int lo = 0, mi = 0, hi = 0; - - if (nint.Size == 8) - { - // 64-bit: at most 2 nuint limbs for 96 bits - if (length > 2) throw new OverflowException(SR.Overflow_Decimal); - - lo = (int)(uint)value._bits[0]; - mi = (int)(uint)(value._bits[0] >> 32); - if (length > 1) - { - if (value._bits[1] > uint.MaxValue) - { - throw new OverflowException(SR.Overflow_Decimal); - } - - hi = (int)(uint)value._bits[1]; - } - } - else - { - if (length > 3) throw new OverflowException(SR.Overflow_Decimal); - - if (length > 2) hi = (int)(uint)value._bits[2]; - if (length > 1) mi = (int)(uint)value._bits[1]; - if (length > 0) lo = (int)(uint)value._bits[0]; - } - - return new decimal(lo, mi, hi, value._sign < 0, 0); + return checked((decimal)(Int128)value); } public static explicit operator double(BigInteger value) @@ -2005,7 +2011,7 @@ public static explicit operator double(BigInteger value) } int length = bits.Length; - int bitsPerLimb = nint.Size * 8; + int bitsPerLimb = BigIntegerCalculator.BitsPerLimb; // The maximum exponent for doubles is 1023, which corresponds to a limb bit length of 1024. // All BigIntegers with bits[] longer than this evaluate to Double.Infinity (or NegativeInfinity). @@ -2090,16 +2096,24 @@ public static explicit operator long(BigInteger value) } int len = value._bits.Length; - int maxLimbs = 8 / nint.Size; + int maxLimbs = sizeof(long) / nint.Size; if (len > maxLimbs) { throw new OverflowException(SR.Overflow_Int64); } - ulong uu = - nint.Size == 8 ? value._bits[0] : - len > 1 ? ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) : - (uint)value._bits[0]; + ulong uu; + + if (nint.Size == 8) + { + uu = value._bits[0]; + } + else + { + uu = len > 1 + ? ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) + : (uint)value._bits[0]; + } long ll = value._sign > 0 ? (long)uu : -(long)uu; if ((ll > 0 && value._sign > 0) || (ll < 0 && value._sign < 0)) @@ -2197,16 +2211,20 @@ public static explicit operator ulong(BigInteger value) } int len = value._bits.Length; - int maxLimbs = 8 / nint.Size; + int maxLimbs = sizeof(long) / nint.Size; if (len > maxLimbs || value._sign < 0) { throw new OverflowException(SR.Overflow_UInt64); } - return - nint.Size == 8 ? value._bits[0] : - len > 1 ? ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) : - (uint)value._bits[0]; + if (nint.Size == 8) + { + return value._bits[0]; + } + + return len > 1 + ? ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) + : (uint)value._bits[0]; } /// Explicitly converts a big integer to a value. @@ -2448,7 +2466,8 @@ public static implicit operator BigInteger(UInt128 value) [CLSCompliant(false)] public static implicit operator BigInteger(nuint value) => value <= int.MaxValue ? new BigInteger((int)value, null) : new BigInteger(+1, [value]); - public static BigInteger operator &(BigInteger left, BigInteger right) => left.IsZero || right.IsZero ? Zero : + public static BigInteger operator &(BigInteger left, BigInteger right) => + left.IsZero || right.IsZero ? Zero : left._bits is null && right._bits is null ? (BigInteger)(left._sign & right._sign) : BitwiseAnd(ref left, ref right); @@ -2469,7 +2488,8 @@ public static implicit operator BigInteger(UInt128 value) : BitwiseOr(ref left, ref right); } - public static BigInteger operator ^(BigInteger left, BigInteger right) => left._bits is null && right._bits is null + public static BigInteger operator ^(BigInteger left, BigInteger right) => + left._bits is null && right._bits is null ? (BigInteger)(left._sign ^ right._sign) : BitwiseXor(ref left, ref right); @@ -2479,48 +2499,18 @@ public static implicit operator BigInteger(UInt128 value) /// private static BigInteger BitwiseAnd(ref readonly BigInteger left, ref readonly BigInteger right) { - bool leftNeg = left._sign < 0; - bool rightNeg = right._sign < 0; - - ReadOnlySpan xBits = left._bits ?? default; - ReadOnlySpan yBits = right._bits ?? default; int xLen = left._bits?.Length ?? 1; int yLen = right._bits?.Length ?? 1; - nuint xInline = (nuint)left._sign; - nuint yInline = (nuint)right._sign; // AND result length: for positive operands, min length suffices (AND with 0 = 0), // plus 1 for sign extension so the two's complement constructor doesn't // misinterpret a high bit in the top limb as a negative sign. // For negative operands (sign-extended with 1s), we need max length + 1 for sign. - int zLen = (leftNeg || rightNeg) + int zLen = (left._sign < 0 || right._sign < 0) ? Math.Max(xLen, yLen) + 1 : Math.Min(xLen, yLen) + 1; - nuint[]? resultBufferFromPool = null; - Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); - - // Borrow initialized to 1: two's complement is ~x + 1, so the first - // limb gets the +1 via borrow, then it propagates through subsequent limbs. - nuint xBorrow = 1, yBorrow = 1; - - for (int i = 0; i < zLen; i++) - { - nuint xu = GetTwosComplementLimb(xBits, xInline, i, xLen, leftNeg, ref xBorrow); - nuint yu = GetTwosComplementLimb(yBits, yInline, i, yLen, rightNeg, ref yBorrow); - z[i] = xu & yu; - } - - BigInteger result = new(z); - - if (resultBufferFromPool is not null) - { - ArrayPool.Shared.Return(resultBufferFromPool); - } - - return result; + return BitwiseOp(in left, in right, zLen); } /// @@ -2528,40 +2518,9 @@ private static BigInteger BitwiseAnd(ref readonly BigInteger left, ref readonly /// private static BigInteger BitwiseOr(ref readonly BigInteger left, ref readonly BigInteger right) { - bool leftNeg = left._sign < 0; - bool rightNeg = right._sign < 0; - - ReadOnlySpan xBits = left._bits ?? default; - ReadOnlySpan yBits = right._bits ?? default; int xLen = left._bits?.Length ?? 1; int yLen = right._bits?.Length ?? 1; - nuint xInline = (nuint)left._sign; - nuint yInline = (nuint)right._sign; - - int zLen = Math.Max(xLen, yLen) + 1; - - nuint[]? resultBufferFromPool = null; - Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); - - nuint xBorrow = 1, yBorrow = 1; - - for (int i = 0; i < zLen; i++) - { - nuint xu = GetTwosComplementLimb(xBits, xInline, i, xLen, leftNeg, ref xBorrow); - nuint yu = GetTwosComplementLimb(yBits, yInline, i, yLen, rightNeg, ref yBorrow); - z[i] = xu | yu; - } - - BigInteger result = new(z); - - if (resultBufferFromPool is not null) - { - ArrayPool.Shared.Return(resultBufferFromPool); - } - - return result; + return BitwiseOp(in left, in right, Math.Max(xLen, yLen) + 1); } /// @@ -2569,31 +2528,23 @@ private static BigInteger BitwiseOr(ref readonly BigInteger left, ref readonly B /// private static BigInteger BitwiseXor(ref readonly BigInteger left, ref readonly BigInteger right) { - bool leftNeg = left._sign < 0; - bool rightNeg = right._sign < 0; - - ReadOnlySpan xBits = left._bits ?? default; - ReadOnlySpan yBits = right._bits ?? default; int xLen = left._bits?.Length ?? 1; int yLen = right._bits?.Length ?? 1; - nuint xInline = (nuint)left._sign; - nuint yInline = (nuint)right._sign; - - int zLen = Math.Max(xLen, yLen) + 1; + return BitwiseOp(in left, in right, Math.Max(xLen, yLen) + 1); + } + private static BigInteger BitwiseOp(ref readonly BigInteger left, ref readonly BigInteger right, int zLen) + where TOp : struct, BigIntegerCalculator.IBitwiseOp + { nuint[]? resultBufferFromPool = null; Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); - nuint xBorrow = 1, yBorrow = 1; - - for (int i = 0; i < zLen; i++) - { - nuint xu = GetTwosComplementLimb(xBits, xInline, i, xLen, leftNeg, ref xBorrow); - nuint yu = GetTwosComplementLimb(yBits, yInline, i, yLen, rightNeg, ref yBorrow); - z[i] = xu ^ yu; - } + BigIntegerCalculator.BitwiseOp( + left._bits, left._sign, + right._bits, right._sign, + z); BigInteger result = new(z); @@ -2605,39 +2556,6 @@ private static BigInteger BitwiseXor(ref readonly BigInteger left, ref readonly return result; } - /// - /// Returns the i-th limb of a BigInteger in two's complement representation, - /// computed on-the-fly from the magnitude without allocating a temp buffer. - /// For positive values, returns magnitude limbs with zero extension. - /// For negative values, computes ~magnitude + 1 with carry propagation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlineValue, int i, int len, bool isNegative, ref nuint borrow) - { - // Get the magnitude limb (or sign-extension beyond the value) - nuint mag; - if (bits.Length > 0) - { - mag = (uint)i < (uint)bits.Length ? bits[i] : 0; - } - else - { - // Inline value: _sign holds the value directly. - // For negative inline: magnitude is Abs(_sign), stored as positive nuint. - mag = i == 0 ? (isNegative ? NumericsHelpers.Abs((int)inlineValue) : inlineValue) : 0; - } - - if (!isNegative) - { - return (uint)i < (uint)len ? mag : 0; - } - - // Two's complement: ~mag + borrow (borrow starts at 1 for the +1) - nuint tc = ~mag + borrow; - borrow = (tc < ~mag || (tc == 0 && borrow != 0)) ? (nuint)1 : 0; - return (uint)i < (uint)len ? tc : nuint.MaxValue; - } - public static BigInteger operator <<(BigInteger value, int shift) { if (shift == 0) @@ -2806,15 +2724,127 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) return result; } - public static BigInteger operator ~(BigInteger value) => -(value + One); + public static BigInteger operator ~(BigInteger value) + { + value.AssertValid(); + + if (value._bits is null) + { + return ~value._sign; // implicit int -> BigInteger handles int.MinValue + } + + nuint[]? bitsFromPool = null; + BigInteger result; + + if (value._sign >= 0) + { + // ~positive = -(positive + 1): add 1 to magnitude, negate + int size = value._bits.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Add(value._bits, 1, bits); + result = new BigInteger(bits, negative: true); + } + else + { + // ~negative = |negative| - 1: subtract 1 from magnitude + Span bits = ((uint)value._bits.Length <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(value._bits.Length)).Slice(0, value._bits.Length); + + BigIntegerCalculator.Subtract(value._bits, 1, bits); + result = new BigInteger(bits, negative: false); + } + + if (bitsFromPool is not null) + { + ArrayPool.Shared.Return(bitsFromPool); + } + + return result; + } public static BigInteger operator -(BigInteger value) => new BigInteger(-value._sign, value._bits); public static BigInteger operator +(BigInteger value) => value; - public static BigInteger operator ++(BigInteger value) => value + One; + public static BigInteger operator ++(BigInteger value) + { + if (value._bits is null) + { + return (long)value._sign + 1; + } - public static BigInteger operator --(BigInteger value) => value - One; + nuint[]? bitsFromPool = null; + BigInteger result; + + if (value._sign >= 0) + { + int size = value._bits.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Add(value._bits, 1, bits); + result = new BigInteger(bits, negative: false); + } + else + { + Span bits = ((uint)value._bits.Length <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(value._bits.Length)).Slice(0, value._bits.Length); + + BigIntegerCalculator.Subtract(value._bits, 1, bits); + result = new BigInteger(bits, negative: true); + } + + if (bitsFromPool is not null) + { + ArrayPool.Shared.Return(bitsFromPool); + } + + return result; + } + + public static BigInteger operator --(BigInteger value) + { + if (value._bits is null) + { + return (long)value._sign - 1; + } + + nuint[]? bitsFromPool = null; + BigInteger result; + + if (value._sign >= 0) + { + Span bits = ((uint)value._bits.Length <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(value._bits.Length)).Slice(0, value._bits.Length); + + BigIntegerCalculator.Subtract(value._bits, 1, bits); + result = new BigInteger(bits, negative: false); + } + else + { + int size = value._bits.Length + 1; + Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] + : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + + BigIntegerCalculator.Add(value._bits, 1, bits); + result = new BigInteger(bits, negative: true); + } + + if (bitsFromPool is not null) + { + ArrayPool.Shared.Return(bitsFromPool); + } + + return result; + } public static BigInteger operator +(BigInteger left, BigInteger right) { @@ -2824,7 +2854,7 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) } return left._sign < 0 != right._sign < 0 - ? Subtract(left._bits, left._sign, right._bits, -1 * right._sign) + ? Subtract(left._bits, left._sign, right._bits, -right._sign) : Add(left._bits, left._sign, right._bits, right._sign); } @@ -2928,19 +2958,17 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO ? stackalloc nuint[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 + //may throw DivideByZeroException + BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient); + + BigInteger result = new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); + + if (quotientFromPool is not null) { - if (quotientFromPool is not null) - { - ArrayPool.Shared.Return(quotientFromPool); - } + ArrayPool.Shared.Return(quotientFromPool); } + + return result; } Debug.Assert(dividend._bits is not null && divisor._bits is not null); @@ -2989,7 +3017,7 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO { Debug.Assert(dividend._bits is not null); nuint remainder = BigIntegerCalculator.Remainder(dividend._bits, NumericsHelpers.Abs(divisor._sign)); - return dividend._sign < 0 ? -1 * (long)remainder : (long)remainder; + return dividend._sign < 0 ? -(long)remainder : (long)remainder; } Debug.Assert(dividend._bits is not null && divisor._bits is not null); @@ -3143,7 +3171,7 @@ private void AssertValid() // Wasted space: _bits[0] could have been packed into _sign Debug.Assert(_bits.Length > 1 || _bits[0] > int.MaxValue); // Wasted space: leading zeros could have been truncated - Debug.Assert(_bits[_bits.Length - 1] != 0); + Debug.Assert(_bits[^1] != 0); // Arrays larger than this can't fit into a Span Debug.Assert(_bits.Length <= MaxLength); } @@ -3359,7 +3387,7 @@ public static BigInteger TrailingZeroCount(BigInteger value) for (int i = 1; (part == 0) && (i < value._bits.Length); i++) { part = value._bits[i]; - result += (uint)(nint.Size * 8); + result += (uint)BigIntegerCalculator.BitsPerLimb; } result += (ulong)BitOperations.TrailingZeroCount(part); @@ -3397,7 +3425,7 @@ int IBinaryInteger.GetShortestBitLength() if (_sign >= 0) { - result += (nint.Size * 8) - BitOperations.LeadingZeroCount(bits[^1]); + result += BigIntegerCalculator.BitsPerLimb - BitOperations.LeadingZeroCount(bits[^1]); } else { @@ -3412,7 +3440,7 @@ int IBinaryInteger.GetShortestBitLength() part -= 1; } - result += (nint.Size * 8) + 1 - BitOperations.LeadingZeroCount(~part); + result += BigIntegerCalculator.BitsPerLimb + 1 - BitOperations.LeadingZeroCount(~part); } return result; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Bitwise.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Bitwise.cs new file mode 100644 index 00000000000000..50674ee980fd03 --- /dev/null +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Bitwise.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Numerics +{ + internal static partial class BigIntegerCalculator + { + internal interface IBitwiseOp + { + static abstract nuint Invoke(nuint x, nuint y); + } + + internal struct BitwiseAndOp : IBitwiseOp + { + public static nuint Invoke(nuint x, nuint y) => x & y; + } + + internal struct BitwiseOrOp : IBitwiseOp + { + public static nuint Invoke(nuint x, nuint y) => x | y; + } + + internal struct BitwiseXorOp : IBitwiseOp + { + public static nuint Invoke(nuint x, nuint y) => x ^ y; + } + + /// + /// Applies a bitwise operation in two's complement, writing the result into . + /// The caller is responsible for allocating with the correct length. + /// + /// Magnitude limbs of the left operand (empty if inline). + /// The _sign field of the left operand (carries sign and inline value). + /// Magnitude limbs of the right operand (empty if inline). + /// The _sign field of the right operand (carries sign and inline value). + /// Pre-allocated destination span for the result limbs. + public static void BitwiseOp( + ReadOnlySpan left, int leftSign, + ReadOnlySpan right, int rightSign, + Span result) + where TOp : struct, IBitwiseOp + { + bool leftNeg = leftSign < 0; + bool rightNeg = rightSign < 0; + + int xLen = left.Length > 0 ? left.Length : 1; + int yLen = right.Length > 0 ? right.Length : 1; + nuint xInline = (nuint)leftSign; + nuint yInline = (nuint)rightSign; + + // Borrow initialized to 1: two's complement is ~x + 1, so the first + // limb gets the +1 via borrow, then it propagates through subsequent limbs. + nuint xBorrow = 1, yBorrow = 1; + + for (int i = 0; i < result.Length; i++) + { + nuint xu = GetTwosComplementLimb(left, xInline, i, xLen, leftNeg, ref xBorrow); + nuint yu = GetTwosComplementLimb(right, yInline, i, yLen, rightNeg, ref yBorrow); + result[i] = TOp.Invoke(xu, yu); + } + } + + /// + /// Returns the i-th limb of a value in two's complement representation, + /// computed on-the-fly from the magnitude without allocating a temp buffer. + /// For positive values, returns magnitude limbs with zero extension. + /// For negative values, computes ~magnitude + 1 with carry propagation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static nuint GetTwosComplementLimb(ReadOnlySpan bits, nuint inlineValue, int i, int len, bool isNegative, ref nuint borrow) + { + // Get the magnitude limb (or sign-extension beyond the value) + nuint mag; + if (bits.Length > 0) + { + mag = (uint)i < (uint)bits.Length ? bits[i] : 0; + } + else + { + // Inline value: _sign holds the value directly. + // For negative inline: magnitude is Abs(_sign), stored as positive nuint. + mag = i == 0 ? (isNegative ? NumericsHelpers.Abs((int)inlineValue) : inlineValue) : 0; + } + + if (!isNegative) + { + return (uint)i < (uint)len ? mag : 0; + } + + // Two's complement: ~mag + borrow (borrow starts at 1 for the +1) + nuint tc = ~mag + borrow; + borrow = (tc < ~mag || (tc == 0 && borrow != 0)) ? (nuint)1 : 0; + return (uint)i < (uint)len ? tc : nuint.MaxValue; + } + } +} 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 a7b3afb13675ca..88975efc84f605 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 @@ -226,8 +226,8 @@ private static void DivideGrammarSchool(Span left, ReadOnlySpan ri // block of the divisor. Thus, guessing digits of the quotient // will be more precise. Additionally we'll get r = a % b. - nuint divHi = right[right.Length - 1]; - nuint divLo = right.Length > 1 ? right[right.Length - 2] : 0; + nuint divHi = right[^1]; + nuint divLo = right.Length > 1 ? right[^2] : 0; // We measure the leading zeros of the divisor int shift = nint.Size == 8 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 573b7c9694fe0c..2ac3a52a1528f0 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 @@ -30,7 +30,7 @@ public FastReducer(ReadOnlySpan modulus, Span r, Span mu, S // Barrett reduction: precompute mu = floor(4^k / m), where k = modulus.Length. // Start by setting r = 4^k (a 1 in the highest position of a 2k+1 limb number). - r[r.Length - 1] = 1; + r[^1] = 1; // Compute mu = floor(r / m) DivRem(r, modulus, mu); 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 75488963f18142..7711bad0323687 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 @@ -284,9 +284,9 @@ private static void ExtractDigits(ReadOnlySpan xBuffer, Debug.Assert(yBuffer.Length >= 3); Debug.Assert(xBuffer.Length >= yBuffer.Length); - ulong xh = xBuffer[xBuffer.Length - 1]; - ulong xm = xBuffer[xBuffer.Length - 2]; - ulong xl = xBuffer[xBuffer.Length - 3]; + ulong xh = xBuffer[^1]; + ulong xm = xBuffer[^2]; + ulong xl = xBuffer[^3]; ulong yh, ym, yl; @@ -294,21 +294,21 @@ private static void ExtractDigits(ReadOnlySpan xBuffer, switch (xBuffer.Length - yBuffer.Length) { case 0: - yh = yBuffer[yBuffer.Length - 1]; - ym = yBuffer[yBuffer.Length - 2]; - yl = yBuffer[yBuffer.Length - 3]; + yh = yBuffer[^1]; + ym = yBuffer[^2]; + yl = yBuffer[^3]; break; case 1: yh = 0UL; - ym = yBuffer[yBuffer.Length - 1]; - yl = yBuffer[yBuffer.Length - 2]; + ym = yBuffer[^1]; + yl = yBuffer[^2]; break; case 2: yh = 0UL; ym = 0UL; - yl = yBuffer[yBuffer.Length - 1]; + yl = yBuffer[^1]; break; default: @@ -330,8 +330,8 @@ private static void ExtractDigits(ReadOnlySpan xBuffer, Debug.Assert(yBuffer.Length >= 2); Debug.Assert(xBuffer.Length >= yBuffer.Length); - ulong xh = xBuffer[xBuffer.Length - 1]; - ulong xl = xBuffer[xBuffer.Length - 2]; + ulong xh = xBuffer[^1]; + ulong xl = xBuffer[^2]; ulong yh, yl; @@ -339,13 +339,13 @@ private static void ExtractDigits(ReadOnlySpan xBuffer, switch (xBuffer.Length - yBuffer.Length) { case 0: - yh = yBuffer[yBuffer.Length - 1]; - yl = yBuffer[yBuffer.Length - 2]; + yh = yBuffer[^1]; + yl = yBuffer[^2]; break; case 1: yh = 0UL; - yl = yBuffer[yBuffer.Length - 1]; + yl = yBuffer[^1]; break; default: 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 edf58491ceec0e..649f45a405c399 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 @@ -214,7 +214,7 @@ private static nuint PowCore(nuint value, ReadOnlySpan power, nuint modul } } - return PowCore(value, power[power.Length - 1], modulus, result); + return PowCore(value, power[^1], modulus, result); } private static nuint PowCore(nuint value, nuint power, nuint modulus, nuint result) @@ -883,7 +883,7 @@ private static Span PowCore(Span value, int valueLength, } } - return PowCore(value, valueLength, power[power.Length - 1], modulus, result, resultLength, temp); + return PowCore(value, valueLength, power[^1], modulus, result, resultLength, temp); } private static Span PowCore(Span value, int valueLength, @@ -945,7 +945,7 @@ private static Span PowCore(Span value, int valueLength, } } - return PowCore(value, valueLength, power[power.Length - 1], reducer, result, resultLength, temp); + return PowCore(value, valueLength, power[^1], reducer, result, resultLength, temp); } private static Span PowCore(Span value, int valueLength, 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 index ccb848cf0f24e9..176d91f12966db 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs @@ -159,7 +159,7 @@ internal static nuint SubWithBorrow(nuint a, nuint b, nuint borrowIn, out nuint } /// - /// Widening divide: (hi:lo) / divisor → (quotient, remainder). + /// Widening divide: (hi:lo) / divisor -> (quotient, remainder). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remainder) @@ -179,8 +179,8 @@ internal static nuint DivRem(nuint hi, nuint lo, nuint divisor, out nuint remain // When divisor fits in 32 bits, split lo into two 32-bit halves // and chain two native 64-bit divisions (avoids UInt128 overhead): - // (hi * 2^32 + lo_hi) / divisor → (q_hi, r1) [fits: hi < divisor < 2^32] - // (r1 * 2^32 + lo_lo) / divisor → (q_lo, r2) [fits: r1 < divisor < 2^32] + // (hi * 2^32 + lo_hi) / divisor -> (q_hi, r1) [fits: hi < divisor < 2^32] + // (r1 * 2^32 + lo_lo) / divisor -> (q_lo, r2) [fits: r1 < divisor < 2^32] if ((ulong)divisor <= uint.MaxValue) { ulong lo_hi = (ulong)lo >> 32; diff --git a/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs b/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs index 6531b64e3f9d10..721ac8bc3fe9d7 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigIntegerTests.GenericMath.cs @@ -418,7 +418,8 @@ public static void GetShortestBitLengthTest() [Fact] public static void TryWriteBigEndianTest() { - Span destination = stackalloc byte[24]; + int maxBytes = nint.Size == 8 ? 24 : 20; + Span destination = stackalloc byte[maxBytes]; int bytesWritten = 0; Assert.True(BinaryIntegerHelper.TryWriteBigEndian(Zero, destination, out bytesWritten)); @@ -479,13 +480,13 @@ public static void TryWriteBigEndianTest() Assert.False(BinaryIntegerHelper.TryWriteBigEndian(default, Span.Empty, out bytesWritten)); Assert.Equal(0, bytesWritten); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, destination.ToArray()); } [Fact] public static void TryWriteLittleEndianTest() { - Span destination = stackalloc byte[24]; + int maxBytes = nint.Size == 8 ? 24 : 20; + Span destination = stackalloc byte[maxBytes]; int bytesWritten = 0; Assert.True(BinaryIntegerHelper.TryWriteLittleEndian(Zero, destination, out bytesWritten)); @@ -546,7 +547,6 @@ public static void TryWriteLittleEndianTest() Assert.False(BinaryIntegerHelper.TryWriteLittleEndian(default, Span.Empty, out bytesWritten)); Assert.Equal(0, bytesWritten); - Assert.Equal(nint.Size == 8 ? new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } : new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }, destination.ToArray()); } // From 3c7b88c939afbb371497aad9c79c14fc52cb3362 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 20 Mar 2026 08:45:27 -0400 Subject: [PATCH 25/27] Address feedback --- .../src/System.Runtime.Numerics.csproj | 1 + .../src/System/Number.BigInteger.cs | 163 ++++------- .../Numerics/BigInteger.RentedBuffer.cs | 73 +++++ .../src/System/Numerics/BigInteger.cs | 277 +++++------------- .../Numerics/BigIntegerCalculator.DivRem.cs | 139 ++------- .../Numerics/BigIntegerCalculator.GcdInv.cs | 11 +- .../Numerics/BigIntegerCalculator.PowMod.cs | 182 +++--------- .../Numerics/BigIntegerCalculator.ShiftRot.cs | 11 +- .../Numerics/BigIntegerCalculator.SquMul.cs | 67 +---- .../Numerics/BigIntegerCalculator.Utils.cs | 8 - 10 files changed, 271 insertions(+), 661 deletions(-) create mode 100644 src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.RentedBuffer.cs 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 efc9713bdb6861..e6b91e1990ea09 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -11,6 +11,7 @@ + diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index d690c03d7ff332..4b10f89a88d42c 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -339,71 +339,60 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big return ParsingStatus.Failed; } - nuint[]? base1E9FromPool = null; scoped Span base1E9; + ReadOnlySpan intDigits = number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); + int intDigitsEnd = intDigits.IndexOf(0); + if (intDigitsEnd < 0) { - ReadOnlySpan intDigits = number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); - int intDigitsEnd = intDigits.IndexOf(0); - if (intDigitsEnd < 0) + // Check for nonzero digits after the decimal point. + ReadOnlySpan fracDigitsSpan = number.Digits.Slice(intDigits.Length); + foreach (byte digitChar in fracDigitsSpan) { - // Check for nonzero digits after the decimal point. - ReadOnlySpan fracDigitsSpan = number.Digits.Slice(intDigits.Length); - foreach (byte digitChar in fracDigitsSpan) + if (digitChar == '\0') { - if (digitChar == '\0') - { - break; - } + break; + } - if (digitChar != '0') - { - result = default; - return ParsingStatus.Failed; - } + if (digitChar != '0') + { + result = default; + return ParsingStatus.Failed; } } - else - { - intDigits = intDigits.Slice(0, intDigitsEnd); - } + } + else + { + intDigits = intDigits.Slice(0, intDigitsEnd); + } - int base1E9Length = (intDigits.Length + PowersOf1e9.MaxPartialDigits - 1) / PowersOf1e9.MaxPartialDigits; - base1E9 = ( - base1E9Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : base1E9FromPool = ArrayPool.Shared.Rent(base1E9Length)).Slice(0, base1E9Length); + int base1E9Length = (intDigits.Length + PowersOf1e9.MaxPartialDigits - 1) / PowersOf1e9.MaxPartialDigits; + base1E9 = BigInteger.RentedBuffer.Create(base1E9Length, out BigInteger.RentedBuffer base1E9Rental); - int di = base1E9Length; - ReadOnlySpan leadingDigits = intDigits[..(intDigits.Length % PowersOf1e9.MaxPartialDigits)]; - if (leadingDigits.Length != 0) - { - uint.TryParse(leadingDigits, out uint leadingVal); - base1E9[--di] = leadingVal; - } - - intDigits = intDigits.Slice(leadingDigits.Length); - Debug.Assert(intDigits.Length % PowersOf1e9.MaxPartialDigits == 0); + int di = base1E9Length; + ReadOnlySpan leadingDigits = intDigits[..(intDigits.Length % PowersOf1e9.MaxPartialDigits)]; + if (leadingDigits.Length != 0) + { + uint.TryParse(leadingDigits, out uint leadingVal); + base1E9[--di] = leadingVal; + } - for (--di; di >= 0; --di) - { - uint.TryParse(intDigits.Slice(0, PowersOf1e9.MaxPartialDigits), out uint partialVal); - base1E9[di] = partialVal; - intDigits = intDigits.Slice(PowersOf1e9.MaxPartialDigits); - } + intDigits = intDigits.Slice(leadingDigits.Length); + Debug.Assert(intDigits.Length % PowersOf1e9.MaxPartialDigits == 0); - Debug.Assert(intDigits.Length == 0); + for (--di; di >= 0; --di) + { + uint.TryParse(intDigits.Slice(0, PowersOf1e9.MaxPartialDigits), out uint partialVal); + base1E9[di] = partialVal; + intDigits = intDigits.Slice(PowersOf1e9.MaxPartialDigits); } + Debug.Assert(intDigits.Length == 0); + // Estimate limb count needed for the decimal value. double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.BitsPerLimb; // log_{2^BitsPerLimb}(10) int resultLength = checked((int)(digitRatio * number.Scale) + 1 + 2); - nuint[]? resultBufferFromPool = null; - Span resultBuffer = ( - resultLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(resultLength)).Slice(0, resultLength); - resultBuffer.Clear(); + Span resultBuffer = BigInteger.RentedBuffer.Create(resultLength, out BigInteger.RentedBuffer resultRental); int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); int trailingZeroCount = number.Scale - totalDigitCount; @@ -419,15 +408,8 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big result = new BigInteger(resultBuffer, number.IsNegative); - if (base1E9FromPool != null) - { - ArrayPool.Shared.Return(base1E9FromPool); - } - - if (resultBufferFromPool != null) - { - ArrayPool.Shared.Return(resultBufferFromPool); - } + base1E9Rental.Dispose(); + resultRental.Dispose(); return ParsingStatus.OK; @@ -442,22 +424,14 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, { double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.BitsPerLimb; int leadingLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * base1E9.Length) + 3); - nuint[]? leadingFromPool = null; - Span leading = ( - leadingLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : leadingFromPool = ArrayPool.Shared.Rent(leadingLength)).Slice(0, leadingLength); - leading.Clear(); + Span leading = BigInteger.RentedBuffer.Create(leadingLength, out BigInteger.RentedBuffer leadingBuffer); Recursive(powersOf1e9, maxIndex, base1E9, leading); leading = leading.Slice(0, BigIntegerCalculator.ActualLength(leading)); powersOf1e9.MultiplyPowerOfTen(leading, trailingZeroCount, bits); - if (leadingFromPool != null) - { - ArrayPool.Shared.Return(leadingFromPool); - } + leadingBuffer.Dispose(); } else { @@ -490,12 +464,7 @@ static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnly double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.BitsPerLimb; int bufferLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * multiplier1E9Length) + 1 + 2); - nuint[]? bufferFromPool = null; - scoped Span buffer = ( - bufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bufferFromPool = ArrayPool.Shared.Rent(bufferLength)).Slice(0, bufferLength); - buffer.Clear(); + scoped Span buffer = BigInteger.RentedBuffer.Create(bufferLength, out BigInteger.RentedBuffer bufferRental); Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[multiplier1E9Length..], buffer); @@ -509,10 +478,7 @@ static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnly BigIntegerCalculator.AddSelf(bits, buffer.Slice(0, BigIntegerCalculator.ActualLength(buffer))); - if (bufferFromPool != null) - { - ArrayPool.Shared.Return(bufferFromPool); - } + bufferRental.Dispose(); } static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) @@ -862,11 +828,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan base1E9Buffer = ((uint)base1E9BufferLength <= BigIntegerCalculator.StackAllocThreshold ? - stackalloc nuint[base1E9BufferLength] : - (base1E9BufferFromPool = ArrayPool.Shared.Rent(base1E9BufferLength))).Slice(0, base1E9BufferLength); - base1E9Buffer.Clear(); + Span base1E9Buffer = BigInteger.RentedBuffer.Create(base1E9BufferLength, out BigInteger.RentedBuffer base1E9Rental); BigIntegerToBase1E9(value._bits, base1E9Buffer, out int written); @@ -985,10 +947,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan.Shared.Return(base1E9BufferFromPool); - } + base1E9Rental.Dispose(); return strResult; } @@ -1059,16 +1018,10 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn } int upperLength = bits.Length - powOfTen.Length - omittedLength + 1; - nuint[]? upperFromPool = null; - Span upper = ((uint)upperLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : upperFromPool = ArrayPool.Shared.Rent(upperLength)).Slice(0, upperLength); + Span upper = BigInteger.RentedBuffer.Create(upperLength, out BigInteger.RentedBuffer upperBuffer); int lowerLength = bits.Length; - nuint[]? lowerFromPool = null; - Span lower = ((uint)lowerLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : lowerFromPool = ArrayPool.Shared.Rent(lowerLength)).Slice(0, lowerLength); + Span lower = BigInteger.RentedBuffer.Create(lowerLength, out BigInteger.RentedBuffer lowerBuffer); bits.Slice(0, omittedLength).CopyTo(lower); BigIntegerCalculator.Divide(bits.Slice(omittedLength), powOfTen, upper, lower.Slice(omittedLength)); @@ -1084,10 +1037,7 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn base1E9Buffer, out int lowerWritten); - if (lowerFromPool != null) - { - ArrayPool.Shared.Return(lowerFromPool); - } + lowerBuffer.Dispose(); Debug.Assert(lower1E9Length >= lowerWritten); @@ -1098,10 +1048,7 @@ static void DivideAndConquer(in PowersOf1e9 powersOf1e9, int powersIndex, ReadOn base1E9Buffer.Slice(lower1E9Length), out base1E9Written); - if (upperFromPool != null) - { - ArrayPool.Shared.Return(upperFromPool); - } + upperBuffer.Dispose(); base1E9Written += lower1E9Length; } @@ -1478,12 +1425,7 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, return; } - nuint[]? powersOfTenFromPool = null; - - Span powersOfTen = ( - bits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : powersOfTenFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + Span powersOfTen = BigInteger.RentedBuffer.Create(bits.Length, out BigInteger.RentedBuffer powersOfTenBuffer); scoped Span powersOfTen2 = bits; int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); @@ -1537,10 +1479,7 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, BigIntegerCalculator.Multiply(left, powersOfTen, bits2); - if (powersOfTenFromPool != null) - { - ArrayPool.Shared.Return(powersOfTenFromPool); - } + powersOfTenBuffer.Dispose(); if (remainingTrailingZeroCount > 0) { diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.RentedBuffer.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.RentedBuffer.cs new file mode 100644 index 00000000000000..b1d8ec3ba864be --- /dev/null +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.RentedBuffer.cs @@ -0,0 +1,73 @@ +// 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.Runtime.CompilerServices; + +namespace System.Numerics +{ + public readonly partial struct BigInteger + { + /// + /// Provides temporary buffer management for operations, using either stack-allocated inline storage or pooled arrays depending on size requirements. + /// + internal ref struct RentedBuffer : IDisposable + { + private const int InlineBufferSize = 64; + private InlineBuffer _inline; + private nuint[]? _array; + + /// + /// Creates a buffer of the specified size and returns a span to it. + /// + /// The number of elements required in the buffer. + /// When this method returns, contains the instance that manages the buffer lifetime. This parameter is treated as uninitialized. + /// A span of elements with the requested size, initialized to zero. + public static Span Create(int size, [UnscopedRef] out RentedBuffer rentedBuffer) + { + if (size <= InlineBufferSize) + { + rentedBuffer = default; + return ((Span)rentedBuffer._inline)[..size]; + } + else + { + nuint[] array = ArrayPool.Shared.Rent(size); + + rentedBuffer._array = array; + Unsafe.SkipInit(out rentedBuffer._inline); + + Span resultBuffer = array.AsSpan(0, size); + resultBuffer.Clear(); + + return resultBuffer; + } + } + + /// + /// Returns the rented array to the pool, if one was allocated. + /// + /// + /// This method returns the underlying array to if it was rented. + /// If inline storage was used, this method does nothing. + /// + public void Dispose() + { + nuint[]? array = _array; + if (array is not null) + { + _array = null; + ArrayPool.Shared.Return(array); + } + } + + [InlineArray(InlineBufferSize)] + private struct InlineBuffer + { + private nuint _value0; + } + } + } +} 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 28a2728cdf35aa..0bc4c4d1a7acb7 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.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.Buffers; using System.Buffers.Binary; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -14,7 +13,7 @@ namespace System.Numerics [Serializable] [TypeForwardedFrom("System.Numerics, Version=4.0.0.0, PublicKeyToken=b77a5c561934e089")] [DebuggerDisplay("{DebuggerDisplay,nq}")] - public readonly struct BigInteger + public readonly partial struct BigInteger : ISpanFormattable, IComparable, IComparable, @@ -838,19 +837,13 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big if (trivialDivisor) { - nuint[]? bitsFromPool = null; int size = dividend._bits.Length; - Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span quotient = RentedBuffer.Create(size, out RentedBuffer quotientBuffer); // may throw DivideByZeroException BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient, out nuint rest); - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); - } + quotientBuffer.Dispose(); remainder = dividend._sign < 0 ? -(long)rest : (long)rest; return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); @@ -865,32 +858,19 @@ public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out Big } else { - nuint[]? remainderFromPool = null; int size = dividend._bits.Length; - Span rest = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : remainderFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span rest = RentedBuffer.Create(size, out RentedBuffer restBuffer); - nuint[]? quotientFromPool = null; size = dividend._bits.Length - divisor._bits.Length + 1; - Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span quotient = RentedBuffer.Create(size, out RentedBuffer quotientBuffer); BigIntegerCalculator.Divide(dividend._bits, divisor._bits, quotient, rest); remainder = new(rest, dividend._sign < 0); BigInteger result = new(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - if (remainderFromPool is not null) - { - ArrayPool.Shared.Return(remainderFromPool); - } - - if (quotientFromPool is not null) - { - ArrayPool.Shared.Return(quotientFromPool); - } + restBuffer.Dispose(); + quotientBuffer.Dispose(); return result; } @@ -1005,7 +985,6 @@ private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, Re { Debug.Assert(BigIntegerCalculator.Compare(leftBits, rightBits) >= 0); - nuint[]? bitsFromPool = null; BigInteger result; // Short circuits to spare some allocations... @@ -1016,9 +995,7 @@ private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, Re } else if (nint.Size == 4 && rightBits.Length == 2) { - Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); + Span bits = RentedBuffer.Create(leftBits.Length, out RentedBuffer bitsBuffer); BigIntegerCalculator.Remainder(leftBits, rightBits, bits); @@ -1026,20 +1003,15 @@ private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, Re ulong right = ((ulong)bits[1] << 32) | (uint)bits[0]; result = BigIntegerCalculator.Gcd(left, right); + bitsBuffer.Dispose(); } else { - Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(leftBits.Length)).Slice(0, leftBits.Length); + Span bits = RentedBuffer.Create(leftBits.Length, out RentedBuffer bitsBuffer); BigIntegerCalculator.Gcd(leftBits, rightBits, bits); result = new BigInteger(bits, negative: false); - } - - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); } return result; @@ -1077,11 +1049,7 @@ public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigIntege else { int size = (modulus._bits?.Length ?? 1) << 1; - nuint[]? bitsFromPool = null; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - bits.Clear(); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); if (trivialValue) { if (trivialExponent) @@ -1104,10 +1072,7 @@ public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigIntege result = new BigInteger(bits, value._sign < 0 && !exponent.IsEven); - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); - } + bitsBuffer.Dispose(); } return result; @@ -1130,7 +1095,6 @@ public static BigInteger Pow(BigInteger value, int exponent) bool trivialValue = value._bits is null; nuint power = NumericsHelpers.Abs(exponent); - nuint[]? bitsFromPool = null; BigInteger result; if (trivialValue) @@ -1151,29 +1115,20 @@ public static BigInteger Pow(BigInteger value, int exponent) } int size = BigIntegerCalculator.PowBound(power, 1); - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - bits.Clear(); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), power, bits); result = new BigInteger(bits, value._sign < 0 && (exponent & 1) != 0); + bitsBuffer.Dispose(); } else { int size = BigIntegerCalculator.PowBound(power, value._bits!.Length); - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - bits.Clear(); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Pow(value._bits, power, bits); result = new BigInteger(bits, value._sign < 0 && (exponent & 1) != 0); - } - - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); } return result; @@ -1843,60 +1798,50 @@ private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOn Debug.Assert(!(trivialLeft && trivialRight), "Trivial cases should be handled on the caller operator"); BigInteger result; - nuint[]? bitsFromPool = null; if (trivialLeft) { Debug.Assert(!rightBits.IsEmpty); int size = rightBits.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Add(rightBits, NumericsHelpers.Abs(leftSign), bits); result = new BigInteger(bits, leftSign < 0); + bitsBuffer.Dispose(); } else if (trivialRight) { Debug.Assert(!leftBits.IsEmpty); int size = leftBits.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Add(leftBits, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, leftSign < 0); + bitsBuffer.Dispose(); } else if (leftBits.Length < rightBits.Length) { Debug.Assert(!leftBits.IsEmpty && !rightBits.IsEmpty); int size = rightBits.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Add(rightBits, leftBits, bits); result = new BigInteger(bits, leftSign < 0); + bitsBuffer.Dispose(); } else { Debug.Assert(!leftBits.IsEmpty && !rightBits.IsEmpty); int size = leftBits.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Add(leftBits, rightBits, bits); result = new BigInteger(bits, leftSign < 0); - } - - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); } return result; @@ -1922,58 +1867,48 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, R Debug.Assert(!(trivialLeft && trivialRight), "Trivial cases should be handled on the caller operator"); BigInteger result; - nuint[]? bitsFromPool = null; if (trivialLeft) { Debug.Assert(!rightBits.IsEmpty); int size = rightBits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Subtract(rightBits, NumericsHelpers.Abs(leftSign), bits); result = new BigInteger(bits, leftSign >= 0); + bitsBuffer.Dispose(); } else if (trivialRight) { Debug.Assert(!leftBits.IsEmpty); int size = leftBits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Subtract(leftBits, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, leftSign < 0); + bitsBuffer.Dispose(); } else if (BigIntegerCalculator.Compare(leftBits, rightBits) < 0) { int size = rightBits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Subtract(rightBits, leftBits, bits); result = new BigInteger(bits, leftSign >= 0); + bitsBuffer.Dispose(); } else { Debug.Assert(!leftBits.IsEmpty && !rightBits.IsEmpty); int size = leftBits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Subtract(leftBits, rightBits, bits); result = new BigInteger(bits, leftSign < 0); - } - - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); } return result; @@ -2536,10 +2471,7 @@ private static BigInteger BitwiseXor(ref readonly BigInteger left, ref readonly private static BigInteger BitwiseOp(ref readonly BigInteger left, ref readonly BigInteger right, int zLen) where TOp : struct, BigIntegerCalculator.IBitwiseOp { - nuint[]? resultBufferFromPool = null; - Span z = ((uint)zLen <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(zLen)).Slice(0, zLen); + Span z = RentedBuffer.Create(zLen, out RentedBuffer zBuffer); BigIntegerCalculator.BitwiseOp( left._bits, left._sign, @@ -2548,10 +2480,7 @@ private static BigInteger BitwiseOp(ref readonly BigInteger left, ref reado BigInteger result = new(z); - if (resultBufferFromPool is not null) - { - ArrayPool.Shared.Return(resultBufferFromPool); - } + zBuffer.Dispose(); return result; } @@ -2693,10 +2622,7 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) return new BigInteger(value._sign >> 31, null); } - nuint[]? zFromPool = null; - Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + Span zd = RentedBuffer.Create(zLength, out RentedBuffer zdBuffer); zd[^1] = 0; bits.Slice(digitShift).CopyTo(zd); @@ -2716,10 +2642,7 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) BigInteger result = new(zd, neg); - if (zFromPool is not null) - { - ArrayPool.Shared.Return(zFromPool); - } + zdBuffer.Dispose(); return result; } @@ -2733,34 +2656,26 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) return ~value._sign; // implicit int -> BigInteger handles int.MinValue } - nuint[]? bitsFromPool = null; BigInteger result; if (value._sign >= 0) { // ~positive = -(positive + 1): add 1 to magnitude, negate int size = value._bits.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Add(value._bits, 1, bits); result = new BigInteger(bits, negative: true); + bitsBuffer.Dispose(); } else { // ~negative = |negative| - 1: subtract 1 from magnitude - Span bits = ((uint)value._bits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(value._bits.Length)).Slice(0, value._bits.Length); + Span bits = RentedBuffer.Create(value._bits.Length, out RentedBuffer bitsBuffer); BigIntegerCalculator.Subtract(value._bits, 1, bits); result = new BigInteger(bits, negative: false); - } - - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); } return result; @@ -2777,32 +2692,24 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) return (long)value._sign + 1; } - nuint[]? bitsFromPool = null; BigInteger result; if (value._sign >= 0) { int size = value._bits.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Add(value._bits, 1, bits); result = new BigInteger(bits, negative: false); + bitsBuffer.Dispose(); } else { - Span bits = ((uint)value._bits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(value._bits.Length)).Slice(0, value._bits.Length); + Span bits = RentedBuffer.Create(value._bits.Length, out RentedBuffer bitsBuffer); BigIntegerCalculator.Subtract(value._bits, 1, bits); result = new BigInteger(bits, negative: true); - } - - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); } return result; @@ -2815,32 +2722,24 @@ private static BigInteger LeftShift(int value, int digitShift, int smallShift) return (long)value._sign - 1; } - nuint[]? bitsFromPool = null; BigInteger result; if (value._sign >= 0) { - Span bits = ((uint)value._bits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(value._bits.Length)).Slice(0, value._bits.Length); + Span bits = RentedBuffer.Create(value._bits.Length, out RentedBuffer bitsBuffer); BigIntegerCalculator.Subtract(value._bits, 1, bits); result = new BigInteger(bits, negative: false); + bitsBuffer.Dispose(); } else { int size = value._bits.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Add(value._bits, 1, bits); result = new BigInteger(bits, negative: true); - } - - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); } return result; @@ -2871,60 +2770,48 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO Debug.Assert(!(trivialLeft && trivialRight), "Trivial cases should be handled on the caller operator"); BigInteger result; - nuint[]? bitsFromPool = null; if (trivialLeft) { Debug.Assert(!right.IsEmpty); int size = right.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Multiply(right, NumericsHelpers.Abs(leftSign), bits); result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); + bitsBuffer.Dispose(); } else if (trivialRight) { Debug.Assert(!left.IsEmpty); int size = left.Length + 1; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Multiply(left, NumericsHelpers.Abs(rightSign), bits); result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); + bitsBuffer.Dispose(); } else if (left == right) { int size = left.Length + right.Length; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - bits.Clear(); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Square(left, bits); result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); + bitsBuffer.Dispose(); } else { Debug.Assert(!left.IsEmpty && !right.IsEmpty); int size = left.Length + right.Length; - Span bits = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - bits.Clear(); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Multiply(left, right, bits); result = new BigInteger(bits, (leftSign < 0) ^ (rightSign < 0)); - } - - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); } return result; @@ -2947,26 +2834,19 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO return s_zero; } - nuint[]? quotientFromPool = null; - if (trivialDivisor) { Debug.Assert(dividend._bits is not null); int size = dividend._bits.Length; - Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span quotient = RentedBuffer.Create(size, out RentedBuffer quotientBuffer); //may throw DivideByZeroException BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient); BigInteger result = new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - if (quotientFromPool is not null) - { - ArrayPool.Shared.Return(quotientFromPool); - } + quotientBuffer.Dispose(); return result; } @@ -2980,17 +2860,12 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO else { int size = dividend._bits.Length - divisor._bits.Length + 1; - Span quotient = ((uint)size < BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span quotient = RentedBuffer.Create(size, out RentedBuffer quotientBuffer); BigIntegerCalculator.Divide(dividend._bits, divisor._bits, quotient); BigInteger result = new(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - if (quotientFromPool is not null) - { - ArrayPool.Shared.Return(quotientFromPool); - } + quotientBuffer.Dispose(); return result; } @@ -3027,19 +2902,13 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadO return dividend; } - nuint[]? bitsFromPool = null; int size = dividend._bits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); BigIntegerCalculator.Remainder(dividend._bits, divisor._bits, bits); BigInteger result = new(bits, dividend._sign < 0); - if (bitsFromPool is not null) - { - ArrayPool.Shared.Return(bitsFromPool); - } + bitsBuffer.Dispose(); return result; } @@ -3330,10 +3199,7 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long r ++zLength; } - nuint[]? zFromPool = null; - Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + Span zd = RentedBuffer.Create(zLength, out RentedBuffer zdBuffer); zd[^1] = 0; bits.CopyTo(zd); @@ -3361,10 +3227,7 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long r BigInteger result = new(zd, negative); - if (zFromPool is not null) - { - ArrayPool.Shared.Return(zFromPool); - } + zdBuffer.Dispose(); return result; } @@ -5104,10 +4967,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va goto Excess; } - nuint[]? zFromPool = null; - Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc nuint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + Span zd = RentedBuffer.Create(zLength, out RentedBuffer zdBuffer); zd[^1] = 0; bits.Slice(digitShift).CopyTo(zd); @@ -5149,10 +5009,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va result = new BigInteger(zd, false); } - if (zFromPool is not null) - { - ArrayPool.Shared.Return(zFromPool); - } + zdBuffer.Dispose(); return result; Excess: 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 88975efc84f605..a7df8bab99608d 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,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.Buffers; using System.Diagnostics; namespace System.Numerics @@ -95,21 +94,14 @@ public static void Divide(ReadOnlySpan left, ReadOnlySpan right, S { // Same as above, but only returning the quotient. - nuint[]? leftCopyFromPool = null; - // 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 nuint[StackAllocThreshold] - : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); + Span leftCopy = BigInteger.RentedBuffer.Create(left.Length, out BigInteger.RentedBuffer leftCopyBuffer); left.CopyTo(leftCopy); DivideGrammarSchool(leftCopy, right, quotient); - if (leftCopyFromPool != null) - { - ArrayPool.Shared.Return(leftCopyFromPool); - } + leftCopyBuffer.Dispose(); } else { @@ -135,18 +127,12 @@ public static void Remainder(ReadOnlySpan left, ReadOnlySpan right else { int quotientLength = left.Length - right.Length + 1; - nuint[]? quotientFromPool = null; - Span quotient = (quotientLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); + Span quotient = BigInteger.RentedBuffer.Create(quotientLength, out BigInteger.RentedBuffer quotientBuffer); DivideBurnikelZiegler(left, right, quotient, remainder); - if (quotientFromPool != null) - { - ArrayPool.Shared.Return(quotientFromPool); - } + quotientBuffer.Dispose(); } } @@ -172,42 +158,19 @@ private static void DivRem(Span left, ReadOnlySpan right, Span leftCopy = (left.Length <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : leftCopyFromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); + Span leftCopy = BigInteger.RentedBuffer.Create(left.Length, out BigInteger.RentedBuffer leftCopyBuffer); left.CopyTo(leftCopy); - nuint[]? quotientActualFromPool = null; - scoped Span quotientActual; - - if (quotient.Length == 0) - { - int quotientLength = left.Length - right.Length + 1; - - quotientActual = (quotientLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : quotientActualFromPool = ArrayPool.Shared.Rent(quotientLength)).Slice(0, quotientLength); - } - else - { - quotientActual = quotient; - } + int quotientLength = quotient.Length > 0 ? 0 : left.Length - right.Length + 1; + Span quotientAllocated = BigInteger.RentedBuffer.Create(quotientLength, out BigInteger.RentedBuffer quotientActualBuffer); + Span quotientActual = quotient.Length > 0 ? quotient : quotientAllocated; DivideBurnikelZiegler(leftCopy, right, quotientActual, left); - if (quotientActualFromPool != null) - { - ArrayPool.Shared.Return(quotientActualFromPool); - } - - if (leftCopyFromPool != null) - { - ArrayPool.Shared.Return(leftCopyFromPool); - } + quotientActualBuffer.Dispose(); + leftCopyBuffer.Dispose(); } } @@ -374,11 +337,7 @@ private static void DivideBurnikelZiegler(ReadOnlySpan left, ReadOnlySpan ? BitOperations.LeadingZeroCount((ulong)right[^1]) : BitOperations.LeadingZeroCount((uint)right[^1]); - nuint[]? bFromPool = null; - - Span b = (n <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : bFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); + Span b = BigInteger.RentedBuffer.Create(n, out BigInteger.RentedBuffer bBuffer); int aLength = left.Length + sigmaDigit; @@ -393,11 +352,7 @@ private static void DivideBurnikelZiegler(ReadOnlySpan left, ReadOnlySpan ++aLength; } - nuint[]? aFromPool = null; - - Span a = (aLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : aFromPool = ArrayPool.Shared.Rent(aLength)).Slice(0, aLength); + Span a = BigInteger.RentedBuffer.Create(aLength, out BigInteger.RentedBuffer aBuffer); static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, Span bits) { @@ -433,35 +388,23 @@ static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, S int t = Math.Max(2, (a.Length + n - 1) / n); // Max(2, Ceil(a.Length/n)) Debug.Assert(t < a.Length || (t == a.Length && (nint)a[^1] >= 0)); - nuint[]? rFromPool = null; - Span r = ((n + 1) <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : rFromPool = ArrayPool.Shared.Rent(n + 1)).Slice(0, n + 1); + Span r = BigInteger.RentedBuffer.Create(n + 1, out BigInteger.RentedBuffer rBuffer); - nuint[]? zFromPool = null; - Span z = (2 * n <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(2 * n)).Slice(0, 2 * n); + Span z = BigInteger.RentedBuffer.Create(2 * n, out BigInteger.RentedBuffer zBuffer); a.Slice((t - 2) * n).CopyTo(z); z.Slice(a.Length - (t - 2) * n).Clear(); Span quotientUpper = quotient.Slice((t - 2) * n); if (quotientUpper.Length < n) { - nuint[]? qFromPool = null; - Span q = (n <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : qFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); + Span q = BigInteger.RentedBuffer.Create(n, out BigInteger.RentedBuffer qBuffer); BurnikelZieglerD2n1n(z, b, q, r); Debug.Assert(!q.Slice(quotientUpper.Length).ContainsAnyExcept((nuint)0)); q.Slice(0, quotientUpper.Length).CopyTo(quotientUpper); - if (qFromPool != null) - { - ArrayPool.Shared.Return(qFromPool); - } + qBuffer.Dispose(); } else { @@ -476,20 +419,11 @@ static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, S BurnikelZieglerD2n1n(z, b, quotient.Slice(i * n, n), r); } - if (zFromPool != null) - { - ArrayPool.Shared.Return(zFromPool); - } + zBuffer.Dispose(); - if (bFromPool != null) - { - ArrayPool.Shared.Return(bFromPool); - } + bBuffer.Dispose(); - if (aFromPool != null) - { - ArrayPool.Shared.Return(aFromPool); - } + aBuffer.Dispose(); Debug.Assert(r[^1] == 0); Debug.Assert(!r.Slice(0, sigmaDigit).ContainsAnyExcept((nuint)0)); @@ -520,10 +454,7 @@ static void Normalize(ReadOnlySpan src, int sigmaDigit, int sigmaSmall, S } } - if (rFromPool != null) - { - ArrayPool.Shared.Return(rFromPool); - } + rBuffer.Dispose(); } private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySpan right, Span quotient, Span remainder) @@ -573,10 +504,7 @@ private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySp } else { - nuint[]? r1FromPool = null; - Span r1 = (left.Length <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : r1FromPool = ArrayPool.Shared.Rent(left.Length)).Slice(0, left.Length); + Span r1 = BigInteger.RentedBuffer.Create(left.Length, out BigInteger.RentedBuffer r1Buffer); left.CopyTo(r1); int quotientLength = Math.Min(left.Length - right.Length + 1, quotient.Length); @@ -595,10 +523,7 @@ private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySp r1.Slice(0, remainder.Length).CopyTo(remainder); } - if (r1FromPool != null) - { - ArrayPool.Shared.Return(r1FromPool); - } + r1Buffer.Dispose(); } } @@ -619,18 +544,12 @@ private static void BurnikelZieglerD2n1n(ReadOnlySpan left, ReadOnlySpan< int halfN = right.Length >> 1; - nuint[]? r1FromPool = null; - Span r1 = ((right.Length + 1) <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : r1FromPool = ArrayPool.Shared.Rent(right.Length + 1)).Slice(0, right.Length + 1); + Span r1 = BigInteger.RentedBuffer.Create(right.Length + 1, out BigInteger.RentedBuffer r1Buffer); BurnikelZieglerD3n2n(left.Slice(right.Length), left.Slice(halfN, halfN), right, quotient.Slice(halfN), r1); BurnikelZieglerD3n2n(r1.Slice(0, right.Length), left.Slice(0, halfN), right, quotient.Slice(0, halfN), remainder); - if (r1FromPool != null) - { - ArrayPool.Shared.Return(r1FromPool); - } + r1Buffer.Dispose(); } private static void BurnikelZieglerD3n2n(ReadOnlySpan left12, ReadOnlySpan left3, ReadOnlySpan right, Span quotient, Span remainder) @@ -650,10 +569,7 @@ private static void BurnikelZieglerD3n2n(ReadOnlySpan left12, ReadOnlySpa ReadOnlySpan b1 = right.Slice(n); ReadOnlySpan b2 = right.Slice(0, n); Span r1 = remainder.Slice(n); - nuint[]? dFromPool = null; - Span d = (right.Length <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : dFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); + Span d = BigInteger.RentedBuffer.Create(right.Length, out BigInteger.RentedBuffer dBuffer); if (CompareActual(a1, b1) < 0) { @@ -692,10 +608,7 @@ private static void BurnikelZieglerD3n2n(ReadOnlySpan left12, ReadOnlySpa SubtractSelf(rr, d); - if (dFromPool != null) - { - ArrayPool.Shared.Return(dFromPool); - } + dBuffer.Dispose(); static void MultiplyActual(ReadOnlySpan left, ReadOnlySpan right, Span bits) { 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 7711bad0323687..3becef35ff4900 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,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.Buffers; using System.Diagnostics; using System.Runtime.InteropServices; @@ -78,18 +77,12 @@ public static void Gcd(ReadOnlySpan left, ReadOnlySpan right, Span left.CopyTo(result); - nuint[]? rightCopyFromPool = null; - Span rightCopy = (right.Length <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : rightCopyFromPool = ArrayPool.Shared.Rent(right.Length)).Slice(0, right.Length); + Span rightCopy = BigInteger.RentedBuffer.Create(right.Length, out BigInteger.RentedBuffer rightCopyBuffer); right.CopyTo(rightCopy); Gcd(result, rightCopy); - if (rightCopyFromPool != null) - { - ArrayPool.Shared.Return(rightCopyFromPool); - } + rightCopyBuffer.Dispose(); } private static void Gcd(Span left, Span right) 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 649f45a405c399..6e3161a2b976d8 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 @@ -21,16 +21,9 @@ public static void Pow(ReadOnlySpan value, nuint power, Span bits) { Debug.Assert(bits.Length == PowBound(power, value.Length)); - nuint[]? tempFromPool = null; - Span temp = (bits.Length <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); - temp.Clear(); - - nuint[]? valueCopyFromPool = null; - Span valueCopy = (bits.Length <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : valueCopyFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + Span temp = BigInteger.RentedBuffer.Create(bits.Length, out BigInteger.RentedBuffer tempBuffer); + + Span valueCopy = BigInteger.RentedBuffer.Create(bits.Length, out BigInteger.RentedBuffer valueCopyBuffer); value.CopyTo(valueCopy); valueCopy.Slice(value.Length).Clear(); @@ -38,15 +31,8 @@ public static void Pow(ReadOnlySpan value, nuint power, Span bits) result.CopyTo(bits); bits.Slice(result.Length).Clear(); - if (tempFromPool != null) - { - ArrayPool.Shared.Return(tempFromPool); - } - - if (valueCopyFromPool != null) - { - ArrayPool.Shared.Return(valueCopyFromPool); - } + tempBuffer.Dispose(); + valueCopyBuffer.Dispose(); } private static Span PowCore(Span value, int valueLength, Span temp, nuint power, Span result) @@ -276,11 +262,8 @@ public static void Pow(ReadOnlySpan value, nuint power, // The big modulus pow method for a big integer // raised by a 32-bit integer... - nuint[]? valueCopyFromPool = null; int size = Math.Max(value.Length, bits.Length); - Span valueCopy = (size <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span valueCopy = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer valueCopyBuffer); // smallish optimization here: // subsequent operations will copy the elements to the beginning of the buffer, @@ -296,23 +279,12 @@ public static void Pow(ReadOnlySpan value, nuint power, value.CopyTo(valueCopy); } - nuint[]? tempFromPool = null; - Span temp = (bits.Length <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); - temp.Clear(); + Span temp = BigInteger.RentedBuffer.Create(bits.Length, out BigInteger.RentedBuffer tempBuffer); PowCore(valueCopy, ActualLength(valueCopy), power, modulus, temp, bits); - if (valueCopyFromPool != null) - { - ArrayPool.Shared.Return(valueCopyFromPool); - } - - if (tempFromPool != null) - { - ArrayPool.Shared.Return(tempFromPool); - } + valueCopyBuffer.Dispose(); + tempBuffer.Dispose(); } public static void Pow(nuint value, ReadOnlySpan power, @@ -331,10 +303,7 @@ public static void Pow(ReadOnlySpan value, ReadOnlySpan power, // raised by a big integer... int size = Math.Max(value.Length, bits.Length); - nuint[]? valueCopyFromPool = null; - Span valueCopy = (size <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span valueCopy = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer valueCopyBuffer); // smallish optimization here: // subsequent operations will copy the elements to the beginning of the buffer, @@ -350,23 +319,12 @@ public static void Pow(ReadOnlySpan value, ReadOnlySpan power, value.CopyTo(valueCopy); } - nuint[]? tempFromPool = null; - Span temp = (bits.Length <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : tempFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); - temp.Clear(); + Span temp = BigInteger.RentedBuffer.Create(bits.Length, out BigInteger.RentedBuffer tempBuffer); PowCore(valueCopy, ActualLength(valueCopy), power, modulus, temp, bits); - if (valueCopyFromPool != null) - { - ArrayPool.Shared.Return(valueCopyFromPool); - } - - if (tempFromPool != null) - { - ArrayPool.Shared.Return(tempFromPool); - } + valueCopyBuffer.Dispose(); + tempBuffer.Dispose(); } internal @@ -430,57 +388,27 @@ private static void PowCoreBarrett(Span value, int valueLength, Span temp, Span bits) { int size = modulus.Length * 2 + 1; - nuint[]? rFromPool = null; - Span r = ((uint)size <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : rFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - r.Clear(); + Span r = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer rBuffer); size = r.Length - modulus.Length + 1; - nuint[]? muFromPool = null; - Span mu = ((uint)size <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - mu.Clear(); + Span mu = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer muBuffer); size = modulus.Length * 2 + 2; - nuint[]? q1FromPool = null; - Span q1 = ((uint)size <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - q1.Clear(); - - nuint[]? q2FromPool = null; - Span q2 = ((uint)size <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : q2FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - q2.Clear(); + Span q1 = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer q1Buffer); + + Span q2 = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer q2Buffer); FastReducer reducer = new(modulus, r, mu, q1, q2); - if (rFromPool != null) - { - ArrayPool.Shared.Return(rFromPool); - } + rBuffer.Dispose(); Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); result.CopyTo(bits); bits.Slice(result.Length).Clear(); - if (muFromPool != null) - { - ArrayPool.Shared.Return(muFromPool); - } - - if (q1FromPool != null) - { - ArrayPool.Shared.Return(q1FromPool); - } - - if (q2FromPool != null) - { - ArrayPool.Shared.Return(q2FromPool); - } + muBuffer.Dispose(); + q1Buffer.Dispose(); + q2Buffer.Dispose(); } /// @@ -572,11 +500,7 @@ private static void PowCoreMontgomery(Span value, int valueLength, // Convert value to Montgomery form: montValue = (value << k*wordBits) mod n int shiftLen = k + valueLength; - nuint[]? shiftPool = null; - Span shifted = ((uint)shiftLen <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : shiftPool = ArrayPool.Shared.Rent(shiftLen)).Slice(0, shiftLen); - shifted.Clear(); + Span shifted = BigInteger.RentedBuffer.Create(shiftLen, out BigInteger.RentedBuffer shiftedBuffer); value.Slice(0, valueLength).CopyTo(shifted.Slice(k)); if (shifted.Length >= modulus.Length) @@ -588,30 +512,17 @@ private static void PowCoreMontgomery(Span value, int valueLength, value.Slice(k).Clear(); valueLength = ActualLength(value.Slice(0, k)); - if (shiftPool is not null) - { - ArrayPool.Shared.Return(shiftPool); - } + shiftedBuffer.Dispose(); // Compute R mod n (Montgomery form of 1) and save for later - nuint[]? rModNPool = null; - Span rModN = ((uint)k <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : rModNPool = ArrayPool.Shared.Rent(k)).Slice(0, k); + Span rModN = BigInteger.RentedBuffer.Create(k, out BigInteger.RentedBuffer rModNBuffer); { int oneShiftLen = k + 1; - nuint[]? oneShiftPool = null; - Span oneShifted = ((uint)oneShiftLen <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : oneShiftPool = ArrayPool.Shared.Rent(oneShiftLen)).Slice(0, oneShiftLen); - oneShifted.Clear(); + Span oneShifted = BigInteger.RentedBuffer.Create(oneShiftLen, out BigInteger.RentedBuffer oneShiftedBuffer); oneShifted[k] = 1; DivRem(oneShifted, modulus, default); oneShifted.Slice(0, k).CopyTo(rModN); - if (oneShiftPool is not null) - { - ArrayPool.Shared.Return(oneShiftPool); - } + oneShiftedBuffer.Dispose(); } int rModNLength = ActualLength(rModN); @@ -627,10 +538,7 @@ private static void PowCoreMontgomery(Span value, int valueLength, int resultLength = MontgomeryReduce(bits, modulus, n0inv); bits.Slice(0, resultLength).CopyTo(originalBits); originalBits.Slice(resultLength).Clear(); - if (rModNPool is not null) - { - ArrayPool.Shared.Return(rModNPool); - } + rModNBuffer.Dispose(); return; } @@ -659,19 +567,11 @@ private static void PowCoreMontgomery(Span value, int valueLength, { // Use a separate product buffer for precomputation to avoid // corrupting bits/temp (which are needed pristine for the main loop). - nuint[]? prodPool = null; - Span prod = ((uint)bufLen <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : prodPool = ArrayPool.Shared.Rent(bufLen)).Slice(0, bufLen); + Span prod = BigInteger.RentedBuffer.Create(bufLen, out BigInteger.RentedBuffer prodBuffer); // Compute base^2 in Montgomery form - nuint[]? base2Pool = null; - Span base2 = ((uint)k <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : base2Pool = ArrayPool.Shared.Rent(k)).Slice(0, k); - base2.Clear(); + Span base2 = BigInteger.RentedBuffer.Create(k, out BigInteger.RentedBuffer base2Buffer); - prod.Clear(); Square(value.Slice(0, valueLength), prod.Slice(0, valueLength * 2)); MontgomeryReduce(prod, modulus, n0inv); prod.Slice(0, k).CopyTo(base2); @@ -690,15 +590,8 @@ private static void PowCoreMontgomery(Span value, int valueLength, prod.Slice(0, k).CopyTo(table.Slice(i * k, k)); } - if (base2Pool is not null) - { - ArrayPool.Shared.Return(base2Pool); - } - - if (prodPool is not null) - { - ArrayPool.Shared.Return(prodPool); - } + base2Buffer.Dispose(); + prodBuffer.Dispose(); } // Initialize result to R mod n (bits and temp are untouched from caller) @@ -706,10 +599,7 @@ private static void PowCoreMontgomery(Span value, int valueLength, rModN.Slice(0, rModNLength).CopyTo(bits); int resultLen = rModNLength; - if (rModNPool is not null) - { - ArrayPool.Shared.Return(rModNPool); - } + rModNBuffer.Dispose(); // Left-to-right sliding window exponentiation int bitPos = expBitLength - 1; @@ -717,7 +607,7 @@ private static void PowCoreMontgomery(Span value, int valueLength, { if (GetBit(power, bitPos) == 0) { - resultLen = SquareSelf(ref bits, resultLen, ref temp); + SquareSelf(ref bits, resultLen, ref temp); resultLen = MontgomeryReduce(bits, modulus, n0inv); bitPos--; } @@ -742,7 +632,7 @@ private static void PowCoreMontgomery(Span value, int valueLength, // Square for each bit in the window for (int i = 0; i < wLen; i++) { - resultLen = SquareSelf(ref bits, resultLen, ref temp); + SquareSelf(ref bits, resultLen, ref temp); resultLen = MontgomeryReduce(bits, modulus, n0inv); } @@ -750,7 +640,7 @@ private static void PowCoreMontgomery(Span value, int valueLength, Debug.Assert(wValue >= 1 && (wValue & 1) == 1); ReadOnlySpan entry = table.Slice(((wValue - 1) >> 1) * k, k); int entryLength = ActualLength(entry); - resultLen = MultiplySelf(ref bits, resultLen, entry.Slice(0, entryLength), ref temp); + MultiplySelf(ref bits, resultLen, entry.Slice(0, entryLength), ref temp); resultLen = MontgomeryReduce(bits, modulus, n0inv); bitPos -= wLen; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs index ac23b451fe80f4..0ea6e11b122f5d 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.ShiftRot.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.Buffers; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -82,10 +81,7 @@ private static void SwapUpperAndLower(Span bits, int lowerLength) Span lowerDst = bits.Slice(upperLength); int tmpLength = Math.Min(lowerLength, upperLength); - nuint[]? tmpFromPool = null; - Span tmp = ((uint)tmpLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : tmpFromPool = ArrayPool.Shared.Rent(tmpLength)).Slice(0, tmpLength); + Span tmp = BigInteger.RentedBuffer.Create(tmpLength, out BigInteger.RentedBuffer tmpBuffer); if (upperLength < lowerLength) { @@ -100,10 +96,7 @@ private static void SwapUpperAndLower(Span bits, int lowerLength) tmp.CopyTo(lowerDst); } - if (tmpFromPool != null) - { - ArrayPool.Shared.Return(tmpFromPool); - } + tmpBuffer.Dispose(); } public static void LeftShiftSelf(Span bits, int shift, out nuint carry) 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 8f16d54bca9f76..3c8686419ae15f 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 @@ -120,18 +120,10 @@ static void Karatsuba(ReadOnlySpan value, Span bits) Square(valueHigh, bitsHigh); int foldLength = valueHigh.Length + 1; - nuint[]? foldFromPool = null; - Span fold = ((uint)foldLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : foldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); - fold.Clear(); + Span fold = BigInteger.RentedBuffer.Create(foldLength, out BigInteger.RentedBuffer foldBuffer); int coreLength = foldLength + foldLength; - nuint[]? coreFromPool = null; - Span core = ((uint)coreLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); - core.Clear(); + Span core = BigInteger.RentedBuffer.Create(coreLength, out BigInteger.RentedBuffer coreBuffer); // ... compute z_a = a_1 + a_0 (call it fold...) Add(valueHigh, valueLow, fold); @@ -139,20 +131,14 @@ static void Karatsuba(ReadOnlySpan value, Span bits) // ... compute z_1 = z_a * z_a - z_0 - z_2 Square(fold, core); - if (foldFromPool != null) - { - ArrayPool.Shared.Return(foldFromPool); - } + foldBuffer.Dispose(); SubtractCore(bitsHigh, bitsLow, core); // ... and finally merge the result! :-) AddSelf(bits.Slice(n), core); - if (coreFromPool != null) - { - ArrayPool.Shared.Return(coreFromPool); - } + coreBuffer.Dispose(); } static void Naive(ReadOnlySpan value, Span bits) @@ -463,17 +449,9 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span< Multiply(leftHigh, rightHigh, bitsHigh); int foldLength = n + 1; - nuint[]? leftFoldFromPool = null; - Span leftFold = ((uint)foldLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : leftFoldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); - leftFold.Clear(); - - nuint[]? rightFoldFromPool = null; - Span rightFold = ((uint)foldLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : rightFoldFromPool = ArrayPool.Shared.Rent(foldLength)).Slice(0, foldLength); - rightFold.Clear(); + Span leftFold = BigInteger.RentedBuffer.Create(foldLength, out BigInteger.RentedBuffer leftFoldBuffer); + + Span rightFold = BigInteger.RentedBuffer.Create(foldLength, out BigInteger.RentedBuffer rightFoldBuffer); // ... compute z_a = a_1 + a_0 (call it fold...) Add(leftLow, leftHigh, leftFold); @@ -482,24 +460,14 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span< Add(rightLow, rightHigh, rightFold); int coreLength = foldLength + foldLength; - nuint[]? coreFromPool = null; - Span core = ((uint)coreLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); - core.Clear(); + Span core = BigInteger.RentedBuffer.Create(coreLength, out BigInteger.RentedBuffer coreBuffer); // ... compute z_ab = z_a * z_b Multiply(leftFold, rightFold, core); - if (leftFoldFromPool != null) - { - ArrayPool.Shared.Return(leftFoldFromPool); - } + leftFoldBuffer.Dispose(); - if (rightFoldFromPool != null) - { - ArrayPool.Shared.Return(rightFoldFromPool); - } + rightFoldBuffer.Dispose(); // ... compute z_1 = z_a * z_b - z_0 - z_2 = a_0 * b_1 + a_1 * b_0 SubtractCore(bitsLow, bitsHigh, core); @@ -509,10 +477,7 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span< // ... and finally merge the result! :-) AddSelf(bits.Slice(n), core.TrimEnd((nuint)0)); - if (coreFromPool != null) - { - ArrayPool.Shared.Return(coreFromPool); - } + coreBuffer.Dispose(); } static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span bits, int n) @@ -536,10 +501,7 @@ static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span Multiply(leftLow, right, bitsLow); int carryLength = right.Length; - nuint[]? carryFromPool = null; - Span carry = ((uint)carryLength <= StackAllocThreshold - ? stackalloc nuint[StackAllocThreshold] - : carryFromPool = ArrayPool.Shared.Rent(carryLength)).Slice(0, carryLength); + Span carry = BigInteger.RentedBuffer.Create(carryLength, out BigInteger.RentedBuffer carryBuffer); Span carryOrig = bitsHigh.Slice(0, right.Length); carryOrig.CopyTo(carry); @@ -550,10 +512,7 @@ static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span AddSelf(bitsHigh, carry); - if (carryFromPool != null) - { - ArrayPool.Shared.Return(carryFromPool); - } + carryBuffer.Dispose(); } static void Naive(ReadOnlySpan left, ReadOnlySpan right, Span bits) 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 index 176d91f12966db..b15462deec3ee4 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs @@ -10,14 +10,6 @@ namespace System.Numerics { internal static partial class BigIntegerCalculator { - internal -#if DEBUG - static // Mutable for unit testing... -#else - const -#endif - int StackAllocThreshold = 64; - /// Number of bits per native-width limb: 32 on 32-bit, 64 on 64-bit. internal static int BitsPerLimb { From 23ee9c7a1d024766c90ff62a9c9d28cef148c129 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 20 Mar 2026 10:15:10 -0400 Subject: [PATCH 26/27] Fix GetSignBitsIfValid XML doc to clarify 'valid hex digit and considered positive' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System.Runtime.Numerics/src/System/Number.BigInteger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 4b10f89a88d42c..eebcbc0e5431b5 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -1567,7 +1567,7 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span d public static NumberStyles BlockNumberStyle => NumberStyles.AllowHexSpecifier; - /// Returns all-zero bits if is a valid hex digit ('0'-'7'), or all-one bits otherwise. + /// Returns all-zero bits if is a valid hex digit and considered positive ('0'-'7'), or all-one bits otherwise. public static nuint GetSignBitsIfValid(uint ch) => (nuint)(nint)((ch & 0b_1111_1000) == 0b_0011_0000 ? 0 : -1); [MethodImpl(MethodImplOptions.AggressiveInlining)] From 79c944db5e1efe337c54199e4b02024762b83bf6 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 20 Mar 2026 11:15:45 -0400 Subject: [PATCH 27/27] Add comment explaining nuint[] for base-1e9 intermediate representation Explains why the intermediate decimal representation uses nuint[] (wasting 32 bits per element on 64-bit) rather than uint[] or base-1e19: - Base-1e19 benchmarked as net regression (UInt128 division too slow) - uint[] would require duplicating BigIntegerCalculator arithmetic routines Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.BigInteger.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index eebcbc0e5431b5..9bf763374c0e31 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -339,9 +339,14 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big return ParsingStatus.Failed; } + // The intermediate decimal representation uses base 10^9 stored in nuint elements. + // On 64-bit this wastes 32 bits per element, but switching to base 10^19 regresses + // ToString (UInt128 division by 10^19 is ~10x slower than JIT-optimized ulong / 10^9), + // and using Span would require duplicating BigIntegerCalculator arithmetic routines + // since the D&C algorithm reuses Multiply/Square/Divide on these spans. scoped Span base1E9; - ReadOnlySpan intDigits = number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); + ReadOnlySpan intDigits= number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); int intDigitsEnd = intDigits.IndexOf(0); if (intDigitsEnd < 0) {