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 452cbd3759fea..48c4ae1c97d18 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -11,6 +11,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using FxResources.System.Runtime.Numerics; +using static System.Number; namespace System { @@ -295,366 +297,254 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle.Shared.Return(arrayFromPoolForResultBuffer); - } - } + uint[]? base1E9FromPool = null; + scoped Span base1E9; - ParsingStatus Naive(ref NumberBuffer number, out BigInteger result) { - Span stackBuffer = stackalloc uint[BigIntegerCalculator.StackAllocThreshold]; - Span currentBuffer = stackBuffer; - uint partialValue = 0; - int partialDigitCount = 0; - - if (!ProcessChunk(number.Digits[..number.DigitsCount], ref currentBuffer)) + ReadOnlySpan intDigits = number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); + int intDigitsEnd = intDigits.IndexOf(0); + if (intDigitsEnd < 0) { - result = default; - return ParsingStatus.Failed; - } - - if (partialDigitCount > 0) - { - MultiplyAdd(ref currentBuffer, UInt32PowersOfTen[partialDigitCount], partialValue); - } - - result = NumberBufferToBigInteger(currentBuffer, number.IsNegative); - return ParsingStatus.OK; - - bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) - { - int remainingIntDigitCount = Math.Max(numberScale - totalDigitCount, 0); - ReadOnlySpan intDigitsSpan = chunkDigits.Slice(0, Math.Min(remainingIntDigitCount, chunkDigits.Length)); - - bool endReached = false; - - // Storing these captured variables in locals for faster access in the loop. - uint _partialValue = partialValue; - int _partialDigitCount = partialDigitCount; - int _totalDigitCount = totalDigitCount; - - for (int i = 0; i < intDigitsSpan.Length; i++) + // Check for nonzero digits after the decimal point. + ReadOnlySpan fracDigitsSpan = number.Digits.Slice(intDigits.Length); + foreach (byte digitChar in fracDigitsSpan) { - char digitChar = (char)chunkDigits[i]; if (digitChar == '\0') { - endReached = true; break; } - - _partialValue = _partialValue * 10 + (uint)(digitChar - '0'); - _partialDigitCount++; - _totalDigitCount++; - - // Update the buffer when enough partial digits have been accumulated. - if (_partialDigitCount == MaxPartialDigits) + if (digitChar != '0') { - MultiplyAdd(ref currentBuffer, TenPowMaxPartial, _partialValue); - _partialValue = 0; - _partialDigitCount = 0; + result = default; + return ParsingStatus.Failed; } } + } + else + intDigits = intDigits.Slice(0, intDigitsEnd); - // Check for nonzero digits after the decimal point. - if (!endReached) - { - ReadOnlySpan fracDigitsSpan = chunkDigits.Slice(intDigitsSpan.Length); - for (int i = 0; i < fracDigitsSpan.Length; i++) - { - char digitChar = (char)fracDigitsSpan[i]; - if (digitChar == '\0') - { - break; - } - if (digitChar != '0') - { - return false; - } - } - } + 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); - partialValue = _partialValue; - partialDigitCount = _partialDigitCount; - totalDigitCount = _totalDigitCount; + int di = base1E9Length; + ReadOnlySpan leadingDigits = intDigits[..(intDigits.Length % PowersOf1e9.MaxPartialDigits)]; + if (leadingDigits.Length != 0) + { + uint.TryParse(leadingDigits, out base1E9[--di]); + } + + intDigits = intDigits.Slice(leadingDigits.Length); + Debug.Assert(intDigits.Length % PowersOf1e9.MaxPartialDigits == 0); - return true; + 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); } - ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger result) + const double digitRatio = 0.10381025297; // log_{2^32}(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(); + + int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); + int trailingZeroCount = number.Scale - totalDigitCount; + + if (number.Scale <= BigIntegerParseNaiveThreshold) { - Span currentBuffer; - int[]? arrayFromPoolForMultiplier = null; - try - { - totalDigitCount = Math.Min(number.DigitsCount, numberScale); - int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; - - Span buffer = new uint[bufferSize]; - arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(bufferSize); - Span newBuffer = MemoryMarshal.Cast(arrayFromPoolForResultBuffer).Slice(0, bufferSize); - newBuffer.Clear(); - - // Separate every MaxPartialDigits digits and store them in the buffer. - // Buffers are treated as little-endian. That means, the array { 234567890, 1 } - // represents the number 1234567890. - int bufferIndex = bufferSize - 1; - uint currentBlock = 0; - int shiftUntil = (totalDigitCount - 1) % MaxPartialDigits; - int remainingIntDigitCount = totalDigitCount; - - ReadOnlySpan digitsChunkSpan = number.Digits[..number.DigitsCount]; - ReadOnlySpan intDigitsSpan = digitsChunkSpan.Slice(0, Math.Min(remainingIntDigitCount, digitsChunkSpan.Length)); - - for (int i = 0; i < intDigitsSpan.Length; i++) - { - char digitChar = (char)intDigitsSpan[i]; - Debug.Assert(char.IsDigit(digitChar)); - currentBlock *= 10; - currentBlock += unchecked((uint)(digitChar - '0')); - if (shiftUntil == 0) - { - buffer[bufferIndex] = currentBlock; - currentBlock = 0; - bufferIndex--; - shiftUntil = MaxPartialDigits; - } - shiftUntil--; - } - remainingIntDigitCount -= intDigitsSpan.Length; - Debug.Assert(0 <= remainingIntDigitCount); + Naive(base1E9, trailingZeroCount, resultBuffer); + } + else + { + DivideAndConquer(base1E9, trailingZeroCount, resultBuffer); + } - ReadOnlySpan fracDigitsSpan = digitsChunkSpan.Slice(intDigitsSpan.Length); - for (int i = 0; i < fracDigitsSpan.Length; i++) - { - char digitChar = (char)fracDigitsSpan[i]; - if (digitChar == '\0') - { - break; - } - if (digitChar != '0') - { - result = default; - return ParsingStatus.Failed; - } - } + result = new BigInteger(resultBuffer, number.IsNegative); - Debug.Assert(currentBlock == 0); - Debug.Assert(bufferIndex == -1); + if (base1E9FromPool != null) + ArrayPool.Shared.Return(base1E9FromPool); + if (resultBufferFromPool != null) + ArrayPool.Shared.Return(resultBufferFromPool); - int blockSize = 1; - arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(blockSize); - Span multiplier = MemoryMarshal.Cast(arrayFromPoolForMultiplier).Slice(0, blockSize); - multiplier[0] = TenPowMaxPartial; + return ParsingStatus.OK; - // This loop is executed ceil(log_2(bufferSize)) times. - while (true) - { - // merge each block pairs. - // When buffer represents: - // | A | B | C | D | - // Make newBuffer like: - // | A + B * multiplier | C + D * multiplier | - for (int i = 0; i < bufferSize; i += blockSize * 2) - { - Span curBuffer = buffer.Slice(i); - Span curNewBuffer = newBuffer.Slice(i); + static void DivideAndConquer(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) + { + int valueDigits = (base1E9.Length - 1) * PowersOf1e9.MaxPartialDigits + FormattingHelpers.CountDigits(base1E9[^1]); - int len = Math.Min(bufferSize - i, blockSize * 2); - int lowerLen = Math.Min(len, blockSize); - int upperLen = len - lowerLen; - if (upperLen != 0) - { - Debug.Assert(blockSize == lowerLen); - Debug.Assert(blockSize == multiplier.Length); - Debug.Assert(multiplier.Length == lowerLen); - BigIntegerCalculator.Multiply(multiplier, curBuffer.Slice(blockSize, upperLen), curNewBuffer.Slice(0, len)); - } + int powersOf1e9BufferLength = PowersOf1e9.GetBufferSize(Math.Max(valueDigits, trailingZeroCount + 1), 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(); - long carry = 0; - int j = 0; - for (; j < lowerLen; j++) - { - long digit = (curBuffer[j] + carry) + curNewBuffer[j]; - curNewBuffer[j] = unchecked((uint)digit); - carry = digit >> 32; - } - if (carry != 0) - { - while (true) - { - curNewBuffer[j]++; - if (curNewBuffer[j] != 0) - { - break; - } - j++; - } - } - } + PowersOf1e9 powersOf1e9 = new PowersOf1e9(powersOf1e9Buffer); + + if (trailingZeroCount > 0) + { + 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 tmp = buffer; - buffer = newBuffer; - newBuffer = tmp; - blockSize *= 2; + Recursive(powersOf1e9, maxIndex, base1E9, leading); + leading = leading.Slice(0, BigIntegerCalculator.ActualLength(leading)); - if (bufferSize <= blockSize) - { - break; - } - newBuffer.Clear(); - int[]? arrayToReturn = arrayFromPoolForMultiplier; - - arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(blockSize); - Span newMultiplier = MemoryMarshal.Cast(arrayFromPoolForMultiplier).Slice(0, blockSize); - newMultiplier.Clear(); - BigIntegerCalculator.Square(multiplier, newMultiplier); - multiplier = newMultiplier; - if (arrayToReturn is not null) - { - ArrayPool.Shared.Return(arrayToReturn); - } - } + powersOf1e9.MultiplyPowerOfTen(leading, trailingZeroCount, bits); - // shrink buffer to the currently used portion. - // First, calculate the rough size of the buffer from the ratio that the number - // of digits follows. Then, shrink the size until there is no more space left. - // The Ratio is calculated as: log_{2^32}(10^9) - const double digitRatio = 0.934292276687070661; - currentBufferSize = Math.Min((int)(bufferSize * digitRatio) + 1, bufferSize); - Debug.Assert(buffer.Length == currentBufferSize || buffer[currentBufferSize] == 0); - while (0 < currentBufferSize && buffer[currentBufferSize - 1] == 0) - { - currentBufferSize--; - } - currentBuffer = buffer.Slice(0, currentBufferSize); - result = NumberBufferToBigInteger(currentBuffer, number.IsNegative); + if (leadingFromPool != null) + ArrayPool.Shared.Return(leadingFromPool); } - finally + else { - if (arrayFromPoolForMultiplier != null) - { - ArrayPool.Shared.Return(arrayFromPoolForMultiplier); - } + Recursive(powersOf1e9, maxIndex, base1E9, bits); } - return ParsingStatus.OK; + + if (powersOf1e9BufferFromPool != null) + ArrayPool.Shared.Return(powersOf1e9BufferFromPool); } - BigInteger NumberBufferToBigInteger(Span currentBuffer, bool signa) + static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnlySpan base1E9, Span bits) { - int trailingZeroCount = numberScale - totalDigitCount; + Debug.Assert(bits.Trim(0u).Length == 0); + Debug.Assert(BigIntegerParseNaiveThresholdInRecursive > 1); - while (trailingZeroCount >= MaxPartialDigits) + base1E9 = base1E9.Slice(0, BigIntegerCalculator.ActualLength(base1E9)); + if (base1E9.Length < BigIntegerParseNaiveThresholdInRecursive) { - MultiplyAdd(ref currentBuffer, TenPowMaxPartial, 0); - trailingZeroCount -= MaxPartialDigits; + NaiveBase1E9ToBits(base1E9, bits); + return; } - if (trailingZeroCount > 0) + int multiplier1E9Length = 1 << powersOf1e9Index; + while (base1E9.Length <= multiplier1E9Length) { - MultiplyAdd(ref currentBuffer, UInt32PowersOfTen[trailingZeroCount], 0); + multiplier1E9Length = 1 << (--powersOf1e9Index); } + ReadOnlySpan multiplier = powersOf1e9.GetSpan(powersOf1e9Index); + int multiplierTrailingZeroCount = PowersOf1e9.OmittedLength(powersOf1e9Index); - int sign; - uint[]? bits; + Debug.Assert(multiplier1E9Length < base1E9.Length && base1E9.Length <= multiplier1E9Length * 2); - if (currentBufferSize == 0) - { - sign = 0; - bits = null; - } - else if (currentBufferSize == 1 && currentBuffer[0] <= int.MaxValue) - { - sign = (int)(signa ? -currentBuffer[0] : currentBuffer[0]); - bits = null; - } + 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(); + + Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[multiplier1E9Length..], buffer); + + ReadOnlySpan buffer2 = buffer.Slice(0, BigIntegerCalculator.ActualLength(buffer)); + Span bitsUpper = bits.Slice(multiplierTrailingZeroCount, buffer2.Length + multiplier.Length); + if (multiplier.Length < buffer2.Length) + BigIntegerCalculator.Multiply(buffer2, multiplier, bitsUpper); else - { - sign = signa ? -1 : 1; - bits = currentBuffer.Slice(0, currentBufferSize).ToArray(); - } + BigIntegerCalculator.Multiply(multiplier, buffer2, bitsUpper); - return new BigInteger(sign, bits); + buffer.Clear(); + + Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[..multiplier1E9Length], buffer); + + BigIntegerCalculator.AddSelf(bits, buffer.Slice(0, BigIntegerCalculator.ActualLength(buffer))); + + if (bufferFromPool != null) + ArrayPool.Shared.Return(bufferFromPool); } - // This function should only be used for result buffer. - void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) + static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) { - Span curBits = currentBuffer.Slice(0, currentBufferSize); - uint carry = addValue; - - for (int i = 0; i < curBits.Length; i++) + if (base1E9.Length == 0) { - ulong p = (ulong)multiplier * curBits[i] + carry; - curBits[i] = (uint)p; - carry = (uint)(p >> 32); + // number is 0. + return; } + int resultLength = NaiveBase1E9ToBits(base1E9, bits); - if (carry == 0) + int trailingPartialCount = Math.DivRem(trailingZeroCount, PowersOf1e9.MaxPartialDigits, out int remainingTrailingZeroCount); + for (int i = 0; i < trailingPartialCount; i++) { - return; + uint carry = MultiplyAdd(bits.Slice(0, resultLength), PowersOf1e9.TenPowMaxPartial, 0); + Debug.Assert(bits[resultLength] == 0); + if (carry != 0) + bits[resultLength++] = carry; } - if (currentBufferSize == currentBuffer.Length) + if (remainingTrailingZeroCount != 0) { - int[]? arrayToReturn = arrayFromPoolForResultBuffer; + uint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; + uint carry = MultiplyAdd(bits.Slice(0, resultLength), multiplier, 0); + Debug.Assert(bits[resultLength] == 0); + if (carry != 0) + bits[resultLength++] = carry; + } + } - arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(checked(currentBufferSize * 2)); - Span newBuffer = MemoryMarshal.Cast(arrayFromPoolForResultBuffer); - currentBuffer.CopyTo(newBuffer); - currentBuffer = newBuffer; + static int NaiveBase1E9ToBits(ReadOnlySpan base1E9, Span bits) + { + if (base1E9.Length == 0) + return 0; - if (arrayToReturn != null) - { - ArrayPool.Shared.Return(arrayToReturn); - } + 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]); + Debug.Assert(bits[resultLength] == 0); + if (carry != 0) + bits[resultLength++] = carry; } + return resultLength; + } + + static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) + { + uint carry = addValue; - currentBuffer[currentBufferSize] = carry; - currentBufferSize++; + 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; } } @@ -851,9 +741,6 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo return spanSuccess; } - private const uint kuBase = 1_000_000_000; // 10^9 - private const int kcchBase = 9; - private static unsafe string? FormatBigInteger( bool targetSpan, BigInteger value, string? formatString, ReadOnlySpan formatSpan, @@ -861,6 +748,9 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo { Debug.Assert(formatString == null || formatString.Length == formatSpan.Length); + const uint TenPowMaxPartial = PowersOf1e9.TenPowMaxPartial; + const int MaxPartialDigits = PowersOf1e9.MaxPartialDigits; + int digits = 0; char fmt = ParseFormatSpecifier(formatSpan, out digits); if (fmt == 'x' || fmt == 'X') @@ -897,8 +787,8 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo int cuSrc = value._bits.Length; // A quick conservative max length of base 10^9 representation // A uint contributes to no more than 10/9 of 10^9 block, +1 for ceiling of division - int cuMax = cuSrc * (kcchBase + 1) / kcchBase + 1; - Debug.Assert((long)BigInteger.MaxLength * (kcchBase + 1) / kcchBase + 1 < (long)int.MaxValue); // won't overflow + int cuMax = cuSrc * (MaxPartialDigits + 1) / MaxPartialDigits + 1; + Debug.Assert((long)BigInteger.MaxLength * (MaxPartialDigits + 1) / MaxPartialDigits + 1 < (long)int.MaxValue); // won't overflow uint[]? bufferToReturn = null; Span base1E9Buffer = cuMax < BigIntegerCalculator.StackAllocThreshold ? @@ -912,17 +802,17 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo uint uCarry = value._bits[iuSrc]; for (int iuDst = 0; iuDst < cuDst; iuDst++) { - Debug.Assert(base1E9Buffer[iuDst] < kuBase); + Debug.Assert(base1E9Buffer[iuDst] < TenPowMaxPartial); // Use X86Base.DivRem when stable ulong uuRes = NumericsHelpers.MakeUInt64(base1E9Buffer[iuDst], uCarry); - (ulong quo, ulong rem) = Math.DivRem(uuRes, kuBase); + (ulong quo, ulong rem) = Math.DivRem(uuRes, TenPowMaxPartial); uCarry = (uint)quo; base1E9Buffer[iuDst] = (uint)rem; } if (uCarry != 0) { - (uCarry, base1E9Buffer[cuDst++]) = Math.DivRem(uCarry, kuBase); + (uCarry, base1E9Buffer[cuDst++]) = Math.DivRem(uCarry, TenPowMaxPartial); if (uCarry != 0) base1E9Buffer[cuDst++] = uCarry; } @@ -930,7 +820,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo ReadOnlySpan base1E9Value = base1E9Buffer[..cuDst]; - int valueDigits = (base1E9Value.Length - 1) * kcchBase + FormattingHelpers.CountDigits(base1E9Value[^1]); + int valueDigits = (base1E9Value.Length - 1) * MaxPartialDigits + FormattingHelpers.CountDigits(base1E9Value[^1]); string? strResult; @@ -1038,12 +928,281 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo // The base 10^9 value is in reverse order for (int i = 0; i < base1E9Value.Length - 1; i++) { - bufferEnd = UInt32ToDecChars(bufferEnd, base1E9Value[i], kcchBase); - digits -= kcchBase; + bufferEnd = UInt32ToDecChars(bufferEnd, base1E9Value[i], PowersOf1e9.MaxPartialDigits); + digits -= PowersOf1e9.MaxPartialDigits; } return UInt32ToDecChars(bufferEnd, base1E9Value[^1], digits); } + + internal readonly ref struct PowersOf1e9 + { + // Holds 1000000000^(1<< pow1E9; + public const uint 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 => + [ + 0, + 1, + 3, + 6, + 12, + 23, + 44, + 86, + 170, + 338, + 673, + 1342, + 2680, + 5355, + 10705, + 21405, + 42804, + 85602, + 171198, + 342390, + 684773, + 1369538, + 2739067, + 5478125, + 10956241, + 21912473, + 43824936, + 87649862, + 175299713, + 484817143, + 969634274, + 1939268536, + ]; + + // The PowersOf1e9 structure holds 1000000000^(1<< LeadingPowers1E9 => + [ + // 1000000000^(1<<0) + 1000000000, + // 1000000000^(1<<1) + 2808348672, + 232830643, + // 1000000000^(1<<2) + 3008077584, + 2076772117, + 12621774, + // 1000000000^(1<<3) + 4130660608, + 835571558, + 1441351422, + 977976457, + 264170013, + 37092, + // 1000000000^(1<<4) + 767623168, + 4241160024, + 1260959332, + 2541775228, + 2965753944, + 1796720685, + 484800439, + 1311835347, + 2945126454, + 3563705203, + 1375821026, + // 1000000000^(1<<5) + 3940379521, + 184513341, + 2872588323, + 2214530454, + 38258512, + 2980860351, + 114267010, + 2188874685, + 234079247, + 2101059099, + 1948702207, + 947446250, + 864457656, + 507589568, + 1321007357, + 3911984176, + 1011110295, + 2382358050, + 2389730781, + 730678769, + 440721283, + ]; + + public PowersOf1e9(Span pow1E9) + { + Debug.Assert(pow1E9.Length >= 1); + Debug.Assert(Indexes[6] == LeadingPowers1E9.Length); + if (pow1E9.Length <= LeadingPowers1E9.Length) + { + this.pow1E9 = LeadingPowers1E9; + return; + } + LeadingPowers1E9.CopyTo(pow1E9.Slice(0, LeadingPowers1E9.Length)); + this.pow1E9 = pow1E9; + + 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); + BigIntegerCalculator.Square(src, dst); + int from = toExclusive; + toExclusive = Indexes[i + 1]; + src = pow1E9.Slice(from, toExclusive - from); + Debug.Assert(toExclusive == pow1E9.Length || pow1E9[toExclusive] == 0); + } + } + + public static int GetBufferSize(int digits, out int maxIndex) + { + uint scale1E9 = (uint)(digits - 1) / MaxPartialDigits; + maxIndex = BitOperations.Log2(scale1E9); + int index = maxIndex + 1; + int bufferSize; + if ((uint)index < (uint)Indexes.Length) + bufferSize = Indexes[index]; + else + { + maxIndex = Indexes.Length - 2; + bufferSize = Indexes[^1]; + } + + return ++bufferSize; + } + + public ReadOnlySpan GetSpan(int index) + { + // Returns 1E9^(1<> (32*(9*(1<> 5; + } + + public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, Span bits) + { + Debug.Assert(trailingZeroCount >= 0); + if (trailingZeroCount < UInt32PowersOfTen.Length) + { + BigIntegerCalculator.Multiply(left, UInt32PowersOfTen[trailingZeroCount], bits.Slice(0, left.Length + 1)); + 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; + + int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); + + int fi = BitOperations.TrailingZeroCount(trailingPartialCount); + int omittedLength = OmittedLength(fi); + + // Copy first + ReadOnlySpan first = GetSpan(fi); + int curLength = first.Length; + trailingPartialCount >>= fi; + trailingPartialCount >>= 1; + + if ((BitOperations.PopCount((uint)trailingPartialCount) & 1) != 0) + { + powersOfTen2 = powersOfTen; + powersOfTen = bits; + powersOfTen2.Clear(); + } + + first.CopyTo(powersOfTen); + + for (++fi; trailingPartialCount != 0; ++fi, trailingPartialCount >>= 1) + { + Debug.Assert(fi + 1 < Indexes.Length); + if ((trailingPartialCount & 1) != 0) + { + omittedLength += OmittedLength(fi); + + ReadOnlySpan power = GetSpan(fi); + Span src = powersOfTen.Slice(0, curLength); + Span dst = powersOfTen2.Slice(0, curLength += power.Length); + + if (power.Length < src.Length) + BigIntegerCalculator.Multiply(src, power, dst); + else + BigIntegerCalculator.Multiply(power, src, dst); + + Span tmp = powersOfTen; + powersOfTen = powersOfTen2; + powersOfTen2 = tmp; + powersOfTen2.Clear(); + + // Trim + while (--curLength >= 0 && powersOfTen[curLength] == 0) ; + ++curLength; + } + } + + Debug.Assert(Unsafe.AreSame(ref bits[0], ref powersOfTen2[0])); + + powersOfTen = powersOfTen.Slice(0, curLength); + Span bits2 = bits.Slice(omittedLength, curLength += left.Length); + if (left.Length < powersOfTen.Length) + BigIntegerCalculator.Multiply(powersOfTen, left, bits2); + else + BigIntegerCalculator.Multiply(left, powersOfTen, bits2); + + if (powersOfTenFromPool != null) + ArrayPool.Shared.Return(powersOfTenFromPool); + + if (remainingTrailingZeroCount > 0) + { + uint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; + uint carry = 0; + 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) + { + bits[omittedLength + curLength] = carry; + } + } + } + } } internal interface IBigIntegerHexOrBinaryParser 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 59fef1a6d5da6..bd3c5ef819c35 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -494,7 +494,7 @@ internal BigInteger(int n, uint[]? rgu) /// /// The absolute value of the number /// The bool indicating the sign of the value. - private 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 ^ 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 19523609ebe55..a151c983bd5d0 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 @@ -56,7 +56,7 @@ public static void Add(ReadOnlySpan left, ReadOnlySpan right, Span left, ReadOnlySpan right) + public static void AddSelf(Span left, ReadOnlySpan right) { Debug.Assert(left.Length >= right.Length); @@ -129,7 +129,7 @@ public static void Subtract(ReadOnlySpan left, ReadOnlySpan right, S Subtract(left, bits, ref resultPtr, startIndex: i, initialCarry: carry); } - private 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 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 47c0738ed1299..2af543d113f26 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 @@ -31,7 +31,7 @@ public static int Compare(ReadOnlySpan left, ReadOnlySpan right) return left[iv] < right[iv] ? -1 : 1; } - private 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 diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs index d94c235c65b62..1859174c22f63 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs @@ -33,22 +33,6 @@ public static void RunMultiply_TwoLargeBigIntegers() } } - [Fact] - public static void RunMultiply_TwoLargeBigIntegers_Threshold() - { - // Again, with lower threshold - BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () => - BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyKaratsubaThreshold, 8, RunMultiply_TwoLargeBigIntegers) - ); - - // Again, with lower threshold - BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () => - BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyKaratsubaThreshold, 8, () => - BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.StackAllocThreshold, 8, RunMultiply_TwoLargeBigIntegers) - ) - ); - } - [Fact] public static void RunMultiply_TwoSmallBigIntegers() { @@ -293,4 +277,24 @@ private static string Print(byte[] bytes) return MyBigIntImp.Print(bytes); } } + + [Collection(nameof(DisableParallelization))] + public class multiplyTestThreshold + { + [Fact] + public static void RunMultiply_TwoLargeBigIntegers() + { + // Again, with lower threshold + BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () => + BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyKaratsubaThreshold, 8, multiplyTest.RunMultiply_TwoLargeBigIntegers) + ); + + // Again, with lower threshold + BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () => + BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyKaratsubaThreshold, 8, () => + BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.StackAllocThreshold, 8, multiplyTest.RunMultiply_TwoLargeBigIntegers) + ) + ); + } + } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 9852ea93bf1aa..85a2fcd182af7 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -5,9 +5,11 @@ using System.Diagnostics; using System.Globalization; using System.Tests; +using System.Text.RegularExpressions; using System.Threading; using Microsoft.DotNet.RemoteExecutor; using Xunit; +using static System.Net.Mime.MediaTypeNames; namespace System.Numerics.Tests { @@ -36,57 +38,51 @@ public static IEnumerable Cultures [OuterLoop] public static void RunParseToStringTests(CultureInfo culture) { - Test(); - BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test); - - void Test() - { - byte[] tempByteArray1 = new byte[0]; - using (new ThreadCultureChange(culture)) + byte[] tempByteArray1 = new byte[0]; + using (new ThreadCultureChange(culture)) + { + //default style + VerifyDefaultParse(s_random); + + //single NumberStyles + VerifyNumberStyles(NumberStyles.None, s_random); + VerifyNumberStyles(NumberStyles.AllowLeadingWhite, s_random); + VerifyNumberStyles(NumberStyles.AllowTrailingWhite, s_random); + VerifyNumberStyles(NumberStyles.AllowLeadingSign, s_random); + VerifyNumberStyles(NumberStyles.AllowTrailingSign, s_random); + VerifyNumberStyles(NumberStyles.AllowParentheses, s_random); + VerifyNumberStyles(NumberStyles.AllowDecimalPoint, s_random); + VerifyNumberStyles(NumberStyles.AllowThousands, s_random); + VerifyNumberStyles(NumberStyles.AllowExponent, s_random); + VerifyNumberStyles(NumberStyles.AllowCurrencySymbol, s_random); + VerifyNumberStyles(NumberStyles.AllowHexSpecifier, s_random); + VerifyBinaryNumberStyles(NumberStyles.AllowBinarySpecifier, s_random); + + //composite NumberStyles + VerifyNumberStyles(NumberStyles.Integer, s_random); + VerifyNumberStyles(NumberStyles.HexNumber, s_random); + VerifyBinaryNumberStyles(NumberStyles.BinaryNumber, s_random); + VerifyNumberStyles(NumberStyles.Number, s_random); + VerifyNumberStyles(NumberStyles.Float, s_random); + VerifyNumberStyles(NumberStyles.Currency, s_random); + VerifyNumberStyles(NumberStyles.Any, s_random); + + //invalid number style + // ******InvalidNumberStyles + NumberStyles invalid = (NumberStyles)0x7c00; + AssertExtensions.Throws("style", () => { - //default style - VerifyDefaultParse(s_random); - - //single NumberStyles - VerifyNumberStyles(NumberStyles.None, s_random); - VerifyNumberStyles(NumberStyles.AllowLeadingWhite, s_random); - VerifyNumberStyles(NumberStyles.AllowTrailingWhite, s_random); - VerifyNumberStyles(NumberStyles.AllowLeadingSign, s_random); - VerifyNumberStyles(NumberStyles.AllowTrailingSign, s_random); - VerifyNumberStyles(NumberStyles.AllowParentheses, s_random); - VerifyNumberStyles(NumberStyles.AllowDecimalPoint, s_random); - VerifyNumberStyles(NumberStyles.AllowThousands, s_random); - VerifyNumberStyles(NumberStyles.AllowExponent, s_random); - VerifyNumberStyles(NumberStyles.AllowCurrencySymbol, s_random); - VerifyNumberStyles(NumberStyles.AllowHexSpecifier, s_random); - VerifyBinaryNumberStyles(NumberStyles.AllowBinarySpecifier, s_random); - - //composite NumberStyles - VerifyNumberStyles(NumberStyles.Integer, s_random); - VerifyNumberStyles(NumberStyles.HexNumber, s_random); - VerifyBinaryNumberStyles(NumberStyles.BinaryNumber, s_random); - VerifyNumberStyles(NumberStyles.Number, s_random); - VerifyNumberStyles(NumberStyles.Float, s_random); - VerifyNumberStyles(NumberStyles.Currency, s_random); - VerifyNumberStyles(NumberStyles.Any, s_random); - - //invalid number style - // ******InvalidNumberStyles - NumberStyles invalid = (NumberStyles)0x7c00; - AssertExtensions.Throws("style", () => - { - BigInteger.Parse("1", invalid).ToString("d"); - }); - AssertExtensions.Throws("style", () => - { - BigInteger junk; - BigInteger.TryParse("1", invalid, null, out junk); - Assert.Equal("1", junk.ToString("d")); - }); + BigInteger.Parse("1", invalid).ToString("d"); + }); + AssertExtensions.Throws("style", () => + { + BigInteger junk; + BigInteger.TryParse("1", invalid, null, out junk); + Assert.Equal("1", junk.ToString("d")); + }); - //FormatProvider tests - RunFormatProviderParseStrings(); - } + //FormatProvider tests + RunFormatProviderParseStrings(); } } @@ -99,36 +95,23 @@ void Test() [InlineData("1\03456789", 0, 1, "1")] [InlineData("1\03456789", 0, 2, "1")] [InlineData("123456789\0", 0, 10, "123456789")] - public void Parse_Subspan_Success(string input, int offset, int length, string expected) + public static void Parse_Subspan_Success(string input, int offset, int length, string expected) { - Test(); - - BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test); - - void Test() - { - Eval(BigInteger.Parse(input.AsSpan(offset, length)), expected); - Assert.True(BigInteger.TryParse(input.AsSpan(offset, length), out BigInteger test)); - Eval(test, expected); - } + Eval(BigInteger.Parse(input.AsSpan(offset, length)), expected); + Assert.True(BigInteger.TryParse(input.AsSpan(offset, length), out BigInteger test)); + Eval(test, expected); } [Fact] - public void Parse_EmptySubspan_Fails() + public static void Parse_EmptySubspan_Fails() { - Test(); - BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test); - - void Test() - { - BigInteger result; + BigInteger result; - Assert.False(BigInteger.TryParse("12345".AsSpan(0, 0), out result)); - Assert.Equal(0, result); + Assert.False(BigInteger.TryParse("12345".AsSpan(0, 0), out result)); + Assert.Equal(0, result); - Assert.False(BigInteger.TryParse([], out result)); - Assert.Equal(0, result); - } + Assert.False(BigInteger.TryParse([], out result)); + Assert.Equal(0, result); } [Fact] @@ -148,7 +131,7 @@ public void Parse_Hex32Bits() Assert.True(BigInteger.TryParse("0F0000001", NumberStyles.HexNumber, null, out result)); Assert.Equal(0xF0000001u, result); - + Assert.True(BigInteger.TryParse("F00000001", NumberStyles.HexNumber, null, out result)); Assert.Equal(-0xFFFFFFFFL, result); @@ -210,16 +193,10 @@ public static IEnumerable RegressionIssueRuntime94610_TestData() [Theory] [MemberData(nameof(RegressionIssueRuntime94610_TestData))] - public void RegressionIssueRuntime94610(string text) + public static void RegressionIssueRuntime94610(string text) { // Regression test for: https://github.com/dotnet/runtime/issues/94610 - Test(); - BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test); - - void Test() - { - VerifyParseToString(text, NumberStyles.Integer, true); - } + VerifyParseToString(text, NumberStyles.Integer, true); } private static void RunFormatProviderParseStrings() @@ -268,6 +245,19 @@ private static void VerifyDefaultParse(Random random) VerifyParseToString(GetDigitSequence(100, 1000, random)); } + // Trailing Zero - Small + VerifyParseToString("99000000000"); + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetDigitSequence(1, 10, random) + new string('0', random.Next(10, 50))); + } + + // Trailing Zero - Large + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetDigitSequence(10, 100, random) + new string('0', random.Next(100, 1000))); + } + // Leading White for (int i = 0; i < s_samples; i++) { @@ -531,6 +521,13 @@ private static void VerifyNumberStyles(NumberStyles ns, Random random) VerifyParseToString(GetDigitSequence(100, 1000, random), ns, true); } + // Trailing Zero + VerifyParseToString("99000000000", ns, true); + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetDigitSequence(1, 10, random) + "1000000000", ns, true); + } + // Leading White for (int i = 0; i < s_samples; i++) { @@ -1169,4 +1166,59 @@ private static void Eval(BigInteger x, string expected) Assert.Equal(expected, actual); } } + + [Collection(nameof(DisableParallelization))] + public class parseTestThreshold + { + public static IEnumerable Cultures => parseTest.Cultures; + [Theory] + [MemberData(nameof(Cultures))] + [OuterLoop] + public static void RunParseToStringTests(CultureInfo culture) + { + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () => + { + parseTest.RunParseToStringTests(culture); + })); + } + + [Theory] + [InlineData("123456789", 0, 9, "123456789")] + [InlineData("123456789", 0, 1, "1")] + [InlineData("123456789", 1, 3, "234")] + [InlineData("123456789", 8, 1, "9")] + [InlineData("123456789abc", 8, 1, "9")] + [InlineData("1\03456789", 0, 1, "1")] + [InlineData("1\03456789", 0, 2, "1")] + [InlineData("123456789\0", 0, 10, "123456789")] + public static void Parse_Subspan_Success(string input, int offset, int length, string expected) + { + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () => + { + parseTest.Parse_Subspan_Success(input, offset, length, expected); + })); + } + + [Fact] + public static void Parse_EmptySubspan_Fails() + { + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, parseTest.Parse_EmptySubspan_Fails)); + } + + public static IEnumerable RegressionIssueRuntime94610_TestData() => parseTest.RegressionIssueRuntime94610_TestData(); + + [Theory] + [MemberData(nameof(RegressionIssueRuntime94610_TestData))] + public static void RegressionIssueRuntime94610(string text) + { + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () => + { + parseTest.RegressionIssueRuntime94610(text); + })); + } + } } 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 025b7d5fef398..8e2c0ce29543b 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 @@ -55,6 +55,7 @@ +