From 1e88ab35cfeee24664850596931db4705c158c86 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 1 Dec 2023 00:35:09 +0800 Subject: [PATCH 01/21] Introduce intrinsics to S.R.Numerics --- .../System.Runtime.Numerics/src/System.Runtime.Numerics.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj index b5142e8ca5377..dfc3acc553d17 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -40,6 +40,7 @@ + From 45d1939318936ad4c0f34c7fc2b1e4f9cb85260e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 1 Dec 2023 00:35:25 +0800 Subject: [PATCH 02/21] Vectorize DangerousMakeTwosComplement --- .../src/System/Numerics/NumericsHelpers.cs | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs index 797b848b496d5..aee6007626d21 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; namespace System.Numerics { @@ -97,23 +99,58 @@ public static double GetDoubleFromParts(int sign, int exp, ulong man) // a mutation and needs to be used with care for immutable types. public static void DangerousMakeTwosComplement(Span d) { - if (d.Length > 0) + // Given a number: + // XXXXXXXXXXXY00000 + // where Y is non-zero, + // The result of two's complement is + // AAAAAAAAAAAB00000 + // where A = ~X and B = -Y + + // Trim trailing 0s (at the first in little endian array) + d = d.TrimStart(0u); + + if (d.IsEmpty) { - d[0] = unchecked(~d[0] + 1); + return; + } - int i = 1; + // Make one's complement for every element + int offset = 0; - // first do complement and +1 as long as carry is needed - for (; d[i - 1] == 0 && i < d.Length; i++) - { - d[i] = unchecked(~d[i] + 1); - } - // now ones complement is sufficient - for (; i < d.Length; i++) - { - d[i] = ~d[i]; - } + ref uint start = ref MemoryMarshal.GetReference(d); + + while (Vector512.IsHardwareAccelerated && d.Length - offset >= Vector512.Count) + { + Vector512 vector = Vector512.LoadUnsafe(ref start, (nuint)offset); + Vector512 complement = Vector512.OnesComplement(vector); + Vector512.StoreUnsafe(complement, ref start, (nuint)offset); + offset += Vector512.Count; } + + while (Vector256.IsHardwareAccelerated && d.Length - offset >= Vector256.Count) + { + Vector256 vector = Vector256.LoadUnsafe(ref start, (nuint)offset); + Vector256 complement = Vector256.OnesComplement(vector); + Vector256.StoreUnsafe(complement, ref start, (nuint)offset); + offset += Vector256.Count; + } + + while (Vector128.IsHardwareAccelerated && d.Length - offset >= Vector128.Count) + { + Vector128 vector = Vector128.LoadUnsafe(ref start, (nuint)offset); + Vector128 complement = Vector128.OnesComplement(vector); + Vector128.StoreUnsafe(complement, ref start, (nuint)offset); + offset += Vector128.Count; + } + + for (; offset < d.Length; offset++) + { + d[offset] = ~d[offset]; + } + + // Adjust the first non-zero element to be two's complement + Debug.Assert(d[0] != uint.MaxValue); + d[0]++; } public static ulong MakeUInt64(uint uHi, uint uLo) From 12011fa5d49bb312a8e7d602f871a0dca5314c13 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 1 Dec 2023 02:50:51 +0800 Subject: [PATCH 03/21] Rewrite hex parsing --- .../src/System/Number.BigInteger.cs | 136 ++++++++++-------- 1 file changed, 76 insertions(+), 60 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 62272c70bd014..34f4c95c8a04c 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -275,6 +275,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -429,93 +430,108 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan= 8; - uint partialValue = (isNegative && partialDigitCount > 0) ? 0xFFFFFFFFu : 0; - - uint[]? arrayFromPool = null; - - Span bitsBuffer = ((uint)blockCount <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : arrayFromPool = ArrayPool.Shared.Rent(blockCount)).Slice(0, blockCount); + value = isNegative ? value.TrimStart(['f', 'F']) : value.TrimStart('0'); - int bitsBufferPos = blockCount - 1; - - try + // Corner case: the value fits in int after trimming + if (value.Length <= DigitsPerBlock) { - for (int i = 0; i < value.Length; i++) + if (!int.TryParse(value, NumberStyles.AllowHexSpecifier, null, out int smallValue)) { - char digitChar = value[i]; - - if (!HexConverter.IsHexChar(digitChar)) goto FailExit; - int hexValue = HexConverter.FromChar(digitChar); - - partialValue = (partialValue << 4) | (uint)hexValue; - partialDigitCount++; - - if (partialDigitCount == DigitsPerBlock) - { - bitsBuffer[bitsBufferPos] = partialValue; - bitsBufferPos--; - partialValue = 0; - partialDigitCount = 0; - } + goto FailExit; } - Debug.Assert(partialDigitCount == 0 && bitsBufferPos == -1); - if (isNegative) { - NumericsHelpers.DangerousMakeTwosComplement(bitsBuffer); + // Fill leading bits of negative value to 1 + smallValue |= -1 << (value.Length * BitsPerDigit); } - // BigInteger requires leading zero blocks to be truncated. - bitsBuffer = bitsBuffer.TrimEnd(0u); + // If the sign differs with int (0{F~8}xxxxxxx or F{0-7}xxxxxxx), + // or if the value is int.MinValue, store the value in _bits + if ((smallValue & int.MinValue) != (isNegative ? int.MinValue : 0) + || smallValue == int.MinValue) + { + result = new BigInteger(isNegative ? -1 : 1, [(uint)smallValue]); + return ParsingStatus.OK; + } - int sign; - uint[]? bits; + // Otherwise it fits in _sign + result = new BigInteger(smallValue, null); + return ParsingStatus.OK; + } + + // Now the size of bits array can be definitely calculated + (int wholeBlockCount, int leadingBitsCount) = Math.DivRem(value.Length, DigitsPerBlock); + int totalUIntCount = leadingBitsCount != 0 ? wholeBlockCount + 1 : wholeBlockCount; + + // Early out for too large input + if (totalUIntCount > BigInteger.MaxLength) + { + result = default; + return ParsingStatus.Overflow; + } + + uint[] bits = new uint[totalUIntCount]; + ref char finalWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(value), value.Length - DigitsPerBlock); // value[^DigitsPerBlock] - if (bitsBuffer.IsEmpty) + // TODO: Vectorized parsing for whole uints + for (int i = 0; i < wholeBlockCount; i++) + { + if (!uint.TryParse( + MemoryMarshal.CreateSpan(ref Unsafe.Subtract(ref finalWholeBlockStart, i * DigitsPerBlock), DigitsPerBlock), + NumberStyles.AllowHexSpecifier, null, out bits[i])) { - sign = 0; - bits = null; + goto FailExit; } - else if (bitsBuffer.Length == 1 && bitsBuffer[0] <= int.MaxValue) + } + + // Parse the leading uint + if (leadingBitsCount != 0) + { + if (!uint.TryParse(value[0..leadingBitsCount], NumberStyles.AllowHexSpecifier, null, out bits[^1])) { - sign = (int)bitsBuffer[0] * (isNegative ? -1 : 1); - bits = null; + goto FailExit; } - else + + // Fill leading sign bits + if (isNegative) { - sign = isNegative ? -1 : 1; - bits = bitsBuffer.ToArray(); + bits[^1] |= (uint)(-1 << (leadingBitsCount * BitsPerDigit)); } + } - result = new BigInteger(sign, bits); + if (isNegative) + { + // For negative values, negate the whole array + NumericsHelpers.DangerousMakeTwosComplement(bits); + + result = new BigInteger(-1, bits); return ParsingStatus.OK; } - finally + else { - if (arrayFromPool != null) - { - ArrayPool.Shared.Return(arrayFromPool); - } + // For positive values, it's done + result = new BigInteger(1, bits); + return ParsingStatus.OK; } FailExit: From 5a7eca8105eb75eb607fb1f7b820a7887c8fa746 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 1 Dec 2023 02:54:44 +0800 Subject: [PATCH 04/21] Port to binary parsing --- .../src/System/Number.BigInteger.cs | 141 ++++++++++-------- 1 file changed, 78 insertions(+), 63 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 34f4c95c8a04c..c29fabd4d1e45 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -572,93 +572,108 @@ internal static ParsingStatus TryParseBigIntegerBinaryNumberStyle(ReadOnlySpan buffer = ((uint)blockCount <= BigIntegerCalculator.StackAllocThreshold - ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] - : arrayFromPool = ArrayPool.Shared.Rent(blockCount)).Slice(0, blockCount); + result = smallValue; // Defer to implicit operator to handle int.MinValue + return ParsingStatus.OK; + } - int bufferPos = blockCount - 1; + // Trim unnecessary leading 0/1's + // Remember the sign before trimming + if (!HexConverter.IsHexChar(value[0])) goto FailExit; + bool isNegative = HexConverter.FromChar(value[0]) >= 8; + value = isNegative ? value.TrimStart('1') : value.TrimStart('0'); - try + // Corner case: the value fits in int after trimming + if (value.Length <= DigitsPerBlock) { - for (int i = 0; i < value.Length; i++) + if (!int.TryParse(value, NumberStyles.AllowBinarySpecifier, null, out int smallValue)) { - char digitChar = value[i]; - - if (digitChar is not ('0' or '1')) goto FailExit; - currentBlock = (currentBlock << 1) | (uint)(digitChar - '0'); - partialDigitCount++; + goto FailExit; + } - if (partialDigitCount == BigInteger.kcbitUint) - { - buffer[bufferPos--] = currentBlock; - partialDigitCount = 0; + if (isNegative) + { + // Fill leading bits of negative value to 1 + smallValue |= -1 << (value.Length * BitsPerDigit); + } - // we do not need to reset currentBlock now, because it should always set all its bits by left shift in subsequent iterations - } + // If the sign differs with int (01xxxxxxx or 10xxxxxxx), + // or if the value is int.MinValue, store the value in _bits + if ((smallValue & int.MinValue) != (isNegative ? int.MinValue : 0) + || smallValue == int.MinValue) + { + result = new BigInteger(isNegative ? -1 : 1, [(uint)smallValue]); + return ParsingStatus.OK; } - Debug.Assert(partialDigitCount == 0 && bufferPos == -1); + // Otherwise it fits in _sign + result = new BigInteger(smallValue, null); + return ParsingStatus.OK; + } - buffer = buffer.TrimEnd(0u); + // Now the size of bits array can be definitely calculated + (int wholeBlockCount, int leadingBitsCount) = Math.DivRem(value.Length, DigitsPerBlock); + int totalUIntCount = leadingBitsCount != 0 ? wholeBlockCount + 1 : wholeBlockCount; - int sign; - uint[]? bits; + // Early out for too large input + if (totalUIntCount > BigInteger.MaxLength) + { + result = default; + return ParsingStatus.Overflow; + } + + uint[] bits = new uint[totalUIntCount]; + ref char finalWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(value), value.Length - DigitsPerBlock); // value[^DigitsPerBlock] - if (buffer.IsEmpty) + // TODO: Vectorized parsing for whole uints + for (int i = 0; i < wholeBlockCount; i++) + { + if (!uint.TryParse( + MemoryMarshal.CreateSpan(ref Unsafe.Subtract(ref finalWholeBlockStart, i * DigitsPerBlock), DigitsPerBlock), + NumberStyles.AllowBinarySpecifier, null, out bits[i])) { - sign = 0; - bits = null; + goto FailExit; } - else if (buffer.Length == 1) - { - sign = (int)buffer[0]; - bits = null; + } - if ((!isNegative && sign < 0) || sign == int.MinValue) - { - bits = new[] { (uint)sign }; - sign = isNegative ? -1 : 1; - } - } - else + // Parse the leading uint + if (leadingBitsCount != 0) + { + if (!uint.TryParse(value[0..leadingBitsCount], NumberStyles.AllowBinarySpecifier, null, out bits[^1])) { - sign = isNegative ? -1 : 1; - bits = buffer.ToArray(); + goto FailExit; + } - if (isNegative) - { - NumericsHelpers.DangerousMakeTwosComplement(bits); - } + // Fill leading sign bits + if (isNegative) + { + bits[^1] |= (uint)(-1 << (leadingBitsCount * BitsPerDigit)); } + } - result = new BigInteger(sign, bits); + if (isNegative) + { + // For negative values, negate the whole array + NumericsHelpers.DangerousMakeTwosComplement(bits); + + result = new BigInteger(-1, bits); return ParsingStatus.OK; } - finally + else { - if (arrayFromPool is not null) - { - ArrayPool.Shared.Return(arrayFromPool); - } + // For positive values, it's done + result = new BigInteger(1, bits); + return ParsingStatus.OK; } FailExit: From a533953ad3201cda106da71ed852d338f575f176 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 1 Dec 2023 19:07:07 +0800 Subject: [PATCH 05/21] Move NumericsHelper processing --- .../src/System/Numerics/NumericsHelpers.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs index aee6007626d21..4705cd8157923 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs @@ -109,12 +109,19 @@ public static void DangerousMakeTwosComplement(Span d) // Trim trailing 0s (at the first in little endian array) d = d.TrimStart(0u); + // Make the first non-zero element to be two's complement + if (d.Length > 0) + { + d[0] = (uint)(-(int)d[0]); + d = d.Slice(1); + } + if (d.IsEmpty) { return; } - // Make one's complement for every element + // Make one's complement for other elements int offset = 0; ref uint start = ref MemoryMarshal.GetReference(d); @@ -147,10 +154,6 @@ public static void DangerousMakeTwosComplement(Span d) { d[offset] = ~d[offset]; } - - // Adjust the first non-zero element to be two's complement - Debug.Assert(d[0] != uint.MaxValue); - d[0]++; } public static ulong MakeUInt64(uint uHi, uint uLo) From 499973c9ccd3d6d05f8c414b1b0bfe44d798396d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 1 Dec 2023 23:52:30 +0800 Subject: [PATCH 06/21] Use vectorization --- .../src/System/Number.BigInteger.cs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 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 c29fabd4d1e45..7149a26e88ca1 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -491,17 +491,23 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan wholeBlockDestination = leadingBitsCount != 0 ? bits[1..] : bits; + ReadOnlySpan wholeBlockSource = value[leadingBitsCount..]; - // TODO: Vectorized parsing for whole uints - for (int i = 0; i < wholeBlockCount; i++) + Debug.Assert(wholeBlockDestination.Length * DigitsPerBlock == wholeBlockSource.Length); + + if (Convert.FromHexString(wholeBlockSource, MemoryMarshal.AsBytes(wholeBlockDestination), out _, out _) != OperationStatus.Done) { - if (!uint.TryParse( - MemoryMarshal.CreateSpan(ref Unsafe.Subtract(ref finalWholeBlockStart, i * DigitsPerBlock), DigitsPerBlock), - NumberStyles.AllowHexSpecifier, null, out bits[i])) - { - goto FailExit; - } + goto FailExit; + } + + if (BitConverter.IsLittleEndian) + { + MemoryMarshal.AsBytes(wholeBlockDestination).Reverse(); + } + else + { + wholeBlockDestination.Reverse(); } // Parse the leading uint From 9081d391d9705161c277fc89596738521128b035 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 2 Dec 2023 02:24:23 +0800 Subject: [PATCH 07/21] Rewrite --- .../src/System/Number.BigInteger.cs | 90 +++++++++---------- 1 file changed, 40 insertions(+), 50 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 7149a26e88ca1..8d2e8edd05e8c 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -399,7 +399,7 @@ internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyle internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) { - int whiteIndex = 0; + int whiteIndex; // Skip past any whitespace at the beginning. if ((style & NumberStyles.AllowLeadingWhite) != 0) @@ -433,55 +433,60 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan= 8; - value = isNegative ? value.TrimStart(['f', 'F']) : value.TrimStart('0'); - - // Corner case: the value fits in int after trimming - if (value.Length <= DigitsPerBlock) + // Skip all the blocks consists of the same bit of sign + while (!value.IsEmpty && leading == signBits) { - if (!int.TryParse(value, NumberStyles.AllowHexSpecifier, null, out int smallValue)) + // Potentially vectorizable if single-block vectorization is available + if (!int.TryParse(value[0..DigitsPerBlock], NumberStyles.AllowHexSpecifier, null, out leading)) { goto FailExit; } + value = value[DigitsPerBlock..]; + wholeBlockCount--; + } - if (isNegative) + if (value.IsEmpty) + { + // There's nothing beyond significant leading block. Return it as the result. + if ((leading ^ signBits) < 0 || leading == int.MinValue) { - // Fill leading bits of negative value to 1 - smallValue |= -1 << (value.Length * BitsPerDigit); + // The sign of result differs with leading digit, or it's int.MinValue. + // Require to store in _bits. + result = new BigInteger(signBits | 1, [(uint)leading]); + return ParsingStatus.OK; } - - // If the sign differs with int (0{F~8}xxxxxxx or F{0-7}xxxxxxx), - // or if the value is int.MinValue, store the value in _bits - if ((smallValue & int.MinValue) != (isNegative ? int.MinValue : 0) - || smallValue == int.MinValue) + else { - result = new BigInteger(isNegative ? -1 : 1, [(uint)smallValue]); + // Small value that fits in _sign. + result = new BigInteger(leading, null); return ParsingStatus.OK; } - - // Otherwise it fits in _sign - result = new BigInteger(smallValue, null); - return ParsingStatus.OK; } // Now the size of bits array can be definitely calculated - (int wholeBlockCount, int leadingBitsCount) = Math.DivRem(value.Length, DigitsPerBlock); - int totalUIntCount = leadingBitsCount != 0 ? wholeBlockCount + 1 : wholeBlockCount; + int totalUIntCount = wholeBlockCount + 1; // Early out for too large input if (totalUIntCount > BigInteger.MaxLength) @@ -491,12 +496,10 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan wholeBlockDestination = leadingBitsCount != 0 ? bits[1..] : bits; - ReadOnlySpan wholeBlockSource = value[leadingBitsCount..]; + Span wholeBlockDestination = bits.AsSpan(0, wholeBlockCount); - Debug.Assert(wholeBlockDestination.Length * DigitsPerBlock == wholeBlockSource.Length); - - if (Convert.FromHexString(wholeBlockSource, MemoryMarshal.AsBytes(wholeBlockDestination), out _, out _) != OperationStatus.Done) + // Vectorized parsing for whole blocks + if (Convert.FromHexString(value, MemoryMarshal.AsBytes(wholeBlockDestination), out _, out _) != OperationStatus.Done) { goto FailExit; } @@ -510,22 +513,9 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan Date: Sat, 2 Dec 2023 02:35:15 +0800 Subject: [PATCH 08/21] Port to binary --- .../src/System/Number.BigInteger.cs | 94 +++++++++---------- 1 file changed, 44 insertions(+), 50 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 8d2e8edd05e8c..c110a9361dfd5 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -537,7 +537,7 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) { - int whiteIndex = 0; + int whiteIndex; // Skip past any whitespace at the beginning. if ((style & NumberStyles.AllowLeadingWhite) != 0) @@ -571,55 +571,60 @@ internal static ParsingStatus TryParseBigIntegerBinaryNumberStyle(ReadOnlySpan> 31; + + // Start from leading blocks. Leading blocks can be unaligned, or whole of 0/F's that need to be trimmed. + (int wholeBlockCount, int leadingBitsCount) = Math.DivRem(value.Length, DigitsPerBlock); + + int leading = signBits; + // First parse unanligned leading block if exists. + if (leadingBitsCount != 0) { - if (!int.TryParse(value, NumberStyles.AllowBinarySpecifier, null, out int smallValue)) + if (!int.TryParse(value[0..leadingBitsCount], NumberStyles.AllowBinarySpecifier, null, out leading)) { goto FailExit; } - result = smallValue; // Defer to implicit operator to handle int.MinValue - return ParsingStatus.OK; + // Fill leading sign bits + leading |= signBits << (leadingBitsCount * BitsPerDigit); + value = value[leadingBitsCount..]; } - // Trim unnecessary leading 0/1's - // Remember the sign before trimming - if (!HexConverter.IsHexChar(value[0])) goto FailExit; - bool isNegative = HexConverter.FromChar(value[0]) >= 8; - value = isNegative ? value.TrimStart('1') : value.TrimStart('0'); - - // Corner case: the value fits in int after trimming - if (value.Length <= DigitsPerBlock) + // Skip all the blocks consists of the same bit of sign + while (!value.IsEmpty && leading == signBits) { - if (!int.TryParse(value, NumberStyles.AllowBinarySpecifier, null, out int smallValue)) + // Potentially vectorizable if single-block vectorization is available + if (!int.TryParse(value[0..DigitsPerBlock], NumberStyles.AllowBinarySpecifier, null, out leading)) { goto FailExit; } + value = value[DigitsPerBlock..]; + wholeBlockCount--; + } - if (isNegative) + if (value.IsEmpty) + { + // There's nothing beyond significant leading block. Return it as the result. + if ((leading ^ signBits) < 0 || leading == int.MinValue) { - // Fill leading bits of negative value to 1 - smallValue |= -1 << (value.Length * BitsPerDigit); + // The sign of result differs with leading digit, or it's int.MinValue. + // Require to store in _bits. + result = new BigInteger(signBits | 1, [(uint)leading]); + return ParsingStatus.OK; } - - // If the sign differs with int (01xxxxxxx or 10xxxxxxx), - // or if the value is int.MinValue, store the value in _bits - if ((smallValue & int.MinValue) != (isNegative ? int.MinValue : 0) - || smallValue == int.MinValue) + else { - result = new BigInteger(isNegative ? -1 : 1, [(uint)smallValue]); + // Small value that fits in _sign. + result = new BigInteger(leading, null); return ParsingStatus.OK; } - - // Otherwise it fits in _sign - result = new BigInteger(smallValue, null); - return ParsingStatus.OK; } // Now the size of bits array can be definitely calculated - (int wholeBlockCount, int leadingBitsCount) = Math.DivRem(value.Length, DigitsPerBlock); - int totalUIntCount = leadingBitsCount != 0 ? wholeBlockCount + 1 : wholeBlockCount; + int totalUIntCount = wholeBlockCount + 1; // Early out for too large input if (totalUIntCount > BigInteger.MaxLength) @@ -629,35 +634,24 @@ internal static ParsingStatus TryParseBigIntegerBinaryNumberStyle(ReadOnlySpan Date: Sat, 2 Dec 2023 14:46:36 +0800 Subject: [PATCH 09/21] Add regression test for binary --- .../tests/BigInteger/parse.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 94c17c0562f0c..c7f3f4d9854c9 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -139,6 +139,9 @@ public void Parse_Hex32Bits() Assert.Equal(new BigInteger(-2), result); Assert.Equal(-2, result); + Assert.True(BigInteger.TryParse("F", NumberStyles.HexNumber, null, out result)); + Assert.Equal(-1, result); + Assert.Throws(() => { BigInteger.Parse("zzz", NumberStyles.HexNumber); @@ -150,6 +153,18 @@ public void Parse_Hex32Bits() }); } + [Theory] + [InlineData("1", -1L)] + [InlineData("01", 1L)] + [InlineData("10000000000000000000000000000000", (long)int.MinValue)] + [InlineData("010000000000000000000000000000001", 0x080000001L)] + [InlineData("111111111111111111111111111111110", -2L)] + public void Parse_BinSpecialCases(string input, long expectedValue) + { + Assert.True(BigInteger.TryParse(input, NumberStyles.BinaryNumber, null, out BigInteger result)); + Assert.Equal(expectedValue, result); + } + private static void RunFormatProviderParseStrings() { NumberFormatInfo nfi = new NumberFormatInfo(); From 10351a66baf04a14286b213adcdb44c72e362ed8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 2 Dec 2023 18:05:34 +0800 Subject: [PATCH 10/21] Unify bin and hex --- .../src/System/Number.BigInteger.cs | 282 +++++++----------- 1 file changed, 113 insertions(+), 169 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 c110a9361dfd5..911e7f968a811 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -331,12 +331,12 @@ internal static unsafe ParsingStatus TryParseBigInteger(ReadOnlySpan value if ((style & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseBigIntegerHexNumberStyle(value, style, out result); + return TryParseBigIntegerHexOrBinaryNumberStyle, char>(value, style, out result); } if ((style & NumberStyles.AllowBinarySpecifier) != 0) { - return TryParseBigIntegerBinaryNumberStyle(value, style, out result); + return TryParseBigIntegerHexOrBinaryNumberStyle, char>(value, style, out result); } return TryParseBigIntegerNumber(value, style, info, out result); @@ -397,7 +397,9 @@ internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyle return result; } - internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) + internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) + where TParsingInfo : struct, IBigIntegerHexOrBinaryParsingInfo + where TChar : unmanaged, IBinaryInteger { int whiteIndex; @@ -406,7 +408,7 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan= 0; whiteIndex--) { - if (!IsWhite(value[whiteIndex])) + if (!IsWhite(uint.CreateTruncating(value[whiteIndex]))) break; } @@ -430,62 +432,57 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan wholeBlockDestination = bits.AsSpan(0, wholeBlockCount); - // Vectorized parsing for whole blocks - if (Convert.FromHexString(value, MemoryMarshal.AsBytes(wholeBlockDestination), out _, out _) != OperationStatus.Done) + if (!TParsingInfo.TryParseWholeBlocks(value, wholeBlockDestination)) { goto FailExit; } - if (BitConverter.IsLittleEndian) - { - MemoryMarshal.AsBytes(wholeBlockDestination).Reverse(); - } - else - { - wholeBlockDestination.Reverse(); - } - - bits[^1] = (uint)leading; - - if (signBits != 0) - { - // For negative values, negate the whole array - NumericsHelpers.DangerousMakeTwosComplement(bits); - - result = new BigInteger(-1, bits); - return ParsingStatus.OK; - } - else - { - // For positive values, it's done - result = new BigInteger(1, bits); - return ParsingStatus.OK; - } - - FailExit: - result = default; - return ParsingStatus.Failed; - } - - internal static ParsingStatus TryParseBigIntegerBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) - { - int whiteIndex; - - // Skip past any whitespace at the beginning. - if ((style & NumberStyles.AllowLeadingWhite) != 0) - { - for (whiteIndex = 0; whiteIndex < value.Length; whiteIndex++) - { - if (!IsWhite(value[whiteIndex])) - break; - } - - value = value[whiteIndex..]; - } - - // Skip past any whitespace at the end. - if ((style & NumberStyles.AllowTrailingWhite) != 0) - { - for (whiteIndex = value.Length - 1; whiteIndex >= 0; whiteIndex--) - { - if (!IsWhite(value[whiteIndex])) - break; - } - - value = value[..(whiteIndex + 1)]; - } - - if (value.IsEmpty) - { - goto FailExit; - } - - const int BitsPerDigit = 1; - const int DigitsPerBlock = sizeof(uint) * 8 / BitsPerDigit; - - // Remember the sign from original leading input - // Taking the LSB is enough for distinguishing 0/1 - // Invalid digits will be caught in parsing below - int signBits = (value[0] << 31) >> 31; - - // Start from leading blocks. Leading blocks can be unaligned, or whole of 0/F's that need to be trimmed. - (int wholeBlockCount, int leadingBitsCount) = Math.DivRem(value.Length, DigitsPerBlock); - - int leading = signBits; - // First parse unanligned leading block if exists. - if (leadingBitsCount != 0) - { - if (!int.TryParse(value[0..leadingBitsCount], NumberStyles.AllowBinarySpecifier, null, out leading)) - { - goto FailExit; - } - - // Fill leading sign bits - leading |= signBits << (leadingBitsCount * BitsPerDigit); - value = value[leadingBitsCount..]; - } - - // Skip all the blocks consists of the same bit of sign - while (!value.IsEmpty && leading == signBits) - { - // Potentially vectorizable if single-block vectorization is available - if (!int.TryParse(value[0..DigitsPerBlock], NumberStyles.AllowBinarySpecifier, null, out leading)) - { - goto FailExit; - } - value = value[DigitsPerBlock..]; - wholeBlockCount--; - } - - if (value.IsEmpty) - { - // There's nothing beyond significant leading block. Return it as the result. - if ((leading ^ signBits) < 0 || leading == int.MinValue) - { - // The sign of result differs with leading digit, or it's int.MinValue. - // Require to store in _bits. - result = new BigInteger(signBits | 1, [(uint)leading]); - return ParsingStatus.OK; - } - else - { - // Small value that fits in _sign. - result = new BigInteger(leading, null); - return ParsingStatus.OK; - } - } - - // Now the size of bits array can be definitely calculated - int totalUIntCount = wholeBlockCount + 1; - - // Early out for too large input - if (totalUIntCount > BigInteger.MaxLength) - { - result = default; - return ParsingStatus.Overflow; - } - - uint[] bits = new uint[totalUIntCount]; - ref char lastWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(value), value.Length - DigitsPerBlock); - - // TODO: Vectorized parsing for whole blocks - for (int i = 0; i < bits.Length - 1; i++) - { - if (!uint.TryParse( - MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Subtract(ref lastWholeBlockStart, i * DigitsPerBlock), DigitsPerBlock), - NumberStyles.BinaryNumber, - null, - out bits[i])) - { - goto FailExit; - } - } - - bits[^1] = (uint)leading; + bits[^1] = leading; if (signBits != 0) { @@ -1458,4 +1309,97 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } } } + + internal interface IBigIntegerHexOrBinaryParsingInfo + where TParsingInfo : struct, IBigIntegerHexOrBinaryParsingInfo + where TChar : unmanaged, IBinaryInteger + { + static abstract int BitsPerDigit { get; } + + static virtual int DigitsPerBlock => sizeof(uint) * 8 / TParsingInfo.BitsPerDigit; + + static abstract NumberStyles BlockNumberStyle { get; } + + static abstract uint GetSignBitsIfValid(uint ch); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out uint result) + { + if (typeof(TChar) == typeof(char)) + { + return uint.TryParse(MemoryMarshal.Cast(input), TParsingInfo.BlockNumberStyle, null, out result); + } + + throw new NotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static virtual bool TryParseSingleBlock(ReadOnlySpan input, out uint result) + => TParsingInfo.TryParseUnalignedBlock(input, out result); + + static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destiniation) + { + Debug.Assert(destiniation.Length * TParsingInfo.DigitsPerBlock == input.Length); + ref TChar lastWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(input), input.Length - TParsingInfo.DigitsPerBlock); + + for (int i = 0; i < destiniation.Length - 1; i++) + { + if (!TParsingInfo.TryParseSingleBlock( + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Subtract(ref lastWholeBlockStart, i * TParsingInfo.DigitsPerBlock), TParsingInfo.DigitsPerBlock), + out destiniation[i])) + { + return false; + } + } + + return true; + } + } + + internal readonly struct BigIntegerHexParsingInfo : IBigIntegerHexOrBinaryParsingInfo, TChar> + where TChar : unmanaged, IBinaryInteger + { + public static int BitsPerDigit => 4; + + public static NumberStyles BlockNumberStyle => NumberStyles.AllowHexSpecifier; + + // A valid ASCII hex digit is positive (0-7) if it starts with 00110 + public static uint GetSignBitsIfValid(uint ch) => (uint)((ch & 0b_1111_1000) == 0b_0011_0000 ? 0 : -1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseWholeBlocks(ReadOnlySpan input, Span destiniation) + { + if (typeof(TChar) == typeof(char)) + { + if (Convert.FromHexString(MemoryMarshal.Cast(input), MemoryMarshal.AsBytes(destiniation), out _, out _) != OperationStatus.Done) + { + return false; + } + + if (BitConverter.IsLittleEndian) + { + MemoryMarshal.AsBytes(destiniation).Reverse(); + } + else + { + destiniation.Reverse(); + } + + return true; + } + + throw new NotSupportedException(); + } + } + + internal readonly struct BigIntegerBinaryParsingInfo : IBigIntegerHexOrBinaryParsingInfo, TChar> + where TChar : unmanaged, IBinaryInteger + { + public static int BitsPerDigit => 1; + + public static NumberStyles BlockNumberStyle => NumberStyles.AllowBinarySpecifier; + + // Taking the LSB is enough for distinguishing 0/1 + public static uint GetSignBitsIfValid(uint ch) => (uint)(((int)ch << 31) >> 31); + } } From a3edd54bfabafdce024fb117772c6dc115378589 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 2 Dec 2023 18:59:24 +0800 Subject: [PATCH 11/21] Use TParser to follow corelib naming --- .../src/System/Number.BigInteger.cs | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 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 911e7f968a811..3d53af61e3c1b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -331,12 +331,12 @@ internal static unsafe ParsingStatus TryParseBigInteger(ReadOnlySpan value if ((style & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseBigIntegerHexOrBinaryNumberStyle, char>(value, style, out result); + return TryParseBigIntegerHexOrBinaryNumberStyle, char>(value, style, out result); } if ((style & NumberStyles.AllowBinarySpecifier) != 0) { - return TryParseBigIntegerHexOrBinaryNumberStyle, char>(value, style, out result); + return TryParseBigIntegerHexOrBinaryNumberStyle, char>(value, style, out result); } return TryParseBigIntegerNumber(value, style, info, out result); @@ -397,8 +397,8 @@ internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyle return result; } - internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) - where TParsingInfo : struct, IBigIntegerHexOrBinaryParsingInfo + internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) + where TParser : struct, IBigIntegerHexOrBinaryParser where TChar : unmanaged, IBinaryInteger { int whiteIndex; @@ -434,33 +434,33 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle wholeBlockDestination = bits.AsSpan(0, wholeBlockCount); - if (!TParsingInfo.TryParseWholeBlocks(value, wholeBlockDestination)) + if (!TParser.TryParseWholeBlocks(value, wholeBlockDestination)) { goto FailExit; } @@ -1310,13 +1310,13 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } } - internal interface IBigIntegerHexOrBinaryParsingInfo - where TParsingInfo : struct, IBigIntegerHexOrBinaryParsingInfo + internal interface IBigIntegerHexOrBinaryParser + where TParser : struct, IBigIntegerHexOrBinaryParser where TChar : unmanaged, IBinaryInteger { static abstract int BitsPerDigit { get; } - static virtual int DigitsPerBlock => sizeof(uint) * 8 / TParsingInfo.BitsPerDigit; + static virtual int DigitsPerBlock => sizeof(uint) * 8 / TParser.BitsPerDigit; static abstract NumberStyles BlockNumberStyle { get; } @@ -1327,7 +1327,7 @@ static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out uint r { if (typeof(TChar) == typeof(char)) { - return uint.TryParse(MemoryMarshal.Cast(input), TParsingInfo.BlockNumberStyle, null, out result); + return uint.TryParse(MemoryMarshal.Cast(input), TParser.BlockNumberStyle, null, out result); } throw new NotSupportedException(); @@ -1335,17 +1335,17 @@ static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out uint r [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual bool TryParseSingleBlock(ReadOnlySpan input, out uint result) - => TParsingInfo.TryParseUnalignedBlock(input, out result); + => TParser.TryParseUnalignedBlock(input, out result); static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destiniation) { - Debug.Assert(destiniation.Length * TParsingInfo.DigitsPerBlock == input.Length); - ref TChar lastWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(input), input.Length - TParsingInfo.DigitsPerBlock); + Debug.Assert(destiniation.Length * TParser.DigitsPerBlock == input.Length); + ref TChar lastWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(input), input.Length - TParser.DigitsPerBlock); for (int i = 0; i < destiniation.Length - 1; i++) { - if (!TParsingInfo.TryParseSingleBlock( - MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Subtract(ref lastWholeBlockStart, i * TParsingInfo.DigitsPerBlock), TParsingInfo.DigitsPerBlock), + if (!TParser.TryParseSingleBlock( + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Subtract(ref lastWholeBlockStart, i * TParser.DigitsPerBlock), TParser.DigitsPerBlock), out destiniation[i])) { return false; @@ -1356,7 +1356,7 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span de } } - internal readonly struct BigIntegerHexParsingInfo : IBigIntegerHexOrBinaryParsingInfo, TChar> + internal readonly struct BigIntegerHexParser : IBigIntegerHexOrBinaryParser, TChar> where TChar : unmanaged, IBinaryInteger { public static int BitsPerDigit => 4; @@ -1392,7 +1392,7 @@ public static bool TryParseWholeBlocks(ReadOnlySpan input, Span des } } - internal readonly struct BigIntegerBinaryParsingInfo : IBigIntegerHexOrBinaryParsingInfo, TChar> + internal readonly struct BigIntegerBinaryParser : IBigIntegerHexOrBinaryParser, TChar> where TChar : unmanaged, IBinaryInteger { public static int BitsPerDigit => 1; From 5d1fd71b293d4ef406a75dbd1132e0eb877334eb Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 11 Jan 2024 21:00:42 +0800 Subject: [PATCH 12/21] Fix whole block counting --- .../System.Runtime.Numerics/src/System/Number.BigInteger.cs | 2 +- src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs | 1 + 2 files changed, 2 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 3d53af61e3c1b..03593e06a3e68 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -1342,7 +1342,7 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span de Debug.Assert(destiniation.Length * TParser.DigitsPerBlock == input.Length); ref TChar lastWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(input), input.Length - TParser.DigitsPerBlock); - for (int i = 0; i < destiniation.Length - 1; i++) + for (int i = 0; i < destiniation.Length; i++) { if (!TParser.TryParseSingleBlock( MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Subtract(ref lastWholeBlockStart, i * TParser.DigitsPerBlock), TParser.DigitsPerBlock), diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index c7f3f4d9854c9..8c6b4ff0ce349 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -159,6 +159,7 @@ public void Parse_Hex32Bits() [InlineData("10000000000000000000000000000000", (long)int.MinValue)] [InlineData("010000000000000000000000000000001", 0x080000001L)] [InlineData("111111111111111111111111111111110", -2L)] + [InlineData("0111111111111111111111111111111111", 0x1FFFFFFFFL)] public void Parse_BinSpecialCases(string input, long expectedValue) { Assert.True(BigInteger.TryParse(input, NumberStyles.BinaryNumber, null, out BigInteger result)); From 08c214c10991d3878c9a8ac61fe401bc5d2d03df Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 13 Jan 2024 17:56:14 +0800 Subject: [PATCH 13/21] Fix typo --- .../src/System/Number.BigInteger.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 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 75e89887b46fb..5b014e366b50b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -1343,16 +1343,16 @@ static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out uint r static virtual bool TryParseSingleBlock(ReadOnlySpan input, out uint result) => TParser.TryParseUnalignedBlock(input, out result); - static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destiniation) + static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) { - Debug.Assert(destiniation.Length * TParser.DigitsPerBlock == input.Length); + Debug.Assert(destination.Length * TParser.DigitsPerBlock == input.Length); ref TChar lastWholeBlockStart = ref Unsafe.Add(ref MemoryMarshal.GetReference(input), input.Length - TParser.DigitsPerBlock); - for (int i = 0; i < destiniation.Length; i++) + for (int i = 0; i < destination.Length; i++) { if (!TParser.TryParseSingleBlock( MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Subtract(ref lastWholeBlockStart, i * TParser.DigitsPerBlock), TParser.DigitsPerBlock), - out destiniation[i])) + out destination[i])) { return false; } @@ -1373,22 +1373,22 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span de public static uint GetSignBitsIfValid(uint ch) => (uint)((ch & 0b_1111_1000) == 0b_0011_0000 ? 0 : -1); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseWholeBlocks(ReadOnlySpan input, Span destiniation) + public static bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) { if (typeof(TChar) == typeof(char)) { - if (Convert.FromHexString(MemoryMarshal.Cast(input), MemoryMarshal.AsBytes(destiniation), out _, out _) != OperationStatus.Done) + if (Convert.FromHexString(MemoryMarshal.Cast(input), MemoryMarshal.AsBytes(destination), out _, out _) != OperationStatus.Done) { return false; } if (BitConverter.IsLittleEndian) { - MemoryMarshal.AsBytes(destiniation).Reverse(); + MemoryMarshal.AsBytes(destination).Reverse(); } else { - destiniation.Reverse(); + destination.Reverse(); } return true; From c372811e8238f205eb015a1010a9b48516284fb5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 13 Jan 2024 18:00:19 +0800 Subject: [PATCH 14/21] Simplify vector operation --- .../src/System/Numerics/NumericsHelpers.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs index 4705cd8157923..6190f14433e33 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs @@ -128,24 +128,21 @@ public static void DangerousMakeTwosComplement(Span d) while (Vector512.IsHardwareAccelerated && d.Length - offset >= Vector512.Count) { - Vector512 vector = Vector512.LoadUnsafe(ref start, (nuint)offset); - Vector512 complement = Vector512.OnesComplement(vector); + Vector512 complement = ~Vector512.LoadUnsafe(ref start, (nuint)offset); Vector512.StoreUnsafe(complement, ref start, (nuint)offset); offset += Vector512.Count; } while (Vector256.IsHardwareAccelerated && d.Length - offset >= Vector256.Count) { - Vector256 vector = Vector256.LoadUnsafe(ref start, (nuint)offset); - Vector256 complement = Vector256.OnesComplement(vector); + Vector256 complement = ~Vector256.LoadUnsafe(ref start, (nuint)offset); Vector256.StoreUnsafe(complement, ref start, (nuint)offset); offset += Vector256.Count; } while (Vector128.IsHardwareAccelerated && d.Length - offset >= Vector128.Count) { - Vector128 vector = Vector128.LoadUnsafe(ref start, (nuint)offset); - Vector128 complement = Vector128.OnesComplement(vector); + Vector128 complement = ~Vector128.LoadUnsafe(ref start, (nuint)offset); Vector128.StoreUnsafe(complement, ref start, (nuint)offset); offset += Vector128.Count; } From 850edc860bc83daf7b4c9a8135756c00913d85b9 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Thu, 25 Jan 2024 05:52:41 +0900 Subject: [PATCH 15/21] Add Tests --- .../tests/BigInteger/parse.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 6f4ec530189ff..f444445cbbaa5 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -146,6 +146,13 @@ public void Parse_Hex32Bits() Assert.True(BigInteger.TryParse("F", NumberStyles.HexNumber, null, out result)); Assert.Equal(-1, result); + for (int i = 0; i < 40; i++) + { + string test = "F" + new string('0', i); + Assert.True(BigInteger.TryParse(test, NumberStyles.HexNumber, null, out result)); + Assert.Equal(BigInteger.MinusOne << (4 * i), result); + } + Assert.Throws(() => { BigInteger.Parse("zzz", NumberStyles.HexNumber); @@ -477,6 +484,12 @@ private static void VerifyBinaryNumberStyles(NumberStyles ns, Random random) { VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + GetRandomInvalidChar(random) + GetBinaryDigitSequence(1, 10, random), ns, false); } + + // Power of 2 + for (int i = 0; i < 70; i++) + { + VerifyParseToString("1" + new string('0', i), ns, true); + } } private static void VerifyNumberStyles(NumberStyles ns, Random random) From 8a788167660efda41bb647b5a3ec2f1bf9398e07 Mon Sep 17 00:00:00 2001 From: kzrnm Date: Thu, 25 Jan 2024 06:00:11 +0900 Subject: [PATCH 16/21] Avoid allocate if value == int.MinValue --- .../System.Runtime.Numerics/src/System/Number.BigInteger.cs | 6 +++--- 1 file changed, 3 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 5b014e366b50b..900f1fee2eb9a 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -466,9 +466,9 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle Date: Thu, 25 Jan 2024 06:01:49 +0900 Subject: [PATCH 17/21] Fix parsing power of 2 --- .../src/System/Number.BigInteger.cs | 28 +++++++++++++++++-- 1 file changed, 25 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 900f1fee2eb9a..b2d9b07e916ab 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -470,12 +470,23 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle trimmed = new Span(bits).TrimStart(0u); + if (trimmed.Length == 0) + { + bits = new uint[bits.Length + 1]; + bits[^1] = 1; + } + else + { + NumericsHelpers.DangerousMakeTwosComplement(trimmed); + } result = new BigInteger(-1, bits); return ParsingStatus.OK; } else { + Debug.Assert(leading != 0); + // For positive values, it's done result = new BigInteger(1, bits); return ParsingStatus.OK; From 883512baaaf7ba91f3522c8f02fdfd30cec58073 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 1 Feb 2024 18:53:07 +0800 Subject: [PATCH 18/21] Optimize the small value case --- .../src/System/Number.BigInteger.cs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 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 1590410a40fc1..4f4069a34c352 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -471,29 +471,27 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle 0) + { + // Small value that fits in Int32. + // Delegate to the constructor for int.MinValue handling. + result = new BigInteger((int)leading); + return ParsingStatus.OK; + } + else if (leading != 0) { // The sign of result differs with leading digit. // Require to store in _bits. - if ((int)signBits < 0) - { - // Negative value - result = leading == 0 - ? new BigInteger(-1, [leading, 1u]) - : new BigInteger(-1, [unchecked((uint)-(int)leading)]); - } - else - { - // Positive value - result = new BigInteger(1, [leading]); - } + + // Positive: sign=1, bits=[leading] + // Negative: sign=-1, bits=[leading ^ -1 + 1]=[-leading] + result = new BigInteger((int)signBits | 1, [leading ^ signBits - signBits]); return ParsingStatus.OK; } else { - // Small value that fits in Int32. - result = new BigInteger((int)leading); - return ParsingStatus.OK; + // -1 << 32, which requires an additional uint + result = new BigInteger(-1, [0, 1]); } } From ecfac7a89f4385e73796f1dabc0721955aa5b805 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 1 Feb 2024 19:13:57 +0800 Subject: [PATCH 19/21] Use ContainsAnyExcept --- .../src/System/Number.BigInteger.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 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 4f4069a34c352..5b7666c23c89e 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -495,7 +495,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle trimmed = new Span(bits).TrimStart(0u); - if (trimmed.Length == 0) + if (bits.AsSpan().ContainsAnyExcept(0u)) { - bits = new uint[bits.Length + 1]; - bits[^1] = 1; + NumericsHelpers.DangerousMakeTwosComplement(bits); } else { - NumericsHelpers.DangerousMakeTwosComplement(trimmed); + // For negative values with all-zero trailing digits, + // It requires additional leading 1. + bits = new uint[bits.Length + 1]; + bits[^1] = 1; } result = new BigInteger(-1, bits); From 6a72ae0627ff9b38af7f23b3ee2a15347a0cbe84 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 1 Feb 2024 21:01:49 +0800 Subject: [PATCH 20/21] Fix missing return --- .../System.Runtime.Numerics/src/System/Number.BigInteger.cs | 1 + 1 file changed, 1 insertion(+) 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 5b7666c23c89e..8f829e8027ae6 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -492,6 +492,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle Date: Sat, 3 Feb 2024 01:58:09 +0800 Subject: [PATCH 21/21] Fix bit hack --- .../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 8f829e8027ae6..0b3c7dd6b8929 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -471,7 +471,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle 0) + if ((int)(leading ^ signBits) >= 0) { // Small value that fits in Int32. // Delegate to the constructor for int.MinValue handling.