From ed7fcbf57cbcb703adf58780b5809838483aa9e8 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Sat, 27 Jan 2024 19:10:49 +0900 Subject: [PATCH 01/19] Optimize NumberToBigInteger --- .../src/System/Number.BigInteger.cs | 360 ++++++++++++------ .../Numerics/BigIntegerCalculator.SquMul.cs | 6 +- 2 files changed, 243 insertions(+), 123 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 01db0ef3777e8..9d18d85dadc7e 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -569,49 +569,33 @@ internal const int s_naiveThreshold = 1233; private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out BigInteger result) { - int currentBufferSize = 0; - - int totalDigitCount = 0; - int numberScale = number.Scale; - const int MaxPartialDigits = 9; const uint TenPowMaxPartial = 1000000000; - int[]? arrayFromPoolForResultBuffer = null; - - if (numberScale == int.MaxValue) - { - result = default; - return ParsingStatus.Overflow; - } - - if (numberScale < 0) + if ((uint)number.Scale >= int.MaxValue) { result = default; - return ParsingStatus.Failed; + return number.Scale == int.MaxValue + ? ParsingStatus.Overflow + : ParsingStatus.Failed; } - try + if (number.Scale <= s_naiveThreshold) { - if (number.DigitsCount <= s_naiveThreshold) - { - return Naive(ref number, out result); - } - else - { - return DivideAndConquer(ref number, out result); - } + return Naive(ref number, out result); } - finally + else { - if (arrayFromPoolForResultBuffer != null) - { - ArrayPool.Shared.Return(arrayFromPoolForResultBuffer); - } + return DivideAndConquer(ref number, out result); } - ParsingStatus Naive(ref NumberBuffer number, out BigInteger result) + static ParsingStatus Naive(ref NumberBuffer number, out BigInteger result) { + int numberScale = number.Scale; + + uint[]? arrayFromPoolForResultBuffer = null; + int currentBufferSize = 0; + int totalDigitCount = 0; Span stackBuffer = stackalloc uint[BigIntegerCalculator.StackAllocThreshold]; Span currentBuffer = stackBuffer; uint partialValue = 0; @@ -628,7 +612,18 @@ ParsingStatus Naive(ref NumberBuffer number, out BigInteger result) MultiplyAdd(ref currentBuffer, UInt32PowersOfTen[partialDigitCount], partialValue); } - result = NumberBufferToBigInteger(currentBuffer, number.IsNegative); + int trailingZeroCount = numberScale - totalDigitCount; + while (trailingZeroCount >= MaxPartialDigits) + { + MultiplyAdd(ref currentBuffer, TenPowMaxPartial, 0); + trailingZeroCount -= MaxPartialDigits; + } + if (trailingZeroCount > 0) + { + MultiplyAdd(ref currentBuffer, UInt32PowersOfTen[trailingZeroCount], 0); + } + + result = NumberBufferToBigInteger(currentBuffer.Slice(0, currentBufferSize), number.IsNegative); return ParsingStatus.OK; bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) @@ -689,22 +684,75 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) return true; } + + // This function should only be used for result buffer. + void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) + { + Span curBits = currentBuffer.Slice(0, currentBufferSize); + uint carry = addValue; + + for (int i = 0; i < curBits.Length; i++) + { + ulong p = (ulong)multiplier * curBits[i] + carry; + curBits[i] = (uint)p; + carry = (uint)(p >> 32); + } + + if (carry == 0) + { + return; + } + + if (currentBufferSize == currentBuffer.Length) + { + uint[]? arrayToReturn = arrayFromPoolForResultBuffer; + + arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(checked(currentBufferSize * 2)); + Span newBuffer = new Span(arrayFromPoolForResultBuffer); + currentBuffer.CopyTo(newBuffer); + currentBuffer = newBuffer; + + if (arrayToReturn != null) + { + ArrayPool.Shared.Return(arrayToReturn); + } + } + + currentBuffer[currentBufferSize] = carry; + currentBufferSize++; + } } - ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger result) + static ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger result) { + // log_{2^32}(10^9) + const double digitRatio = 0.934292276687070661; + Span currentBuffer; - int[]? arrayFromPoolForMultiplier = null; + uint[]? arrayFromPoolForMultiplier = null; + uint[]? arrayFromPoolForResultBuffer = null; + uint[]? arrayFromPoolForResultBuffer2 = null; + uint[]? arrayFromPoolForTrailingZero = null; try { - totalDigitCount = Math.Min(number.DigitsCount, numberScale); + int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); + int trailingZeroCount = number.Scale - totalDigitCount; int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; - Span buffer = new uint[bufferSize]; - arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(bufferSize); - Span newBuffer = MemoryMarshal.Cast(arrayFromPoolForResultBuffer).Slice(0, bufferSize); + Span buffer = new Span(arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(bufferSize), 0, bufferSize); + Span newBuffer = new Span(arrayFromPoolForResultBuffer2 = ArrayPool.Shared.Rent(bufferSize), 0, bufferSize); newBuffer.Clear(); + int trailingZeroE9 = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int trailingZeroRemainder); + int trailingZeroBufferLength = checked((int)(digitRatio * (trailingZeroE9 + Math.Max(trailingZeroRemainder, 1))) + 1); + Span trailingZeroBuffer = (trailingZeroBufferLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : arrayFromPoolForTrailingZero = ArrayPool.Shared.Rent(trailingZeroBufferLength)).Slice(0, trailingZeroBufferLength); + + int currentTrailingZeroBufferLength = 1; + trailingZeroBuffer.Slice(1).Clear(); + trailingZeroBuffer[0] = UInt32PowersOfTen[trailingZeroRemainder]; + // 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. @@ -753,13 +801,37 @@ ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger result) Debug.Assert(bufferIndex == -1); int blockSize = 1; - arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(blockSize); - Span multiplier = MemoryMarshal.Cast(arrayFromPoolForMultiplier).Slice(0, blockSize); - multiplier[0] = TenPowMaxPartial; + int multiplierSize = 1; + Span multiplier = stackalloc uint[1] { TenPowMaxPartial }; // This loop is executed ceil(log_2(bufferSize)) times. while (true) { + if ((trailingZeroE9 & 1) != 0) + { + uint[]? previousTrailingZeroBufferFromPool = null; + Span previousTrailingZeroBuffer = new Span( + previousTrailingZeroBufferFromPool = ArrayPool.Shared.Rent(currentTrailingZeroBufferLength), + 0, currentTrailingZeroBufferLength); + + trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).CopyTo(previousTrailingZeroBuffer); + trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).Clear(); + if (multiplier.Length < previousTrailingZeroBuffer.Length) + BigIntegerCalculator.Multiply(previousTrailingZeroBuffer, multiplier, trailingZeroBuffer); + else + BigIntegerCalculator.Multiply(multiplier, previousTrailingZeroBuffer, trailingZeroBuffer); + + currentTrailingZeroBufferLength += multiplier.Length; + while (--currentTrailingZeroBufferLength >= 0 && trailingZeroBuffer[currentTrailingZeroBufferLength] == 0) ; + ++currentTrailingZeroBufferLength; + + if (previousTrailingZeroBufferFromPool != null) + ArrayPool.Shared.Return(previousTrailingZeroBufferFromPool); + + Debug.Assert(currentTrailingZeroBufferLength >= 1); + } + trailingZeroE9 >>= 1; + // merge each block pairs. // When buffer represents: // | A | B | C | D | @@ -776,9 +848,10 @@ ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger result) 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)); + Debug.Assert(blockSize >= multiplier.Length); + Debug.Assert(multiplier.Length >= curBuffer.Slice(blockSize, upperLen).TrimEnd(0u).Length); + + BigIntegerCalculator.Multiply(multiplier, curBuffer.Slice(blockSize, upperLen).TrimEnd(0u), curNewBuffer.Slice(0, len)); } long carry = 0; @@ -806,122 +879,167 @@ ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger result) Span tmp = buffer; buffer = newBuffer; newBuffer = tmp; - blockSize *= 2; + blockSize <<= 1; if (bufferSize <= blockSize) { break; } + multiplierSize <<= 1; newBuffer.Clear(); - int[]? arrayToReturn = arrayFromPoolForMultiplier; + uint[]? arrayToReturn = arrayFromPoolForMultiplier; - arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(blockSize); - Span newMultiplier = MemoryMarshal.Cast(arrayFromPoolForMultiplier).Slice(0, blockSize); + Span newMultiplier = new Span( + arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(multiplierSize), + 0, multiplierSize); newMultiplier.Clear(); BigIntegerCalculator.Square(multiplier, newMultiplier); multiplier = newMultiplier; + + while (--multiplierSize >= 0 && multiplier[multiplierSize] == 0) ; + multiplier = multiplier.Slice(0, ++multiplierSize); + if (arrayToReturn is not null) { - ArrayPool.Shared.Return(arrayToReturn); + ArrayPool.Shared.Return(arrayToReturn); } } + while (trailingZeroE9 != 0) + { + multiplierSize <<= 1; + uint[]? arrayToReturn = arrayFromPoolForMultiplier; + + Span newMultiplier = new Span( + arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(multiplierSize), + 0, multiplierSize); + newMultiplier.Clear(); + BigIntegerCalculator.Square(multiplier, newMultiplier); + multiplier = newMultiplier; + + while (--multiplierSize >= 0 && multiplier[multiplierSize] == 0) ; + multiplier = multiplier.Slice(0, ++multiplierSize); + + if (arrayToReturn is not null) + { + ArrayPool.Shared.Return(arrayToReturn); + } + + if ((trailingZeroE9 & 1) != 0) + { + uint[]? previousTrailingZeroBufferFromPool = null; + Span previousTrailingZeroBuffer = new Span( + previousTrailingZeroBufferFromPool = ArrayPool.Shared.Rent(currentTrailingZeroBufferLength), + 0, currentTrailingZeroBufferLength); + + trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).CopyTo(previousTrailingZeroBuffer); + trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).Clear(); + if (multiplier.Length < previousTrailingZeroBuffer.Length) + BigIntegerCalculator.Multiply(previousTrailingZeroBuffer, multiplier, trailingZeroBuffer); + else + BigIntegerCalculator.Multiply(multiplier, previousTrailingZeroBuffer, trailingZeroBuffer); + + currentTrailingZeroBufferLength += multiplier.Length; + while (--currentTrailingZeroBufferLength >= 0 && trailingZeroBuffer[currentTrailingZeroBufferLength] == 0) ; + ++currentTrailingZeroBufferLength; + + if (previousTrailingZeroBufferFromPool != null) + ArrayPool.Shared.Return(previousTrailingZeroBufferFromPool); + + Debug.Assert(currentTrailingZeroBufferLength >= 1); + } + trailingZeroE9 >>= 1; + } + + // 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--; - } + int currentBufferSize = Math.Min((int)(bufferSize * digitRatio) + 1, bufferSize); + Debug.Assert(buffer.Length == currentBufferSize || buffer.Slice(currentBufferSize).Trim(0u).Length == 0); + while (--currentBufferSize >= 0 && buffer[currentBufferSize] == 0) ; + ++currentBufferSize; currentBuffer = buffer.Slice(0, currentBufferSize); - result = NumberBufferToBigInteger(currentBuffer, number.IsNegative); - } - finally - { - if (arrayFromPoolForMultiplier != null) + + trailingZeroBuffer = trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength); + if (trailingZeroBuffer.Length <= 1) { - ArrayPool.Shared.Return(arrayFromPoolForMultiplier); - } - } - return ParsingStatus.OK; - } + Debug.Assert(trailingZeroBuffer.Length == 1); + uint trailingZero = trailingZeroBuffer[0]; + if (trailingZero != 1) + { + int i = 0; + ulong carry = 0UL; - BigInteger NumberBufferToBigInteger(Span currentBuffer, bool signa) - { - int trailingZeroCount = numberScale - totalDigitCount; + for (; i < currentBuffer.Length; i++) + { + ulong digits = (ulong)currentBuffer[i] * trailingZero + carry; + currentBuffer[i] = unchecked((uint)digits); + carry = digits >> 32; + } + if (carry != 0) + { + currentBuffer = buffer.Slice(0, ++currentBufferSize); + currentBuffer[i] = (uint)carry; + } + } - while (trailingZeroCount >= MaxPartialDigits) - { - MultiplyAdd(ref currentBuffer, TenPowMaxPartial, 0); - trailingZeroCount -= MaxPartialDigits; - } + result = NumberBufferToBigInteger(currentBuffer, number.IsNegative); + } + else + { + int resultBufferLength = checked(currentBufferSize + trailingZeroBuffer.Length); + Span resultBuffer = (resultBufferLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : arrayFromPoolForTrailingZero = ArrayPool.Shared.Rent(resultBufferLength)).Slice(0, resultBufferLength); + resultBuffer.Clear(); - if (trailingZeroCount > 0) - { - MultiplyAdd(ref currentBuffer, UInt32PowersOfTen[trailingZeroCount], 0); - } + if (trailingZeroBuffer.Length < currentBuffer.Length) + BigIntegerCalculator.Multiply(currentBuffer, trailingZeroBuffer, resultBuffer); + else + BigIntegerCalculator.Multiply(trailingZeroBuffer, currentBuffer, resultBuffer); - int sign; - uint[]? bits; + while (--resultBufferLength >= 0 && resultBuffer[resultBufferLength] == 0) ; + ++resultBufferLength; - if (currentBufferSize == 0) - { - sign = 0; - bits = null; - } - else if (currentBufferSize == 1 && currentBuffer[0] <= int.MaxValue) - { - sign = (int)(signa ? -currentBuffer[0] : currentBuffer[0]); - bits = null; + result = NumberBufferToBigInteger(resultBuffer.Slice(0, resultBufferLength), number.IsNegative); + } } - else + finally { - sign = signa ? -1 : 1; - bits = currentBuffer.Slice(0, currentBufferSize).ToArray(); - } + if (arrayFromPoolForMultiplier != null) + ArrayPool.Shared.Return(arrayFromPoolForMultiplier); + + if (arrayFromPoolForResultBuffer != null) + ArrayPool.Shared.Return(arrayFromPoolForResultBuffer); + + if (arrayFromPoolForResultBuffer2 != null) + ArrayPool.Shared.Return(arrayFromPoolForResultBuffer2); - return new BigInteger(sign, bits); + if (arrayFromPoolForTrailingZero != null) + ArrayPool.Shared.Return(arrayFromPoolForTrailingZero); + } + return ParsingStatus.OK; } - // This function should only be used for result buffer. - void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) + static BigInteger NumberBufferToBigInteger(ReadOnlySpan currentBuffer, bool isNegative) { - Span curBits = currentBuffer.Slice(0, currentBufferSize); - uint carry = addValue; - - for (int i = 0; i < curBits.Length; i++) + if (currentBuffer.Length == 0) { - ulong p = (ulong)multiplier * curBits[i] + carry; - curBits[i] = (uint)p; - carry = (uint)(p >> 32); + return new BigInteger(0); } - - if (carry == 0) + else if (currentBuffer.Length == 1 && currentBuffer[0] <= int.MaxValue) { - return; + int v = (int)currentBuffer[0]; + if (isNegative) + v = -v; + return new BigInteger(v, null); } - - if (currentBufferSize == currentBuffer.Length) + else { - int[]? arrayToReturn = arrayFromPoolForResultBuffer; - - arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(checked(currentBufferSize * 2)); - Span newBuffer = MemoryMarshal.Cast(arrayFromPoolForResultBuffer); - currentBuffer.CopyTo(newBuffer); - currentBuffer = newBuffer; - - if (arrayToReturn != null) - { - ArrayPool.Shared.Return(arrayToReturn); - } + return new BigInteger(isNegative ? -1 : 1, currentBuffer.ToArray()); } - - currentBuffer[currentBufferSize] = carry; - currentBufferSize++; } } 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 a31153ceec834..0530c84124dec 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 @@ -163,6 +163,8 @@ public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, S Debug.Assert(bits.Length >= left.Length + right.Length); Debug.Assert(!bits.ContainsAnyExcept(0u)); + bits = bits.Slice(0, left.Length + right.Length); + if (left.Length - right.Length < 3) { MultiplyNearLength(left, right, bits); @@ -176,7 +178,7 @@ public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, S private static void MultiplyFarLength(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(left.Length - right.Length >= 3); - Debug.Assert(bits.Length >= left.Length + right.Length); + Debug.Assert(bits.Length == left.Length + right.Length); Debug.Assert(!bits.ContainsAnyExcept(0u)); // Executes different algorithms for computing z = a * b @@ -372,7 +374,7 @@ stackalloc uint[StackAllocThreshold] private static void MultiplyNearLength(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(left.Length - right.Length < 3); - Debug.Assert(bits.Length >= left.Length + right.Length); + Debug.Assert(bits.Length == left.Length + right.Length); Debug.Assert(!bits.ContainsAnyExcept(0u)); // Executes different algorithms for computing z = a * b From cfabb121fb0fb64fbcede7e2735525d80a018e79 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Fri, 19 Jan 2024 18:07:29 +0900 Subject: [PATCH 02/19] DisableParallelization --- .../tests/BigInteger/multiply.cs | 36 ++-- .../tests/BigInteger/parse.cs | 186 ++++++++++-------- .../System.Runtime.Numerics.Tests.csproj | 1 + 3 files changed, 127 insertions(+), 96 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs index d1abae863ea10..73fcf94268cb9 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.MultiplyThreshold, 8, RunMultiply_TwoLargeBigIntegers) - ); - - // Again, with lower threshold - BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () => - BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyThreshold, 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.MultiplyThreshold, 8, multiplyTest.RunMultiply_TwoLargeBigIntegers) + ); + + // Again, with lower threshold + BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () => + BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyThreshold, 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..c688e4135bf5f 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -36,57 +36,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 +93,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] @@ -210,16 +191,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() @@ -1169,4 +1144,55 @@ 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.s_naiveThreshold, 0, () => + { + 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.s_naiveThreshold, 0, () => + { + parseTest.Parse_Subspan_Success(input, offset, length, expected); + }); + } + + [Fact] + public static void Parse_EmptySubspan_Fails() + { + BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, 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.s_naiveThreshold, 0, () => + { + 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 @@ + From b449f905df14164e843d02c726505c0c13e2d81f Mon Sep 17 00:00:00 2001 From: kzrnm Date: Thu, 1 Feb 2024 20:17:22 +0900 Subject: [PATCH 03/19] Trailing zero --- .../src/System/Number.BigInteger.cs | 2 +- .../tests/BigInteger/parse.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 9d18d85dadc7e..5f3e2ac838fcb 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -963,7 +963,7 @@ static ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger re currentBuffer = buffer.Slice(0, currentBufferSize); trailingZeroBuffer = trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength); - if (trailingZeroBuffer.Length <= 1) + if (trailingZeroBuffer.Length <= 1 && currentBufferSize < buffer.Length) { Debug.Assert(trailingZeroBuffer.Length == 1); uint trailingZero = trailingZeroBuffer[0]; diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index c688e4135bf5f..31dc5a30eab10 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -243,6 +243,13 @@ private static void VerifyDefaultParse(Random random) VerifyParseToString(GetDigitSequence(100, 1000, random)); } + // Trailing Zero + VerifyParseToString("99000000000"); + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetDigitSequence(1, 10, random) + "1000000000"); + } + // Leading White for (int i = 0; i < s_samples; i++) { @@ -506,6 +513,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++) { From 15f9b4a5cf51c940d2b1a3030d67cd38ad74e84e Mon Sep 17 00:00:00 2001 From: kzrnm Date: Fri, 2 Feb 2024 23:59:24 +0900 Subject: [PATCH 04/19] Fix assertion --- .../src/System/Number.BigInteger.cs | 24 +++++++++++-------- .../Numerics/BigIntegerCalculator.SquMul.cs | 4 +--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 5f3e2ac838fcb..6c874fc1a9844 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -816,12 +816,14 @@ static ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger re trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).CopyTo(previousTrailingZeroBuffer); trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).Clear(); + + int newTrailingZeroBufferLength = currentTrailingZeroBufferLength + multiplier.Length; if (multiplier.Length < previousTrailingZeroBuffer.Length) - BigIntegerCalculator.Multiply(previousTrailingZeroBuffer, multiplier, trailingZeroBuffer); + BigIntegerCalculator.Multiply(previousTrailingZeroBuffer, multiplier, trailingZeroBuffer.Slice(0, newTrailingZeroBufferLength)); else - BigIntegerCalculator.Multiply(multiplier, previousTrailingZeroBuffer, trailingZeroBuffer); + BigIntegerCalculator.Multiply(multiplier, previousTrailingZeroBuffer, trailingZeroBuffer.Slice(0, newTrailingZeroBufferLength)); - currentTrailingZeroBufferLength += multiplier.Length; + currentTrailingZeroBufferLength = newTrailingZeroBufferLength; while (--currentTrailingZeroBufferLength >= 0 && trailingZeroBuffer[currentTrailingZeroBufferLength] == 0) ; ++currentTrailingZeroBufferLength; @@ -847,11 +849,11 @@ static ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger re int upperLen = len - lowerLen; if (upperLen != 0) { - Debug.Assert(blockSize == lowerLen); Debug.Assert(blockSize >= multiplier.Length); - Debug.Assert(multiplier.Length >= curBuffer.Slice(blockSize, upperLen).TrimEnd(0u).Length); - - BigIntegerCalculator.Multiply(multiplier, curBuffer.Slice(blockSize, upperLen).TrimEnd(0u), curNewBuffer.Slice(0, len)); + ReadOnlySpan curBufferTrimmed = curBuffer.Slice(blockSize, upperLen).TrimEnd(0u); + Debug.Assert(multiplier.Length >= curBufferTrimmed.Length); + Debug.Assert(multiplier.Length + curBufferTrimmed.Length <= len); + BigIntegerCalculator.Multiply(multiplier, curBufferTrimmed, curNewBuffer.Slice(0, multiplier.Length + curBufferTrimmed.Length)); } long carry = 0; @@ -934,12 +936,14 @@ static ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger re trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).CopyTo(previousTrailingZeroBuffer); trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).Clear(); + + int newTrailingZeroBufferLength = currentTrailingZeroBufferLength + multiplier.Length; if (multiplier.Length < previousTrailingZeroBuffer.Length) - BigIntegerCalculator.Multiply(previousTrailingZeroBuffer, multiplier, trailingZeroBuffer); + BigIntegerCalculator.Multiply(previousTrailingZeroBuffer, multiplier, trailingZeroBuffer.Slice(0, newTrailingZeroBufferLength)); else - BigIntegerCalculator.Multiply(multiplier, previousTrailingZeroBuffer, trailingZeroBuffer); + BigIntegerCalculator.Multiply(multiplier, previousTrailingZeroBuffer, trailingZeroBuffer.Slice(0, newTrailingZeroBufferLength)); - currentTrailingZeroBufferLength += multiplier.Length; + currentTrailingZeroBufferLength = newTrailingZeroBufferLength; while (--currentTrailingZeroBufferLength >= 0 && trailingZeroBuffer[currentTrailingZeroBufferLength] == 0) ; ++currentTrailingZeroBufferLength; 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 0530c84124dec..ce36b82f6ae1f 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 @@ -160,11 +160,9 @@ internal const public static void Multiply(ReadOnlySpan left, ReadOnlySpan right, Span bits) { Debug.Assert(left.Length >= right.Length); - Debug.Assert(bits.Length >= left.Length + right.Length); + Debug.Assert(bits.Length == left.Length + right.Length); Debug.Assert(!bits.ContainsAnyExcept(0u)); - bits = bits.Slice(0, left.Length + right.Length); - if (left.Length - right.Length < 3) { MultiplyNearLength(left, right, bits); From 58dd8690e8e68d58b257614796d43eadf5b6e017 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Tue, 6 Feb 2024 01:46:07 +0900 Subject: [PATCH 05/19] Recursive parse --- .../src/System/Number.BigInteger.cs | 782 +++++++++--------- .../Numerics/BigIntegerCalculator.AddSub.cs | 4 +- .../Numerics/BigIntegerCalculator.Utils.cs | 2 +- .../tests/BigInteger/parse.cs | 18 +- 4 files changed, 413 insertions(+), 393 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 6c874fc1a9844..cecbe5c9d0ed2 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -566,223 +566,238 @@ internal static #else internal const #endif - int s_naiveThreshold = 1233; + int + BigIntegerParseNaiveThreshold = 1233, + BigIntegerParseNaiveThresholdInRecursive = 9 * (1 << 7); private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out BigInteger result) { - const int MaxPartialDigits = 9; - const uint TenPowMaxPartial = 1000000000; + const uint TenPowMaxPartial = PowersOf1e9.TenPowMaxPartial; + const int MaxPartialDigits = PowersOf1e9.MaxPartialDigits; - if ((uint)number.Scale >= int.MaxValue) + if (number.Scale == int.MaxValue) { result = default; - return number.Scale == int.MaxValue - ? ParsingStatus.Overflow - : ParsingStatus.Failed; + return ParsingStatus.Overflow; } - if (number.Scale <= s_naiveThreshold) + if (number.Scale < 0) { - return Naive(ref number, out result); + result = default; + return ParsingStatus.Failed; } - else + + const double digitRatio = 0.10381025297; // log_{2^32}(10) + int resultLength = checked((int)(number.Scale * digitRatio) + 1 + 2); + uint[]? resultBufferFromPool = null; + Span resultBuffer = ( + resultLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : resultBufferFromPool = ArrayPool.Shared.Rent(resultLength)).Slice(0, resultLength); + resultBuffer.Clear(); + + if (number.Scale <= BigIntegerParseNaiveThreshold + ? !Naive(ref number, resultBuffer) + : !DivideAndConquer(ref number, resultBuffer)) { - return DivideAndConquer(ref number, out result); + result = default; + return ParsingStatus.Failed; } - static ParsingStatus Naive(ref NumberBuffer number, out BigInteger result) - { - int numberScale = number.Scale; + result = NumberBufferToBigInteger(resultBuffer.Slice(0, BigIntegerCalculator.ActualLength(resultBuffer)), number.IsNegative); - uint[]? arrayFromPoolForResultBuffer = null; - int currentBufferSize = 0; - int totalDigitCount = 0; - Span stackBuffer = stackalloc uint[BigIntegerCalculator.StackAllocThreshold]; - Span currentBuffer = stackBuffer; - uint partialValue = 0; - int partialDigitCount = 0; + if (resultBufferFromPool != null) + ArrayPool.Shared.Return(resultBufferFromPool); - if (!ProcessChunk(number.Digits[..number.DigitsCount], ref currentBuffer)) - { - result = default; - return ParsingStatus.Failed; - } - - if (partialDigitCount > 0) - { - MultiplyAdd(ref currentBuffer, UInt32PowersOfTen[partialDigitCount], partialValue); - } + return ParsingStatus.OK; - int trailingZeroCount = numberScale - totalDigitCount; - while (trailingZeroCount >= MaxPartialDigits) - { - MultiplyAdd(ref currentBuffer, TenPowMaxPartial, 0); - trailingZeroCount -= MaxPartialDigits; - } - if (trailingZeroCount > 0) - { - MultiplyAdd(ref currentBuffer, UInt32PowersOfTen[trailingZeroCount], 0); - } - - result = NumberBufferToBigInteger(currentBuffer.Slice(0, currentBufferSize), number.IsNegative); - return ParsingStatus.OK; - - bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) + static bool DivideAndConquer(ref NumberBuffer number, scoped Span bits) + { + ReadOnlySpan intDigits = number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); + int intDigitsEnd = intDigits.IndexOf(0); + if (intDigitsEnd < 0) { - 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); + for (int i = 0; i < fracDigitsSpan.Length; i++) { - char digitChar = (char)chunkDigits[i]; + char digitChar = (char)fracDigitsSpan[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; + return false; } } + } + 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 totalDigitCount = Math.Min(number.DigitsCount, number.Scale); + int trailingZeroCount = number.Scale - totalDigitCount; - partialValue = _partialValue; - partialDigitCount = _partialDigitCount; - totalDigitCount = _totalDigitCount; + int powersOf1e9BufferLength = PowersOf1e9.GetBufferSize(Math.Max(totalDigitCount, trailingZeroCount)); + uint[]? powersOf1e9BufferFromPool = null; + Span powersOf1e9Buffer = ( + powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : powersOf1e9BufferFromPool = ArrayPool.Shared.Rent(powersOf1e9BufferLength)).Slice(0, powersOf1e9BufferLength); + powersOf1e9Buffer.Clear(); - return true; - } + PowersOf1e9 powersOf1e9 = new PowersOf1e9(powersOf1e9Buffer); - // This function should only be used for result buffer. - void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) + const double digitRatio = 0.103810253; // log_{2^32}(10) + + if (trailingZeroCount > 0) { - Span curBits = currentBuffer.Slice(0, currentBufferSize); - uint carry = addValue; + int leadingLength = checked((int)(digitRatio * totalDigitCount) + 2); + uint[]? leadingFromPool = null; + Span leading = ( + leadingLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : leadingFromPool = ArrayPool.Shared.Rent(leadingLength)).Slice(0, leadingLength); + leading.Clear(); - for (int i = 0; i < curBits.Length; i++) - { - ulong p = (ulong)multiplier * curBits[i] + carry; - curBits[i] = (uint)p; - carry = (uint)(p >> 32); - } + Recursive(powersOf1e9, intDigits, leading); + leading = leading.Slice(0, BigIntegerCalculator.ActualLength(leading)); - if (carry == 0) - { - return; - } + int trailingZeroBufferLength = checked((int)(digitRatio * trailingZeroCount) + 2); + uint[]? trailingZeroBufferFromPool = null; + Span trailingZeroBuffer = ( + trailingZeroBufferLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : trailingZeroBufferFromPool = ArrayPool.Shared.Rent(trailingZeroBufferLength)).Slice(0, trailingZeroBufferLength); + trailingZeroBuffer.Clear(); - if (currentBufferSize == currentBuffer.Length) - { - uint[]? arrayToReturn = arrayFromPoolForResultBuffer; + powersOf1e9.CalculatePowerOfTen(trailingZeroCount, trailingZeroBuffer); + trailingZeroBuffer = trailingZeroBuffer.Slice(0, BigIntegerCalculator.ActualLength(trailingZeroBuffer)); - arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(checked(currentBufferSize * 2)); - Span newBuffer = new Span(arrayFromPoolForResultBuffer); - currentBuffer.CopyTo(newBuffer); - currentBuffer = newBuffer; + // Merge leading and trailing + Span bitsResult = bits.Slice(0, trailingZeroBuffer.Length + leading.Length); - if (arrayToReturn != null) - { - ArrayPool.Shared.Return(arrayToReturn); - } - } + if (trailingZeroBuffer.Length < leading.Length) + BigIntegerCalculator.Multiply(leading, trailingZeroBuffer, bitsResult); + else + BigIntegerCalculator.Multiply(trailingZeroBuffer, leading, bitsResult); - currentBuffer[currentBufferSize] = carry; - currentBufferSize++; + if (leadingFromPool != null) + ArrayPool.Shared.Return(leadingFromPool); + if (trailingZeroBufferFromPool != null) + ArrayPool.Shared.Return(trailingZeroBufferFromPool); } + else + { + Recursive(powersOf1e9, intDigits, bits); + } + + + if (powersOf1e9BufferFromPool != null) + ArrayPool.Shared.Return(powersOf1e9BufferFromPool); + + return true; } - static ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger result) + static void Recursive(in PowersOf1e9 powersOf1e9, ReadOnlySpan digits, Span bits) { - // log_{2^32}(10^9) - const double digitRatio = 0.934292276687070661; + Debug.Assert(BigIntegerParseNaiveThresholdInRecursive >= MaxPartialDigits); + if (digits.Length < BigIntegerParseNaiveThresholdInRecursive) + { + NaiveDigits(digits, bits); + return; + } + + int lengthe9 = (digits.Length + MaxPartialDigits - 1) / MaxPartialDigits; + int log2 = BitOperations.Log2((uint)(lengthe9 - 1)); + int powOfTenSize = 1 << log2; - Span currentBuffer; - uint[]? arrayFromPoolForMultiplier = null; - uint[]? arrayFromPoolForResultBuffer = null; - uint[]? arrayFromPoolForResultBuffer2 = null; - uint[]? arrayFromPoolForTrailingZero = null; - try + ReadOnlySpan digitsUpper = digits.Slice(0, digits.Length - MaxPartialDigits * powOfTenSize); + ReadOnlySpan digitsLower = digits.Slice(digitsUpper.Length); { - int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); - int trailingZeroCount = number.Scale - totalDigitCount; - int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; + int iv = 0; + while ((uint)iv < (uint)digitsLower.Length && digitsLower[iv] == 0) ++iv; + digitsLower = digitsLower.Slice(iv); + } - Span buffer = new Span(arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(bufferSize), 0, bufferSize); - Span newBuffer = new Span(arrayFromPoolForResultBuffer2 = ArrayPool.Shared.Rent(bufferSize), 0, bufferSize); - newBuffer.Clear(); + int upperBufferLength = checked((int)(digitsUpper.Length * digitRatio) + 1 + 2); + uint[]? upperBufferFromPool = null; + Span upperBuffer = ( + upperBufferLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : upperBufferFromPool = ArrayPool.Shared.Rent(upperBufferLength)).Slice(0, upperBufferLength); + upperBuffer.Clear(); + { + Recursive(powersOf1e9, digitsUpper, upperBuffer); + upperBuffer = upperBuffer.Slice(0, BigIntegerCalculator.ActualLength(upperBuffer)); + ReadOnlySpan multiplier = powersOf1e9.GetSpan(log2); + Span bitsUpper = bits.Slice(0, upperBuffer.Length + multiplier.Length); - int trailingZeroE9 = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int trailingZeroRemainder); - int trailingZeroBufferLength = checked((int)(digitRatio * (trailingZeroE9 + Math.Max(trailingZeroRemainder, 1))) + 1); - Span trailingZeroBuffer = (trailingZeroBufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : arrayFromPoolForTrailingZero = ArrayPool.Shared.Rent(trailingZeroBufferLength)).Slice(0, trailingZeroBufferLength); + if (multiplier.Length < upperBuffer.Length) + BigIntegerCalculator.Multiply(upperBuffer, multiplier, bitsUpper); + else + BigIntegerCalculator.Multiply(multiplier, upperBuffer, bitsUpper); + } + if (upperBufferFromPool != null) + ArrayPool.Shared.Return(upperBufferFromPool); + + + int lowerBufferLength = checked((int)(digitsLower.Length * digitRatio) + 1 + 2); + uint[]? lowerBufferFromPool = null; + Span lowerBuffer = ( + lowerBufferLength <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : lowerBufferFromPool = ArrayPool.Shared.Rent(lowerBufferLength)).Slice(0, lowerBufferLength); + lowerBuffer.Clear(); + + Recursive(powersOf1e9, digitsLower, lowerBuffer); + lowerBuffer = lowerBuffer.Slice(0, BigIntegerCalculator.ActualLength(lowerBuffer)); - int currentTrailingZeroBufferLength = 1; - trailingZeroBuffer.Slice(1).Clear(); - trailingZeroBuffer[0] = UInt32PowersOfTen[trailingZeroRemainder]; + BigIntegerCalculator.AddSelf(bits, lowerBuffer); - // 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; + if (lowerBufferFromPool != null) + ArrayPool.Shared.Return(lowerBufferFromPool); + } + + static int NaiveDigits(ReadOnlySpan intDigits, Span bits) + { + int leadingLength = intDigits.Length % 9; + uint partialValue = 0; + foreach (uint dig in intDigits.Slice(0, leadingLength)) + { + partialValue = partialValue * 10 + (dig - '0'); + } + bits[0] = partialValue; - ReadOnlySpan digitsChunkSpan = number.Digits[..number.DigitsCount]; - ReadOnlySpan intDigitsSpan = digitsChunkSpan.Slice(0, Math.Min(remainingIntDigitCount, digitsChunkSpan.Length)); + int partialDigitCount = 0; + int resultLength = 1; + partialValue = 0; + for (int i = leadingLength; i < intDigits.Length; i++) + { + partialValue = partialValue * 10 + (uint)(intDigits[i] - '0'); - for (int i = 0; i < intDigitsSpan.Length; i++) + // Update the buffer when enough partial digits have been accumulated. + if (++partialDigitCount == MaxPartialDigits) { - 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--; + uint carry = MultiplyAdd(bits.Slice(0, resultLength), TenPowMaxPartial, partialValue); + partialValue = 0; + partialDigitCount = 0; + Debug.Assert(bits[resultLength] == 0); + if (carry != 0) + bits[resultLength++] = carry; } - remainingIntDigitCount -= intDigitsSpan.Length; - Debug.Assert(0 <= remainingIntDigitCount); + } - ReadOnlySpan fracDigitsSpan = digitsChunkSpan.Slice(intDigitsSpan.Length); + return resultLength; + } + + static bool Naive(ref NumberBuffer number, scoped Span bits) + { + 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); for (int i = 0; i < fracDigitsSpan.Length; i++) { char digitChar = (char)fracDigitsSpan[i]; @@ -792,258 +807,259 @@ static ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger re } if (digitChar != '0') { - result = default; - return ParsingStatus.Failed; + return false; } } + } + else + intDigits = intDigits.Slice(0, intDigitsEnd); - Debug.Assert(currentBlock == 0); - Debug.Assert(bufferIndex == -1); - - int blockSize = 1; - int multiplierSize = 1; - Span multiplier = stackalloc uint[1] { TenPowMaxPartial }; - - // This loop is executed ceil(log_2(bufferSize)) times. - while (true) - { - if ((trailingZeroE9 & 1) != 0) - { - uint[]? previousTrailingZeroBufferFromPool = null; - Span previousTrailingZeroBuffer = new Span( - previousTrailingZeroBufferFromPool = ArrayPool.Shared.Rent(currentTrailingZeroBufferLength), - 0, currentTrailingZeroBufferLength); - - trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).CopyTo(previousTrailingZeroBuffer); - trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).Clear(); - - int newTrailingZeroBufferLength = currentTrailingZeroBufferLength + multiplier.Length; - if (multiplier.Length < previousTrailingZeroBuffer.Length) - BigIntegerCalculator.Multiply(previousTrailingZeroBuffer, multiplier, trailingZeroBuffer.Slice(0, newTrailingZeroBufferLength)); - else - BigIntegerCalculator.Multiply(multiplier, previousTrailingZeroBuffer, trailingZeroBuffer.Slice(0, newTrailingZeroBufferLength)); - - currentTrailingZeroBufferLength = newTrailingZeroBufferLength; - while (--currentTrailingZeroBufferLength >= 0 && trailingZeroBuffer[currentTrailingZeroBufferLength] == 0) ; - ++currentTrailingZeroBufferLength; - - if (previousTrailingZeroBufferFromPool != null) - ArrayPool.Shared.Return(previousTrailingZeroBufferFromPool); - Debug.Assert(currentTrailingZeroBufferLength >= 1); - } - trailingZeroE9 >>= 1; - - // 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); - - int len = Math.Min(bufferSize - i, blockSize * 2); - int lowerLen = Math.Min(len, blockSize); - int upperLen = len - lowerLen; - if (upperLen != 0) - { - Debug.Assert(blockSize >= multiplier.Length); - ReadOnlySpan curBufferTrimmed = curBuffer.Slice(blockSize, upperLen).TrimEnd(0u); - Debug.Assert(multiplier.Length >= curBufferTrimmed.Length); - Debug.Assert(multiplier.Length + curBufferTrimmed.Length <= len); - BigIntegerCalculator.Multiply(multiplier, curBufferTrimmed, curNewBuffer.Slice(0, multiplier.Length + curBufferTrimmed.Length)); - } - - 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++; - } - } - } + int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); + if (totalDigitCount == 0) + { + // number is 0. + return true; + } + int resultLength = NaiveDigits(intDigits, bits); - Span tmp = buffer; - buffer = newBuffer; - newBuffer = tmp; - blockSize <<= 1; + int trailingZeroCount = number.Scale - totalDigitCount; + int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); + for (int i = 0; i < trailingPartialCount; i++) + { + uint carry = MultiplyAdd(bits.Slice(0, resultLength), TenPowMaxPartial, 0); + Debug.Assert(bits[resultLength] == 0); + if (carry != 0) + bits[resultLength++] = carry; + } - if (bufferSize <= blockSize) - { - break; - } - multiplierSize <<= 1; - newBuffer.Clear(); - uint[]? arrayToReturn = arrayFromPoolForMultiplier; + if (remainingTrailingZeroCount != 0) + { + uint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; + uint carry = MultiplyAdd(bits.Slice(0, resultLength), multiplier, 0); + Debug.Assert(bits[resultLength] == 0); + if (carry != 0) + bits[resultLength++] = carry; + } - Span newMultiplier = new Span( - arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(multiplierSize), - 0, multiplierSize); - newMultiplier.Clear(); - BigIntegerCalculator.Square(multiplier, newMultiplier); - multiplier = newMultiplier; + return true; + } - while (--multiplierSize >= 0 && multiplier[multiplierSize] == 0) ; - multiplier = multiplier.Slice(0, ++multiplierSize); + static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) + { + uint carry = addValue; - if (arrayToReturn is not null) - { - ArrayPool.Shared.Return(arrayToReturn); - } - } + 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; + } - while (trailingZeroE9 != 0) - { - multiplierSize <<= 1; - uint[]? arrayToReturn = arrayFromPoolForMultiplier; + static BigInteger NumberBufferToBigInteger(ReadOnlySpan result, bool isNegative) + { + Debug.Assert(result.Length == 0 || result[^1] != 0); - Span newMultiplier = new Span( - arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(multiplierSize), - 0, multiplierSize); - newMultiplier.Clear(); - BigIntegerCalculator.Square(multiplier, newMultiplier); - multiplier = newMultiplier; + if (result.Length == 0) + { + return BigInteger.Zero; + } + else if (result is [uint leading] && (leading <= int.MaxValue || isNegative && leading == unchecked((uint)(int.MaxValue + 1)))) + { + return new BigInteger((int)(isNegative ? -leading : leading)); + } + else + { + return new BigInteger(isNegative ? -1 : 1, result.ToArray()); + } + } + } + internal readonly ref struct PowersOf1e9 + { + private readonly ReadOnlySpan 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< Indexes => new int[] { + 0, + 1, + 3, + 7, + 15, + 30, + 60, + 120, + 240, + 480, + 959, + 1916, + 3830, + 7657, + 15311, + 30619, + 61234, + 122464, + 244924, + 489844, + 979683, + 1959360, + 3918713, + 7837419, + 15674831, + 31349655, + 62699302, + 125398596, + 250797183, + 501594357, + 1003188704, + 2006377398, + }; + + public PowersOf1e9(Span pow1E9) + { + this.pow1E9 = pow1E9; - while (--multiplierSize >= 0 && multiplier[multiplierSize] == 0) ; - multiplier = multiplier.Slice(0, ++multiplierSize); + Debug.Assert(pow1E9.Length >= 1); + pow1E9[0] = TenPowMaxPartial; + ReadOnlySpan src = pow1E9.Slice(0, 1); + int toExclusive = 1; + for (int i = 1; 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); + } + } + public ReadOnlySpan GetSpan(int index) + { + // Returns 1E9^(1<.Shared.Return(arrayToReturn); - } + public void CalculatePowerOfTen(int trailingZeroCount, Span bits) + { + Debug.Assert(bits.Length > unchecked((int)(0.934292276687070661 / 9 * trailingZeroCount)) + 1); + Debug.Assert(trailingZeroCount >= 0); - if ((trailingZeroE9 & 1) != 0) - { - uint[]? previousTrailingZeroBufferFromPool = null; - Span previousTrailingZeroBuffer = new Span( - previousTrailingZeroBufferFromPool = ArrayPool.Shared.Rent(currentTrailingZeroBufferLength), - 0, currentTrailingZeroBufferLength); - trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).CopyTo(previousTrailingZeroBuffer); - trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength).Clear(); + int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); - int newTrailingZeroBufferLength = currentTrailingZeroBufferLength + multiplier.Length; - if (multiplier.Length < previousTrailingZeroBuffer.Length) - BigIntegerCalculator.Multiply(previousTrailingZeroBuffer, multiplier, trailingZeroBuffer.Slice(0, newTrailingZeroBufferLength)); - else - BigIntegerCalculator.Multiply(multiplier, previousTrailingZeroBuffer, trailingZeroBuffer.Slice(0, newTrailingZeroBufferLength)); + if (trailingPartialCount == 0) + { + bits[0] = UInt32PowersOfTen[remainingTrailingZeroCount]; + return; + } - currentTrailingZeroBufferLength = newTrailingZeroBufferLength; - while (--currentTrailingZeroBufferLength >= 0 && trailingZeroBuffer[currentTrailingZeroBufferLength] == 0) ; - ++currentTrailingZeroBufferLength; + int popCount = BitOperations.PopCount((uint)trailingPartialCount); - if (previousTrailingZeroBufferFromPool != null) - ArrayPool.Shared.Return(previousTrailingZeroBufferFromPool); + int bits2Length = (bits.Length + 1) >> 1; + uint[]? bits2FromPool = null; + scoped Span bits2; + if (popCount > 1) + { + bits2 = ( + bits2Length <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bits2FromPool = ArrayPool.Shared.Rent(bits2Length)).Slice(0, bits2Length); + bits2.Clear(); + } + else + bits2 = default; - Debug.Assert(currentTrailingZeroBufferLength >= 1); - } - trailingZeroE9 >>= 1; - } + int curLength; + scoped Span curBits, otherBits; + if ((popCount & 1) != 0) + { + curBits = bits; + otherBits = bits2; + } + else + { + curBits = bits2; + otherBits = 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) - int currentBufferSize = Math.Min((int)(bufferSize * digitRatio) + 1, bufferSize); - Debug.Assert(buffer.Length == currentBufferSize || buffer.Slice(currentBufferSize).Trim(0u).Length == 0); - while (--currentBufferSize >= 0 && buffer[currentBufferSize] == 0) ; - ++currentBufferSize; - currentBuffer = buffer.Slice(0, currentBufferSize); + // Copy first + int fi = BitOperations.TrailingZeroCount(trailingPartialCount); + { + ReadOnlySpan first = GetSpan(fi); + first.CopyTo(curBits); + curLength = first.Length; + trailingPartialCount >>= fi; + trailingPartialCount >>= 1; + } - trailingZeroBuffer = trailingZeroBuffer.Slice(0, currentTrailingZeroBufferLength); - if (trailingZeroBuffer.Length <= 1 && currentBufferSize < buffer.Length) - { - Debug.Assert(trailingZeroBuffer.Length == 1); - uint trailingZero = trailingZeroBuffer[0]; - if (trailingZero != 1) - { - int i = 0; - ulong carry = 0UL; - - for (; i < currentBuffer.Length; i++) - { - ulong digits = (ulong)currentBuffer[i] * trailingZero + carry; - currentBuffer[i] = unchecked((uint)digits); - carry = digits >> 32; - } - if (carry != 0) - { - currentBuffer = buffer.Slice(0, ++currentBufferSize); - currentBuffer[i] = (uint)carry; - } - } - result = NumberBufferToBigInteger(currentBuffer, number.IsNegative); - } - else + for (int i = fi + 1; trailingPartialCount != 0 && i + 1 < Indexes.Length; i++, trailingPartialCount >>= 1) + { + Debug.Assert(GetSpan(i).Length >= curLength); + if ((trailingPartialCount & 1) != 0) { - int resultBufferLength = checked(currentBufferSize + trailingZeroBuffer.Length); - Span resultBuffer = (resultBufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : arrayFromPoolForTrailingZero = ArrayPool.Shared.Rent(resultBufferLength)).Slice(0, resultBufferLength); - resultBuffer.Clear(); + ReadOnlySpan power = GetSpan(i); - if (trailingZeroBuffer.Length < currentBuffer.Length) - BigIntegerCalculator.Multiply(currentBuffer, trailingZeroBuffer, resultBuffer); - else - BigIntegerCalculator.Multiply(trailingZeroBuffer, currentBuffer, resultBuffer); + Span src = curBits.Slice(0, curLength); + Span dst = otherBits.Slice(0, curLength += power.Length); + BigIntegerCalculator.Multiply(power, src, dst); - while (--resultBufferLength >= 0 && resultBuffer[resultBufferLength] == 0) ; - ++resultBufferLength; + Span tmp = curBits; + curBits = otherBits; + otherBits = tmp; + otherBits.Clear(); - result = NumberBufferToBigInteger(resultBuffer.Slice(0, resultBufferLength), number.IsNegative); + // Trim + while (--curLength >= 0 && curBits[curLength] == 0) ; + ++curLength; } } - finally - { - if (arrayFromPoolForMultiplier != null) - ArrayPool.Shared.Return(arrayFromPoolForMultiplier); - if (arrayFromPoolForResultBuffer != null) - ArrayPool.Shared.Return(arrayFromPoolForResultBuffer); + Debug.Assert(Unsafe.AreSame(ref bits[0], ref curBits[0])); - if (arrayFromPoolForResultBuffer2 != null) - ArrayPool.Shared.Return(arrayFromPoolForResultBuffer2); + curBits = curBits.Slice(0, curLength); + uint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; + uint carry = 0; + for (int i = 0; i < curBits.Length; i++) + { + ulong p = (ulong)multiplier * curBits[i] + carry; + curBits[i] = (uint)p; + carry = (uint)(p >> 32); + } - if (arrayFromPoolForTrailingZero != null) - ArrayPool.Shared.Return(arrayFromPoolForTrailingZero); + if (carry != 0) + { + bits[curLength] = carry; } - return ParsingStatus.OK; + + if (bits2FromPool != null) + ArrayPool.Shared.Return(bits2FromPool); } - static BigInteger NumberBufferToBigInteger(ReadOnlySpan currentBuffer, bool isNegative) + public static int GetBufferSize(int digits) { - if (currentBuffer.Length == 0) - { - return new BigInteger(0); - } - else if (currentBuffer.Length == 1 && currentBuffer[0] <= int.MaxValue) - { - int v = (int)currentBuffer[0]; - if (isNegative) - v = -v; - return new BigInteger(v, null); - } - else - { - return new BigInteger(isNegative ? -1 : 1, currentBuffer.ToArray()); - } + int scale1E9 = digits / MaxPartialDigits; + int log2 = BitOperations.Log2((uint)scale1E9) + 1; + return (uint)log2 < (uint)Indexes.Length ? Indexes[log2] + 1 : Indexes[^1]; } } 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/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 31dc5a30eab10..c9fd0d610ba40 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -1168,10 +1168,11 @@ public class parseTestThreshold [OuterLoop] public static void RunParseToStringTests(CultureInfo culture) { - BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () => { parseTest.RunParseToStringTests(culture); - }); + })); } [Theory] @@ -1185,16 +1186,18 @@ public static void RunParseToStringTests(CultureInfo culture) [InlineData("123456789\0", 0, 10, "123456789")] public static void Parse_Subspan_Success(string input, int offset, int length, string expected) { - BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, () => + 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.s_naiveThreshold, 0, parseTest.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(); @@ -1203,10 +1206,11 @@ public static void Parse_EmptySubspan_Fails() [MemberData(nameof(RegressionIssueRuntime94610_TestData))] public static void RegressionIssueRuntime94610(string text) { - BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () => { parseTest.RegressionIssueRuntime94610(text); - }); + })); } } } From da5402fb208063fdbe4b9a6e6e94bcfa4cd80d55 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Thu, 15 Feb 2024 09:06:01 +0900 Subject: [PATCH 06/19] Skip trailing zeros --- .../src/System/Number.BigInteger.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index cecbe5c9d0ed2..26934a6917a13 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -732,6 +732,10 @@ static void Recursive(in PowersOf1e9 powersOf1e9, ReadOnlySpan digits, Spa ReadOnlySpan multiplier = powersOf1e9.GetSpan(log2); Span bitsUpper = bits.Slice(0, upperBuffer.Length + multiplier.Length); + int multiplierTrailingZeroCountUInt32 = (MaxPartialDigits * (1 << log2)) >> 5; + multiplier = multiplier.Slice(multiplierTrailingZeroCountUInt32); + bitsUpper = bitsUpper.Slice(multiplierTrailingZeroCountUInt32); + if (multiplier.Length < upperBuffer.Length) BigIntegerCalculator.Multiply(upperBuffer, multiplier, bitsUpper); else @@ -962,7 +966,6 @@ public void CalculatePowerOfTen(int trailingZeroCount, Span bits) Debug.Assert(bits.Length > unchecked((int)(0.934292276687070661 / 9 * trailingZeroCount)) + 1); Debug.Assert(trailingZeroCount >= 0); - int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); if (trailingPartialCount == 0) @@ -987,7 +990,7 @@ public void CalculatePowerOfTen(int trailingZeroCount, Span bits) else bits2 = default; - int curLength; + int curLength, curTrailingZeroCount; scoped Span curBits, otherBits; if ((popCount & 1) != 0) @@ -1007,6 +1010,7 @@ public void CalculatePowerOfTen(int trailingZeroCount, Span bits) ReadOnlySpan first = GetSpan(fi); first.CopyTo(curBits); curLength = first.Length; + curTrailingZeroCount = MaxPartialDigits * (1 << fi); trailingPartialCount >>= fi; trailingPartialCount >>= 1; } @@ -1018,10 +1022,23 @@ public void CalculatePowerOfTen(int trailingZeroCount, Span bits) if ((trailingPartialCount & 1) != 0) { ReadOnlySpan power = GetSpan(i); - Span src = curBits.Slice(0, curLength); Span dst = otherBits.Slice(0, curLength += power.Length); - BigIntegerCalculator.Multiply(power, src, dst); + + int powerTrailingZeroCount = MaxPartialDigits * (1 << i); + int powerTrailingZeroCountUInt32 = powerTrailingZeroCount >> 5; + int curTrailingZeroCountUInt32 = curTrailingZeroCount >> 5; + + Debug.Assert(powerTrailingZeroCountUInt32 < power.Length + && power.Slice(0, powerTrailingZeroCountUInt32).Trim(0u).Length == 0 + && power[powerTrailingZeroCountUInt32] != 0); + Debug.Assert(curTrailingZeroCountUInt32 < src.Length + && src.Slice(0, curTrailingZeroCountUInt32).Trim(0u).Length == 0 + && src[curTrailingZeroCountUInt32] != 0); + + BigIntegerCalculator.Multiply(power.Slice(powerTrailingZeroCountUInt32), src.Slice(curTrailingZeroCountUInt32), dst.Slice(powerTrailingZeroCountUInt32 + curTrailingZeroCountUInt32)); + + curTrailingZeroCount += powerTrailingZeroCount; Span tmp = curBits; curBits = otherBits; @@ -1039,7 +1056,7 @@ public void CalculatePowerOfTen(int trailingZeroCount, Span bits) curBits = curBits.Slice(0, curLength); uint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; uint carry = 0; - for (int i = 0; i < curBits.Length; i++) + for (int i = (curTrailingZeroCount >> 5); i < curBits.Length; i++) { ulong p = (ulong)multiplier * curBits[i] + carry; curBits[i] = (uint)p; From 1f4722c9ae669629ffa86925f3069a34ac359956 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Fri, 16 Feb 2024 00:00:31 +0900 Subject: [PATCH 07/19] Shrink PowersOf1e9 --- .../src/System/Number.BigInteger.cs | 104 ++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 26934a6917a13..974128ce6efb9 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -730,10 +730,9 @@ static void Recursive(in PowersOf1e9 powersOf1e9, ReadOnlySpan digits, Spa Recursive(powersOf1e9, digitsUpper, upperBuffer); upperBuffer = upperBuffer.Slice(0, BigIntegerCalculator.ActualLength(upperBuffer)); ReadOnlySpan multiplier = powersOf1e9.GetSpan(log2); - Span bitsUpper = bits.Slice(0, upperBuffer.Length + multiplier.Length); - int multiplierTrailingZeroCountUInt32 = (MaxPartialDigits * (1 << log2)) >> 5; - multiplier = multiplier.Slice(multiplierTrailingZeroCountUInt32); + + Span bitsUpper = bits.Slice(0, upperBuffer.Length + multiplier.Length + multiplierTrailingZeroCountUInt32); bitsUpper = bitsUpper.Slice(multiplierTrailingZeroCountUInt32); if (multiplier.Length < upperBuffer.Length) @@ -896,41 +895,42 @@ internal readonly ref struct PowersOf1e9 // for (int i = 0; i + 1 < indexes.Length; i++) // { // int length = unchecked((int)(digitRatio * (1 << i)) + 1); + // length -= (9*(1<> 5; // indexes[i+1] = indexes[i] + length; // } private static ReadOnlySpan Indexes => new int[] { 0, 1, 3, - 7, - 15, - 30, - 60, - 120, - 240, - 480, - 959, - 1916, - 3830, - 7657, - 15311, - 30619, - 61234, - 122464, - 244924, - 489844, - 979683, - 1959360, - 3918713, - 7837419, - 15674831, - 31349655, - 62699302, - 125398596, - 250797183, - 501594357, - 1003188704, - 2006377398, + 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, }; public PowersOf1e9(Span pow1E9) @@ -948,14 +948,29 @@ public PowersOf1e9(Span pow1E9) break; Span dst = pow1E9.Slice(toExclusive, src.Length << 1); BigIntegerCalculator.Square(src, dst); + if (dst[0] == 0) + { + dst.Slice(1).CopyTo(dst); + dst[^1] = 0; + } int from = toExclusive; toExclusive = Indexes[i + 1]; src = pow1E9.Slice(from, toExclusive - from); + Debug.Assert(toExclusive == pow1E9.Length || pow1E9[toExclusive] == 0); + } +#if DEBUG + for (int i = 0; i + 1 < Indexes.Length; i++) + { + int startIndex = Indexes[i]; + int endIndex = Indexes[i + 1]; + if (endIndex >= pow1E9.Length) break; + Debug.Assert(pow1E9[startIndex] != 0); } +#endif } public ReadOnlySpan GetSpan(int index) { - // Returns 1E9^(1<> (32*(9*(1< bits) // Copy first int fi = BitOperations.TrailingZeroCount(trailingPartialCount); { - ReadOnlySpan first = GetSpan(fi); - first.CopyTo(curBits); - curLength = first.Length; curTrailingZeroCount = MaxPartialDigits * (1 << fi); trailingPartialCount >>= fi; trailingPartialCount >>= 1; + + ReadOnlySpan first = GetSpan(fi); + first.CopyTo(curBits.Slice(curTrailingZeroCount >> 5)); + + curLength = first.Length; } for (int i = fi + 1; trailingPartialCount != 0 && i + 1 < Indexes.Length; i++, trailingPartialCount >>= 1) { - Debug.Assert(GetSpan(i).Length >= curLength); + Debug.Assert(GetSpan(i).Length >= curLength - (curTrailingZeroCount >> 5)); if ((trailingPartialCount & 1) != 0) { - ReadOnlySpan power = GetSpan(i); - Span src = curBits.Slice(0, curLength); - Span dst = otherBits.Slice(0, curLength += power.Length); - int powerTrailingZeroCount = MaxPartialDigits * (1 << i); int powerTrailingZeroCountUInt32 = powerTrailingZeroCount >> 5; int curTrailingZeroCountUInt32 = curTrailingZeroCount >> 5; - Debug.Assert(powerTrailingZeroCountUInt32 < power.Length - && power.Slice(0, powerTrailingZeroCountUInt32).Trim(0u).Length == 0 - && power[powerTrailingZeroCountUInt32] != 0); + ReadOnlySpan power = GetSpan(i); + Span src = curBits.Slice(0, curLength); + Span dst = otherBits.Slice(0, curLength += power.Length + powerTrailingZeroCountUInt32); + Debug.Assert(curTrailingZeroCountUInt32 < src.Length && src.Slice(0, curTrailingZeroCountUInt32).Trim(0u).Length == 0 && src[curTrailingZeroCountUInt32] != 0); - BigIntegerCalculator.Multiply(power.Slice(powerTrailingZeroCountUInt32), src.Slice(curTrailingZeroCountUInt32), dst.Slice(powerTrailingZeroCountUInt32 + curTrailingZeroCountUInt32)); + BigIntegerCalculator.Multiply(power, src.Slice(curTrailingZeroCountUInt32), dst.Slice(powerTrailingZeroCountUInt32 + curTrailingZeroCountUInt32)); curTrailingZeroCount += powerTrailingZeroCount; From 9a7f04df59fa582dcf5c48c63f0cb887e953a64a Mon Sep 17 00:00:00 2001 From: kzrnm Date: Sat, 17 Feb 2024 02:00:18 +0900 Subject: [PATCH 08/19] OmittedLength --- .../src/System/Number.BigInteger.cs | 189 ++++++++---------- 1 file changed, 81 insertions(+), 108 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 974128ce6efb9..e3aa9058d1b81 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -569,6 +569,7 @@ internal const int BigIntegerParseNaiveThreshold = 1233, BigIntegerParseNaiveThresholdInRecursive = 9 * (1 << 7); + private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out BigInteger result) { const uint TenPowMaxPartial = PowersOf1e9.TenPowMaxPartial; @@ -647,7 +648,6 @@ static bool DivideAndConquer(ref NumberBuffer number, scoped Span bits) PowersOf1e9 powersOf1e9 = new PowersOf1e9(powersOf1e9Buffer); - const double digitRatio = 0.103810253; // log_{2^32}(10) if (trailingZeroCount > 0) { @@ -662,29 +662,10 @@ static bool DivideAndConquer(ref NumberBuffer number, scoped Span bits) Recursive(powersOf1e9, intDigits, leading); leading = leading.Slice(0, BigIntegerCalculator.ActualLength(leading)); - int trailingZeroBufferLength = checked((int)(digitRatio * trailingZeroCount) + 2); - uint[]? trailingZeroBufferFromPool = null; - Span trailingZeroBuffer = ( - trailingZeroBufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : trailingZeroBufferFromPool = ArrayPool.Shared.Rent(trailingZeroBufferLength)).Slice(0, trailingZeroBufferLength); - trailingZeroBuffer.Clear(); - - powersOf1e9.CalculatePowerOfTen(trailingZeroCount, trailingZeroBuffer); - trailingZeroBuffer = trailingZeroBuffer.Slice(0, BigIntegerCalculator.ActualLength(trailingZeroBuffer)); - - // Merge leading and trailing - Span bitsResult = bits.Slice(0, trailingZeroBuffer.Length + leading.Length); - - if (trailingZeroBuffer.Length < leading.Length) - BigIntegerCalculator.Multiply(leading, trailingZeroBuffer, bitsResult); - else - BigIntegerCalculator.Multiply(trailingZeroBuffer, leading, bitsResult); + powersOf1e9.MultiplyPowerOfTen(leading, trailingZeroCount, bits); if (leadingFromPool != null) ArrayPool.Shared.Return(leadingFromPool); - if (trailingZeroBufferFromPool != null) - ArrayPool.Shared.Return(trailingZeroBufferFromPool); } else { @@ -730,7 +711,7 @@ static void Recursive(in PowersOf1e9 powersOf1e9, ReadOnlySpan digits, Spa Recursive(powersOf1e9, digitsUpper, upperBuffer); upperBuffer = upperBuffer.Slice(0, BigIntegerCalculator.ActualLength(upperBuffer)); ReadOnlySpan multiplier = powersOf1e9.GetSpan(log2); - int multiplierTrailingZeroCountUInt32 = (MaxPartialDigits * (1 << log2)) >> 5; + int multiplierTrailingZeroCountUInt32 = PowersOf1e9.OmittedLength(log2); Span bitsUpper = bits.Slice(0, upperBuffer.Length + multiplier.Length + multiplierTrailingZeroCountUInt32); bitsUpper = bitsUpper.Slice(multiplierTrailingZeroCountUInt32); @@ -968,132 +949,124 @@ public PowersOf1e9(Span pow1E9) } #endif } + + public static int GetBufferSize(int digits) + { + int scale1E9 = digits / MaxPartialDigits; + int log2 = BitOperations.Log2((uint)scale1E9) + 1; + return (uint)log2 < (uint)Indexes.Length ? Indexes[log2] + 1 : Indexes[^1]; + } + public ReadOnlySpan GetSpan(int index) { - // Returns 1E9^(1<> (32*(9*(1<> (32*(9*(1< bits) + public static int OmittedLength(int index) { - Debug.Assert(bits.Length > unchecked((int)(0.934292276687070661 / 9 * trailingZeroCount)) + 1); - Debug.Assert(trailingZeroCount >= 0); - - int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); + // Returns 9*(1<> 5; + } - if (trailingPartialCount == 0) + public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, Span bits) + { + Debug.Assert(trailingZeroCount >= 0); + if (trailingZeroCount <= UInt32PowersOfTen.Length) { - bits[0] = UInt32PowersOfTen[remainingTrailingZeroCount]; + BigIntegerCalculator.Multiply(left, UInt32PowersOfTen[trailingZeroCount], bits.Slice(0, left.Length + 1)); return; } - int popCount = BitOperations.PopCount((uint)trailingPartialCount); + uint[]? powersOfTenFromPool = null; - int bits2Length = (bits.Length + 1) >> 1; - uint[]? bits2FromPool = null; - scoped Span bits2; - if (popCount > 1) - { - bits2 = ( - bits2Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bits2FromPool = ArrayPool.Shared.Rent(bits2Length)).Slice(0, bits2Length); - bits2.Clear(); - } - else - bits2 = default; - - int curLength, curTrailingZeroCount; - scoped Span curBits, otherBits; + Span powersOfTen = ( + bits.Length <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : powersOfTenFromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + scoped Span powersOfTen2 = bits; - if ((popCount & 1) != 0) - { - curBits = bits; - otherBits = bits2; - } - else - { - curBits = bits2; - otherBits = bits; - } + int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); - // Copy first int fi = BitOperations.TrailingZeroCount(trailingPartialCount); - { - curTrailingZeroCount = MaxPartialDigits * (1 << fi); - trailingPartialCount >>= fi; - trailingPartialCount >>= 1; + int omittedLength = OmittedLength(fi); - ReadOnlySpan first = GetSpan(fi); - first.CopyTo(curBits.Slice(curTrailingZeroCount >> 5)); + // Copy first + ReadOnlySpan first = GetSpan(fi); + int curLength = first.Length; + trailingPartialCount >>= fi; + trailingPartialCount >>= 1; - curLength = first.Length; + if ((BitOperations.PopCount((uint)trailingPartialCount) & 1) != 0) + { + powersOfTen2 = powersOfTen; + powersOfTen = bits; + powersOfTen2.Clear(); } + first.CopyTo(powersOfTen); - for (int i = fi + 1; trailingPartialCount != 0 && i + 1 < Indexes.Length; i++, trailingPartialCount >>= 1) + for (++fi; trailingPartialCount != 0; ++fi, trailingPartialCount >>= 1) { - Debug.Assert(GetSpan(i).Length >= curLength - (curTrailingZeroCount >> 5)); + Debug.Assert(fi + 1 < Indexes.Length); if ((trailingPartialCount & 1) != 0) { - int powerTrailingZeroCount = MaxPartialDigits * (1 << i); - int powerTrailingZeroCountUInt32 = powerTrailingZeroCount >> 5; - int curTrailingZeroCountUInt32 = curTrailingZeroCount >> 5; - - ReadOnlySpan power = GetSpan(i); - Span src = curBits.Slice(0, curLength); - Span dst = otherBits.Slice(0, curLength += power.Length + powerTrailingZeroCountUInt32); - - Debug.Assert(curTrailingZeroCountUInt32 < src.Length - && src.Slice(0, curTrailingZeroCountUInt32).Trim(0u).Length == 0 - && src[curTrailingZeroCountUInt32] != 0); + omittedLength += OmittedLength(fi); - BigIntegerCalculator.Multiply(power, src.Slice(curTrailingZeroCountUInt32), dst.Slice(powerTrailingZeroCountUInt32 + curTrailingZeroCountUInt32)); + ReadOnlySpan power = GetSpan(fi); + Span src = powersOfTen.Slice(0, curLength); + Span dst = powersOfTen2.Slice(0, curLength += power.Length); - curTrailingZeroCount += powerTrailingZeroCount; + if (power.Length < src.Length) + BigIntegerCalculator.Multiply(src, power, dst); + else + BigIntegerCalculator.Multiply(power, src, dst); - Span tmp = curBits; - curBits = otherBits; - otherBits = tmp; - otherBits.Clear(); + Span tmp = powersOfTen; + powersOfTen = powersOfTen2; + powersOfTen2 = tmp; + powersOfTen2.Clear(); // Trim - while (--curLength >= 0 && curBits[curLength] == 0) ; + while (--curLength >= 0 && powersOfTen[curLength] == 0) ; ++curLength; } } - Debug.Assert(Unsafe.AreSame(ref bits[0], ref curBits[0])); + Debug.Assert(Unsafe.AreSame(ref bits[0], ref powersOfTen2[0])); - curBits = curBits.Slice(0, curLength); - uint multiplier = UInt32PowersOfTen[remainingTrailingZeroCount]; - uint carry = 0; - for (int i = (curTrailingZeroCount >> 5); i < curBits.Length; i++) - { - ulong p = (ulong)multiplier * curBits[i] + carry; - curBits[i] = (uint)p; - carry = (uint)(p >> 32); - } + 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 (carry != 0) - { - bits[curLength] = carry; - } + if (powersOfTenFromPool != null) + ArrayPool.Shared.Return(powersOfTenFromPool); - if (bits2FromPool != null) - ArrayPool.Shared.Return(bits2FromPool); - } + 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); + } - public static int GetBufferSize(int digits) - { - int scale1E9 = digits / MaxPartialDigits; - int log2 = BitOperations.Log2((uint)scale1E9) + 1; - return (uint)log2 < (uint)Indexes.Length ? Indexes[log2] + 1 : Indexes[^1]; + if (carry != 0) + { + bits[curLength] = carry; + } + } } } + internal static char ParseFormatSpecifier(ReadOnlySpan format, out int digits) { digits = -1; From 64d30c19322aa0ed75890287981d4d39486650fd Mon Sep 17 00:00:00 2001 From: kzrnm Date: Fri, 16 Feb 2024 21:28:44 +0900 Subject: [PATCH 09/19] pre-calculate 1000000000^(1<<5) --- .../src/System/Number.BigInteger.cs | 93 +++++++++++++++---- 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index e3aa9058d1b81..b5ec08921929b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -862,6 +862,7 @@ static BigInteger NumberBufferToBigInteger(ReadOnlySpan result, bool isNeg } internal readonly ref struct PowersOf1e9 { + // Holds 1000000000^(1<< pow1E9; public const uint TenPowMaxPartial = 1000000000; public const int MaxPartialDigits = 9; @@ -879,7 +880,8 @@ internal readonly ref struct PowersOf1e9 // length -= (9*(1<> 5; // indexes[i+1] = indexes[i] + length; // } - private static ReadOnlySpan Indexes => new int[] { + private static ReadOnlySpan Indexes => new int[] + { 0, 1, 3, @@ -914,40 +916,91 @@ internal readonly ref struct PowersOf1e9 1939268536, }; + // The PowersOf1e9 structure holds 1000000000^(1<< FivePowers1E9 => new uint[] + { + // 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] == FivePowers1E9.Length); + if (pow1E9.Length < Indexes[7]) + { + this.pow1E9 = FivePowers1E9; + return; + } + FivePowers1E9.CopyTo(pow1E9.Slice(0, FivePowers1E9.Length)); this.pow1E9 = pow1E9; - Debug.Assert(pow1E9.Length >= 1); - pow1E9[0] = TenPowMaxPartial; - ReadOnlySpan src = pow1E9.Slice(0, 1); - int toExclusive = 1; - for (int i = 1; i + 1 < Indexes.Length; i++) + 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); - if (dst[0] == 0) - { - dst.Slice(1).CopyTo(dst); - dst[^1] = 0; - } int from = toExclusive; toExclusive = Indexes[i + 1]; src = pow1E9.Slice(from, toExclusive - from); Debug.Assert(toExclusive == pow1E9.Length || pow1E9[toExclusive] == 0); } -#if DEBUG - for (int i = 0; i + 1 < Indexes.Length; i++) - { - int startIndex = Indexes[i]; - int endIndex = Indexes[i + 1]; - if (endIndex >= pow1E9.Length) break; - Debug.Assert(pow1E9[startIndex] != 0); - } -#endif } public static int GetBufferSize(int digits) From 6ce0f7edaf971a37b1b8a4b6fd194e3e6495fc9d Mon Sep 17 00:00:00 2001 From: kzrnm Date: Sun, 18 Feb 2024 08:59:42 +0900 Subject: [PATCH 10/19] Move PowersOf1e9 --- .../src/System/Number.BigInteger.cs | 518 +++++++++--------- 1 file changed, 259 insertions(+), 259 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index b5ec08921929b..54b8e9f530c7a 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -860,265 +860,6 @@ static BigInteger NumberBufferToBigInteger(ReadOnlySpan result, bool isNeg } } } - 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 => new int[] - { - 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<< FivePowers1E9 => new uint[] - { - // 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] == FivePowers1E9.Length); - if (pow1E9.Length < Indexes[7]) - { - this.pow1E9 = FivePowers1E9; - return; - } - FivePowers1E9.CopyTo(pow1E9.Slice(0, FivePowers1E9.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) - { - int scale1E9 = digits / MaxPartialDigits; - int log2 = BitOperations.Log2((uint)scale1E9) + 1; - return (uint)log2 < (uint)Indexes.Length ? Indexes[log2] + 1 : Indexes[^1]; - } - - 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[curLength] = carry; - } - } - } - } - internal static char ParseFormatSpecifier(ReadOnlySpan format, out int digits) { @@ -1540,6 +1281,265 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo return null; } } + + 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) + { + int scale1E9 = digits / MaxPartialDigits; + int log2 = BitOperations.Log2((uint)scale1E9) + 1; + return (uint)log2 < (uint)Indexes.Length ? Indexes[log2] + 1 : Indexes[^1]; + } + + 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[curLength] = carry; + } + } + } + } } internal interface IBigIntegerHexOrBinaryParser From 9435597d3a40806616f4ee6eef1d5e42bf8f69cb Mon Sep 17 00:00:00 2001 From: kzrnm Date: Sun, 18 Feb 2024 10:07:10 +0900 Subject: [PATCH 11/19] fix --- .../System.Runtime.Numerics/src/System/Number.BigInteger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 54b8e9f530c7a..adc7629bf8782 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -1449,7 +1449,7 @@ public static int OmittedLength(int index) public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, Span bits) { Debug.Assert(trailingZeroCount >= 0); - if (trailingZeroCount <= UInt32PowersOfTen.Length) + if (trailingZeroCount < UInt32PowersOfTen.Length) { BigIntegerCalculator.Multiply(left, UInt32PowersOfTen[trailingZeroCount], bits.Slice(0, left.Length + 1)); return; From 742951bc46b39cece9b643e340164b79921ab96e Mon Sep 17 00:00:00 2001 From: kzrnm Date: Sun, 18 Feb 2024 12:50:40 +0900 Subject: [PATCH 12/19] Fix large case --- .../src/System/Number.BigInteger.cs | 2 +- .../System.Runtime.Numerics/tests/BigInteger/parse.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index adc7629bf8782..68a438f48a397 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -1535,7 +1535,7 @@ public void MultiplyPowerOfTen(ReadOnlySpan left, int trailingZeroCount, S if (carry != 0) { - bits[curLength] = carry; + bits[omittedLength + curLength] = carry; } } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index c9fd0d610ba40..6cfd223326d13 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -243,11 +243,17 @@ private static void VerifyDefaultParse(Random random) VerifyParseToString(GetDigitSequence(100, 1000, random)); } - // Trailing Zero + // Trailing Zero - Small VerifyParseToString("99000000000"); for (int i = 0; i < s_samples; i++) { - VerifyParseToString(GetDigitSequence(1, 10, random) + "1000000000"); + 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 From 8ddb8e0ba4b7b5e89cd95bc6a6d2987fb1f031ea Mon Sep 17 00:00:00 2001 From: kzrnm Date: Mon, 6 May 2024 03:21:20 +0900 Subject: [PATCH 13/19] public --- .../System.Runtime.Numerics/src/System/Number.BigInteger.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 65ba2d58631c6..e2dbfefbf0fa3 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -295,9 +295,9 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle Date: Mon, 6 May 2024 09:23:37 +0900 Subject: [PATCH 14/19] Use PowersOf1e9.MaxPartialDigits, PowersOf1e9.TenPowMaxPartial --- .../src/System/Number.BigInteger.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index e2dbfefbf0fa3..5d3885df22b62 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -787,9 +787,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, @@ -797,6 +794,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') @@ -833,8 +833,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 ? @@ -848,17 +848,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; } @@ -866,7 +866,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; @@ -974,8 +974,8 @@ 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); From 7568c5b7f18b6bf86e8446706449031230e18eac Mon Sep 17 00:00:00 2001 From: kzrnm Date: Mon, 13 May 2024 02:15:09 +0900 Subject: [PATCH 15/19] Remove NumberBufferToBigInteger --- .../src/System/Number.BigInteger.cs | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 5d3885df22b62..c622dc860fb9e 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -337,7 +337,14 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big return ParsingStatus.Failed; } - result = NumberBufferToBigInteger(resultBuffer.Slice(0, BigIntegerCalculator.ActualLength(resultBuffer)), number.IsNegative); + resultBuffer = resultBuffer.Slice(0, BigIntegerCalculator.ActualLength(resultBuffer)); + Debug.Assert(resultBuffer.Length == 0 || resultBuffer[^1] != 0); + + result = resultBuffer.Length == 0 + ? BigInteger.Zero + : resultBuffer is [uint leading] && (leading <= int.MaxValue || number.IsNegative && leading == unchecked((uint)(int.MaxValue + 1))) + ? new BigInteger((int)(number.IsNegative ? -leading : leading)) + : new BigInteger(number.IsNegative ? -1 : 1, resultBuffer.ToArray()); if (resultBufferFromPool != null) ArrayPool.Shared.Return(resultBufferFromPool); @@ -574,24 +581,6 @@ static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) } return carry; } - - static BigInteger NumberBufferToBigInteger(ReadOnlySpan result, bool isNegative) - { - Debug.Assert(result.Length == 0 || result[^1] != 0); - - if (result.Length == 0) - { - return BigInteger.Zero; - } - else if (result is [uint leading] && (leading <= int.MaxValue || isNegative && leading == unchecked((uint)(int.MaxValue + 1)))) - { - return new BigInteger((int)(isNegative ? -leading : leading)); - } - else - { - return new BigInteger(isNegative ? -1 : 1, result.ToArray()); - } - } } private static string? FormatBigIntegerToHex(bool targetSpan, BigInteger value, char format, int digits, NumberFormatInfo info, Span destination, out int charsWritten, out bool spanSuccess) From 456bf8e42f56040aa71dfbd89d1b4cdce36f0904 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Mon, 13 May 2024 02:34:34 +0900 Subject: [PATCH 16/19] intDigits --- .../src/System/Number.BigInteger.cs | 89 +++++++------------ 1 file changed, 32 insertions(+), 57 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index c622dc860fb9e..760098cd88ff4 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -320,6 +320,29 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big return ParsingStatus.Failed; } + 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); + 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; + } + } + } + else + intDigits = intDigits.Slice(0, intDigitsEnd); + const double digitRatio = 0.10381025297; // log_{2^32}(10) int resultLength = checked((int)(number.Scale * digitRatio) + 1 + 2); uint[]? resultBufferFromPool = null; @@ -329,12 +352,13 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big : resultBufferFromPool = ArrayPool.Shared.Rent(resultLength)).Slice(0, resultLength); resultBuffer.Clear(); - if (number.Scale <= BigIntegerParseNaiveThreshold - ? !Naive(ref number, resultBuffer) - : !DivideAndConquer(ref number, resultBuffer)) + if (number.Scale <= BigIntegerParseNaiveThreshold) { - result = default; - return ParsingStatus.Failed; + Naive(ref number, intDigits, resultBuffer); + } + else + { + DivideAndConquer(ref number, intDigits, resultBuffer); } resultBuffer = resultBuffer.Slice(0, BigIntegerCalculator.ActualLength(resultBuffer)); @@ -351,30 +375,8 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big return ParsingStatus.OK; - static bool DivideAndConquer(ref NumberBuffer number, scoped Span bits) + static void DivideAndConquer(ref NumberBuffer number, ReadOnlySpan intDigits, scoped Span bits) { - 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); - for (int i = 0; i < fracDigitsSpan.Length; i++) - { - char digitChar = (char)fracDigitsSpan[i]; - if (digitChar == '\0') - { - break; - } - if (digitChar != '0') - { - return false; - } - } - } - else - intDigits = intDigits.Slice(0, intDigitsEnd); - int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); int trailingZeroCount = number.Scale - totalDigitCount; @@ -415,8 +417,6 @@ static bool DivideAndConquer(ref NumberBuffer number, scoped Span bits) if (powersOf1e9BufferFromPool != null) ArrayPool.Shared.Return(powersOf1e9BufferFromPool); - - return true; } static void Recursive(in PowersOf1e9 powersOf1e9, ReadOnlySpan digits, Span bits) @@ -514,36 +514,13 @@ static int NaiveDigits(ReadOnlySpan intDigits, Span bits) return resultLength; } - static bool Naive(ref NumberBuffer number, scoped Span bits) + static void Naive(ref NumberBuffer number, ReadOnlySpan intDigits, scoped Span bits) { - 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); - for (int i = 0; i < fracDigitsSpan.Length; i++) - { - char digitChar = (char)fracDigitsSpan[i]; - if (digitChar == '\0') - { - break; - } - if (digitChar != '0') - { - return false; - } - } - } - else - intDigits = intDigits.Slice(0, intDigitsEnd); - - int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); if (totalDigitCount == 0) { // number is 0. - return true; + return; } int resultLength = NaiveDigits(intDigits, bits); @@ -565,8 +542,6 @@ static bool Naive(ref NumberBuffer number, scoped Span bits) if (carry != 0) bits[resultLength++] = carry; } - - return true; } static uint MultiplyAdd(Span bits, uint multiplier, uint addValue) From 535b59abc76bb7e4966ef7117246c97f71371fdc Mon Sep 17 00:00:00 2001 From: kzrnm Date: Mon, 13 May 2024 03:24:42 +0900 Subject: [PATCH 17/19] Use BigInteger(ReadOnlySpan value, bool negative) --- .../src/System/Number.BigInteger.cs | 12 ++---------- .../src/System/Numerics/BigInteger.cs | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 760098cd88ff4..a5459957d8074 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -326,9 +326,8 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big { // Check for nonzero digits after the decimal point. ReadOnlySpan fracDigitsSpan = number.Digits.Slice(intDigits.Length); - for (int i = 0; i < fracDigitsSpan.Length; i++) + foreach (byte digitChar in fracDigitsSpan) { - char digitChar = (char)fracDigitsSpan[i]; if (digitChar == '\0') { break; @@ -361,14 +360,7 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big DivideAndConquer(ref number, intDigits, resultBuffer); } - resultBuffer = resultBuffer.Slice(0, BigIntegerCalculator.ActualLength(resultBuffer)); - Debug.Assert(resultBuffer.Length == 0 || resultBuffer[^1] != 0); - - result = resultBuffer.Length == 0 - ? BigInteger.Zero - : resultBuffer is [uint leading] && (leading <= int.MaxValue || number.IsNegative && leading == unchecked((uint)(int.MaxValue + 1))) - ? new BigInteger((int)(number.IsNegative ? -leading : leading)) - : new BigInteger(number.IsNegative ? -1 : 1, resultBuffer.ToArray()); + result = new BigInteger(resultBuffer, number.IsNegative); if (resultBufferFromPool != null) ArrayPool.Shared.Return(resultBufferFromPool); 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 62fc443b8d4bc..b5d1df2638722 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -485,7 +485,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) { if (value.Length > MaxLength) { From 29747f2f6d5f97e5e196ff0407cfe75b0b3db2f2 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Wed, 15 May 2024 01:31:30 +0900 Subject: [PATCH 18/19] base1E9 --- .../src/System/Number.BigInteger.cs | 290 +++++++++++------- .../tests/BigInteger/parse.cs | 27 +- 2 files changed, 209 insertions(+), 108 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index a5459957d8074..1e75a83cf1838 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 { @@ -301,13 +303,10 @@ public const #endif int BigIntegerParseNaiveThreshold = 1233, - BigIntegerParseNaiveThresholdInRecursive = 9 * (1 << 7); + BigIntegerParseNaiveThresholdInRecursive = 1 << 7; private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out BigInteger result) { - const uint TenPowMaxPartial = PowersOf1e9.TenPowMaxPartial; - const int MaxPartialDigits = PowersOf1e9.MaxPartialDigits; - if (number.Scale == int.MaxValue) { result = default; @@ -320,27 +319,55 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big return ParsingStatus.Failed; } - ReadOnlySpan intDigits = number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); - int intDigitsEnd = intDigits.IndexOf(0); - if (intDigitsEnd < 0) + uint[]? base1E9FromPool = null; + scoped Span base1E9; + { - // Check for nonzero digits after the decimal point. - ReadOnlySpan fracDigitsSpan = number.Digits.Slice(intDigits.Length); - foreach (byte digitChar in fracDigitsSpan) + ReadOnlySpan intDigits = number.Digits.Slice(0, Math.Min(number.Scale, number.DigitsCount)); + int intDigitsEnd = intDigits.IndexOf(0); + if (intDigitsEnd < 0) { - if (digitChar == '\0') - { - break; - } - if (digitChar != '0') + // Check for nonzero digits after the decimal point. + ReadOnlySpan fracDigitsSpan = number.Digits.Slice(intDigits.Length); + foreach (byte digitChar in fracDigitsSpan) { - result = default; - return ParsingStatus.Failed; + if (digitChar == '\0') + { + break; + } + if (digitChar != '0') + { + result = default; + return ParsingStatus.Failed; + } } } + 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 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); + + 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); } - else - intDigits = intDigits.Slice(0, intDigitsEnd); const double digitRatio = 0.10381025297; // log_{2^32}(10) int resultLength = checked((int)(number.Scale * digitRatio) + 1 + 2); @@ -351,28 +378,33 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big : 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) { - Naive(ref number, intDigits, resultBuffer); + Naive(base1E9, trailingZeroCount, resultBuffer); } else { - DivideAndConquer(ref number, intDigits, resultBuffer); + DivideAndConquer(base1E9, totalDigitCount, trailingZeroCount, resultBuffer); } result = new BigInteger(resultBuffer, number.IsNegative); + if (base1E9FromPool != null) + ArrayPool.Shared.Return(base1E9FromPool); if (resultBufferFromPool != null) ArrayPool.Shared.Return(resultBufferFromPool); return ParsingStatus.OK; - static void DivideAndConquer(ref NumberBuffer number, ReadOnlySpan intDigits, scoped Span bits) + static void DivideAndConquer(ReadOnlySpan base1E9, int totalDigitCount, int trailingZeroCount, scoped Span bits) { - int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); - int trailingZeroCount = number.Scale - totalDigitCount; + int valueDigits = (base1E9.Length - 1) * PowersOf1e9.MaxPartialDigits + FormattingHelpers.CountDigits(base1E9[^1]); - int powersOf1e9BufferLength = PowersOf1e9.GetBufferSize(Math.Max(totalDigitCount, trailingZeroCount)); + int log2 = PowersOf1e9.GetMaxIndex(Math.Max(valueDigits, trailingZeroCount + 1)); + int powersOf1e9BufferLength = PowersOf1e9.GetBufferSize(log2); uint[]? powersOf1e9BufferFromPool = null; Span powersOf1e9Buffer = ( powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold @@ -382,7 +414,6 @@ static void DivideAndConquer(ref NumberBuffer number, ReadOnlySpan intDigi PowersOf1e9 powersOf1e9 = new PowersOf1e9(powersOf1e9Buffer); - if (trailingZeroCount > 0) { int leadingLength = checked((int)(digitRatio * totalDigitCount) + 2); @@ -393,7 +424,7 @@ static void DivideAndConquer(ref NumberBuffer number, ReadOnlySpan intDigi : leadingFromPool = ArrayPool.Shared.Rent(leadingLength)).Slice(0, leadingLength); leading.Clear(); - Recursive(powersOf1e9, intDigits, leading); + Recursive(powersOf1e9, log2, base1E9, leading); leading = leading.Slice(0, BigIntegerCalculator.ActualLength(leading)); powersOf1e9.MultiplyPowerOfTen(leading, trailingZeroCount, bits); @@ -403,124 +434,142 @@ static void DivideAndConquer(ref NumberBuffer number, ReadOnlySpan intDigi } else { - Recursive(powersOf1e9, intDigits, bits); + Recursive(powersOf1e9, log2, base1E9, bits); } - if (powersOf1e9BufferFromPool != null) ArrayPool.Shared.Return(powersOf1e9BufferFromPool); } - static void Recursive(in PowersOf1e9 powersOf1e9, ReadOnlySpan digits, Span bits) + static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnlySpan base1E9, Span bits) { - Debug.Assert(BigIntegerParseNaiveThresholdInRecursive >= MaxPartialDigits); - if (digits.Length < BigIntegerParseNaiveThresholdInRecursive) + Debug.Assert(bits.Trim(0u).Length == 0); + Debug.Assert(BigIntegerParseNaiveThresholdInRecursive > 1); + + base1E9 = base1E9.Slice(0, BigIntegerCalculator.ActualLength(base1E9)); + if (base1E9.Length < BigIntegerParseNaiveThresholdInRecursive) { - NaiveDigits(digits, bits); + NaiveBase1E9ToBits(base1E9, bits); return; } - int lengthe9 = (digits.Length + MaxPartialDigits - 1) / MaxPartialDigits; - int log2 = BitOperations.Log2((uint)(lengthe9 - 1)); - int powOfTenSize = 1 << log2; - - ReadOnlySpan digitsUpper = digits.Slice(0, digits.Length - MaxPartialDigits * powOfTenSize); - ReadOnlySpan digitsLower = digits.Slice(digitsUpper.Length); + int multiplier1E9Length = 1 << powersOf1e9Index; + while (base1E9.Length <= multiplier1E9Length) { - int iv = 0; - while ((uint)iv < (uint)digitsLower.Length && digitsLower[iv] == 0) ++iv; - digitsLower = digitsLower.Slice(iv); + multiplier1E9Length = 1 << (--powersOf1e9Index); } + ReadOnlySpan multiplier = powersOf1e9.GetSpan(powersOf1e9Index); + int multiplierTrailingZeroCount = PowersOf1e9.OmittedLength(powersOf1e9Index); - int upperBufferLength = checked((int)(digitsUpper.Length * digitRatio) + 1 + 2); - uint[]? upperBufferFromPool = null; - Span upperBuffer = ( - upperBufferLength <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : upperBufferFromPool = ArrayPool.Shared.Rent(upperBufferLength)).Slice(0, upperBufferLength); - upperBuffer.Clear(); + if (multiplier1E9Length + multiplier1E9Length < base1E9.Length) { - Recursive(powersOf1e9, digitsUpper, upperBuffer); - upperBuffer = upperBuffer.Slice(0, BigIntegerCalculator.ActualLength(upperBuffer)); - ReadOnlySpan multiplier = powersOf1e9.GetSpan(log2); - int multiplierTrailingZeroCountUInt32 = PowersOf1e9.OmittedLength(log2); - - Span bitsUpper = bits.Slice(0, upperBuffer.Length + multiplier.Length + multiplierTrailingZeroCountUInt32); - bitsUpper = bitsUpper.Slice(multiplierTrailingZeroCountUInt32); - - if (multiplier.Length < upperBuffer.Length) - BigIntegerCalculator.Multiply(upperBuffer, multiplier, bitsUpper); - else - BigIntegerCalculator.Multiply(multiplier, upperBuffer, bitsUpper); + RecursiveLarge(powersOf1e9, powersOf1e9Index, base1E9, bits); + return; } - if (upperBufferFromPool != null) - ArrayPool.Shared.Return(upperBufferFromPool); + Debug.Assert(multiplier1E9Length < base1E9.Length && base1E9.Length <= multiplier1E9Length * 2); - int lowerBufferLength = checked((int)(digitsLower.Length * digitRatio) + 1 + 2); - uint[]? lowerBufferFromPool = null; - Span lowerBuffer = ( - lowerBufferLength <= BigIntegerCalculator.StackAllocThreshold + int bufferLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * multiplier1E9Length) + 1 + 2); + uint[]? bufferFromPool = null; + scoped Span buffer = ( + bufferLength <= BigIntegerCalculator.StackAllocThreshold ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : lowerBufferFromPool = ArrayPool.Shared.Rent(lowerBufferLength)).Slice(0, lowerBufferLength); - lowerBuffer.Clear(); + : bufferFromPool = ArrayPool.Shared.Rent(bufferLength)).Slice(0, bufferLength); + buffer.Clear(); - Recursive(powersOf1e9, digitsLower, lowerBuffer); - lowerBuffer = lowerBuffer.Slice(0, BigIntegerCalculator.ActualLength(lowerBuffer)); + Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[multiplier1E9Length..], buffer); - BigIntegerCalculator.AddSelf(bits, lowerBuffer); + 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 + BigIntegerCalculator.Multiply(multiplier, buffer2, bitsUpper); - if (lowerBufferFromPool != null) - ArrayPool.Shared.Return(lowerBufferFromPool); + 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); } - static int NaiveDigits(ReadOnlySpan intDigits, Span bits) + [MethodImpl(MethodImplOptions.NoInlining)] + static void RecursiveLarge( + in PowersOf1e9 powersOf1e9, int powersOf1e9Index, + ReadOnlySpan base1E9, scoped Span bits) { - int leadingLength = intDigits.Length % 9; - uint partialValue = 0; - foreach (uint dig in intDigits.Slice(0, leadingLength)) + int multiplier1E9Length = 1 << powersOf1e9Index; + ReadOnlySpan multiplier = powersOf1e9.GetSpan(powersOf1e9Index); + int multiplierTrailingZeroCount = PowersOf1e9.OmittedLength(powersOf1e9Index); + + Span bitsDst = bits; + + uint[]? bits2FromPool = null; + scoped Span bits2 = ( + bits.Length <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : bits2FromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); + bits2.Clear(); + { - partialValue = partialValue * 10 + (dig - '0'); + int upperLength = base1E9.Length & (multiplier1E9Length - 1); + if (upperLength == 0) + upperLength = multiplier1E9Length; + + Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[^upperLength..], bits2); + + base1E9 = base1E9[..^upperLength]; } - bits[0] = partialValue; - int partialDigitCount = 0; - int resultLength = 1; - partialValue = 0; - for (int i = leadingLength; i < intDigits.Length; i++) + while (base1E9.Length > 0) { - partialValue = partialValue * 10 + (uint)(intDigits[i] - '0'); + ReadOnlySpan bitsPrev = bits2.Slice(0, BigIntegerCalculator.ActualLength(bits2)); - // Update the buffer when enough partial digits have been accumulated. - if (++partialDigitCount == MaxPartialDigits) - { - uint carry = MultiplyAdd(bits.Slice(0, resultLength), TenPowMaxPartial, partialValue); - partialValue = 0; - partialDigitCount = 0; - Debug.Assert(bits[resultLength] == 0); - if (carry != 0) - bits[resultLength++] = carry; - } + Debug.Assert(base1E9.Length % multiplier1E9Length == 0); + Span bitsUpper = bits.Slice(multiplierTrailingZeroCount, bitsPrev.Length + multiplier.Length); + if (multiplier.Length < bitsPrev.Length) + BigIntegerCalculator.Multiply(bitsPrev, multiplier, bitsUpper); + else + BigIntegerCalculator.Multiply(multiplier, bitsPrev, bitsUpper); + + bits2.Clear(); + Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[^multiplier1E9Length..], bits2); + + BigIntegerCalculator.AddSelf(bits, bits2); + + base1E9 = base1E9[..^multiplier1E9Length]; + + Span bitsTmp = bits; + bits = bits2; + bits2 = bitsTmp; + bits.Clear(); } - return resultLength; + if (!Unsafe.AreSame(ref MemoryMarshal.GetReference(bits2), ref MemoryMarshal.GetReference(bitsDst))) + { + bits2.CopyTo(bitsDst); + } + + if (bits2FromPool != null) + ArrayPool.Shared.Return(bits2FromPool); } - static void Naive(ref NumberBuffer number, ReadOnlySpan intDigits, scoped Span bits) + static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) { - int totalDigitCount = Math.Min(number.DigitsCount, number.Scale); - if (totalDigitCount == 0) + if (base1E9.Length == 0) { // number is 0. return; } - int resultLength = NaiveDigits(intDigits, bits); + int resultLength = NaiveBase1E9ToBits(base1E9, bits); - int trailingZeroCount = number.Scale - totalDigitCount; - int trailingPartialCount = Math.DivRem(trailingZeroCount, MaxPartialDigits, out int remainingTrailingZeroCount); + int trailingPartialCount = Math.DivRem(trailingZeroCount, PowersOf1e9.MaxPartialDigits, out int remainingTrailingZeroCount); for (int i = 0; i < trailingPartialCount; i++) { - uint carry = MultiplyAdd(bits.Slice(0, resultLength), TenPowMaxPartial, 0); + uint carry = MultiplyAdd(bits.Slice(0, resultLength), PowersOf1e9.TenPowMaxPartial, 0); Debug.Assert(bits[resultLength] == 0); if (carry != 0) bits[resultLength++] = carry; @@ -536,6 +585,23 @@ static void Naive(ref NumberBuffer number, ReadOnlySpan intDigits, scoped } } + 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]); + 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; @@ -958,6 +1024,11 @@ internal readonly ref struct PowersOf1e9 // indexes[i+1] = indexes[i] + length; // } private static ReadOnlySpan Indexes => +#if DEBUG + IndexesArray; + + public static int[] IndexesArray = +#endif [ 0, 1, @@ -1080,11 +1151,16 @@ public PowersOf1e9(Span pow1E9) } } - public static int GetBufferSize(int digits) + public static int GetMaxIndex(int digits) + { + uint scale1E9 = (uint)(digits - 1) / MaxPartialDigits; + return BitOperations.Log2(scale1E9); + } + + public static int GetBufferSize(int index) { - int scale1E9 = digits / MaxPartialDigits; - int log2 = BitOperations.Log2((uint)scale1E9) + 1; - return (uint)log2 < (uint)Indexes.Length ? Indexes[log2] + 1 : Indexes[^1]; + ++index; + return ((uint)index < (uint)Indexes.Length ? Indexes[index] : Indexes[^1]) + 1; } public ReadOnlySpan GetSpan(int index) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 6cfd223326d13..25eae190a96e6 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 { @@ -129,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); @@ -1218,5 +1220,28 @@ public static void RegressionIssueRuntime94610(string text) parseTest.RegressionIssueRuntime94610(text); })); } + + [Theory] + [MemberData(nameof(Cultures))] + [OuterLoop] + public static void Parse_RecursiveLarge(CultureInfo culture) + { +#if DEBUG + int[] defaultIndexesArray = Number.PowersOf1e9.IndexesArray; + try + { + Number.PowersOf1e9.IndexesArray = defaultIndexesArray[..4]; + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () => + BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () => + { + parseTest.RunParseToStringTests(culture); + })); + } + finally + { + Number.PowersOf1e9.IndexesArray = defaultIndexesArray; + } +#endif + } } } From f01a77929a83c1dd4fa33c03fe740cd51bc137b7 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Wed, 15 May 2024 02:11:59 +0900 Subject: [PATCH 19/19] Remove RecursiveLarge --- .../src/System/Number.BigInteger.cs | 106 ++++-------------- .../tests/BigInteger/parse.cs | 23 ---- 2 files changed, 19 insertions(+), 110 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index 1e75a83cf1838..48c4ae1c97d18 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -370,7 +370,7 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big } const double digitRatio = 0.10381025297; // log_{2^32}(10) - int resultLength = checked((int)(number.Scale * digitRatio) + 1 + 2); + int resultLength = checked((int)(digitRatio * number.Scale) + 1 + 2); uint[]? resultBufferFromPool = null; Span resultBuffer = ( resultLength <= BigIntegerCalculator.StackAllocThreshold @@ -387,7 +387,7 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big } else { - DivideAndConquer(base1E9, totalDigitCount, trailingZeroCount, resultBuffer); + DivideAndConquer(base1E9, trailingZeroCount, resultBuffer); } result = new BigInteger(resultBuffer, number.IsNegative); @@ -399,12 +399,11 @@ private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out Big return ParsingStatus.OK; - static void DivideAndConquer(ReadOnlySpan base1E9, int totalDigitCount, 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 log2 = PowersOf1e9.GetMaxIndex(Math.Max(valueDigits, trailingZeroCount + 1)); - int powersOf1e9BufferLength = PowersOf1e9.GetBufferSize(log2); + int powersOf1e9BufferLength = PowersOf1e9.GetBufferSize(Math.Max(valueDigits, trailingZeroCount + 1), out int maxIndex); uint[]? powersOf1e9BufferFromPool = null; Span powersOf1e9Buffer = ( powersOf1e9BufferLength <= BigIntegerCalculator.StackAllocThreshold @@ -416,7 +415,7 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int totalDigitCount, in if (trailingZeroCount > 0) { - int leadingLength = checked((int)(digitRatio * totalDigitCount) + 2); + int leadingLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * base1E9.Length) + 3); uint[]? leadingFromPool = null; Span leading = ( leadingLength <= BigIntegerCalculator.StackAllocThreshold @@ -424,7 +423,7 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int totalDigitCount, in : leadingFromPool = ArrayPool.Shared.Rent(leadingLength)).Slice(0, leadingLength); leading.Clear(); - Recursive(powersOf1e9, log2, base1E9, leading); + Recursive(powersOf1e9, maxIndex, base1E9, leading); leading = leading.Slice(0, BigIntegerCalculator.ActualLength(leading)); powersOf1e9.MultiplyPowerOfTen(leading, trailingZeroCount, bits); @@ -434,7 +433,7 @@ static void DivideAndConquer(ReadOnlySpan base1E9, int totalDigitCount, in } else { - Recursive(powersOf1e9, log2, base1E9, bits); + Recursive(powersOf1e9, maxIndex, base1E9, bits); } if (powersOf1e9BufferFromPool != null) @@ -461,12 +460,6 @@ static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnly ReadOnlySpan multiplier = powersOf1e9.GetSpan(powersOf1e9Index); int multiplierTrailingZeroCount = PowersOf1e9.OmittedLength(powersOf1e9Index); - if (multiplier1E9Length + multiplier1E9Length < base1E9.Length) - { - RecursiveLarge(powersOf1e9, powersOf1e9Index, base1E9, bits); - return; - } - Debug.Assert(multiplier1E9Length < base1E9.Length && base1E9.Length <= multiplier1E9Length * 2); int bufferLength = checked((int)(digitRatio * PowersOf1e9.MaxPartialDigits * multiplier1E9Length) + 1 + 2); @@ -496,67 +489,6 @@ static void Recursive(in PowersOf1e9 powersOf1e9, int powersOf1e9Index, ReadOnly ArrayPool.Shared.Return(bufferFromPool); } - [MethodImpl(MethodImplOptions.NoInlining)] - static void RecursiveLarge( - in PowersOf1e9 powersOf1e9, int powersOf1e9Index, - ReadOnlySpan base1E9, scoped Span bits) - { - int multiplier1E9Length = 1 << powersOf1e9Index; - ReadOnlySpan multiplier = powersOf1e9.GetSpan(powersOf1e9Index); - int multiplierTrailingZeroCount = PowersOf1e9.OmittedLength(powersOf1e9Index); - - Span bitsDst = bits; - - uint[]? bits2FromPool = null; - scoped Span bits2 = ( - bits.Length <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : bits2FromPool = ArrayPool.Shared.Rent(bits.Length)).Slice(0, bits.Length); - bits2.Clear(); - - { - int upperLength = base1E9.Length & (multiplier1E9Length - 1); - if (upperLength == 0) - upperLength = multiplier1E9Length; - - Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[^upperLength..], bits2); - - base1E9 = base1E9[..^upperLength]; - } - - while (base1E9.Length > 0) - { - ReadOnlySpan bitsPrev = bits2.Slice(0, BigIntegerCalculator.ActualLength(bits2)); - - Debug.Assert(base1E9.Length % multiplier1E9Length == 0); - Span bitsUpper = bits.Slice(multiplierTrailingZeroCount, bitsPrev.Length + multiplier.Length); - if (multiplier.Length < bitsPrev.Length) - BigIntegerCalculator.Multiply(bitsPrev, multiplier, bitsUpper); - else - BigIntegerCalculator.Multiply(multiplier, bitsPrev, bitsUpper); - - bits2.Clear(); - Recursive(powersOf1e9, powersOf1e9Index - 1, base1E9[^multiplier1E9Length..], bits2); - - BigIntegerCalculator.AddSelf(bits, bits2); - - base1E9 = base1E9[..^multiplier1E9Length]; - - Span bitsTmp = bits; - bits = bits2; - bits2 = bitsTmp; - bits.Clear(); - } - - if (!Unsafe.AreSame(ref MemoryMarshal.GetReference(bits2), ref MemoryMarshal.GetReference(bitsDst))) - { - bits2.CopyTo(bitsDst); - } - - if (bits2FromPool != null) - ArrayPool.Shared.Return(bits2FromPool); - } - static void Naive(ReadOnlySpan base1E9, int trailingZeroCount, scoped Span bits) { if (base1E9.Length == 0) @@ -1024,11 +956,6 @@ internal readonly ref struct PowersOf1e9 // indexes[i+1] = indexes[i] + length; // } private static ReadOnlySpan Indexes => -#if DEBUG - IndexesArray; - - public static int[] IndexesArray = -#endif [ 0, 1, @@ -1151,16 +1078,21 @@ public PowersOf1e9(Span pow1E9) } } - public static int GetMaxIndex(int digits) + public static int GetBufferSize(int digits, out int maxIndex) { uint scale1E9 = (uint)(digits - 1) / MaxPartialDigits; - return BitOperations.Log2(scale1E9); - } + 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]; + } - public static int GetBufferSize(int index) - { - ++index; - return ((uint)index < (uint)Indexes.Length ? Indexes[index] : Indexes[^1]) + 1; + return ++bufferSize; } public ReadOnlySpan GetSpan(int index) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 25eae190a96e6..85a2fcd182af7 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -1220,28 +1220,5 @@ public static void RegressionIssueRuntime94610(string text) parseTest.RegressionIssueRuntime94610(text); })); } - - [Theory] - [MemberData(nameof(Cultures))] - [OuterLoop] - public static void Parse_RecursiveLarge(CultureInfo culture) - { -#if DEBUG - int[] defaultIndexesArray = Number.PowersOf1e9.IndexesArray; - try - { - Number.PowersOf1e9.IndexesArray = defaultIndexesArray[..4]; - BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThreshold, 0, () => - BigIntTools.Utils.RunWithFakeThreshold(Number.BigIntegerParseNaiveThresholdInRecursive, 10, () => - { - parseTest.RunParseToStringTests(culture); - })); - } - finally - { - Number.PowersOf1e9.IndexesArray = defaultIndexesArray; - } -#endif - } } }