diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs
index 2ca6c7a1bf5ab3..59fef1a6d5da65 100644
--- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs
+++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs
@@ -27,6 +27,15 @@ public readonly struct BigInteger
internal const int kcbitUlong = 64;
internal const int DecimalScaleFactorMask = 0x00FF0000;
+ // Various APIs only allow up to int.MaxValue bits, so we will restrict ourselves
+ // to fit within this given our underlying storage representation and the maximum
+ // array length. This gives us just shy of 256MB as the largest allocation size.
+ //
+ // Such a value allows for almost 646,456,974 digits, which is more than large enough
+ // for typical scenarios. If user code requires more than this, they should likely
+ // roll their own type that utilizes native memory and other specialized techniques.
+ internal static int MaxLength => Array.MaxLength / kcbitUint;
+
// For values int.MinValue < n <= int.MaxValue, the value is stored in sign
// and _bits is null. For all other values, sign is +1 or -1 and the bits are in _bits
internal readonly int _sign; // Do not rename (binary serialization)
@@ -487,22 +496,22 @@ internal BigInteger(int n, uint[]? rgu)
/// The bool indicating the sign of the value.
private BigInteger(ReadOnlySpan value, bool negative)
{
+ // Try to conserve space as much as possible by checking for wasted leading span entries
+ // sometimes the span has leading zeros from bit manipulation operations & and ^
+
+ int length = value.LastIndexOfAnyExcept(0u) + 1;
+ value = value[..length];
+
if (value.Length > MaxLength)
{
ThrowHelper.ThrowOverflowException();
}
- int len;
-
- // Try to conserve space as much as possible by checking for wasted leading span entries
- // sometimes the span has leading zeros from bit manipulation operations & and ^
- for (len = value.Length; len > 0 && value[len - 1] == 0; len--);
-
- if (len == 0)
+ if (value.Length == 0)
{
- this = s_bnZeroInt;
+ this = default;
}
- else if (len == 1 && value[0] < kuMaskHighBit)
+ else if (value.Length == 1 && value[0] < kuMaskHighBit)
{
// Values like (Int32.MaxValue+1) are stored as "0x80000000" and as such cannot be packed into _sign
_sign = negative ? -(int)value[0] : (int)value[0];
@@ -516,7 +525,7 @@ private BigInteger(ReadOnlySpan value, bool negative)
else
{
_sign = negative ? -1 : +1;
- _bits = value.Slice(0, len).ToArray();
+ _bits = value.ToArray();
}
AssertValid();
}
@@ -527,87 +536,88 @@ private BigInteger(ReadOnlySpan value, bool negative)
///
private BigInteger(Span value)
{
+ bool isNegative;
+ int length;
+
+ if ((value.Length > 0) && ((int)value[^1] < 0))
+ {
+ isNegative = true;
+ length = value.LastIndexOfAnyExcept(uint.MaxValue) + 1;
+
+ if ((length == 0) || ((int)value[length - 1] > 0))
+ {
+ // We ne need to preserve the sign bit
+ length++;
+ }
+ Debug.Assert((int)value[length - 1] < 0);
+ }
+ else
+ {
+ isNegative = false;
+ length = value.LastIndexOfAnyExcept(0u) + 1;
+ }
+ value = value[..length];
+
if (value.Length > MaxLength)
{
ThrowHelper.ThrowOverflowException();
}
- int dwordCount = value.Length;
- bool isNegative = dwordCount > 0 && ((value[dwordCount - 1] & kuMaskHighBit) == kuMaskHighBit);
-
- // Try to conserve space as much as possible by checking for wasted leading span entries
- while (dwordCount > 0 && value[dwordCount - 1] == 0) dwordCount--;
-
- if (dwordCount == 0)
+ if (value.Length == 0)
{
- // BigInteger.Zero
+ // 0
this = s_bnZeroInt;
- AssertValid();
- return;
}
- if (dwordCount == 1)
+ else if (value.Length == 1)
{
- if (unchecked((int)value[0]) < 0 && !isNegative)
+ if (isNegative)
{
- _bits = new uint[1];
- _bits[0] = value[0];
- _sign = +1;
+ if (value[0] == uint.MaxValue)
+ {
+ // -1
+ this = s_bnMinusOneInt;
+ }
+ else if (value[0] == kuMaskHighBit)
+ {
+ // int.MinValue
+ this = s_bnMinInt;
+ }
+ else
+ {
+ _sign = unchecked((int)value[0]);
+ _bits = null;
+ }
}
- // Handle the special cases where the BigInteger likely fits into _sign
- else if (int.MinValue == unchecked((int)value[0]))
+ else if (unchecked((int)value[0]) < 0)
{
- this = s_bnMinInt;
+ _sign = +1;
+ _bits = [value[0]];
}
else
{
_sign = unchecked((int)value[0]);
_bits = null;
}
- AssertValid();
- return;
}
-
- if (!isNegative)
+ else
{
- // Handle the simple positive value cases where the input is already in sign magnitude
- _sign = +1;
- value = value.Slice(0, dwordCount);
- _bits = value.ToArray();
- AssertValid();
- return;
- }
-
- // Finally handle the more complex cases where we must transform the input into sign magnitude
- NumericsHelpers.DangerousMakeTwosComplement(value); // mutates val
+ if (isNegative)
+ {
+ NumericsHelpers.DangerousMakeTwosComplement(value);
- // Pack _bits to remove any wasted space after the twos complement
- int len = value.Length;
- while (len > 0 && value[len - 1] == 0) len--;
+ // Retrim any leading zeros carried from the sign
+ length = value.LastIndexOfAnyExcept(0u) + 1;
+ value = value[..length];
- // The number is represented by a single dword
- if (len == 1 && unchecked((int)(value[0])) > 0)
- {
- if (value[0] == 1 /* abs(-1) */)
- {
- this = s_bnMinusOneInt;
- }
- else if (value[0] == kuMaskHighBit /* abs(Int32.MinValue) */)
- {
- this = s_bnMinInt;
+ _sign = -1;
}
else
{
- _sign = (-1) * ((int)value[0]);
- _bits = null;
+ _sign = +1;
}
- }
- else
- {
- _sign = -1;
- _bits = value.Slice(0, len).ToArray();
+ _bits = value.ToArray();
}
AssertValid();
- return;
}
public static BigInteger Zero { get { return s_bnZeroInt; } }
@@ -616,8 +626,6 @@ private BigInteger(Span value)
public static BigInteger MinusOne { get { return s_bnMinusOneInt; } }
- internal static int MaxLength => Array.MaxLength / sizeof(uint);
-
public bool IsPowerOfTwo
{
get
diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/op_rightshift.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/op_rightshift.cs
index da6ac2615b9c1b..404902f61972e7 100644
--- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/op_rightshift.cs
+++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/op_rightshift.cs
@@ -29,19 +29,21 @@ public static void BigShiftsTest()
public static void LargeNegativeBigIntegerShiftTest()
{
// Create a very large negative BigInteger
- BigInteger bigInt = new BigInteger(-1) << int.MaxValue;
- Assert.Equal(2147483647, bigInt.GetBitLength());
+ int bitsPerElement = 8 * sizeof(uint);
+ int maxBitLength = ((Array.MaxLength / bitsPerElement) * bitsPerElement);
+ BigInteger bigInt = new BigInteger(-1) << (maxBitLength - 1);
+ Assert.Equal(maxBitLength - 1, bigInt.GetBitLength());
Assert.Equal(-1, bigInt.Sign);
// Validate internal representation.
- // At this point, bigInt should be a 1 followed by int.MaxValue zeros.
+ // At this point, bigInt should be a 1 followed by maxBitLength - 1 zeros.
// Given this, bigInt._bits is expected to be structured as follows:
- // - _bits.Length == (int.MaxValue + 1) / (8 * sizeof(uint))
+ // - _bits.Length == ceil(maxBitLength / bitsPerElement)
// - First (_bits.Length - 1) elements: 0x00000000
// - Last element: 0x80000000
// ^------ (There's the leading '1')
- Assert.Equal(((uint)int.MaxValue + 1) / (8 * sizeof(uint)), (uint)bigInt._bits.Length);
+ Assert.Equal((maxBitLength + (bitsPerElement - 1)) / bitsPerElement, bigInt._bits.Length);
uint i = 0;
for (; i < (bigInt._bits.Length - 1); i++) {
@@ -52,18 +54,18 @@ public static void LargeNegativeBigIntegerShiftTest()
// Right shift the BigInteger
BigInteger shiftedBigInt = bigInt >> 1;
- Assert.Equal(2147483646, shiftedBigInt.GetBitLength());
+ Assert.Equal(maxBitLength - 2, shiftedBigInt.GetBitLength());
Assert.Equal(-1, shiftedBigInt.Sign);
// Validate internal representation.
- // At this point, shiftedBigInt should be a 1 followed by int.MaxValue - 1 zeros.
+ // At this point, shiftedBigInt should be a 1 followed by maxBitLength - 2 zeros.
// Given this, shiftedBigInt._bits is expected to be structured as follows:
- // - _bits.Length == (int.MaxValue + 1) / (8 * sizeof(uint))
+ // - _bits.Length == ceil((maxBitLength - 1) / bitsPerElement)
// - First (_bits.Length - 1) elements: 0x00000000
// - Last element: 0x40000000
// ^------ (the '1' is now one position to the right)
- Assert.Equal(((uint)int.MaxValue + 1) / (8 * sizeof(uint)), (uint)shiftedBigInt._bits.Length);
+ Assert.Equal(((maxBitLength - 1) + (bitsPerElement - 1)) / bitsPerElement, shiftedBigInt._bits.Length);
i = 0;
for (; i < (shiftedBigInt._bits.Length - 1); i++) {