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.Runtime.Numerics.csproj b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj index 41e3804b045e03..e6b91e1990ea09 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,8 @@ + + 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..9bf763374c0e31 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -23,14 +23,19 @@ 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 nuint[]? s_cachedPowersOf1e9; - [DoesNotReturn] - internal static void ThrowOverflowOrFormatException(ParsingStatus status) => throw GetException(status); + 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]; - private static Exception GetException(ParsingStatus status) + [DoesNotReturn] + internal static void ThrowOverflowOrFormatException(ParsingStatus status) { - return status == ParsingStatus.Failed + throw status == ParsingStatus.Failed ? new FormatException(SR.Overflow_ParseBigInteger) : new OverflowException(SR.Overflow_ParseBigInteger); } @@ -43,6 +48,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) @@ -51,6 +57,7 @@ internal static bool TryValidateParseStyleInteger(NumberStyles style, [NotNullWh return false; } } + e = null; return true; } @@ -101,7 +108,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)) { @@ -151,7 +158,9 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0; whiteIndex--) { if (!IsWhite(TChar.CastToUInt32(value[whiteIndex]))) + { break; + } } value = value[..(whiteIndex + 1)]; @@ -176,12 +187,12 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0) + nint signedLeading = (nint)leading; + if ((nint)(leading ^ signBits) >= 0 && int.MinValue < signedLeading && signedLeading <= int.MaxValue) { - // Small value that fits in Int32. - // Delegate to the constructor for int.MinValue handling. - result = new BigInteger((int)leading); + // 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] @@ -227,13 +239,13 @@ 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 +269,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle base1E9; + // 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)); + 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; - } - if (digitChar != '0') - { - result = default; - return ParsingStatus.Failed; - } + break; + } + + 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 uint[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 base1E9[--di]); - } + 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); + intDigits = intDigits.Slice(leadingDigits.Length); + Debug.Assert(intDigits.Length % PowersOf1e9.MaxPartialDigits == 0); - for (--di; di >= 0; --di) - { - uint.TryParse(intDigits.Slice(0, PowersOf1e9.MaxPartialDigits), out base1E9[di]); - intDigits = intDigits.Slice(PowersOf1e9.MaxPartialDigits); - } - 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); } - const double digitRatio = 0.10381025297; // log_{2^32}(10) + 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); - uint[]? resultBufferFromPool = null; - Span resultBuffer = ( - resultLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[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; @@ -400,57 +413,40 @@ 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; - 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 = ( - (uint)powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[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) { + double digitRatio = 0.10381025297 * 32.0 / BigIntegerCalculator.BitsPerLimb; int leadingLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * base1E9.Length) + 3); - uint[]? leadingFromPool = null; - Span leading = ( - leadingLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[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 { Recursive(powersOf1e9, maxIndex, base1E9, bits); } - - if (powersOf1e9BufferFromPool != null) - 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 +461,20 @@ 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.BitsPerLimb; int bufferLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * multiplier1E9Length) + 1 + 2); - uint[]? bufferFromPool = null; - scoped Span buffer = ( - bufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[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); - 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(); @@ -490,65 +483,87 @@ 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) + static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) { if (base1E9.Length == 0) { // number is 0. return; } + int resultLength = NaiveBase1E9ToBits(base1E9, bits); 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; + } } 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; + } int resultLength = 1; 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; + } } + 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) { - ulong p = (ulong)multiplier * bits[i] + carry; - bits[i] = (uint)p; - carry = (uint)(p >> 32); + 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 + { + for (int i = 0; i < bits.Length; i++) + { + ulong p = (ulong)multiplier * bits[i] + carry; + bits[i] = (uint)p; + carry = (uint)(p >> 32); + } + } + return carry; } } @@ -556,7 +571,7 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint 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; @@ -567,6 +582,7 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint 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 @@ -650,6 +666,7 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) bool success = value.TryWriteBytes(bytes, out _); Debug.Assert(success); } + bytes = bytes.Slice(0, bytesWrittenOrNeeded); Debug.Assert(!bytes.IsEmpty); @@ -672,13 +689,17 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint 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; @@ -710,6 +731,11 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) Debug.Assert(sb.Length == charsIncludeDigits); + if (arrayToReturnToPool is not null) + { + ArrayPool.Shared.Return(arrayToReturnToPool); + } + if (targetSpan) { charsWritten = charsIncludeDigits; @@ -721,13 +747,6 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint 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) { @@ -755,20 +774,20 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan 0 ? $"D{digits}" : "D"; } @@ -784,6 +803,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan, Span>(destination), out charsWritten, formatSpan, info); } + return null; } else @@ -795,39 +815,35 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan base1E9Buffer = ((uint)base1E9BufferLength <= BigIntegerCalculator.StackAllocThreshold ? - stackalloc uint[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); - ReadOnlySpan base1E9Value = base1E9Buffer[..written]; + ReadOnlySpan base1E9Value = base1E9Buffer[..written]; int valueDigits = (base1E9Value.Length - 1) * PowersOf1e9.MaxPartialDigits + FormattingHelpers.CountDigits(base1E9Value[^1]); string? strResult; - if (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R') + if (fmt is 'g' or 'G' or 'd' or 'D' or 'r' or 'R') { int strDigits = Math.Max(digits, valueDigits); ReadOnlySpan sNegative = value.Sign < 0 ? info.NegativeSignTChar() : default; @@ -847,9 +863,11 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan(BigInteger value, ReadOnlySpan(BigInteger value, ReadOnlySpan.Shared.Return(base1E9BufferFromPool); - } + base1E9Rental.Dispose(); return strResult; } @@ -945,11 +960,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,21 +972,21 @@ 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); } + public #if DEBUG - // Mutable for unit testing... - public static + static // Mutable for unit testing... #else - public const + 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,23 +997,11 @@ private static void BigIntegerToBase1E9(ReadOnlySpan bits, Span base } PowersOf1e9.FloorBufferSize(bits.Length, out int powersOf1e9BufferLength, out int maxIndex); - uint[]? powersOf1e9BufferFromPool = null; - Span powersOf1e9Buffer = ( - powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[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) + 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 +1012,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 +1023,15 @@ 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); + Span upper = BigInteger.RentedBuffer.Create(upperLength, out BigInteger.RentedBuffer upperBuffer); 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); + 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)); - Debug.Assert(!upper.Trim(0u).IsEmpty); + Debug.Assert(!upper.Trim((nuint)0).IsEmpty); int lower1E9Length = 1 << powersIndex; @@ -1045,8 +1042,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); @@ -1057,61 +1053,105 @@ 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; } - 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); - for (int iuDst = 0; iuDst < base1E9.Length; iuDst++) + if (nint.Size == 8) { - 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; + // 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 = bits[iuSrc]; + NaiveDigit((uint)(limb >> 32), base1E9Buffer, ref base1E9Written); + NaiveDigit((uint)limb, base1E9Buffer, ref base1E9Written); } - if (uCarry != 0) + else { - (uCarry, base1E9Buffer[base1E9Written++]) = Math.DivRem(uCarry, PowersOf1e9.TenPowMaxPartial); - if (uCarry != 0) - base1E9Buffer[base1E9Written++] = uCarry; + 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] = (uint)(value - quo * Divisor); + uCarry = (uint)quo; + } + + while (uCarry != 0) + { + base1E9Buffer[base1E9Written++] = uCarry % Divisor; + uCarry /= Divisor; + } + } } internal readonly ref struct PowersOf1e9 { - // Holds 1000000000^(1<< pow1E9; - public const uint TenPowMaxPartial = 1000000000; + /// Holds 1000000000^(1<<<n). + 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; - // indexes[i+1] = indexes[i] + length; - // } - private static ReadOnlySpan Indexes => + 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((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; + } + + /// + /// 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 => [ 0, 1, @@ -1147,12 +1187,51 @@ internal readonly ref struct PowersOf1e9 1939268536, ]; - // The PowersOf1e9 structure holds 1000000000^(1<< LeadingPowers1E9 => + 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, + ]; + + /// + /// 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); + + private static ReadOnlySpan LeadingPowers1E9_32 => [ // 1000000000^(1<<0) 1000000000, @@ -1206,7 +1285,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); @@ -1215,18 +1328,34 @@ public PowersOf1e9(Span pow1E9) this.pow1E9 = LeadingPowers1E9; return; } + 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); + + // 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 >= 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) + { + dst.Slice(shift).CopyTo(dst); + dst.Slice(dst.Length - shift).Clear(); + } + int from = toExclusive; toExclusive = Indexes[i + 1]; src = pow1E9.Slice(from, toExclusive - from); @@ -1241,7 +1370,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; @@ -1251,9 +1382,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<> (BitsPerLimb*(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 +1430,8 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, S return; } - uint[]? powersOfTenFromPool = null; - - Span powersOfTen = ( - bits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : powersOfTenFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); - scoped Span powersOfTen2 = bits; + Span powersOfTen = BigInteger.RentedBuffer.Create(bits.Length, out BigInteger.RentedBuffer powersOfTenBuffer); + scoped Span powersOfTen2 = bits; int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); @@ -1312,7 +1439,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 +1460,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 +1480,33 @@ 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); + powersOfTenBuffer.Dispose(); 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] = (uint)p; + carry = (uint)(p >> 32); + } } if (carry != 0) @@ -1386,40 +1524,38 @@ 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); 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; } @@ -1436,11 +1572,11 @@ 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); + /// 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)] - 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) @@ -1469,7 +1605,7 @@ 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); + /// 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.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 2262614b9c4ee4..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, @@ -22,42 +21,56 @@ 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. - internal static int MaxLength => Array.MaxLength / kcbitUint; + /// 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 / BigIntegerCalculator.BitsPerLimb; - // 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 . + /// + /// + /// 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 uint[]? _bits; // 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 uint[] { 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,29 +85,29 @@ public BigInteger(uint value) else { _sign = +1; - _bits = new uint[1]; - _bits[0] = 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 @@ -103,16 +116,13 @@ public BigInteger(long value) _sign = +1; } - if (x <= uint.MaxValue) + if (nint.Size == 8) { - _bits = new uint[1]; - _bits[0] = (uint)x; + _bits = [(nuint)x]; } else { - _bits = new uint[2]; - _bits[0] = unchecked((uint)x); - _bits[1] = (uint)(x >> kcbitUint); + _bits = x <= uint.MaxValue ? [((uint)x)] : [(uint)x, (uint)(x >> BitsPerUInt32)]; } } @@ -127,18 +137,17 @@ public BigInteger(ulong value) _sign = (int)value; _bits = null; } - else if (value <= uint.MaxValue) - { - _sign = +1; - _bits = new uint[1]; - _bits[0] = (uint)value; - } else { _sign = +1; - _bits = new uint[2]; - _bits[0] = unchecked((uint)value); - _bits[1] = (uint)(value >> kcbitUint); + if (nint.Size == 8) + { + _bits = [(nuint)value]; + } + else + { + _bits = value <= uint.MaxValue ? [((uint)value)] : [(uint)value, (uint)(value >> BitsPerUInt32)]; + } } AssertValid(); @@ -152,23 +161,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) { @@ -181,40 +181,66 @@ 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 { - // 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 = BigIntegerCalculator.BitsPerLimb; + + // 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] = (uint)(man >> (cbit + BitsPerUInt32)); + _bits[cu] = (uint)(man >> cbit); + if (cbit > 0) + { + _bits[cu - 1] = (nuint)(uint)man << (BitsPerUInt32 - cbit); + } + } + _sign = sign; } @@ -229,37 +255,66 @@ 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[2] != 0 ? 3 : + bits[1] != 0 ? 2 : + bits[0] != 0 ? 1 : + 0; + 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 { - _bits = new uint[size]; - - unchecked + if (nint.Size == 8) + { + // 64-bit: pack up to 3 uint-sized values into 1-2 nuint limbs + int nuintSize = (size + 1) / 2; + _bits = new nuint[nuintSize]; + _bits[0] = (uint)bits[0]; + if (size > 1) + { + _bits[0] |= (nuint)(uint)bits[1] << 32; + if (size > 2) + { + _bits[1] = (uint)bits[2]; + } + } + } + else { + _bits = new nuint[size]; _bits[0] = (uint)bits[0]; if (size > 1) + { _bits[1] = (uint)bits[1]; - if (size > 2) - _bits[2] = (uint)bits[2]; + if (size > 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.Length is 1 && _bits[0] <= int.MaxValue) + { + _sign = _sign < 0 ? -(int)_bits[0] : (int)_bits[0]; + _bits = null; + } } + AssertValid(); } @@ -288,26 +343,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; } } } @@ -327,7 +369,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE if (byteCount <= 4) { - _sign = isNegative ? unchecked((int)0xffffffff) : 0; + _sign = isNegative ? -1 : 0; if (isBigEndian) { @@ -347,59 +389,57 @@ 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) }; + // int overflow: unsigned value overflows into the int sign bit + _bits = [(uint)_sign]; _sign = +1; } + if (_sign == int.MinValue) { - this = s_bnMinInt; + this = s_int32MinValue; } } else { - int wholeUInt32Count = Math.DivRem(byteCount, 4, out int unalignedBytes); - uint[] val = new uint[wholeUInt32Count + (unalignedBytes == 0 ? 0 : 1)]; + int wholeLimbCount = Math.DivRem(byteCount, nint.Size, 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. - - value.Slice(0, wholeUInt32Count * 4).CopyTo(MemoryMarshal.AsBytes(val.AsSpan())); + // We can just copy the bytes directly into the nuint array. + 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 uints. + // an endianness swap on the resulting limbs. if (!BitConverter.IsLittleEndian) { - BinaryPrimitives.ReverseEndianness(val.AsSpan(0, wholeUInt32Count), val); + Span limbSpan = val.AsSpan(0, wholeLimbCount); + BinaryPrimitives.ReverseEndianness(limbSpan, limbSpan); } - // 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 +447,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 +455,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; } } } @@ -425,40 +465,33 @@ 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; if (len == 1) { - switch (val[0]) + if (val[0] == 1) // abs(-1) + { + this = s_minusOne; + return; + } + else if (val[0] == UInt32HighBit) // abs(int.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_int32MinValue; + return; + } + else if (val[0] < UInt32HighBit) // fits in int as negative + { + _sign = -(int)val[0]; + _bits = null; + AssertValid(); + return; } } if (len != val.Length) { _sign = -1; - _bits = new uint[len]; - Array.Copy(val, _bits, len); + _bits = val.AsSpan(0, len).ToArray(); } else { @@ -472,6 +505,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE _bits = val; } } + AssertValid(); } @@ -481,7 +515,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE /// /// the sign field /// the bits field - internal BigInteger(int sign, uint[]? 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. @@ -498,12 +532,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) @@ -515,58 +549,53 @@ internal BigInteger(ReadOnlySpan value, bool negative) { this = default; } - else if (value.Length == 1) + else if (value.Length == 1 && value[0] < UInt32HighBit) { - if (value[0] < kuMaskHighBit) - { - _sign = negative ? -(int)value[0] : (int)value[0]; - _bits = null; - } - else if (negative && value[0] == kuMaskHighBit) - { - // Although Int32.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] == UInt32HighBit) + { + // Although int.MinValue fits in _sign, we represent this case differently for negate + this = s_int32MinValue; } else { _sign = negative ? -1 : +1; _bits = value.ToArray(); } + AssertValid(); } /// - /// 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]; if (value.Length > MaxLength) @@ -577,36 +606,57 @@ private BigInteger(Span value) if (value.Length == 0) { // 0 - this = s_bnZeroInt; + this = s_zero; } else if (value.Length == 1) { if (isNegative) { - if (value[0] == uint.MaxValue) + if (value[0] == nuint.MaxValue) { // -1 - this = s_bnMinusOneInt; + this = s_minusOne; } - else if (value[0] == kuMaskHighBit) + else if (nint.Size == 4 && value[0] == UInt32HighBit) { // int.MinValue - this = s_bnMinInt; + this = s_int32MinValue; } else { - _sign = unchecked((int)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 < UInt32HighBit) + { + _sign = -(int)magnitude; + _bits = null; + } + else if (nint.Size == 8) + { + // 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 + { + // On 32-bit, magnitude > int.MaxValue always needs _bits + _sign = -1; + _bits = [magnitude]; + } } } - else if (unchecked((int)value[0]) < 0) + else if (value[0] >= UInt32HighBit) { _sign = +1; _bits = [value[0]]; } else { - _sign = unchecked((int)value[0]); + _sign = (int)value[0]; _bits = null; } } @@ -617,7 +667,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; @@ -626,45 +676,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(0u); + 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 >> (kcbitUint - 1)) - (-_sign >> (kcbitUint - 1)); } - } + public int Sign => (_sign >> 31) - (-_sign >> 31); public static BigInteger Parse(string value) { @@ -734,8 +784,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((int)NumericsHelpers.Abs(value._sign), value._bits); } public static BigInteger Add(BigInteger left, BigInteger right) @@ -765,85 +814,63 @@ 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) { BigInteger quotient; - (quotient, remainder) = Math.DivRem(dividend._sign, divisor._sign); + (int q, int r) = Math.DivRem(dividend._sign, divisor._sign); + quotient = q; + remainder = r; return quotient; } 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) { - uint rest; - - uint[]? bitsFromPool = null; int size = dividend._bits.Length; - Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span quotient = RentedBuffer.Create(size, out RentedBuffer quotientBuffer); - try - { - // may throw DivideByZeroException - BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient, out rest); + // may throw DivideByZeroException + BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient, out nuint rest); - remainder = dividend._sign < 0 ? -1 * rest : rest; - return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - } - finally - { - if (bitsFromPool != null) - ArrayPool.Shared.Return(bitsFromPool); - } + quotientBuffer.Dispose(); + + remainder = dividend._sign < 0 ? -(long)rest : (long)rest; + return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); } - 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 { - uint[]? remainderFromPool = null; int size = dividend._bits.Length; - Span rest = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : remainderFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span rest = RentedBuffer.Create(size, out RentedBuffer restBuffer); - uint[]? quotientFromPool = null; size = dividend._bits.Length - divisor._bits.Length + 1; - Span quotient = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span quotient = RentedBuffer.Create(size, out RentedBuffer quotientBuffer); 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) - ArrayPool.Shared.Return(remainderFromPool); - - if (quotientFromPool != null) - ArrayPool.Shared.Return(quotientFromPool); + restBuffer.Dispose(); + quotientBuffer.Dispose(); return result; } @@ -862,24 +889,54 @@ 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) + } + + if (value._bits is null) + { return Math.Log(value._sign, baseValue); + } + + ulong h, m, l; + int c; + long b; + ulong x; + + if (nint.Size == 8) + { + h = value._bits[^1]; + m = value._bits.Length > 1 ? value._bits[^2] : 0; - 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; + 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[^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 - int c = BitOperations.LeadingZeroCount((uint)h); - long b = (long)value._bits.Length * 32 - c; + // 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 ) @@ -893,11 +950,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) { @@ -906,119 +960,96 @@ 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(right._bits, negative: false); + : new BigInteger(+1, right._bits); } 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(left._bits, negative: false); + : 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) + private static BigInteger GreatestCommonDivisor(ReadOnlySpan leftBits, ReadOnlySpan rightBits) { Debug.Assert(BigIntegerCalculator.Compare(leftBits, rightBits) >= 0); - uint[]? bitsFromPool = null; BigInteger result; // Short circuits to spare some allocations... if (rightBits.Length == 1) { - uint temp = BigIntegerCalculator.Remainder(leftBits, rightBits[0]); + 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 uint[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); - 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); + bitsBuffer.Dispose(); } else { - Span bits = (leftBits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[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); + bitsBuffer.Dispose(); } - if (bitsFromPool != 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) { - uint bits = 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)); + 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 ? -(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); - bits.Clear(); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); if (trivialValue) { if (trivialExponent) @@ -1041,8 +1072,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); + bitsBuffer.Dispose(); } return result; @@ -1052,61 +1082,64 @@ 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; - uint power = NumericsHelpers.Abs(exponent); - uint[]? bitsFromPool = null; + nuint power = NumericsHelpers.Abs(exponent); BigInteger result; 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 uint[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 uint[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); + bitsBuffer.Dispose(); } - if (bitsFromPool != 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())); @@ -1116,90 +1149,143 @@ 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; - if ((_sign ^ other) < 0 || (cu = _bits.Length) > 2) + int maxLimbs = sizeof(long) / nint.Size; + if ((_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 _bits[0] == uu; + } + else + { + return 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; - if (cu > 2) + int maxLimbs = sizeof(long) / nint.Size; + if (cu > maxLimbs) + { return false; - if (cu == 1) + } + + if (nint.Size == 8) + { return _bits[0] == other; - return NumericsHelpers.MakeUInt64(_bits[1], _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) { - 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; - if ((_sign ^ other) < 0 || (cu = _bits.Length) > 2) + int maxLimbs = sizeof(long) / nint.Size; + if ((_sign ^ other) < 0 || (cu = _bits.Length) > maxLimbs) + { return _sign; + } + ulong uu = other < 0 ? (ulong)-other : (ulong)other; - ulong uuTmp = cu == 2 ? NumericsHelpers.MakeUInt64(_bits[1], _bits[0]) : _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 _sign * uuTmp.CompareTo(uu); } [CLSCompliant(false)] public int CompareTo(ulong other) { - AssertValid(); - if (_sign < 0) + { return -1; - if (_bits == null) - return ((ulong)_sign).CompareTo(other); + } + + if (_bits is null) + { + return ((ulong)(uint)_sign).CompareTo(other); + } + int cu = _bits.Length; - if (cu > 2) + int maxLimbs = sizeof(long) / 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 = _bits[0]; + } + else + { + uuTmp = 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. @@ -1207,15 +1293,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 -other._sign; + return + other._bits is not null ? -other._sign : + _sign < other._sign ? -1 : + _sign > other._sign ? +1 : + 0; } - if (other._bits == null) + if (other._bits is null) + { return _sign; + } int bitsResult = BigIntegerCalculator.Compare(_bits, other._bits); return _sign < 0 ? -bitsResult : bitsResult; @@ -1223,11 +1313,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); } /// @@ -1294,28 +1383,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; } @@ -1345,7 +1435,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; @@ -1354,10 +1444,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) @@ -1365,6 +1457,7 @@ private enum GetBytesMode destination[0] = 0; return Array.Empty(); } + return null; } } @@ -1374,14 +1467,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; - if (bits == null) + int nonZeroLimbIndex = 0; + nuint highLimb; + nuint[]? bits = _bits; + if (bits is null) { highByte = (byte)((sign < 0) ? 0xff : 0x00); - highDword = unchecked((uint)sign); + highLimb = (nuint)sign; } else if (sign == -1) { @@ -1393,57 +1487,53 @@ 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) - { - nonZeroDwordIndex++; - } + Debug.Assert(bits[^1] != 0); + nonZeroLimbIndex = ((ReadOnlySpan)bits).IndexOfAnyExcept((nuint)0); - highDword = ~bits[bits.Length - 1]; - if (bits.Length - 1 == nonZeroDwordIndex) + highLimb = ~bits[^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[^1]; } + // Find the most significant byte index within the high limb. + // Use LeadingZeroCount for O(1) instead of byte-scanning loop. byte msb; int msbIndex; - if ((msb = unchecked((byte)(highDword >> 24))) != highByte) + if (highByte == 0x00) { - msbIndex = 3; - } - else if ((msb = unchecked((byte)(highDword >> 16))) != highByte) - { - msbIndex = 2; - } - else if ((msb = unchecked((byte)(highDword >> 8))) != highByte) - { - msbIndex = 1; + // Positive: find highest non-zero byte + int lzc = BitOperations.LeadingZeroCount(highLimb); + msbIndex = Math.Max(0, bytesPerLimb - 1 - (lzc / 8)); } else { - msb = unchecked((byte)highDword); - msbIndex = 0; + // Negative: find highest non-0xFF byte + int lzc = BitOperations.LeadingZeroCount(~highLimb); + msbIndex = Math.Max(0, bytesPerLimb - 1 - (lzc / 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(4 * (bits.Length - 1) + length); + length = checked(bytesPerLimb * (bits.Length - 1) + length); } byte[] array; @@ -1452,15 +1542,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; } @@ -1468,7 +1561,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) { @@ -1491,26 +1584,26 @@ 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++; } } if (isBigEndian) { - curByte -= 4; - BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(curByte), dword); + curByte -= bytesPerLimb; + BinaryPrimitives.WriteUIntPtrBigEndian(destination.Slice(curByte), limb); } else { - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(curByte), dword); - curByte += 4; + BinaryPrimitives.WriteUIntPtrLittleEndian(destination.Slice(curByte), limb); + curByte += bytesPerLimb; } } } @@ -1521,27 +1614,20 @@ private enum GetBytesMode curByte--; } - Debug.Assert(msbIndex >= 0 && msbIndex <= 3); - destination[curByte] = unchecked((byte)highDword); - if (msbIndex != 0) + // Write significant bytes of the high limb + Debug.Assert(msbIndex >= 0 && msbIndex < bytesPerLimb); + for (int byteIdx = 0; byteIdx <= msbIndex; byteIdx++) { - curByte += increment; - destination[curByte] = unchecked((byte)(highDword >> 8)); - if (msbIndex != 1) + destination[curByte] = (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. - 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) @@ -1555,20 +1641,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] = (nuint)_sign; + highLimb = (_sign < 0) ? nuint.MaxValue : 0; } else { @@ -1576,29 +1662,28 @@ 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 - int msb = buffer.Length - 2; - while (msb > 0 && buffer[msb] == highDWord) - { - msb--; - } + // Find highest significant limb and ensure high bit is 0 if positive, 1 if negative + int msb = Math.Max(0, buffer[..^1].LastIndexOfAnyExcept(highLimb)); // Ensure high bit is 0 if positive, 1 if negative - bool needExtraByte = (buffer[msb] & 0x80000000) != (highDWord & 0x80000000); + nuint highBitMask = (nuint)1 << (BigIntegerCalculator.BitsPerLimb - 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[^1] = highLimb; } else { @@ -1646,16 +1731,28 @@ 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) - 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; + const double Log10Of2 = 0.3010299956639812; // Log10(2) + 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 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); @@ -1693,7 +1790,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, int leftSign, ReadOnlySpan rightBits, int rightSign) { bool trivialLeft = leftBits.IsEmpty; bool trivialRight = rightBits.IsEmpty; @@ -1701,77 +1798,68 @@ 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; 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 = 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 uint[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 uint[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 uint[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); + bitsBuffer.Dispose(); } - if (bitsFromPool != 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, -right._sign) + : 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, int leftSign, ReadOnlySpan rightBits, int rightSign) { bool trivialLeft = leftBits.IsEmpty; bool trivialRight = rightBits.IsEmpty; @@ -1779,58 +1867,50 @@ 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; 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 = 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 uint[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 uint[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 uint[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); + bitsBuffer.Dispose(); } - if (bitsFromPool != null) - ArrayPool.Shared.Return(bitsFromPool); - return result; } @@ -1838,73 +1918,68 @@ private static BigInteger Subtract(ReadOnlySpan leftBits, int leftSign, Re // 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 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 (value._bits is null) { - if (length > 2) hi = (int)value._bits[2]; - if (length > 1) mi = (int)value._bits[1]; - if (length > 0) lo = (int)value._bits[0]; + return value._sign; } - return new decimal(lo, mi, hi, value._sign < 0, 0); + return checked((decimal)(Int128)value); } public static explicit operator double(BigInteger value) { - value.AssertValid(); - int sign = value._sign; - uint[]? bits = value._bits; + nuint[]? bits = value._bits; - if (bits == null) + if (bits is null) + { return sign; + } int length = bits.Length; + int bitsPerLimb = BigIntegerCalculator.BitsPerLimb; - // 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; - else - return double.NegativeInfinity; + return sign == 1 ? double.PositiveInfinity : 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; - int z = BitOperations.LeadingZeroCount((uint)h); + if (nint.Size == 8) + { + h = bits[length - 1]; + m = length > 1 ? bits[length - 2] : 0; - int exp = (length - 2) * 32 - z; - ulong man = (h << 32 + z) | (m << z) | (l >> 32 - z); + z = BitOperations.LeadingZeroCount(h); + exp = (length - 1) * 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; + + 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); } @@ -1912,78 +1987,76 @@ 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; // Value packed into int32 sign + return value._sign; } + if (value._bits.Length > 1) { - // More than 32 bits + // 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; } int len = value._bits.Length; - if (len > 2) + int maxLimbs = sizeof(long) / nint.Size; + if (len > maxLimbs) { throw new OverflowException(SR.Overflow_Int64); } ulong uu; - if (len > 1) + + if (nint.Size == 8) { - uu = NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]); + uu = value._bits[0]; } else { - uu = value._bits[0]; + uu = 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); } @@ -1992,118 +2065,101 @@ 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; } 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) { - uu = new UInt128( - NumericsHelpers.MakeUInt64((len > 3) ? value._bits[3] : 0, value._bits[2]), - NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]) - ); + uu = len > 1 ? new UInt128(value._bits[1], value._bits[0]) : (UInt128)(ulong)value._bits[0]; } - else if (len > 1) + else if (len > 2) { - uu = NumericsHelpers.MakeUInt64(value._bits[1], value._bits[0]); + uu = new UInt128( + ((ulong)((len > 3) ? (uint)value._bits[3] : 0) << 32 | (uint)value._bits[2]), + ((ulong)(uint)value._bits[1] << 32 | (uint)value._bits[0]) + ); } else { - uu = 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 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); } int len = value._bits.Length; - if (len > 2 || value._sign < 0) + int maxLimbs = sizeof(long) / 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 value._bits[0]; } - 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. @@ -2112,79 +2168,63 @@ 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); } 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) + { + return len > 1 + ? new UInt128(value._bits[1], value._bits[0]) + : (UInt128)(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. /// 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. @@ -2195,45 +2235,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. @@ -2241,23 +2264,24 @@ public static implicit operator BigInteger(long value) public static implicit operator BigInteger(Int128 value) { int sign; - uint[]? bits; + nuint[]? bits; if ((int.MinValue < value) && (value <= int.MaxValue)) { - sign = (int)value; + if (value == int.MinValue) + { + return s_int32MinValue; + } + + sign = (int)(long)value; bits = null; } - else if (value == int.MinValue) - { - return s_bnMinInt; - } else { UInt128 x; if (value < 0) { - x = unchecked((UInt128)(-value)); + x = (UInt128)(-value); sign = -1; } else @@ -2266,31 +2290,36 @@ 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)); + bits = x <= ulong.MaxValue + ? [(nuint)(ulong)x] + : [(nuint)(ulong)x, (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 = [(uint)(x >> (BitsPerUInt32 * 0))]; + } + else if (x <= ulong.MaxValue) + { + bits = [(uint)(x >> (BitsPerUInt32 * 0)), + (uint)(x >> (BitsPerUInt32 * 1))]; + } + else if (x <= new UInt128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) + { + bits = [(uint)(x >> (BitsPerUInt32 * 0)), + (uint)(x >> (BitsPerUInt32 * 1)), + (uint)(x >> (BitsPerUInt32 * 2))]; + } + else + { + bits = [(uint)(x >> (BitsPerUInt32 * 0)), + (uint)(x >> (BitsPerUInt32 * 1)), + (uint)(x >> (BitsPerUInt32 * 2)), + (uint)(x >> (BitsPerUInt32 * 3))]; + } } } @@ -2300,41 +2329,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) - { - if (Environment.Is64BitProcess) - { - return new BigInteger(value); - } - else - { - return new BigInteger((int)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. @@ -2343,38 +2350,46 @@ public static implicit operator BigInteger(ulong value) public static implicit operator BigInteger(UInt128 value) { int sign = +1; - uint[]? bits; + nuint[]? bits; - if (value <= (uint)int.MaxValue) + if (value <= (ulong)int.MaxValue) { - sign = (int)value; + sign = (int)(ulong)value; bits = null; } + else if (nint.Size == 8) + { + if (value <= ulong.MaxValue) + { + bits = [(nuint)(ulong)value]; + } + else + { + bits = [(nuint)(ulong)value, + (nuint)(ulong)(value >> 64)]; + } + } else if (value <= uint.MaxValue) { - bits = new uint[1]; - bits[0] = (uint)(value >> (kcbitUint * 0)); + bits = [(uint)(value >> (BitsPerUInt32 * 0))]; } else if (value <= ulong.MaxValue) { - bits = new uint[2]; - bits[0] = (uint)(value >> (kcbitUint * 0)); - bits[1] = (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 uint[3]; - bits[0] = (uint)(value >> (kcbitUint * 0)); - bits[1] = (uint)(value >> (kcbitUint * 1)); - bits[2] = (uint)(value >> (kcbitUint * 2)); + bits = [(uint)(value >> (BitsPerUInt32 * 0)), + (uint)(value >> (BitsPerUInt32 * 1)), + (uint)(value >> (BitsPerUInt32 * 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 = [(uint)(value >> (BitsPerUInt32 * 0)), + (uint)(value >> (BitsPerUInt32 * 1)), + (uint)(value >> (BitsPerUInt32 * 2)), + (uint)(value >> (BitsPerUInt32 * 3))]; } return new BigInteger(sign, bits); @@ -2384,177 +2399,88 @@ 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 (Environment.Is64BitProcess) - { - return new BigInteger(value); - } - else - { - return new BigInteger((uint)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; - } - - uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; - uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; - - uint[]? leftBufferFromPool = null; - int size = (left._bits?.Length ?? 1) + 1; - Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - x = x.Slice(0, left.WriteTo(x)); + public static implicit operator BigInteger(nuint value) => value <= int.MaxValue ? new BigInteger((int)value, null) : new BigInteger(+1, [value]); - uint[]? rightBufferFromPool = null; - size = (right._bits?.Length ?? 1) + 1; - Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - y = y.Slice(0, right.WriteTo(y)); - - uint[]? resultBufferFromPool = null; - size = Math.Max(x.Length, y.Length); - Span z = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - - for (int i = 0; i < z.Length; i++) - { - uint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; - uint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; - 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) - ArrayPool.Shared.Return(resultBufferFromPool); - - return result; - } + 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 right; } - uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; - uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; - - uint[]? leftBufferFromPool = null; - int size = (left._bits?.Length ?? 1) + 1; - Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - x = x.Slice(0, left.WriteTo(x)); - - uint[]? rightBufferFromPool = null; - size = (right._bits?.Length ?? 1) + 1; - Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - y = y.Slice(0, right.WriteTo(y)); - - uint[]? resultBufferFromPool = null; - size = Math.Max(x.Length, y.Length); - Span z = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - - for (int i = 0; i < z.Length; i++) + if (right.IsZero) { - uint xu = ((uint)i < (uint)x.Length) ? x[i] : xExtend; - uint yu = ((uint)i < (uint)y.Length) ? y[i] : yExtend; - z[i] = xu | yu; + return left; } - if (leftBufferFromPool != null) - ArrayPool.Shared.Return(leftBufferFromPool); + return left._bits is null && right._bits is null + ? (BigInteger)(left._sign | right._sign) + : BitwiseOr(ref left, ref right); + } - if (rightBufferFromPool != null) - ArrayPool.Shared.Return(rightBufferFromPool); + 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); - var result = new BigInteger(z); + /// + /// 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) + { + int xLen = left._bits?.Length ?? 1; + int yLen = right._bits?.Length ?? 1; - if (resultBufferFromPool != null) - ArrayPool.Shared.Return(resultBufferFromPool); + // 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 = (left._sign < 0 || right._sign < 0) + ? Math.Max(xLen, yLen) + 1 + : Math.Min(xLen, yLen) + 1; - return result; + return BitwiseOp(in left, in right, zLen); } - 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._bits is null && right._bits is null) - { - return left._sign ^ right._sign; - } - - uint xExtend = (left._sign < 0) ? uint.MaxValue : 0; - uint yExtend = (right._sign < 0) ? uint.MaxValue : 0; - - uint[]? leftBufferFromPool = null; - int size = (left._bits?.Length ?? 1) + 1; - Span x = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : leftBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - x = x.Slice(0, left.WriteTo(x)); - - uint[]? rightBufferFromPool = null; - size = (right._bits?.Length ?? 1) + 1; - Span y = ((uint)size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : rightBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - y = y.Slice(0, right.WriteTo(y)); - - uint[]? resultBufferFromPool = null; - size = Math.Max(x.Length, y.Length); - Span z = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : resultBufferFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + int xLen = left._bits?.Length ?? 1; + int yLen = right._bits?.Length ?? 1; + return BitwiseOp(in left, in right, Math.Max(xLen, yLen) + 1); + } - 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; - z[i] = xu ^ yu; - } + /// + /// Computes two's complement XOR directly from magnitude representation. + /// + private static BigInteger BitwiseXor(ref readonly BigInteger left, ref readonly BigInteger right) + { + int xLen = left._bits?.Length ?? 1; + int yLen = right._bits?.Length ?? 1; + return BitwiseOp(in left, in right, Math.Max(xLen, yLen) + 1); + } - if (leftBufferFromPool != null) - ArrayPool.Shared.Return(leftBufferFromPool); + private static BigInteger BitwiseOp(ref readonly BigInteger left, ref readonly BigInteger right, int zLen) + where TOp : struct, BigIntegerCalculator.IBitwiseOp + { + Span z = RentedBuffer.Create(zLen, out RentedBuffer zBuffer); - if (rightBufferFromPool != null) - ArrayPool.Shared.Return(rightBufferFromPool); + BigIntegerCalculator.BitwiseOp( + left._bits, left._sign, + right._bits, right._sign, + z); - var result = new BigInteger(z); + BigInteger result = new(z); - if (resultBufferFromPool != null) - ArrayPool.Shared.Return(resultBufferFromPool); + zBuffer.Dispose(); return result; } @@ -2562,201 +2488,281 @@ public static implicit operator BigInteger(nuint value) 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, kcbitUint); + (int digitShift, int smallShift) = Math.DivRem(shift, BigIntegerCalculator.BitsPerLimb); 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.BitsPerLimb - 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) { if (value == 0) - return s_bnZeroInt; + { + return s_zero; + } - uint m = NumericsHelpers.Abs(value); + nuint m = NumericsHelpers.Abs(value); - uint r = m << smallShift; - uint over = - smallShift == 0 + nuint r = m << smallShift; + nuint over = smallShift == 0 ? 0 - : m >> (kcbitUint - smallShift); + : m >> (BigIntegerCalculator.BitsPerLimb - smallShift); - uint[] rgu; + nuint[] rgu; if (over == 0) { - if (digitShift == 0 && r < kuMaskHighBit) - return new BigInteger(value << smallShift, null); + if (digitShift == 0 && r <= int.MaxValue) + { + return new BigInteger(value >= 0 ? (int)r : -(int)r, 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) { 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, kcbitUint); + (int digitShift, int smallShift) = Math.DivRem(shift, BigIntegerCalculator.BitsPerLimb); 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 31-bit right shift on an int type. - smallShift = kcbitUint - 1; + smallShift = 31; } 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 >> 31, null); + } - uint[]? zFromPool = null; - Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[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); - 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(); } - BigInteger result = new BigInteger(zd, neg); + BigInteger result = new(zd, neg); - if (zFromPool != null) - ArrayPool.Shared.Return(zFromPool); + zdBuffer.Dispose(); return result; } public static BigInteger operator ~(BigInteger value) - { - return -(value + One); - } - - public static BigInteger operator -(BigInteger value) { value.AssertValid(); - return new BigInteger(-value._sign, value._bits); - } - public static BigInteger operator +(BigInteger value) - { - value.AssertValid(); - return value; + if (value._bits is null) + { + return ~value._sign; // implicit int -> BigInteger handles int.MinValue + } + + BigInteger result; + + if (value._sign >= 0) + { + // ~positive = -(positive + 1): add 1 to magnitude, negate + int size = value._bits.Length + 1; + 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 = RentedBuffer.Create(value._bits.Length, out RentedBuffer bitsBuffer); + + BigIntegerCalculator.Subtract(value._bits, 1, bits); + result = new BigInteger(bits, negative: false); + bitsBuffer.Dispose(); + } + + 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) { - return value + One; + if (value._bits is null) + { + return (long)value._sign + 1; + } + + BigInteger result; + + if (value._sign >= 0) + { + int size = value._bits.Length + 1; + 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 = RentedBuffer.Create(value._bits.Length, out RentedBuffer bitsBuffer); + + BigIntegerCalculator.Subtract(value._bits, 1, bits); + result = new BigInteger(bits, negative: true); + bitsBuffer.Dispose(); + } + + return result; } public static BigInteger operator --(BigInteger value) { - return value - One; - } + if (value._bits is null) + { + return (long)value._sign - 1; + } - public static BigInteger operator +(BigInteger left, BigInteger right) - { - left.AssertValid(); - right.AssertValid(); + BigInteger result; - if (left._bits == null && right._bits == null) - return (long)left._sign + right._sign; + if (value._sign >= 0) + { + 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 = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); - 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); + BigIntegerCalculator.Add(value._bits, 1, bits); + result = new BigInteger(bits, negative: true); + bitsBuffer.Dispose(); + } + + return result; } - public static BigInteger operator *(BigInteger left, BigInteger right) + 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; + if (left._bits is null && right._bits is null) + { + return (long)left._sign + right._sign; + } - return Multiply(left._bits, left._sign, right._bits, right._sign); + return left._sign < 0 != right._sign < 0 + ? Subtract(left._bits, left._sign, right._bits, -right._sign) + : Add(left._bits, left._sign, right._bits, right._sign); } - private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOnlySpan right, int rightSign) + 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) { bool trivialLeft = left.IsEmpty; bool trivialRight = right.IsEmpty; @@ -2764,70 +2770,57 @@ 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; 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 = 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 uint[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 uint[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 uint[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)); + bitsBuffer.Dispose(); } - if (bitsFromPool != 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) { @@ -2838,51 +2831,41 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn { // The divisor is non-trivial // and therefore the bigger one - return s_bnZeroInt; + return s_zero; } - uint[]? 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 uint[BigIntegerCalculator.StackAllocThreshold] - : quotientFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span quotient = RentedBuffer.Create(size, out RentedBuffer quotientBuffer); - try - { - //may throw DivideByZeroException - BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient); - return new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - } - finally - { - if (quotientFromPool != null) - ArrayPool.Shared.Return(quotientFromPool); - } + //may throw DivideByZeroException + BigIntegerCalculator.Divide(dividend._bits, NumericsHelpers.Abs(divisor._sign), quotient); + + BigInteger result = new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); + + quotientBuffer.Dispose(); + + return result; } - 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 uint[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); - var result = new BigInteger(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); + BigInteger result = new(quotient, (dividend._sign < 0) ^ (divisor._sign < 0)); - if (quotientFromPool != null) - ArrayPool.Shared.Return(quotientFromPool); + quotientBuffer.Dispose(); return result; } @@ -2890,11 +2873,8 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn 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) { @@ -2910,193 +2890,100 @@ 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; + Debug.Assert(dividend._bits is not null); + nuint remainder = BigIntegerCalculator.Remainder(dividend._bits, NumericsHelpers.Abs(divisor._sign)); + return dividend._sign < 0 ? -(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) { return dividend; } - uint[]? bitsFromPool = null; int size = dividend._bits.Length; - Span bits = (size <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bitsFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); + Span bits = RentedBuffer.Create(size, out RentedBuffer bitsBuffer); 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) - ArrayPool.Shared.Return(bitsFromPool); + bitsBuffer.Dispose(); 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. @@ -3105,17 +2992,15 @@ private static BigInteger Multiply(ReadOnlySpan left, int leftSign, ReadOn /// 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(); - - uint highValue; + nuint highValue; int bitsArrayLength; int sign = _sign; - uint[]? bits = _bits; + nuint[]? bits = _bits; - if (bits == null) + if (bits is null) { bitsArrayLength = 1; - highValue = (uint)(sign < 0 ? -sign : sign); + highValue = (nuint)(sign < 0 ? -sign : sign); } else { @@ -3123,49 +3008,45 @@ public long GetBitLength() highValue = bits[bitsArrayLength - 1]; } - long bitLength = bitsArrayLength * 32L - BitOperations.LeadingZeroCount(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] >= kuMaskHighBit); + 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); } else { - // Int32.MinValue should not be stored in the _sign field + // int.MinValue should not be stored in the _sign field Debug.Assert(_sign > int.MinValue); } } @@ -3191,27 +3072,25 @@ public static (BigInteger Quotient, BigInteger Remainder) DivRem(BigInteger left /// public static BigInteger LeadingZeroCount(BigInteger value) { - value.AssertValid(); - if (value._bits is null) { - return int.LeadingZeroCount(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) ? uint.LeadingZeroCount(value._bits[^1]) : 0; + return (value._sign >= 0) + ? BitOperations.LeadingZeroCount(value._bits[^1]) + : 0; } /// public static BigInteger PopCount(BigInteger value) { - value.AssertValid(); - if (value._bits is null) { - return int.PopCount(value._sign); + return nint.PopCount(value._sign); } ulong result = 0; @@ -3222,8 +3101,8 @@ 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 += (ulong)BitOperations.PopCount(part); } } else @@ -3232,14 +3111,14 @@ 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 += (ulong)BitOperations.PopCount(part); i++; } @@ -3250,7 +3129,7 @@ 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 += (ulong)BitOperations.PopCount(part); i++; } } @@ -3261,19 +3140,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) { - uint rs = BitOperations.RotateLeft((uint)value._sign, rotateAmount); + nuint rs = BitOperations.RotateLeft((nuint)value._sign, rotateAmount); return neg - ? new BigInteger((int)rs) - : new BigInteger(rs); + ? new BigInteger((nint)rs) + : new BigInteger(rs); } return Rotate(value._bits, neg, rotateAmount); @@ -3282,48 +3161,45 @@ 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) { - uint rs = BitOperations.RotateRight((uint)value._sign, rotateAmount); + nuint rs = BitOperations.RotateRight((nuint)value._sign, rotateAmount); return neg - ? new BigInteger((int)rs) - : new BigInteger(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.BitsPerLimb - 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 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 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; } - uint[]? zFromPool = null; - Span zd = ((uint)zLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : zFromPool = ArrayPool.Shared.Rent(zLength)).Slice(0, zLength); + Span zd = RentedBuffer.Create(zLength, out RentedBuffer zdBuffer); zd[^1] = 0; bits.CopyTo(zd); @@ -3334,13 +3210,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); } @@ -3349,10 +3225,9 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long ro negative = false; } - var result = new BigInteger(zd, negative); + BigInteger result = new(zd, negative); - if (zFromPool != null) - ArrayPool.Shared.Return(zFromPool); + zdBuffer.Dispose(); return result; } @@ -3360,11 +3235,9 @@ private static BigInteger Rotate(ReadOnlySpan bits, bool negative, long ro /// public static BigInteger TrailingZeroCount(BigInteger value) { - value.AssertValid(); - if (value._bits is null) { - return int.TrailingZeroCount(value._sign); + return nint.TrailingZeroCount(value._sign); } ulong result = 0; @@ -3372,15 +3245,15 @@ 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)BigIntegerCalculator.BitsPerLimb; } - result += uint.TrailingZeroCount(part); + result += (ulong)BitOperations.TrailingZeroCount(part); return result; } @@ -3402,47 +3275,35 @@ static bool IBinaryInteger.TryReadLittleEndian(ReadOnlySpan so /// int IBinaryInteger.GetShortestBitLength() { - AssertValid(); - uint[]? bits = _bits; + nuint[]? bits = _bits; if (bits is null) { int value = _sign; - if (value >= 0) - { - return (sizeof(int) * 8) - BitOperations.LeadingZeroCount((uint)(value)); - } - else - { - return (sizeof(int) * 8) + 1 - BitOperations.LeadingZeroCount((uint)(~value)); - } + return value >= 0 ? 32 - BitOperations.LeadingZeroCount((uint)value) : 33 - BitOperations.LeadingZeroCount(~(uint)value); } - int result = (bits.Length - 1) * 32; + int result = (bits.Length - 1) * BigIntegerCalculator.BitsPerLimb; if (_sign >= 0) { - result += (sizeof(uint) * 8) - BitOperations.LeadingZeroCount(bits[^1]); + result += BigIntegerCalculator.BitsPerLimb - BitOperations.LeadingZeroCount(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 // 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 += (sizeof(uint) * 8) + 1 - BitOperations.LeadingZeroCount(~part); + result += BigIntegerCalculator.BitsPerLimb + 1 - BitOperations.LeadingZeroCount(~part); } return result; @@ -3454,8 +3315,7 @@ int IBinaryInteger.GetShortestBitLength() /// bool IBinaryInteger.TryWriteBigEndian(Span destination, out int bytesWritten) { - AssertValid(); - uint[]? bits = _bits; + nuint[]? bits = _bits; int byteCount = GetGenericMathByteCount(); @@ -3463,27 +3323,18 @@ bool IBinaryInteger.TryWriteBigEndian(Span destination, out in { if (bits is null) { - int value = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(_sign) : _sign; - 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) * sizeof(uint)); + Span dest = destination; - for (int i = 0; i < bits.Length; i++) + for (int i = bits.Length - 1; i >= 0; i--) { - uint part = bits[i]; - - if (BitConverter.IsLittleEndian) - { - part = BinaryPrimitives.ReverseEndianness(part); - } - - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Subtract(ref address, sizeof(uint)); + BinaryPrimitives.WriteUIntPtrBigEndian(dest, bits[i]); + dest = dest.Slice(nint.Size); } } else @@ -3491,55 +3342,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 - sizeof(uint)); - - int i = 0; - uint part; + bool needsSignExtension = byteCount > bits.Length * nint.Size; + Span dest = destination; - do + if (needsSignExtension) { - // first do complement and +1 as long as carry is needed - part = ~bits[i] + 1; - - if (BitConverter.IsLittleEndian) - { - part = BinaryPrimitives.ReverseEndianness(part); - } - - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Subtract(ref address, sizeof(uint)); - - 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) { - part = BinaryPrimitives.ReverseEndianness(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, sizeof(uint)); - - 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, uint.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)))); + BinaryPrimitives.WriteUIntPtrBigEndian(dest, part); + dest = dest.Slice(nint.Size); } } @@ -3556,8 +3393,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,26 +3401,18 @@ bool IBinaryInteger.TryWriteLittleEndian(Span destination, out { if (bits is null) { - int value = BitConverter.IsLittleEndian ? _sign : BinaryPrimitives.ReverseEndianness(_sign); - 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++) { - uint part = bits[i]; - - if (!BitConverter.IsLittleEndian) - { - part = BinaryPrimitives.ReverseEndianness(part); - } - - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, sizeof(uint)); + BinaryPrimitives.WriteUIntPtrLittleEndian(dest, bits[i]); + dest = dest.Slice(nint.Size); } } else @@ -3592,55 +3420,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 - sizeof(uint)); + bool needsSignExtension = byteCount > bits.Length * nint.Size; + Span dest = destination; - int i = 0; - uint 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) { - part = BinaryPrimitives.ReverseEndianness(part); + part = 0; } - - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, sizeof(uint)); - - 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) { - part = BinaryPrimitives.ReverseEndianness(part); + part = ~bits[i] + 1; + } + else + { + part = ~bits[i]; } - Unsafe.WriteUnaligned(ref address, part); - address = ref Unsafe.Add(ref address, sizeof(uint)); - - 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, uint.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)))); + BinaryPrimitives.WriteUIntPtrLittleEndian(dest, nuint.MaxValue); } } @@ -3656,38 +3468,33 @@ 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 // 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 ((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; } } @@ -3707,19 +3514,14 @@ private int GetGenericMathByteCount() /// public static BigInteger Log2(BigInteger value) { - value.AssertValid(); - if (IsNegative(value)) { ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - if (value._bits is null) - { - return 31 ^ uint.LeadingZeroCount((uint)(value._sign | 1)); - } - - return ((value._bits.Length * 32) - 1) ^ uint.LeadingZeroCount(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])); } // @@ -3736,11 +3538,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); @@ -3767,17 +3564,14 @@ static void ThrowMinMaxException(T min, T max) /// 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) { @@ -3796,14 +3590,9 @@ public static BigInteger CopySign(BigInteger value, BigInteger sign) /// static int INumber.Sign(BigInteger value) { - value.AssertValid(); - - if (value._bits is null) - { - return int.Sign(value._sign); - } - - return value._sign; + return value._bits is null + ? value._sign > 0 ? 1 + : (value._sign < 0 ? -1 : 0) : value._sign; } // @@ -3879,13 +3668,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; } /// @@ -3906,7 +3691,6 @@ public static bool IsEvenInteger(BigInteger value) /// public static bool IsNegative(BigInteger value) { - value.AssertValid(); return value._sign < 0; } @@ -3919,19 +3703,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; } @@ -3947,16 +3726,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; } @@ -3967,9 +3742,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; } @@ -4565,7 +4337,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; @@ -4683,7 +4455,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 +4478,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)) { @@ -4786,15 +4558,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 = value._bits[0]; } + else + { + bits = 0; - bits |= value._bits[0]; + if (value._bits.Length >= 2) + { + bits = value._bits[1]; + bits <<= 32; + } + + bits |= value._bits[0]; + } if (IsNegative(value)) { @@ -4820,26 +4601,38 @@ 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 = value._bits[0]; - if (value._bits.Length >= 3) - { - upperBits |= value._bits[2]; + if (value._bits.Length >= 2) + { + upperBits = value._bits[1]; + } } - - if (value._bits.Length >= 2) + else { - lowerBits = value._bits[1]; - lowerBits <<= 32; - } + if (value._bits.Length >= 4) + { + upperBits = value._bits[3]; + upperBits <<= 32; + } + + if (value._bits.Length >= 3) + { + upperBits |= value._bits[2]; + } - lowerBits |= value._bits[0]; + if (value._bits.Length >= 2) + { + lowerBits = value._bits[1]; + lowerBits <<= 32; + } + + lowerBits |= value._bits[0]; + } - UInt128 bits = new UInt128(upperBits, lowerBits); + UInt128 bits = new(upperBits, lowerBits); if (IsNegative(value)) { @@ -4850,7 +4643,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - actualResult = value._sign; + actualResult = (Int128)(long)value._sign; } result = (TOther)(object)actualResult; @@ -4862,15 +4655,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 +4706,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 +4729,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 +4752,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 = value._bits[0]; } + else + { + bits = 0; + + if (value._bits.Length >= 2) + { + bits = value._bits[1]; + bits <<= 32; + } - bits |= value._bits[0]; + bits |= value._bits[0]; + } if (IsNegative(value)) { @@ -5001,26 +4795,38 @@ 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 = value._bits[0]; - if (value._bits.Length >= 3) - { - upperBits |= value._bits[2]; + if (value._bits.Length >= 2) + { + upperBits = value._bits[1]; + } } - - if (value._bits.Length >= 2) + else { - lowerBits = value._bits[1]; - lowerBits <<= 32; - } + if (value._bits.Length >= 4) + { + upperBits = value._bits[3]; + upperBits <<= 32; + } - lowerBits |= value._bits[0]; + if (value._bits.Length >= 3) + { + upperBits |= value._bits[2]; + } + + if (value._bits.Length >= 2) + { + lowerBits = value._bits[1]; + lowerBits <<= 32; + } + + lowerBits |= value._bits[0]; + } - UInt128 bits = new UInt128(upperBits, lowerBits); + UInt128 bits = new(upperBits, lowerBits); if (IsNegative(value)) { @@ -5031,7 +4837,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - actualResult = (UInt128)value._sign; + actualResult = (UInt128)(Int128)(long)value._sign; } result = (TOther)(object)actualResult; @@ -5043,15 +4849,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)) { @@ -5089,18 +4887,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, kcbitUint); + (int digitShift, int smallShift) = Math.DivRem(shiftAmount, BigIntegerCalculator.BitsPerLimb); if (value._bits is null) { @@ -5109,10 +4911,12 @@ 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; + ReadOnlySpan bits = value._bits; Debug.Assert(bits.Length > 0); @@ -5124,32 +4928,37 @@ 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 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 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; - if (bits[^1] == kuMaskHighBit && negLeadingZeroCount == bits.Length - 1) + nuint signBit = (nuint)1 << (BigIntegerCalculator.BitsPerLimb - 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; + { + return s_minusOne; + } 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 +4967,7 @@ 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); + Span zd = RentedBuffer.Create(zLength, out RentedBuffer zdBuffer); zd[^1] = 0; bits.Slice(digitShift).CopyTo(zd); @@ -5175,7 +4981,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 +4992,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; + result = neg ? s_minusOne : default; } - else if (neg && (int)zd[^1] < 0) + else if (neg && (nint)zd[^1] < 0) { NumericsHelpers.DangerousMakeTwosComplement(zd); result = new BigInteger(zd, true); @@ -5203,18 +5009,16 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va result = new BigInteger(zd, false); } - if (zFromPool != null) - ArrayPool.Shared.Return(zFromPool); + zdBuffer.Dispose(); 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 >> 31, null); } // // ISignedNumber - // /// static BigInteger ISignedNumber.NegativeOne => MinusOne; 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..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 @@ -3,196 +3,165 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; 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) + 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); - 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) + public static void Add(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(right.Length >= 1); 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 uint resultPtr = ref MemoryMarshal.GetReference(bits); - ref uint rightPtr = ref MemoryMarshal.GetReference(right); - ref uint 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; - long 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(2^32 - 1) + 1 = 2^33 - 1, our carry c - // has always the value 1 or 0; hence, we're safe here. + nuint carry = 0; - do + for (int i = 0; i < right.Length; i++) { - carry += Unsafe.Add(ref leftPtr, i); - carry += Unsafe.Add(ref rightPtr, i); - Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry); - carry >>= 32; - 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) + public static void AddSelf(Span left, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); int i = 0; - long carry = 0L; - - // Switching to managed references helps eliminating - // index bounds check... - ref uint leftPtr = ref MemoryMarshal.GetReference(left); + nuint carry = 0; - // 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++) { - long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i]; - Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit); - carry = digit >> 32; + left[i] = AddWithCarry(left[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) ? 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, 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); 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 uint resultPtr = ref MemoryMarshal.GetReference(bits); - ref uint rightPtr = ref MemoryMarshal.GetReference(right); - ref uint leftPtr = ref MemoryMarshal.GetReference(left); - - int i = 0; - long carry = 0; + _ = left[right.Length - 1]; + _ = bits[right.Length - 1]; - // 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. + nuint borrow = 0; - do + for (int i = 0; i < right.Length; i++) { - carry += Unsafe.Add(ref leftPtr, i); - carry -= Unsafe.Add(ref rightPtr, i); - Unsafe.Add(ref resultPtr, i) = unchecked((uint)carry); - carry >>= 32; - i++; - } while (i < right.Length); - - Subtract(left, bits, ref resultPtr, startIndex: i, initialCarry: carry); + 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) + 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; - long carry = 0L; - - // Switching to managed references helps eliminating - // index bounds check... - ref uint leftPtr = ref MemoryMarshal.GetReference(left); + nuint borrow = 0; - // 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++) { - long digit = (Unsafe.Add(ref leftPtr, i) + carry) - right[i]; - Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit); - carry = digit >> 32; + left[i] = SubWithBorrow(left[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) ? 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, 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; + + _ = bits[left.Length]; 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) ? 1 : (nuint)0; + bits[i] = sum; } - Unsafe.Add(ref resultPtr, left.Length) = unchecked((uint)carry); + bits[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) ? 1 : (nuint)0; + bits[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 +171,7 @@ private static void Add(ReadOnlySpan left, Span bits, ref uint resul } } - Unsafe.Add(ref resultPtr, left.Length) = unchecked((uint)carry); + bits[left.Length] = carry; if (i < left.Length) { @@ -212,36 +181,41 @@ 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, 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 != 0) + { + _ = bits[left.Length - 1]; + } 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) ? 1 : (nuint)0; + bits[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) ? 1 : (nuint)0; + bits[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.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 0cfec0719933bc..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,71 +1,67 @@ // 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 { 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 = 32; + int DivideBurnikelZieglerThreshold = 64; - 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 = 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 = 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 = 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 +82,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 +94,14 @@ public static void Divide(ReadOnlySpan left, ReadOnlySpan right, Spa { // Same as above, but only returning the quotient. - uint[]? 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 = 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 { @@ -118,7 +109,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 +127,12 @@ public static void Remainder(ReadOnlySpan left, ReadOnlySpan right, else { int quotientLength = left.Length - right.Length + 1; - uint[]? quotientFromPool = null; - Span quotient = (quotientLength <= StackAllocThreshold ? - stackalloc uint[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(); } } @@ -156,7 +143,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,41 +158,23 @@ private static void DivRem(Span left, ReadOnlySpan right, Span } else { - uint[]? 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 = BigInteger.RentedBuffer.Create(left.Length, out BigInteger.RentedBuffer leftCopyBuffer); left.CopyTo(leftCopy); - uint[]? 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); - } - 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(); } } - 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 +189,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[^1]; + nuint divLo = right.Length > 1 ? right[^2] : 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 = BitsPerLimb - 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] : 0; divHi = (divHi << shift) | (divLo >> backShift); divLo = (divLo << shift) | (divNx >> backShift); @@ -241,34 +212,36 @@ 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] : 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] : 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] : 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,93 +256,76 @@ 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 = 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; - - 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) - ++carry; - leftElement -= digit; - } - - return (uint)carry; + return SubMul1(left, right, q); } - 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); 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) @@ -377,48 +333,46 @@ private static void DivideBurnikelZiegler(ReadOnlySpan left, ReadOnlySpan< } int sigmaDigit = n - right.Length; - int sigmaSmall = BitOperations.LeadingZeroCount(right[^1]); - - uint[]? bFromPool = null; + int sigmaSmall = nint.Size == 8 + ? BitOperations.LeadingZeroCount((ulong)right[^1]) + : BitOperations.LeadingZeroCount((uint)right[^1]); - Span b = (n <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : bFromPool = ArrayPool.Shared.Rent(n)).Slice(0, n); + Span b = BigInteger.RentedBuffer.Create(n, out BigInteger.RentedBuffer bBuffer); 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; - - Span a = (aLength <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : aFromPool = ArrayPool.Shared.Rent(aLength)).Slice(0, aLength); + Span a = BigInteger.RentedBuffer.Create(aLength, out BigInteger.RentedBuffer aBuffer); - // 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 <= BitsPerLimb); 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 = BitsPerLimb - sigmaSmall; + nuint carry = 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 +386,25 @@ 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); + Span r = BigInteger.RentedBuffer.Create(n + 1, out BigInteger.RentedBuffer rBuffer); - uint[]? zFromPool = null; - Span z = (2 * n <= StackAllocThreshold ? - stackalloc uint[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); + 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); + Span q = BigInteger.RentedBuffer.Create(n, out BigInteger.RentedBuffer qBuffer); 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); + qBuffer.Dispose(); } else { @@ -475,27 +419,26 @@ stackalloc uint[StackAllocThreshold] BurnikelZieglerD2n1n(z, b, quotient.Slice(i * n, n), r); } - if (zFromPool != null) - ArrayPool.Shared.Return(zFromPool); - if (bFromPool != null) - ArrayPool.Shared.Return(bFromPool); - if (aFromPool != null) - ArrayPool.Shared.Return(aFromPool); + zBuffer.Dispose(); + + bBuffer.Dispose(); + + aBuffer.Dispose(); 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 <= BitsPerLimb); - int carryShift = 32 - sigmaSmall; - uint carry = 0; + int carryShift = BitsPerLimb - sigmaSmall; + nuint carry = 0; for (int i = rt.Length - 1; i >= 0; i--) { @@ -511,11 +454,10 @@ stackalloc uint[StackAllocThreshold] } } - if (rFromPool != null) - ArrayPool.Shared.Return(rFromPool); + rBuffer.Dispose(); } - 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 +479,7 @@ private static void BurnikelZieglerFallback(ReadOnlySpan left, ReadOnlySpa } else if (right.Length == 1) { - ulong carry; + nuint carry; if (quotient.Length < left.Length) { @@ -545,27 +487,24 @@ 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; 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); + 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); @@ -580,16 +519,15 @@ 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); + r1Buffer.Dispose(); } } - 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 +544,15 @@ 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); + 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) + 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 +565,11 @@ 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); + Span d = BigInteger.RentedBuffer.Create(right.Length, out BigInteger.RentedBuffer dBuffer); if (CompareActual(a1, b1) < 0) { @@ -650,9 +581,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 +594,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 +603,14 @@ 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); + dBuffer.Dispose(); - 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..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 @@ -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); @@ -28,10 +28,11 @@ public FastReducer(ReadOnlySpan modulus, Span r, Span mu, Span Debug.Assert(q1.Length == modulus.Length * 2 + 2); Debug.Assert(q2.Length == modulus.Length * 2 + 2); - // Let r = 4^k, with 2^k > m - r[r.Length - 1] = 1; + // 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[^1] = 1; - // Let mu = 4^k / m + // Compute mu = floor(r / m) DivRem(r, modulus, mu); _modulus = modulus; @@ -41,13 +42,15 @@ 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); // Trivial: value is shorter if (value.Length < _modulus.Length) + { return value.Length; + } // Let q1 = v/2^(k-1) * mu _q1.Clear(); @@ -58,14 +61,14 @@ 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(); 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); @@ -73,7 +76,7 @@ private static int DivMul(ReadOnlySpan left, ReadOnlySpan right, Spa // 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^(BitsPerLimb*k). To spare memory allocations // we write the result to an already allocated memory. if (left.Length > k) @@ -89,17 +92,30 @@ 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 - // preceding reduction by 2^(32*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); + } + + // 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.GcdInv.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs index e17ff2e363e995..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,22 +1,35 @@ // 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; 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. - // 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) { - uint temp = left % right; + nuint temp = left % right; left = right; right = temp; } @@ -36,12 +49,14 @@ public static ulong Gcd(ulong left, ulong right) } if (right != 0) + { return Gcd((uint)right, (uint)(left % 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 +64,11 @@ 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); - - return Gcd(right, temp); + nuint remainder = Remainder(left, right); + return Gcd(right, remainder); } - 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 +77,21 @@ 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); + 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) + 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,18 +100,20 @@ 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; - 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; @@ -110,16 +122,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; @@ -127,22 +141,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; @@ -150,7 +168,9 @@ private static void Gcd(Span left, Span right) ++iteration; if (y == c) + { break; + } } if (b == 0) @@ -158,21 +178,21 @@ 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; } 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)); if (iteration % 2 == 1) { // Ensure left is larger than right - Span temp = left; + Span temp = left; left = right; right = temp; } @@ -184,13 +204,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 (right.Length > 1) + if (nint.Size == 4) + { + x = right[0]; + y = left[0]; + + 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 = right[0]; + y = left[0]; } left = left.Slice(0, Overwrite(left, Gcd(x, y))); @@ -200,80 +230,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 = (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[^1]; + ulong xm = xBuffer[^2]; + ulong xl = xBuffer[^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[^1]; + ym = yBuffer[^2]; + yl = yBuffer[^3]; + break; + + case 1: + yh = 0UL; + ym = yBuffer[^1]; + yl = yBuffer[^2]; + break; + + case 2: + yh = 0UL; + ym = 0UL; + yl = yBuffer[^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 = xBuffer[^1]; + ulong xl = xBuffer[^2]; + + ulong yh, yl; + + // arrange the bits + switch (xBuffer.Length - yBuffer.Length) + { + case 0: + yh = yBuffer[^1]; + yl = yBuffer[^2]; + break; - // Use all the bits but one, see [hac] 14.58 (ii) - int z = BitOperations.LeadingZeroCount((uint)xh); + case 1: + yh = 0UL; + yl = yBuffer[^1]; + break; + + default: + yh = 0UL; + yl = 0UL; + break; + } + + // Use all the bits but one, see [hac] 14.58 (ii) + int z = BitOperations.LeadingZeroCount(xh); - x = ((xh << 32 + z) | (xm << z) | (xl >> 32 - z)) >> 1; - y = ((yh << 32 + z) | (ym << z) | (yl >> 32 - z)) >> 1; + 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 +380,60 @@ 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] = (nuint)xDigit; + y[i] = (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 * x32[i] - b * y32[i] + xCarry; + long yDigit = d * y32[i] - c * x32[i] + yCarry; + xCarry = xDigit >> 32; + yCarry = yDigit >> 32; + x32[i] = (uint)xDigit; + y32[i] = (uint)yDigit; + } + } + else + { + // Big-endian fallback: use Int128 for widening arithmetic. + Int128 xCarry = 0, yCarry = 0; + for (int i = 0; i < length; i++) + { + 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] = (nuint)(ulong)xDigit; + y[i] = (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..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 @@ -10,42 +10,32 @@ 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(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); - temp.Clear(); + Span temp = BigInteger.RentedBuffer.Create(bits.Length, out BigInteger.RentedBuffer tempBuffer); - uint[]? valueCopyFromPool = null; - Span valueCopy = (bits.Length <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : valueCopyFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + Span valueCopy = BigInteger.RentedBuffer.Create(bits.Length, out BigInteger.RentedBuffer valueCopyBuffer); 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); - if (valueCopyFromPool != null) - ArrayPool.Shared.Return(valueCopyFromPool); + tempBuffer.Dispose(); + valueCopyBuffer.Dispose(); } - 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); @@ -58,32 +48,39 @@ private static Span PowCore(Span value, int valueLength, Span while (power != 0) { if ((power & 1) == 1) + { resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); + } + if (power != 1) + { valueLength = SquareSelf(ref value, valueLength, ref temp); + } + power >>= 1; } 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); int resultLength = leftLength + right.Length; Multiply(left.Slice(0, leftLength), right, temp.Slice(0, resultLength)); - 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); @@ -91,16 +88,17 @@ 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. @@ -111,95 +109,152 @@ public static int PowBound(uint power, int valueLength) checked { if ((power & 1) == 1) + { resultLength += valueLength; + } + if (power != 1) + { valueLength += valueLength; + } } + power >>= 1; } 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. + // 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++) { - uint p = power[i]; - for (int j = 0; j < 32; j++) + nuint p = power[i]; + for (int j = 0; j < BitsPerLimb; j++) { - if ((p & 1) == 1) - result = (result * value) % modulus; - value = (value * value) % modulus; + if (useUlong) + { + if ((p & 1) == 1) + { + result = (uint)(((ulong)result * value) % modulus); + } + + value = (uint)(((ulong)value * value) % modulus); + } + else + { + 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); + } + } + p >>= 1; } } - return PowCore(value, power[power.Length - 1], modulus, result); + return PowCore(value, power[^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. + // 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 ((power & 1) == 1) - result = (result * value) % modulus; - if (power != 1) - value = (value * value) % modulus; + if (useUlong) + { + if ((power & 1) == 1) + { + result = (uint)(((ulong)result * value) % modulus); + } + + if (power != 1) + { + value = (uint)(((ulong)value * value) % modulus); + } + } + else + { + 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); + } + } + 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 +262,8 @@ 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; 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 = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer valueCopyBuffer); // smallish optimization here: // subsequent operations will copy the elements to the beginning of the buffer, @@ -227,28 +279,22 @@ 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); - 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(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 +303,7 @@ 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); + 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, @@ -276,153 +319,436 @@ 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); - 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 #if DEBUG - // Mutable for unit testing... - internal static + static // Mutable for unit testing... #else - internal const + 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. + if ((modulus[0] & 1) != 0) + { + PowCoreMontgomery(value, valueLength, power, modulus, temp, bits); + return; + } 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); - r.Clear(); - - size = r.Length - modulus.Length + 1; - uint[]? muFromPool = null; - Span mu = ((uint)size <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - mu.Clear(); - - size = modulus.Length * 2 + 2; - uint[]? q1FromPool = null; - Span q1 = ((uint)size <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - q1.Clear(); - - uint[]? q2FromPool = null; - Span q2 = ((uint)size <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : q2FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - q2.Clear(); - - FastReducer reducer = new FastReducer(modulus, r, mu, q1, q2); - - if (rFromPool != null) - ArrayPool.Shared.Return(rFromPool); - - 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, - 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. + if ((modulus[0] & 1) != 0) + { + PowCoreMontgomery(value, valueLength, new ReadOnlySpan(in power), modulus, temp, bits); + return; + } + 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); - r.Clear(); - - size = r.Length - modulus.Length + 1; - uint[]? muFromPool = null; - Span mu = ((uint)size <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : muFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - mu.Clear(); - - size = modulus.Length * 2 + 2; - uint[]? q1FromPool = null; - Span q1 = ((uint)size <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : q1FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - q1.Clear(); - - uint[]? q2FromPool = null; - Span q2 = ((uint)size <= StackAllocThreshold ? - stackalloc uint[StackAllocThreshold] - : q2FromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - q2.Clear(); - - FastReducer reducer = new FastReducer(modulus, r, mu, q1, q2); - - if (rFromPool != null) - ArrayPool.Shared.Return(rFromPool); - - 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; + Span r = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer rBuffer); + + size = r.Length - modulus.Length + 1; + Span mu = BigInteger.RentedBuffer.Create(size, out BigInteger.RentedBuffer muBuffer); + + size = modulus.Length * 2 + 2; + 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); + + rBuffer.Dispose(); + + Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); + + muBuffer.Dispose(); + q1Buffer.Dispose(); + q2Buffer.Dispose(); + } + + /// + /// 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) * BitsPerLimb; + + 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 / BitsPerLimb; + int bitOffset = bitIndex % BitsPerLimb; + return (int)((value[limbIndex] >> bitOffset) & 1); + } + + 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; + int bufLen = k * 2; + nuint n0inv = ComputeMontgomeryInverse(modulus[0]); + + // Convert value to Montgomery form: montValue = (value << k*wordBits) mod n + int shiftLen = k + valueLength; + Span shifted = BigInteger.RentedBuffer.Create(shiftLen, out BigInteger.RentedBuffer shiftedBuffer); + 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)); + + shiftedBuffer.Dispose(); + + // Compute R mod n (Montgomery form of 1) and save for later + Span rModN = BigInteger.RentedBuffer.Create(k, out BigInteger.RentedBuffer rModNBuffer); + { + int oneShiftLen = k + 1; + Span oneShifted = BigInteger.RentedBuffer.Create(oneShiftLen, out BigInteger.RentedBuffer oneShiftedBuffer); + oneShifted[k] = 1; + DivRem(oneShifted, modulus, default); + oneShifted.Slice(0, k).CopyTo(rModN); + oneShiftedBuffer.Dispose(); + } + + int rModNLength = ActualLength(rModN); + + // 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(); + rModNBuffer.Dispose(); + + return; + } + + int windowSize = ChooseWindowSize(expBitLength); + int tableLen = 1 << (windowSize - 1); + + // 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); + } + + // 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) + { + // Use a separate product buffer for precomputation to avoid + // corrupting bits/temp (which are needed pristine for the main loop). + Span prod = BigInteger.RentedBuffer.Create(bufLen, out BigInteger.RentedBuffer prodBuffer); + + // Compute base^2 in Montgomery form + Span base2 = BigInteger.RentedBuffer.Create(k, out BigInteger.RentedBuffer base2Buffer); + + 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++) + { + ReadOnlySpan prev = table.Slice((i - 1) * k, k); + int prevLength = ActualLength(prev); + + prod.Clear(); + 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)); + } + + base2Buffer.Dispose(); + prodBuffer.Dispose(); + } + + // Initialize result to R mod n (bits and temp are untouched from caller) + bits.Clear(); + rModN.Slice(0, rModNLength).CopyTo(bits); + int resultLen = rModNLength; + + rModNBuffer.Dispose(); + + // Left-to-right sliding window exponentiation + int bitPos = expBitLength - 1; + while (bitPos >= 0) + { + if (GetBit(power, bitPos) == 0) + { + 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++) + { + wValue = (wValue << 1) | GetBit(power, bitPos - i); + wLen++; + } + + // Trim trailing zeros to ensure the window value is odd + while ((wValue & 1) == 0) + { + wValue >>= 1; + wLen--; + } + + // Square for each bit in the window + for (int i = 0; i < wLen; i++) + { + 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); + 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(resultLen).Clear(); + resultLen = MontgomeryReduce(bits, modulus, n0inv); + + // Copy result back to the original output buffer + bits.Slice(0, resultLen).CopyTo(originalBits); + originalBits.Slice(resultLen).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); + + // 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++) + { + x *= 2 - n0 * x; + } + + return 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 = 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] = (uint)p; + carry = (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) + { + // 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); + value.Slice(k).Clear(); + + return ActualLength(value.Slice(0, k)); } - 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,27 +758,28 @@ 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 < 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; } } - 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, - 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. @@ -467,21 +794,23 @@ 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; } 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,27 +820,28 @@ 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 < 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; } } - 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, - 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. @@ -526,11 +856,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 9ef5ea7c359d47..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; @@ -10,11 +9,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)(0x80000000 / BitsPerLimb); int digitShift = digitShiftMax; int smallShift = 0; @@ -22,62 +21,67 @@ 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, BitsPerLimb); + } RotateRight(bits, digitShift % bits.Length, smallShift); } else { if (rotateLeftAmount != 0x80000000) - (digitShift, smallShift) = Math.DivRem((int)rotateLeftAmount, 32); + { + (digitShift, smallShift) = Math.DivRem((int)rotateLeftAmount, BitsPerLimb); + } 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) + { return; + } 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) + { return; + } 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); + Span tmp = BigInteger.RentedBuffer.Create(tmpLength, out BigInteger.RentedBuffer tmpBuffer); if (upperLength < lowerLength) { @@ -92,64 +96,67 @@ stackalloc uint[StackAllocThreshold] tmp.CopyTo(lowerDst); } - if (tmpFromPool != null) - ArrayPool.Shared.Return(tmpFromPool); + tmpBuffer.Dispose(); } - 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 < BitsPerLimb); carry = 0; if (shift == 0 || bits.IsEmpty) + { return; + } - int back = 32 - shift; + int back = BitsPerLimb - 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) + // 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; - 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 +166,69 @@ 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 < BitsPerLimb); carry = 0; if (shift == 0 || bits.IsEmpty) + { return; + } - int back = 32 - shift; + int back = BitsPerLimb - 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 +238,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..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 @@ -14,16 +14,20 @@ 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)] - 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 @@ -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); } @@ -47,11 +51,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 +70,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 +79,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 +106,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,18 +120,10 @@ 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); - fold.Clear(); + Span fold = BigInteger.RentedBuffer.Create(foldLength, out BigInteger.RentedBuffer foldBuffer); int coreLength = foldLength + foldLength; - uint[]? coreFromPool = null; - Span core = ((uint)coreLength <= StackAllocThreshold - ? stackalloc uint[StackAllocThreshold] - : coreFromPool = ArrayPool.Shared.Rent(coreLength)).Slice(0, coreLength); - core.Clear(); + Span core = BigInteger.RentedBuffer.Create(coreLength, out BigInteger.RentedBuffer coreBuffer); // ... compute z_a = a_1 + a_0 (call it fold...) Add(valueHigh, valueLow, fold); @@ -135,26 +131,20 @@ 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) + static void Naive(ReadOnlySpan value, Span bits) { Debug.Assert(bits.Length == value.Length + value.Length); - Debug.Assert(bits.Trim(0u).IsEmpty); - - // Switching to managed references helps eliminating - // index bounds check... - ref uint resultPtr = ref MemoryMarshal.GetReference(bits); + Debug.Assert(bits.Trim((nuint)0).IsEmpty); // Squares the bits using the "grammar-school" method. // Envisioning the "rhombus" of a pen-and-paper calculation @@ -163,55 +153,68 @@ 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++) - { - ulong carry = 0UL; - uint v = value[i]; - for (int j = 0; j < i; j++) + // z_i+j + a_j * a_i + c <= 2(2^n - 1) + (2^n - 1)^2 = + // = 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) + { + 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)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); + } + } + else + { + for (int i = 0; i < value.Length; i++) + { + ulong carry = 0; + nuint v = value[i]; + for (int j = 0; j < i; j++) + { + ulong digit1 = bits[i + j] + carry; + ulong digit2 = (ulong)value[j] * v; + bits[i + j] = (uint)(digit1 + (digit2 << 1)); + carry = (digit2 + (digit1 >> 1)) >> 31; + } + + ulong digits = (ulong)v * v + carry; + bits[i + i] = (uint)digits; + bits[i + i + 1] = (uint)(digits >> 32); } - ulong digits = (ulong)v * v + carry; - Unsafe.Add(ref resultPtr, i + i) = unchecked((uint)digits); - Unsafe.Add(ref resultPtr, i + i + 1) = (uint)(digits >> 32); } } } - 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. - // 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. - - int i = 0; - ulong carry = 0UL; - - for (; i < left.Length; i++) - { - ulong digits = (ulong)left[i] * right + carry; - bits[i] = unchecked((uint)digits); - carry = digits >> 32; - } - bits[i] = (uint)carry; + nuint carry = Mul1(bits, left, right); + bits[left.Length] = 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 +222,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); @@ -234,20 +237,28 @@ public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, S // 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) + 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 +279,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 +289,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 +307,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,47 +326,51 @@ 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; if (left0.Length < left2.Length) + { Add(left2, left0, pm1); + } else + { Add(left0, left2, pm1); + } 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 +382,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 +420,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,17 +449,9 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span leftFold = ((uint)foldLength <= StackAllocThreshold - ? stackalloc uint[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); - 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); @@ -453,20 +460,14 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span core = ((uint)coreLength <= StackAllocThreshold - ? stackalloc uint[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); @@ -474,39 +475,35 @@ static void Karatsuba(ReadOnlySpan left, ReadOnlySpan right, Span.Shared.Return(coreFromPool); + coreBuffer.Dispose(); } - 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); + Span carry = BigInteger.RentedBuffer.Create(carryLength, out BigInteger.RentedBuffer carryBuffer); - Span carryOrig = bitsHigh.Slice(0, right.Length); + Span carryOrig = bitsHigh.Slice(0, right.Length); carryOrig.CopyTo(carry); carryOrig.Clear(); @@ -515,71 +512,58 @@ static void RightSmall(ReadOnlySpan left, ReadOnlySpan right, Span.Shared.Return(carryFromPool); + carryBuffer.Dispose(); } - 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); - // 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 = BitsPerLimb. for (int i = 0; i < right.Length; i++) { - uint rv = right[i]; - ulong carry = 0UL; - 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; - } - Unsafe.Add(ref resultPtr, i + left.Length) = (uint)carry; + nuint carry = MulAdd1(bits.Slice(i), left, right[i]); + bits[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 +571,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 +589,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 +608,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 << (BitsPerLimb - 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; + ? 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 +672,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 +684,26 @@ 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 +713,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 +728,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 +747,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 +780,48 @@ 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) + { + ulong oneThird64 = 0x5555_5555_5555_5555; + ulong twoThirds64 = 0xAAAA_AAAA_AAAA_AAAA; + oneThird = (nuint)oneThird64; + twoThirds = (nuint)twoThirds64; + } + else + { + oneThird = 0x5555_5555; + twoThirds = 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] / 3; + nuint rem = bits[i] - quo * 3; Debug.Assert(carry < 3); @@ -845,9 +844,13 @@ private static void DivideThreeSelf(Span bits) else { if (--rem < 3) + { ++quo; + } else + { rem = 2; + } bits[i] = twoThirds + quo; carry = rem; @@ -856,7 +859,8 @@ 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,57 +873,93 @@ 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); - for (; i < right.Length; i++) + if (right.Length != 0) { - 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; + _ = left[right.Length - 1]; + _ = core[left.Length - 1]; } - for (; i < left.Length; i++) + if (nint.Size == 8) { - long digit = (Unsafe.Add(ref corePtr, i) + carry) - left[i]; - Unsafe.Add(ref corePtr, i) = unchecked((uint)digit); - carry = digit >> 32; - } + Int128 carry = 0; + + for (; i < right.Length; i++) + { + Int128 digit = (Int128)(ulong)core[i] + carry - (ulong)left[i] - (ulong)right[i]; + core[i] = (nuint)(ulong)digit; + carry = digit >> 64; + } - for (; carry != 0 && i < core.Length; i++) + for (; i < left.Length; i++) + { + Int128 digit = (Int128)(ulong)core[i] + carry - (ulong)left[i]; + core[i] = (nuint)(ulong)digit; + carry = digit >> 64; + } + + 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 = ((uint)core[i] + carry) - (uint)left[i] - (uint)right[i]; + core[i] = (uint)digit; + carry = digit >> 32; + } + + for (; i < left.Length; i++) + { + 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] = (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); 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) + 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) { @@ -944,17 +984,24 @@ private static void AddSelf(Span left, ref int leftSign, ReadOnlySpan left: compute right - left directly left = left.Slice(0, right.Length); - SubtractSelf(left, right); - NumericsHelpers.DangerousMakeTwosComplement(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); } } } - private static void SubtractSelf(Span 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) { @@ -981,32 +1028,40 @@ private static void SubtractSelf(Span left, ref int leftSign, ReadOnlySpan } else { + // right > left: compute right - left directly left = left.Slice(0, right.Length); - SubtractSelf(left, right); - NumericsHelpers.DangerousMakeTwosComplement(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); } } } - 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] >> (BitsPerLimb - 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] << (BitsPerLimb - 1); bits[i] = value; } } + } } 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..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 @@ -2,56 +2,70 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.X86; +using X86Base = System.Runtime.Intrinsics.X86.X86Base; namespace System.Numerics { internal static partial class BigIntegerCalculator { -#if DEBUG - // Mutable for unit testing... - internal static -#else - internal const -#endif - int StackAllocThreshold = 64; - - public static int Compare(ReadOnlySpan left, ReadOnlySpan right) + /// Number of bits per native-width limb: 32 on 32-bit, 64 on 64-bit. + internal static int BitsPerLimb { - 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; + } int iv = left.Length; while (--iv >= 0 && left[iv] == right[iv]) ; if (iv < 0) + { return 0; + } + 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) { 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); } - 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 @@ -59,11 +73,14 @@ public static int ActualLength(ReadOnlySpan value) int length = value.Length; while (length > 0 && value[length - 1] == 0) + { --length; + } + 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. @@ -73,14 +90,328 @@ private static int Reduce(Span bits, ReadOnlySpan modulus) return ActualLength(bits.Slice(0, modulus.Length)); } + return bits.Length; } [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) + { + nuint sum1 = a + b; + nuint c1 = (sum1 < a) ? 1 : (nuint)0; + nuint sum2 = sum1 + carryIn; + nuint c2 = (sum2 < sum1) ? 1 : (nuint)0; + carryOut = c1 + c2; + return sum2; + } + else + { + ulong sum = (ulong)a + b + carryIn; + carryOut = (uint)(sum >> 32); + return (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) ? 1 : (nuint)0; + nuint diff2 = diff1 - borrowIn; + nuint b2 = (diff2 > diff1) ? 1 : (nuint)0; + borrowOut = b1 + b2; + return diff2; + } + else + { + long diff = (long)a - (long)b - (long)borrowIn; + borrowOut = (uint)(-(int)(diff >> 32)); // 0 or 1 + return (uint)diff; + } + } + + /// + /// 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) + { + // Compute (hi * 2^64 + lo) / 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(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, divisor); + (ulong q_lo, ulong r2) = Math.DivRem((r1 << 32) | lo_lo, divisor); + + remainder = (nuint)r2; + return (nuint)((q_hi << 32) | q_lo); + } + + { +#pragma warning disable SYSLIB5004 // X86Base.DivRem is experimental + if (X86Base.X64.IsSupported) + { + (ulong q, ulong r) = X86Base.X64.DivRem(lo, hi, 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); + return (nuint)(ulong)digit; + } + } + else + { + ulong value = ((ulong)hi << 32) | lo; + ulong digit = value / divisor; + remainder = (uint)(value - digit * divisor); + return (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) + { + ulong hi = Math.BigMul(a, b, out ulong lo); + low = (nuint)lo; + return (nuint)hi; + } + else + { + ulong product = (ulong)a * b; + low = (uint)product; + return (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] = (uint)product; + carry = (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] = (uint)product; + carry = (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] = orig - lo; + hi += (orig < lo) ? 1u : 0; + + carry = hi; + } + } + + return carry; + } } } 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 687cf52e894343..e5340e96c71858 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); @@ -24,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) { @@ -53,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); @@ -91,14 +96,15 @@ 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. - public static void DangerousMakeTwosComplement(Span d) + /// Performs an in-place two's complement. Use with care for immutable types. + public static void DangerousMakeTwosComplement(Span d) { // Given a number: // XXXXXXXXXXXY00000 @@ -108,7 +114,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 +122,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) @@ -127,9 +133,8 @@ 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. - public static void DangerousMakeOnesComplement(Span d) + /// Performs an in-place one's complement. Use with care for immutable types. + public static void DangerousMakeOnesComplement(Span d) { // Given a number: // XXXXXXXXXXX @@ -139,27 +144,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,18 +173,12 @@ public static void DangerousMakeOnesComplement(Span d) } } - public static ulong MakeUInt64(uint uHi, uint uLo) + /// 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) { - return ((ulong)uHi << kcbitUint) | uLo; - } - - public static uint Abs(int a) - { - unchecked - { - uint mask = (uint)(a >> 31); - return ((uint)a ^ mask) - mask; - } + nuint mask = (nuint)(a >> 31); + return ((nuint)a ^ mask) - mask; } } } 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..3cae11dbc42f4c --- /dev/null +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerPropertyTests.cs @@ -0,0 +1,1082 @@ +// 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); + + // 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())); + } + + [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); + } + } + } + + /// + /// 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); + } + + [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] + [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)); + } + + // --- 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] + [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); + } + + [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 + { + /// 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/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..721ac8bc3fe9d7 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,17 @@ public static void GetShortestBitLengthTest() [Fact] public static void TryWriteBigEndianTest() { - Span destination = stackalloc byte[20]; + int maxBytes = nint.Size == 8 ? 24 : 20; + Span destination = stackalloc byte[maxBytes]; 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 +439,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 +459,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 +480,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()); } [Fact] public static void TryWriteLittleEndianTest() { - Span destination = stackalloc byte[20]; + int maxBytes = nint.Size == 8 ? 24 : 20; + Span destination = stackalloc byte[maxBytes]; 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 +506,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 +526,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 +547,6 @@ 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()); } // @@ -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)); 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 @@ +