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 @@
+
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 c43eccf81f1a6..0b3c7dd6b8929 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;
@@ -330,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);
@@ -401,16 +402,18 @@ 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 TParser : struct, IBigIntegerHexOrBinaryParser
+ where TChar : unmanaged, IBinaryInteger
{
- int whiteIndex = 0;
+ 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]))
+ if (!IsWhite(uint.CreateTruncating(value[whiteIndex])))
break;
}
@@ -422,7 +425,7 @@ internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan= 0; whiteIndex--)
{
- if (!IsWhite(value[whiteIndex]))
+ if (!IsWhite(uint.CreateTruncating(value[whiteIndex])))
break;
}
@@ -434,220 +437,111 @@ 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);
-
- int bitsBufferPos = blockCount - 1;
-
- try
- {
- for (int i = 0; i < value.Length; i++)
+ if (!TParser.TryParseUnalignedBlock(value[0..leadingBitsCount], out leading))
{
- 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);
+ // Fill leading sign bits
+ leading |= signBits << (leadingBitsCount * TParser.BitsPerDigit);
+ value = value[leadingBitsCount..];
+ }
- if (isNegative)
+ // Skip all the blocks consists of the same bit of sign
+ while (!value.IsEmpty && leading == signBits)
+ {
+ if (!TParser.TryParseSingleBlock(value[0..TParser.DigitsPerBlock], out leading))
{
- NumericsHelpers.DangerousMakeTwosComplement(bitsBuffer);
+ goto FailExit;
}
+ value = value[TParser.DigitsPerBlock..];
+ }
- // BigInteger requires leading zero blocks to be truncated.
- bitsBuffer = bitsBuffer.TrimEnd(0u);
-
- int sign;
- uint[]? bits;
-
- if (bitsBuffer.IsEmpty)
+ if (value.IsEmpty)
+ {
+ // There's nothing beyond significant leading block. Return it as the result.
+ if ((int)(leading ^ signBits) >= 0)
{
- sign = 0;
- bits = null;
+ // Small value that fits in Int32.
+ // Delegate to the constructor for int.MinValue handling.
+ result = new BigInteger((int)leading);
+ return ParsingStatus.OK;
}
- else if (bitsBuffer.Length == 1 && bitsBuffer[0] <= int.MaxValue)
+ else if (leading != 0)
{
- sign = (int)bitsBuffer[0] * (isNegative ? -1 : 1);
- bits = null;
+ // The sign of result differs with leading digit.
+ // Require to store in _bits.
+
+ // 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
{
- sign = isNegative ? -1 : 1;
- bits = bitsBuffer.ToArray();
- }
-
- result = new BigInteger(sign, bits);
- return ParsingStatus.OK;
- }
- finally
- {
- if (arrayFromPool != null)
- {
- ArrayPool.Shared.Return(arrayFromPool);
+ // -1 << 32, which requires an additional uint
+ result = new BigInteger(-1, [0, 1]);
+ return ParsingStatus.OK;
}
}
- FailExit:
- result = default;
- return ParsingStatus.Failed;
- }
-
- internal static ParsingStatus TryParseBigIntegerBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result)
- {
- int whiteIndex = 0;
+ // Now the size of bits array can be calculated, except edge cases of -2^32N
+ int wholeBlockCount = value.Length / TParser.DigitsPerBlock;
+ int totalUIntCount = wholeBlockCount + 1;
- // Skip past any whitespace at the beginning.
- if ((style & NumberStyles.AllowLeadingWhite) != 0)
+ // Early out for too large input
+ if (totalUIntCount > BigInteger.MaxLength)
{
- for (whiteIndex = 0; whiteIndex < value.Length; whiteIndex++)
- {
- if (!IsWhite(value[whiteIndex]))
- break;
- }
-
- value = value[whiteIndex..];
+ result = default;
+ return ParsingStatus.Overflow;
}
- // 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)];
- }
+ uint[] bits = new uint[totalUIntCount];
+ Span wholeBlockDestination = bits.AsSpan(0, wholeBlockCount);
- if (value.IsEmpty)
+ if (!TParser.TryParseWholeBlocks(value, wholeBlockDestination))
{
goto FailExit;
}
- int totalDigitCount = value.Length;
- int partialDigitCount;
+ bits[^1] = leading;
- (int blockCount, int remainder) = int.DivRem(totalDigitCount, BigInteger.kcbitUint);
- if (remainder == 0)
+ if (signBits != 0)
{
- partialDigitCount = 0;
- }
- else
- {
- blockCount++;
- partialDigitCount = BigInteger.kcbitUint - remainder;
- }
-
- if (value[0] is not ('0' or '1')) goto FailExit;
- bool isNegative = value[0] == '1';
- uint currentBlock = isNegative ? 0xFF_FF_FF_FFu : 0x0;
-
- uint[]? arrayFromPool = null;
- Span buffer = ((uint)blockCount <= BigIntegerCalculator.StackAllocThreshold
- ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold]
- : arrayFromPool = ArrayPool.Shared.Rent(blockCount)).Slice(0, blockCount);
-
- int bufferPos = blockCount - 1;
-
- try
- {
- for (int i = 0; i < value.Length; i++)
+ // For negative values, negate the whole array
+ if (bits.AsSpan().ContainsAnyExcept(0u))
{
- char digitChar = value[i];
-
- if (digitChar is not ('0' or '1')) goto FailExit;
- currentBlock = (currentBlock << 1) | (uint)(digitChar - '0');
- partialDigitCount++;
-
- if (partialDigitCount == BigInteger.kcbitUint)
- {
- buffer[bufferPos--] = currentBlock;
- partialDigitCount = 0;
-
- // we do not need to reset currentBlock now, because it should always set all its bits by left shift in subsequent iterations
- }
- }
-
- Debug.Assert(partialDigitCount == 0 && bufferPos == -1);
-
- buffer = buffer.TrimEnd(0u);
-
- int sign;
- uint[]? bits;
-
- if (buffer.IsEmpty)
- {
- sign = 0;
- bits = null;
- }
- 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;
- }
+ NumericsHelpers.DangerousMakeTwosComplement(bits);
}
else
{
- sign = isNegative ? -1 : 1;
- bits = buffer.ToArray();
-
- if (isNegative)
- {
- NumericsHelpers.DangerousMakeTwosComplement(bits);
- }
+ // 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(sign, bits);
+ result = new BigInteger(-1, bits);
return ParsingStatus.OK;
}
- finally
+ else
{
- if (arrayFromPool is not null)
- {
- ArrayPool.Shared.Return(arrayFromPool);
- }
+ Debug.Assert(leading != 0);
+
+ // For positive values, it's done
+ result = new BigInteger(1, bits);
+ return ParsingStatus.OK;
}
FailExit:
@@ -1452,4 +1346,97 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo
}
}
}
+
+ internal interface IBigIntegerHexOrBinaryParser
+ where TParser : struct, IBigIntegerHexOrBinaryParser
+ where TChar : unmanaged, IBinaryInteger
+ {
+ static abstract int BitsPerDigit { get; }
+
+ static virtual int DigitsPerBlock => sizeof(uint) * 8 / TParser.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), TParser.BlockNumberStyle, null, out result);
+ }
+
+ throw new NotSupportedException();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static virtual bool TryParseSingleBlock(ReadOnlySpan input, out uint result)
+ => TParser.TryParseUnalignedBlock(input, out result);
+
+ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination)
+ {
+ 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 < destination.Length; i++)
+ {
+ if (!TParser.TryParseSingleBlock(
+ MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Subtract(ref lastWholeBlockStart, i * TParser.DigitsPerBlock), TParser.DigitsPerBlock),
+ out destination[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ internal readonly struct BigIntegerHexParser : IBigIntegerHexOrBinaryParser, 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 destination)
+ {
+ if (typeof(TChar) == typeof(char))
+ {
+ if (Convert.FromHexString(MemoryMarshal.Cast(input), MemoryMarshal.AsBytes(destination), out _, out _) != OperationStatus.Done)
+ {
+ return false;
+ }
+
+ if (BitConverter.IsLittleEndian)
+ {
+ MemoryMarshal.AsBytes(destination).Reverse();
+ }
+ else
+ {
+ destination.Reverse();
+ }
+
+ return true;
+ }
+
+ throw new NotSupportedException();
+ }
+ }
+
+ internal readonly struct BigIntegerBinaryParser : IBigIntegerHexOrBinaryParser, 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);
+ }
}
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..6190f14433e33 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,22 +99,57 @@ 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)
{
+ // 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);
+
+ // Make the first non-zero element to be two's complement
if (d.Length > 0)
{
- d[0] = unchecked(~d[0] + 1);
+ d[0] = (uint)(-(int)d[0]);
+ d = d.Slice(1);
+ }
- int i = 1;
+ if (d.IsEmpty)
+ {
+ return;
+ }
- // 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];
- }
+ // Make one's complement for other elements
+ int offset = 0;
+
+ ref uint start = ref MemoryMarshal.GetReference(d);
+
+ while (Vector512.IsHardwareAccelerated && d.Length - offset >= Vector512.Count)
+ {
+ 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 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 complement = ~Vector128.LoadUnsafe(ref start, (nuint)offset);
+ Vector128.StoreUnsafe(complement, ref start, (nuint)offset);
+ offset += Vector128.Count;
+ }
+
+ for (; offset < d.Length; offset++)
+ {
+ d[offset] = ~d[offset];
}
}
diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs
index 508efc963f9d1..86652ef6550a8 100644
--- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs
+++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs
@@ -148,6 +148,16 @@ 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);
+
+ 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);
@@ -159,6 +169,19 @@ public void Parse_Hex32Bits()
});
}
+ [Theory]
+ [InlineData("1", -1L)]
+ [InlineData("01", 1L)]
+ [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));
+ Assert.Equal(expectedValue, result);
+ }
+
public static IEnumerable