From e93dde530bba27636636e2c83d1bd8a56bd12ba6 Mon Sep 17 00:00:00 2001 From: keymoon Date: Tue, 27 Apr 2021 14:38:26 +0900 Subject: [PATCH 1/3] implement divide-and-conquer method for parsing digits --- .../Numerics/BigIntegerCalculator.SquMul.cs | 4 +- .../src/System/Numerics/BigNumber.cs | 284 +++++++++++++----- 2 files changed, 218 insertions(+), 70 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs index f77d02f37ccb3..b2a14cf3ff453 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 @@ -30,7 +30,7 @@ public static unsafe uint[] Square(uint[] value) private static int SquareThreshold = 32; private static int AllocationThreshold = 256; - private static unsafe void Square(uint* value, int valueLength, + internal static unsafe void Square(uint* value, int valueLength, uint* bits, int bitsLength) { Debug.Assert(valueLength >= 0); @@ -208,7 +208,7 @@ public static unsafe uint[] Multiply(uint[] left, uint[] right) // Mutable for unit testing... private static int MultiplyThreshold = 32; - private static unsafe void Multiply(uint* left, int leftLength, + internal static unsafe void Multiply(uint* left, int leftLength, uint* right, int rightLength, uint* bits, int bitsLength) { diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs index 957bd732fed57..52d586d024130 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -494,35 +494,242 @@ private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInt } } + private static int s_naiveThreshold = 20000; private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) { Span stackBuffer = stackalloc uint[BigInteger.StackallocUInt32Limit]; + Span currentBuffer = stackBuffer; int currentBufferSize = 0; + int[]? arrayFromPool = null; - uint partialValue = 0; - int partialDigitCount = 0; int totalDigitCount = 0; int numberScale = number.scale; const int MaxPartialDigits = 9; const uint TenPowMaxPartial = 1000000000; - try { - foreach (ReadOnlyMemory digitsChunk in number.digits.GetChunks()) + if (number.digits.Length <= s_naiveThreshold) { - if (!ProcessChunk(digitsChunk.Span, ref currentBuffer)) + uint partialValue = 0; + int partialDigitCount = 0; + + foreach (ReadOnlyMemory digitsChunk in number.digits.GetChunks()) + { + if (!ProcessChunk(digitsChunk.Span, ref currentBuffer)) + { + result = default; + return false; + } + } + + if (partialDigitCount > 0) + { + MultiplyAdd(ref currentBuffer, s_uint32PowersOfTen[partialDigitCount], partialValue); + } + + bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) + { + int remainingIntDigitCount = Math.Max(numberScale - totalDigitCount, 0); + ReadOnlySpan intDigitsSpan = chunkDigits.Slice(0, Math.Min(remainingIntDigitCount, chunkDigits.Length)); + + bool endReached = false; + + // Storing these captured variables in locals for faster access in the loop. + uint _partialValue = partialValue; + int _partialDigitCount = partialDigitCount; + int _totalDigitCount = totalDigitCount; + + for (int i = 0; i < intDigitsSpan.Length; i++) + { + char digitChar = chunkDigits[i]; + if (digitChar == '\0') + { + endReached = true; + break; + } + + _partialValue = _partialValue * 10 + (uint)(digitChar - '0'); + _partialDigitCount++; + _totalDigitCount++; + + // Update the buffer when enough partial digits have been accumulated. + if (_partialDigitCount == MaxPartialDigits) + { + MultiplyAdd(ref currentBuffer, TenPowMaxPartial, _partialValue); + _partialValue = 0; + _partialDigitCount = 0; + } + } + + // 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 = fracDigitsSpan[i]; + if (digitChar == '\0') + { + break; + } + if (digitChar != '0') + { + return false; + } + } + } + + partialValue = _partialValue; + partialDigitCount = _partialDigitCount; + totalDigitCount = _totalDigitCount; + + return true; + } + } + else + { + if (numberScale < 0) { result = default; return false; } - } + totalDigitCount = Math.Min(number.digits.Length - 1, numberScale); + var bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; - if (partialDigitCount > 0) - { - MultiplyAdd(ref currentBuffer, s_uint32PowersOfTen[partialDigitCount], partialValue); + Span buffer = new uint[bufferSize]; + + int bufferInd = buffer.Length - 1; + uint currentBlock = 0; + int shiftUntil = (totalDigitCount - 1) % MaxPartialDigits; + int remainingIntDigitCount = totalDigitCount; + foreach (ReadOnlyMemory digitsChunk in number.digits.GetChunks()) + { + var digitsChunkSpan = digitsChunk.Span; + ReadOnlySpan intDigitsSpan = digitsChunkSpan.Slice(0, Math.Min(remainingIntDigitCount, digitsChunkSpan.Length)); + + for (int i = 0; i < intDigitsSpan.Length; i++) + { + char digitChar = intDigitsSpan[i]; + Debug.Assert(char.IsDigit(digitChar)); + currentBlock = currentBlock * 10 + digitChar - '0'; + if (shiftUntil == 0) + { + buffer[bufferInd] = currentBlock; + currentBlock = 0; + bufferInd--; + shiftUntil = MaxPartialDigits; + } + shiftUntil--; + } + remainingIntDigitCount -= intDigitsSpan.Length; + + ReadOnlySpan fracDigitsSpan = digitsChunkSpan.Slice(intDigitsSpan.Length); + for (int i = 0; i < fracDigitsSpan.Length; i++) + { + char digitChar = fracDigitsSpan[i]; + if (digitChar == '\0') + { + break; + } + if (digitChar != '0') + { + result = default; + return false; + } + } + } + Debug.Assert(bufferInd == -1); + + unsafe + { + Span newBuffer = new uint[bufferSize]; + + arrayFromPool = ArrayPool.Shared.Rent(1); + Span multiplier = MemoryMarshal.Cast(arrayFromPool); + multiplier[0] = TenPowMaxPartial; + + int blockSize = 1; + while (true) + { + fixed (uint* bufPtr = buffer, newBufPtr = newBuffer, mulPtr = multiplier) + { + uint* curBufPtr = bufPtr; + uint* curNewBufPtr = newBufPtr; + for (int i = 0; i < bufferSize; i += blockSize * 2) + { + int len = Math.Min(bufferSize - i, blockSize * 2); + int lowerLen = Math.Min(len, blockSize); + int upperLen = len - lowerLen; + if (upperLen != 0) + { + BigIntegerCalculator.Multiply(mulPtr, blockSize, curBufPtr + blockSize, upperLen, curNewBufPtr, len); + } + + long carry = 0; + int j = 0; + for (; j < lowerLen; j++) + { + long digit = (curBufPtr[j] + carry) + curNewBufPtr[j]; + curNewBufPtr[j] = unchecked((uint)digit); + carry = digit >> 32; + } + if (carry != 0) + { + while (true) + { + curNewBufPtr[j]++; + if (curNewBufPtr[j] != 0) + { + break; + } + j++; + } + } + + curBufPtr += blockSize * 2; + curNewBufPtr += blockSize * 2; + } + } + + // swap + var tmp = buffer; + buffer = newBuffer; + newBuffer = tmp; + blockSize *= 2; + + if (bufferSize <= blockSize) + { + break; + } + newBuffer.Clear(); + int[]? arrayToReturn = arrayFromPool; + + arrayFromPool = ArrayPool.Shared.Rent(blockSize); + Span newMultiplier = MemoryMarshal.Cast(arrayFromPool); + newMultiplier.Clear(); + fixed (uint* mulPtr = multiplier, newMulPtr = newMultiplier) + { + BigIntegerCalculator.Square(mulPtr, blockSize / 2, newMulPtr, blockSize); + } + multiplier = newMultiplier; + if (arrayToReturn is not null) + { + ArrayPool.Shared.Return(arrayToReturn); + } + } + } + + // = log_{2^32}(10^9) + const double digitRatio = 0.934292276687070661; + currentBufferSize = Math.Min((int)(bufferSize * digitRatio) + 1, bufferSize); + while (buffer[currentBufferSize - 1] == 0) + { + currentBufferSize--; + } + currentBuffer = buffer.Slice(0, currentBufferSize); } int trailingZeroCount = numberScale - totalDigitCount; @@ -568,65 +775,6 @@ private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigIntege } } - bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) - { - int remainingIntDigitCount = Math.Max(numberScale - totalDigitCount, 0); - ReadOnlySpan intDigitsSpan = chunkDigits.Slice(0, Math.Min(remainingIntDigitCount, chunkDigits.Length)); - - bool endReached = false; - - // Storing these captured variables in locals for faster access in the loop. - uint _partialValue = partialValue; - int _partialDigitCount = partialDigitCount; - int _totalDigitCount = totalDigitCount; - - for (int i = 0; i < intDigitsSpan.Length; i++) - { - char digitChar = chunkDigits[i]; - if (digitChar == '\0') - { - endReached = true; - break; - } - - _partialValue = _partialValue * 10 + (uint)(digitChar - '0'); - _partialDigitCount++; - _totalDigitCount++; - - // Update the buffer when enough partial digits have been accumulated. - if (_partialDigitCount == MaxPartialDigits) - { - MultiplyAdd(ref currentBuffer, TenPowMaxPartial, _partialValue); - _partialValue = 0; - _partialDigitCount = 0; - } - } - - // 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 = fracDigitsSpan[i]; - if (digitChar == '\0') - { - break; - } - if (digitChar != '0') - { - return false; - } - } - } - - partialValue = _partialValue; - partialDigitCount = _partialDigitCount; - totalDigitCount = _totalDigitCount; - - return true; - } - void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) { Span curBits = currentBuffer.Slice(0, currentBufferSize); From 2fdbe455bf035e353844a4f1e1289bcd690eb6c1 Mon Sep 17 00:00:00 2001 From: keymoon Date: Tue, 27 Apr 2021 14:40:05 +0900 Subject: [PATCH 2/3] fix argument order in Assert when x equals to 0 --- .../System.Runtime.Numerics/tests/BigInteger/parse.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 23440b41d95e3..228c3a739d164 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -856,9 +856,10 @@ private static void Eval(BigInteger x, string expected) x = -x; } + string actual; if (x == 0) { - Assert.Equal("0", expected); + actual = "0"; } else { @@ -869,10 +870,9 @@ private static void Eval(BigInteger x, string expected) x = x / 10; } number.Reverse(); - string actual = new string(number.ToArray()); - - Assert.Equal(expected, actual); + actual = new string(number.ToArray()); } + Assert.Equal(expected, actual); } } } From 7d89c46821a9c476611419116fc8e1e0e885635a Mon Sep 17 00:00:00 2001 From: key-moon Date: Thu, 27 May 2021 01:15:19 +0900 Subject: [PATCH 3/3] Apply format fix Co-authored-by: Stephen Toub --- .../System.Runtime.Numerics/src/System/Numerics/BigNumber.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs index 52d586d024130..77799933c7e8a 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -595,9 +595,10 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) { result = default; return false; + } totalDigitCount = Math.Min(number.digits.Length - 1, numberScale); - var bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; + int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; Span buffer = new uint[bufferSize];