From e93dde530bba27636636e2c83d1bd8a56bd12ba6 Mon Sep 17 00:00:00 2001 From: keymoon Date: Tue, 27 Apr 2021 14:38:26 +0900 Subject: [PATCH 01/14] 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 f77d02f37ccb32..b2a14cf3ff453c 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 957bd732fed578..52d586d0241306 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 02/14] 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 23440b41d95e38..228c3a739d164b 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 03/14] 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 52d586d0241306..77799933c7e8ab 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]; From 417a8a33b6465b152a09b91830b5121f33426796 Mon Sep 17 00:00:00 2001 From: keymoon Date: Fri, 24 Sep 2021 06:39:59 +0900 Subject: [PATCH 04/14] add test for non-naive algorithm --- .../tests/BigInteger/BigNumberTools.cs | 49 ++++++++ .../tests/BigInteger/parse.cs | 107 ++++++++++-------- .../System.Runtime.Numerics.Tests.csproj | 1 + 3 files changed, 111 insertions(+), 46 deletions(-) create mode 100644 src/libraries/System.Runtime.Numerics/tests/BigInteger/BigNumberTools.cs diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigNumberTools.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigNumberTools.cs new file mode 100644 index 00000000000000..d99ec16f57cf06 --- /dev/null +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigNumberTools.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Numerics; +using System.Reflection; + +namespace BigNumberTools +{ + public class Utils + { + private static TypeInfo InternalNumber + { + get + { + if (s_lazyInternalNumber == null) + { + Type t = typeof(BigInteger).Assembly.GetType("System.Numerics.BigNumber"); + if (t != null) + { + s_lazyInternalNumber = t.GetTypeInfo(); + } + } + return s_lazyInternalNumber; + } + } + + private static volatile TypeInfo s_lazyInternalNumber; + + public static void RunWithFakeThreshold(string name, int value, Action action) + { + TypeInfo internalNumber = InternalNumber; + if (internalNumber == null) + return; // Internal frame types are not reflectable on AoT platforms. Skip the test. + + FieldInfo field = internalNumber.GetDeclaredField(name); + int lastValue = (int)field.GetValue(null); + field.SetValue(null, value); + try + { + action(); + } + finally + { + field.SetValue(null, lastValue); + } + } + } +} diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 228c3a739d164b..b7150e6ad48cc3 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -36,49 +36,54 @@ public static IEnumerable Cultures [OuterLoop] public static void RunParseToStringTests(CultureInfo 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); - - //composite NumberStyles - VerifyNumberStyles(NumberStyles.Integer, s_random); - VerifyNumberStyles(NumberStyles.HexNumber, 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(null, () => - { - BigInteger.Parse("1", invalid).ToString("d"); - }); - AssertExtensions.Throws(null, () => + Test(); + BigNumberTools.Utils.RunWithFakeThreshold("s_naiveThreshold", 0, Test); + void Test() + { + byte[] tempByteArray1 = new byte[0]; + using (new ThreadCultureChange(culture)) { - BigInteger junk; - BigInteger.TryParse("1", invalid, null, out junk); - Assert.Equal("1", junk.ToString("d")); - }); + //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); + + //composite NumberStyles + VerifyNumberStyles(NumberStyles.Integer, s_random); + VerifyNumberStyles(NumberStyles.HexNumber, 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(null, () => + { + BigInteger.Parse("1", invalid).ToString("d"); + }); + AssertExtensions.Throws(null, () => + { + BigInteger junk; + BigInteger.TryParse("1", invalid, null, out junk); + Assert.Equal("1", junk.ToString("d")); + }); - //FormatProvider tests - RunFormatProviderParseStrings(); + //FormatProvider tests + RunFormatProviderParseStrings(); + } } } @@ -93,16 +98,26 @@ public static void RunParseToStringTests(CultureInfo culture) [InlineData("123456789\0", 0, 10, "123456789")] public void Parse_Subspan_Success(string input, int offset, int length, string expected) { - Eval(BigInteger.Parse(input.AsSpan(offset, length)), expected); - Assert.True(BigInteger.TryParse(input.AsSpan(offset, length), out BigInteger test)); - Eval(test, expected); + Test(); + BigNumberTools.Utils.RunWithFakeThreshold("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); + } } [Fact] public void Parse_EmptySubspan_Fails() { - Assert.False(BigInteger.TryParse("12345".AsSpan(0, 0), out BigInteger result)); - Assert.Equal(0, result); + Test(); + BigNumberTools.Utils.RunWithFakeThreshold("s_naiveThreshold", 0, Test); + void Test() + { + Assert.False(BigInteger.TryParse("12345".AsSpan(0, 0), out BigInteger result)); + Assert.Equal(0, result); + } } private static void RunFormatProviderParseStrings() 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 5dd112190af3cb..179b40a2f1408f 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 @@ -8,6 +8,7 @@ + From eb7be98b9bc1d12297a45ee8da417e384474ea6f Mon Sep 17 00:00:00 2001 From: keymoon Date: Fri, 24 Sep 2021 06:44:12 +0900 Subject: [PATCH 05/14] add description for naiveThreshold --- .../src/System/Numerics/BigNumber.cs | 7 +++++++ 1 file changed, 7 insertions(+) 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 77799933c7e8ab..55567a15d70ccb 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -494,6 +494,13 @@ private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInt } } + // + // This threshold is for choosing the algorithm to use based on the number of digits. + // + // Let N be the number of digits. If N is less than or equal to the bound, use a naive + // algorithm with a running time of O(N^2). And if it is greater than the threshold, use + // a divide-and-conquer algorithm with a running time of O(NlogN). + // private static int s_naiveThreshold = 20000; private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) { From 8f79da76ceb3afb7458a6801ff46b4731df9fdf2 Mon Sep 17 00:00:00 2001 From: keymoon Date: Fri, 24 Sep 2021 07:47:08 +0900 Subject: [PATCH 06/14] fix trivial part --- .../System.Runtime.Numerics/src/System/Numerics/BigNumber.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 55567a15d70ccb..816424b3b7d68f 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -602,7 +602,6 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) { result = default; return false; - } totalDigitCount = Math.Min(number.digits.Length - 1, numberScale); int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; @@ -622,7 +621,8 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) { char digitChar = intDigitsSpan[i]; Debug.Assert(char.IsDigit(digitChar)); - currentBlock = currentBlock * 10 + digitChar - '0'; + currentBlock *= 10; + currentBlock += unchecked((uint)(digitChar - '0')); if (shiftUntil == 0) { buffer[bufferInd] = currentBlock; @@ -702,7 +702,6 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } - // swap var tmp = buffer; buffer = newBuffer; newBuffer = tmp; From 9a432db1ab3ee5c6008b3fec70ccd93ee61ed3d9 Mon Sep 17 00:00:00 2001 From: keymoon Date: Fri, 24 Sep 2021 09:23:29 +0900 Subject: [PATCH 07/14] add check for boundary condition --- .../System.Runtime.Numerics/src/System/Numerics/BigNumber.cs | 2 +- 1 file changed, 1 insertion(+), 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 816424b3b7d68f..a6b2fadbfe1cb5 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -732,7 +732,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) // = log_{2^32}(10^9) const double digitRatio = 0.934292276687070661; currentBufferSize = Math.Min((int)(bufferSize * digitRatio) + 1, bufferSize); - while (buffer[currentBufferSize - 1] == 0) + while (0 <= currentBufferSize - 1 && buffer[currentBufferSize - 1] == 0) { currentBufferSize--; } From fce80e8fafc54a9bad9dffe91d306a9397906749 Mon Sep 17 00:00:00 2001 From: keymoon Date: Fri, 24 Sep 2021 10:29:17 +0900 Subject: [PATCH 08/14] add assertions and descriptions --- .../src/System/Numerics/BigNumber.cs | 16 +++++++++++++++- 1 file changed, 15 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 a6b2fadbfe1cb5..03637ae31da179 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -608,6 +608,9 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) Span buffer = new uint[bufferSize]; + // 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 bufferInd = buffer.Length - 1; uint currentBlock = 0; int shiftUntil = (totalDigitCount - 1) % MaxPartialDigits; @@ -633,6 +636,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) shiftUntil--; } remainingIntDigitCount -= intDigitsSpan.Length; + Debug.Assert(0 <= remainingIntDigitCount); ReadOnlySpan fracDigitsSpan = digitsChunkSpan.Slice(intDigitsSpan.Length); for (int i = 0; i < fracDigitsSpan.Length; i++) @@ -649,6 +653,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } } + Debug.Assert(currentBlock == 0); Debug.Assert(bufferInd == -1); unsafe @@ -666,6 +671,11 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) { uint* curBufPtr = bufPtr; uint* curNewBufPtr = newBufPtr; + // merge each block pairs. + // When buffer represents: + // | A | B | C | D | + // Make newBuffer like: + // | A + B * multiplier | C * multiplier + D | for (int i = 0; i < bufferSize; i += blockSize * 2) { int len = Math.Min(bufferSize - i, blockSize * 2); @@ -729,9 +739,13 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } - // = log_{2^32}(10^9) + // 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 - 1 && buffer[currentBufferSize - 1] == 0) { currentBufferSize--; From f6eadca58844cfed08f7e5289b10ad6e80154f7c Mon Sep 17 00:00:00 2001 From: keymoon Date: Fri, 24 Sep 2021 10:42:32 +0900 Subject: [PATCH 09/14] change variable name --- .../src/System/Numerics/BigNumber.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 03637ae31da179..dc3691326c7cde 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -611,7 +611,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) // 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 bufferInd = buffer.Length - 1; + int bufferIndex = buffer.Length - 1; uint currentBlock = 0; int shiftUntil = (totalDigitCount - 1) % MaxPartialDigits; int remainingIntDigitCount = totalDigitCount; @@ -628,9 +628,9 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) currentBlock += unchecked((uint)(digitChar - '0')); if (shiftUntil == 0) { - buffer[bufferInd] = currentBlock; + buffer[bufferIndex] = currentBlock; currentBlock = 0; - bufferInd--; + bufferIndex--; shiftUntil = MaxPartialDigits; } shiftUntil--; @@ -654,7 +654,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } Debug.Assert(currentBlock == 0); - Debug.Assert(bufferInd == -1); + Debug.Assert(bufferIndex == -1); unsafe { From c3903db50a49c2202048b7428a17d2784f5e5f06 Mon Sep 17 00:00:00 2001 From: keymoon Date: Fri, 8 Oct 2021 00:22:14 +0900 Subject: [PATCH 10/14] remove inappropreate use of var --- .../System.Runtime.Numerics/src/System/Numerics/BigNumber.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 dc3691326c7cde..353dbc9ebc2b3b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -617,7 +617,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) int remainingIntDigitCount = totalDigitCount; foreach (ReadOnlyMemory digitsChunk in number.digits.GetChunks()) { - var digitsChunkSpan = digitsChunk.Span; + ReadOnlySpan digitsChunkSpan = digitsChunk.Span; ReadOnlySpan intDigitsSpan = digitsChunkSpan.Slice(0, Math.Min(remainingIntDigitCount, digitsChunkSpan.Length)); for (int i = 0; i < intDigitsSpan.Length; i++) @@ -712,7 +712,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } - var tmp = buffer; + Span tmp = buffer; buffer = newBuffer; newBuffer = tmp; blockSize *= 2; From a9942c515df39408fed9b71225b321b19d94d1cc Mon Sep 17 00:00:00 2001 From: keymoon Date: Fri, 8 Oct 2021 19:46:18 +0900 Subject: [PATCH 11/14] to use ArrayPool.Shared.Rent for newBuffer allocation --- .../src/System/Numerics/BigNumber.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) 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 353dbc9ebc2b3b..568b197dc9e558 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -510,6 +510,7 @@ private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigIntege int currentBufferSize = 0; int[]? arrayFromPool = null; + int[]? rentedBuffer = null; int totalDigitCount = 0; int numberScale = number.scale; @@ -607,11 +608,24 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; Span buffer = new uint[bufferSize]; + rentedBuffer = ArrayPool.Shared.Rent(bufferSize); + Span newBuffer = MemoryMarshal.Cast(rentedBuffer); + newBuffer.Clear(); + + // To ensure finally stored in newBuffer is the borrowed buffer. + int blockSize = 1; + do + { + Span tmp = buffer; + buffer = newBuffer; + newBuffer = tmp; + blockSize *= 2; + } while (blockSize < bufferSize); // 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 = buffer.Length - 1; + int bufferIndex = bufferSize - 1; uint currentBlock = 0; int shiftUntil = (totalDigitCount - 1) % MaxPartialDigits; int remainingIntDigitCount = totalDigitCount; @@ -658,13 +672,11 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) unsafe { - Span newBuffer = new uint[bufferSize]; - arrayFromPool = ArrayPool.Shared.Rent(1); Span multiplier = MemoryMarshal.Cast(arrayFromPool); multiplier[0] = TenPowMaxPartial; - int blockSize = 1; + blockSize = 1; while (true) { fixed (uint* bufPtr = buffer, newBufPtr = newBuffer, mulPtr = multiplier) @@ -739,6 +751,8 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } + Debug.Assert(MemoryMarshal.Cast(rentedBuffer) == newBuffer); + // 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. @@ -794,6 +808,10 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) { ArrayPool.Shared.Return(arrayFromPool); } + if (rentedBuffer != null) + { + ArrayPool.Shared.Return(rentedBuffer); + } } void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) From 460664f758842aa35c434c0d50cb104bfa33724e Mon Sep 17 00:00:00 2001 From: keymoon Date: Sun, 10 Oct 2021 05:52:55 +0900 Subject: [PATCH 12/14] move both algorithms to separate methods --- .../src/System/Numerics/BigNumber.cs | 224 +++++++++--------- 1 file changed, 118 insertions(+), 106 deletions(-) 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 568b197dc9e558..deb7f38a2023cb 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -504,124 +504,138 @@ 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; - int[]? rentedBuffer = null; - int totalDigitCount = 0; int numberScale = number.scale; const int MaxPartialDigits = 9; const uint TenPowMaxPartial = 1000000000; + + int[]? arrayFromPoolForResultBuffer = null; + if (numberScale < 0) + { + result = default; + return false; + } + try { if (number.digits.Length <= s_naiveThreshold) { - uint partialValue = 0; - int partialDigitCount = 0; + return Naive(ref number, out result); + } + else + { + return DivideAndConquer(ref number, out result); + } + } + finally + { + if (arrayFromPoolForResultBuffer != null) + { + ArrayPool.Shared.Return(arrayFromPoolForResultBuffer); + } + } - foreach (ReadOnlyMemory digitsChunk in number.digits.GetChunks()) - { - if (!ProcessChunk(digitsChunk.Span, ref currentBuffer)) - { - result = default; - return false; - } - } + bool Naive(ref BigNumberBuffer number, out BigInteger result) + { + Span stackBuffer = stackalloc uint[BigInteger.StackallocUInt32Limit]; + Span currentBuffer = stackBuffer; + uint partialValue = 0; + int partialDigitCount = 0; - if (partialDigitCount > 0) + foreach (ReadOnlyMemory digitsChunk in number.digits.GetChunks()) + { + if (!ProcessChunk(digitsChunk.Span, ref currentBuffer)) { - MultiplyAdd(ref currentBuffer, s_uint32PowersOfTen[partialDigitCount], partialValue); + result = default; + return false; } + } - bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) - { - int remainingIntDigitCount = Math.Max(numberScale - totalDigitCount, 0); - ReadOnlySpan intDigitsSpan = chunkDigits.Slice(0, Math.Min(remainingIntDigitCount, chunkDigits.Length)); + if (partialDigitCount > 0) + { + MultiplyAdd(ref currentBuffer, s_uint32PowersOfTen[partialDigitCount], partialValue); + } + + result = NumberBufferToBigInteger(currentBuffer, number.sign); + return true; - bool endReached = false; + bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) + { + int remainingIntDigitCount = Math.Max(numberScale - totalDigitCount, 0); + ReadOnlySpan intDigitsSpan = chunkDigits.Slice(0, Math.Min(remainingIntDigitCount, chunkDigits.Length)); - // Storing these captured variables in locals for faster access in the loop. - uint _partialValue = partialValue; - int _partialDigitCount = partialDigitCount; - int _totalDigitCount = totalDigitCount; + bool endReached = false; - for (int i = 0; i < intDigitsSpan.Length; i++) + // 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') { - char digitChar = chunkDigits[i]; - if (digitChar == '\0') - { - endReached = true; - break; - } + endReached = true; + break; + } - _partialValue = _partialValue * 10 + (uint)(digitChar - '0'); - _partialDigitCount++; - _totalDigitCount++; + _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; - } + // 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) + // Check for nonzero digits after the decimal point. + if (!endReached) + { + ReadOnlySpan fracDigitsSpan = chunkDigits.Slice(intDigitsSpan.Length); + for (int i = 0; i < fracDigitsSpan.Length; i++) { - ReadOnlySpan fracDigitsSpan = chunkDigits.Slice(intDigitsSpan.Length); - for (int i = 0; i < fracDigitsSpan.Length; i++) + char digitChar = fracDigitsSpan[i]; + if (digitChar == '\0') { - char digitChar = fracDigitsSpan[i]; - if (digitChar == '\0') - { - break; - } - if (digitChar != '0') - { - return false; - } + break; + } + if (digitChar != '0') + { + return false; } } + } - partialValue = _partialValue; - partialDigitCount = _partialDigitCount; - totalDigitCount = _totalDigitCount; + partialValue = _partialValue; + partialDigitCount = _partialDigitCount; + totalDigitCount = _totalDigitCount; - return true; - } + return true; } - else + } + + bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) + { + Span currentBuffer; + int[]? arrayFromPoolForMultiplier = null; + try { - if (numberScale < 0) - { - result = default; - return false; - } totalDigitCount = Math.Min(number.digits.Length - 1, numberScale); int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; Span buffer = new uint[bufferSize]; - rentedBuffer = ArrayPool.Shared.Rent(bufferSize); - Span newBuffer = MemoryMarshal.Cast(rentedBuffer); + arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(bufferSize); + Span newBuffer = MemoryMarshal.Cast(arrayFromPoolForResultBuffer); newBuffer.Clear(); - // To ensure finally stored in newBuffer is the borrowed buffer. - int blockSize = 1; - do - { - Span tmp = buffer; - buffer = newBuffer; - newBuffer = tmp; - blockSize *= 2; - } while (blockSize < bufferSize); - // 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. @@ -672,11 +686,11 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) unsafe { - arrayFromPool = ArrayPool.Shared.Rent(1); - Span multiplier = MemoryMarshal.Cast(arrayFromPool); + arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(1); + Span multiplier = MemoryMarshal.Cast(arrayFromPoolForMultiplier); multiplier[0] = TenPowMaxPartial; - blockSize = 1; + int blockSize = 1; while (true) { fixed (uint* bufPtr = buffer, newBufPtr = newBuffer, mulPtr = multiplier) @@ -734,10 +748,10 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) break; } newBuffer.Clear(); - int[]? arrayToReturn = arrayFromPool; + int[]? arrayToReturn = arrayFromPoolForMultiplier; - arrayFromPool = ArrayPool.Shared.Rent(blockSize); - Span newMultiplier = MemoryMarshal.Cast(arrayFromPool); + arrayFromPoolForMultiplier = ArrayPool.Shared.Rent(blockSize); + Span newMultiplier = MemoryMarshal.Cast(arrayFromPoolForMultiplier); newMultiplier.Clear(); fixed (uint* mulPtr = multiplier, newMulPtr = newMultiplier) { @@ -751,8 +765,6 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } - Debug.Assert(MemoryMarshal.Cast(rentedBuffer) == newBuffer); - // 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. @@ -765,8 +777,20 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) currentBufferSize--; } currentBuffer = buffer.Slice(0, currentBufferSize); + result = NumberBufferToBigInteger(currentBuffer, number.sign); + } + finally + { + if (arrayFromPoolForMultiplier != null) + { + ArrayPool.Shared.Return(arrayFromPoolForMultiplier); + } } + return true; + } + BigInteger NumberBufferToBigInteger(Span currentBuffer, bool signa) + { int trailingZeroCount = numberScale - totalDigitCount; while (trailingZeroCount >= MaxPartialDigits) @@ -790,28 +814,16 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } else if (currentBufferSize == 1 && currentBuffer[0] <= int.MaxValue) { - sign = (int)(number.sign ? -currentBuffer[0] : currentBuffer[0]); + sign = (int)(signa ? -currentBuffer[0] : currentBuffer[0]); bits = null; } else { - sign = number.sign ? -1 : 1; + sign = signa ? -1 : 1; bits = currentBuffer.Slice(0, currentBufferSize).ToArray(); } - result = new BigInteger(sign, bits); - return true; - } - finally - { - if (arrayFromPool != null) - { - ArrayPool.Shared.Return(arrayFromPool); - } - if (rentedBuffer != null) - { - ArrayPool.Shared.Return(rentedBuffer); - } + return new BigInteger(sign, bits); } void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) @@ -833,10 +845,10 @@ void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) if (currentBufferSize == currentBuffer.Length) { - int[]? arrayToReturn = arrayFromPool; + int[]? arrayToReturn = arrayFromPoolForResultBuffer; - arrayFromPool = ArrayPool.Shared.Rent(checked(currentBufferSize * 2)); - Span newBuffer = MemoryMarshal.Cast(arrayFromPool); + arrayFromPoolForResultBuffer = ArrayPool.Shared.Rent(checked(currentBufferSize * 2)); + Span newBuffer = MemoryMarshal.Cast(arrayFromPoolForResultBuffer); currentBuffer.CopyTo(newBuffer); currentBuffer = newBuffer; From 607f51c0962b3c47d3444e97335d05a20c6310ab Mon Sep 17 00:00:00 2001 From: keymoon Date: Sun, 10 Oct 2021 06:10:41 +0900 Subject: [PATCH 13/14] add and fix comments --- .../System.Runtime.Numerics/src/System/Numerics/BigNumber.cs | 4 +++- 1 file changed, 3 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 deb7f38a2023cb..2a9ecb80419df1 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -691,6 +691,7 @@ bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) multiplier[0] = TenPowMaxPartial; int blockSize = 1; + // This loop is executed ceil(log_2(bufferSize)) times. while (true) { fixed (uint* bufPtr = buffer, newBufPtr = newBuffer, mulPtr = multiplier) @@ -701,7 +702,7 @@ bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) // When buffer represents: // | A | B | C | D | // Make newBuffer like: - // | A + B * multiplier | C * multiplier + D | + // | A + B * multiplier | C + D * multiplier | for (int i = 0; i < bufferSize; i += blockSize * 2) { int len = Math.Min(bufferSize - i, blockSize * 2); @@ -826,6 +827,7 @@ BigInteger NumberBufferToBigInteger(Span currentBuffer, bool signa) return new BigInteger(sign, bits); } + // This function should only be used for result buffer. void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) { Span curBits = currentBuffer.Slice(0, currentBufferSize); From 470d7618f779ee47fb019d9563ce4f551ff9d81c Mon Sep 17 00:00:00 2001 From: keymoon Date: Thu, 27 Jan 2022 13:25:12 +0900 Subject: [PATCH 14/14] trivial fix --- .../System.Runtime.Numerics/src/System/Numerics/BigNumber.cs | 2 +- src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 451465afe34389..481fb25ea0a82e 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -765,7 +765,7 @@ bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) const double digitRatio = 0.934292276687070661; currentBufferSize = Math.Min((int)(bufferSize * digitRatio) + 1, bufferSize); Debug.Assert(buffer.Length == currentBufferSize || buffer[currentBufferSize] == 0); - while (0 <= currentBufferSize - 1 && buffer[currentBufferSize - 1] == 0) + while (0 < currentBufferSize && buffer[currentBufferSize - 1] == 0) { currentBufferSize--; } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 48adc54fb09a1a..5b76b54b933d90 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -40,7 +40,7 @@ public static void RunParseToStringTests(CultureInfo culture) BigNumberTools.Utils.RunWithFakeThreshold("s_naiveThreshold", 0, Test); void Test() { - byte[] tempByteArray1 = new byte[0]; + byte[] tempByteArray1 = Array.Empty(); using (new ThreadCultureChange(culture)) { //default style