Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Merge pull request #22689 from stephentoub/biginteger_span
Browse files Browse the repository at this point in the history
Add Span-based APIs to BigInteger
  • Loading branch information
stephentoub committed Aug 2, 2017
2 parents d0f2f62 + fd2b64a commit 91f993e
Show file tree
Hide file tree
Showing 16 changed files with 584 additions and 117 deletions.
10 changes: 3 additions & 7 deletions src/Common/src/System/Globalization/FormatProvider.Number.cs
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ private static unsafe bool ParseNumber(ref char* str, NumberStyles options, ref
return false;
}

private static bool TrailingZeros(string s, int index)
private static bool TrailingZeros(ReadOnlySpan<char> s, int index)
{
// For compatibility, we need to allow trailing zeros at the end of a number string
for (int i = index; i < s.Length; i++)
Expand All @@ -555,15 +555,11 @@ private static bool TrailingZeros(string s, int index)
return true;
}

internal static unsafe bool TryStringToNumber(string str, NumberStyles options, ref NumberBuffer number, StringBuilder sb, NumberFormatInfo numfmt, bool parseDecimal)
internal static unsafe bool TryStringToNumber(ReadOnlySpan<char> str, NumberStyles options, ref NumberBuffer number, StringBuilder sb, NumberFormatInfo numfmt, bool parseDecimal)
{
if (str == null)
{
return false;
}
Debug.Assert(numfmt != null);

fixed (char* stringPointer = str)
fixed (char* stringPointer = &str.DangerousGetPinnableReference())
{
char* p = stringPointer;
if (!ParseNumber(ref p, options, ref number, sb, numfmt, parseDecimal)
Expand Down
5 changes: 5 additions & 0 deletions src/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public partial struct BigInteger : System.IComparable, System.IComparable<System
{
[System.CLSCompliantAttribute(false)]
public BigInteger(byte[] value) { throw null; }
public BigInteger(System.ReadOnlySpan<byte> value) { throw null; }
public BigInteger(decimal value) { throw null; }
public BigInteger(double value) { throw null; }
public BigInteger(int value) { throw null; }
Expand Down Expand Up @@ -46,6 +47,7 @@ public partial struct BigInteger : System.IComparable, System.IComparable<System
[System.CLSCompliantAttribute(false)]
public bool Equals(ulong other) { throw null; }
public override int GetHashCode() { throw null; }
public int GetByteCount() { throw null; }
public static System.Numerics.BigInteger GreatestCommonDivisor(System.Numerics.BigInteger left, System.Numerics.BigInteger right) { throw null; }
public static double Log(System.Numerics.BigInteger value) { throw null; }
public static double Log(System.Numerics.BigInteger value, double baseValue) { throw null; }
Expand Down Expand Up @@ -146,6 +148,7 @@ public partial struct BigInteger : System.IComparable, System.IComparable<System
public static System.Numerics.BigInteger Parse(string value, System.Globalization.NumberStyles style) { throw null; }
public static System.Numerics.BigInteger Parse(string value, System.Globalization.NumberStyles style, System.IFormatProvider provider) { throw null; }
public static System.Numerics.BigInteger Parse(string value, System.IFormatProvider provider) { throw null; }
public static System.Numerics.BigInteger Parse(System.ReadOnlySpan<char> value, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider provider = null) { throw null; }
public static System.Numerics.BigInteger Pow(System.Numerics.BigInteger value, int exponent) { throw null; }
public static System.Numerics.BigInteger Remainder(System.Numerics.BigInteger dividend, System.Numerics.BigInteger divisor) { throw null; }
public static System.Numerics.BigInteger Subtract(System.Numerics.BigInteger left, System.Numerics.BigInteger right) { throw null; }
Expand All @@ -156,6 +159,8 @@ public partial struct BigInteger : System.IComparable, System.IComparable<System
public string ToString(string format, System.IFormatProvider provider) { throw null; }
public static bool TryParse(string value, System.Globalization.NumberStyles style, System.IFormatProvider provider, out System.Numerics.BigInteger result) { throw null; }
public static bool TryParse(string value, out System.Numerics.BigInteger result) { throw null; }
public static bool TryParse(ReadOnlySpan<char> value, out System.Numerics.BigInteger result, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider provider = null) { throw null; }
public bool TryWriteBytes(System.Span<byte> destination, out int bytesWritten) { throw null; }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public partial struct Complex : System.IEquatable<System.Numerics.Complex>, System.IFormattable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal static string FormatBigInteger(int precision, int scale, bool sign, str

[SecurityCritical]
internal static bool TryStringToBigInteger(
string s,
ReadOnlySpan<char> s,
NumberStyles styles,
NumberFormatInfo numberFormatInfo,
StringBuilder receiver, // Receives the decimal digits
Expand Down
215 changes: 166 additions & 49 deletions src/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,17 +251,32 @@ public BigInteger(decimal value)
/// </summary>
/// <param name="value"></param>
[CLSCompliant(false)]
public BigInteger(byte[] value)
public BigInteger(byte[] value) :
this(new ReadOnlySpan<byte>(value ?? throw new ArgumentNullException(nameof(value))))
{
if (value == null)
throw new ArgumentNullException(nameof(value));
Contract.EndContractBlock();
}

public BigInteger(ReadOnlySpan<byte> value)
{
int byteCount = value.Length;
bool isNegative = byteCount > 0 && ((value[byteCount - 1] & 0x80) == 0x80);

// Try to conserve space as much as possible by checking for wasted leading byte[] entries
while (byteCount > 0 && value[byteCount - 1] == 0) byteCount--;
bool isNegative;
if (byteCount > 0)
{
byte lastByte = value[byteCount - 1];
isNegative = (lastByte & 0x80) != 0;
if (lastByte == 0)
{
// Try to conserve space as much as possible by checking for wasted leading byte[] entries
byteCount -= 2;
while (byteCount >= 0 && value[byteCount] == 0) byteCount--;
byteCount++;
}
}
else
{
isNegative = false;
}

if (byteCount == 0)
{
Expand All @@ -274,29 +289,26 @@ public BigInteger(byte[] value)

if (byteCount <= 4)
{
if (isNegative)
_sign = unchecked((int)0xffffffff);
else
_sign = 0;
_sign = isNegative ? unchecked((int)0xffffffff) : 0;
for (int i = byteCount - 1; i >= 0; i--)
{
_sign <<= 8;
_sign |= value[i];
_sign = (_sign << 8) | value[i];
}
_bits = null;

_bits = null;
if (_sign < 0 && !isNegative)
{
// Int32 overflow
// Example: Int64 value 2362232011 (0xCB, 0xCC, 0xCC, 0x8C, 0x0)
// can be naively packed into 4 bytes (due to the leading 0x0)
// it overflows into the int32 sign bit
_bits = new uint[1];
_bits[0] = unchecked((uint)_sign);
_bits = new uint[1] { unchecked((uint)_sign) };
_sign = +1;
}
if (_sign == int.MinValue)
{
this = s_bnMinInt;
}
}
else
{
Expand All @@ -306,18 +318,15 @@ public BigInteger(byte[] value)
uint[] val = new uint[dwordCount];

// Copy all dwords, except but don't do the last one if it's not a full four bytes
int curDword, curByte, byteInDword;
curByte = 3;
int curDword, curByte = 3;
for (curDword = 0; curDword < dwordCount - (unalignedBytes == 0 ? 0 : 1); curDword++)
{
byteInDword = 0;
while (byteInDword < 4)
for (int byteInDword = 0; byteInDword < 4; byteInDword++)
{
if (value[curByte] != 0x00) isZero = false;
val[curDword] <<= 8;
val[curDword] |= value[curByte];
byte curByteValue = value[curByte];
if (curByteValue != 0x00) isZero = false;
val[curDword] = (val[curDword] << 8) | curByteValue;
curByte--;
byteInDword++;
}
curByte += 8;
}
Expand All @@ -328,9 +337,9 @@ public BigInteger(byte[] value)
if (isNegative) val[dwordCount - 1] = 0xffffffff;
for (curByte = byteCount - 1; curByte >= byteCount - unalignedBytes; curByte--)
{
if (value[curByte] != 0x00) isZero = false;
val[curDword] <<= 8;
val[curDword] |= value[curByte];
byte curByteValue = value[curByte];
if (curByteValue != 0x00) isZero = false;
val[curDword] = (val[curDword] << 8) | curByteValue;
}
}

Expand All @@ -343,16 +352,17 @@ public BigInteger(byte[] value)
NumericsHelpers.DangerousMakeTwosComplement(val); // Mutates val

// Pack _bits to remove any wasted space after the twos complement
int len = val.Length;
while (len > 0 && val[len - 1] == 0)
len--;
int len = val.Length - 1;
while (len >= 0 && val[len] == 0) len--;
len++;

if (len == 1 && unchecked((int)(val[0])) > 0)
{
if (val[0] == 1 /* abs(-1) */)
{
this = s_bnMinusOneInt;
}
else if (val[0] == kuMaskHighBit /* abs(Int32.MinValue) */)
else if (val[0] == kuMaskHighBit) // abs(Int32.MinValue)
{
this = s_bnMinInt;
}
Expand Down Expand Up @@ -605,6 +615,16 @@ public static bool TryParse(string value, NumberStyles style, IFormatProvider pr
return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result);
}

public static BigInteger Parse(ReadOnlySpan<char> value, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
{
return BigNumber.ParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider));
}

public static bool TryParse(ReadOnlySpan<char> value, out BigInteger result, NumberStyles style = NumberStyles.Integer, IFormatProvider provider = null)
{
return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result);
}

public static int Compare(BigInteger left, BigInteger right)
{
return left.CompareTo(right);
Expand Down Expand Up @@ -1024,10 +1044,76 @@ public int CompareTo(object obj)
/// <returns></returns>
public byte[] ToByteArray()
{
int ignored = 0;
return TryGetBytes(GetBytesMode.AllocateArray, default(Span<byte>), ref ignored);
}

/// <summary>
/// Copies the value of this BigInteger as little-endian twos-complement
/// bytes, using the fewest number of bytes possible. If the value is zero,
/// outputs one byte whose element is 0x00.
/// </summary>
/// <param name="destination">The destination span to which the resulting bytes should be written.</param>
/// <param name="bytesWritten">The number of bytes written to <paramref name="destination"/>.</param>
/// <returns>true if the bytes fit in <see cref="destination"/>; false if not all bytes could be written due to lack of space.</returns>
public bool TryWriteBytes(Span<byte> destination, out int bytesWritten)
{
bytesWritten = 0;
return TryGetBytes(GetBytesMode.Span, destination, ref bytesWritten) != null;
}

/// <summary>Gets the number of bytes that will be output by <see cref="ToByteArray"/> and <see cref="TryWriteBytes(Span{byte}, out int)"/>.</summary>
/// <returns>The number of bytes.</returns>
public int GetByteCount()
{
int count = 0;
TryGetBytes(GetBytesMode.Count, default(Span<byte>), ref count);
return count;
}

/// <summary>Mode used to enable sharing <see cref="TryGetBytes(GetBytesMode, Span{byte}, out byte[], out int)"/> for multiple purposes.</summary>
private enum GetBytesMode { AllocateArray, Count, Span }

/// <summary>Dummy array returned from TryGetBytes to indicate success when in span mode.</summary>
private static readonly byte[] s_success = Array.Empty<byte>();

/// <summary>Shared logic for <see cref="ToByteArray"/>, <see cref="TryWriteBytes(Span{byte}, out int)"/>, and <see cref="GetByteCount"/>.</summary>
/// <param name="mode">Which entry point is being used.</param>
/// <param name="destination">The destination span, if mode is <see cref="GetBytesMode.Span"/>.</param>
/// <param name="bytesWritten">
/// If <paramref name="mode"/>==<see cref="GetBytesMode.AllocateArray"/>, ignored.
/// If <paramref name="mode"/>==<see cref="GetBytesMode.Count"/>, the number of bytes that would be written.
/// If <paramref name="mode"/>==<see cref="GetBytesMode.Span"/>, the number of bytes written to the span.
/// </param>
/// <returns>
/// If <paramref name="mode"/>==<see cref="GetBytesMode.AllocateArray"/>, the result array.
/// If <paramref name="mode"/>==<see cref="GetBytesMode.Count"/>, null.
/// If <paramref name="mode"/>==<see cref="GetBytesMode.Span"/>, non-null if the span was long enough, null if there wasn't enough room.
/// </returns>
private byte[] TryGetBytes(GetBytesMode mode, Span<byte> destination, ref int bytesWritten)
{
Debug.Assert(mode == GetBytesMode.AllocateArray || mode == GetBytesMode.Count || mode == GetBytesMode.Span, $"Unexpected mode {mode}.");
Debug.Assert(mode == GetBytesMode.Span || destination.IsEmpty, $"If we're not in span mode, we shouldn't have been passed a destination.");

int sign = _sign;
if (sign == 0)
{
return new byte[] { 0 };
switch (mode)
{
case GetBytesMode.AllocateArray:
return new byte[] { 0 };
case GetBytesMode.Count:
bytesWritten = 1;
return null;
default: // case GetBytesMode.Span:
if (destination.Length != 0)
{
destination[0] = 0;
bytesWritten = 1;
return s_success;
}
return null;
}
}

byte highByte;
Expand Down Expand Up @@ -1096,20 +1182,38 @@ public byte[] ToByteArray()

// Ensure high bit is 0 if positive, 1 if negative
bool needExtraByte = (msb & 0x80) != (highByte & 0x80);
byte[] bytes;
int curByte = 0;
if (bits == null)
int length = msbIndex + 1 + (needExtraByte ? 1 : 0);
if (bits != null)
{
bytes = new byte[msbIndex + 1 + (needExtraByte ? 1 : 0)];
Debug.Assert(bytes.Length <= 4);
length = checked(4 * (bits.Length - 1) + length);
}
else

byte[] array;
switch (mode)
{
bytes = new byte[checked(4 * (bits.Length - 1) + msbIndex + 1 + (needExtraByte ? 1 : 0))];
case GetBytesMode.AllocateArray:
destination = array = new byte[length];
break;
case GetBytesMode.Count:
bytesWritten = length;
return null;
default: // case GetBytesMode.Span:
if (destination.Length < length)
{
return null;
}
bytesWritten = length;
array = s_success;
break;
}

int curByte = 0;
if (bits != null)
{
for (int i = 0; i < bits.Length - 1; i++)
{
uint dword = bits[i];

if (sign == -1)
{
dword = ~dword;
Expand All @@ -1118,23 +1222,36 @@ public byte[] ToByteArray()
dword = unchecked(dword + 1U);
}
}
for (int j = 0; j < 4; j++)
{
bytes[curByte++] = unchecked((byte)dword);
dword >>= 8;
}

destination[curByte++] = unchecked((byte)dword);
destination[curByte++] = unchecked((byte)(dword >> 8));
destination[curByte++] = unchecked((byte)(dword >> 16));
destination[curByte++] = unchecked((byte)(dword >> 24));
}
}
for (int j = 0; j <= msbIndex; j++)

Debug.Assert(msbIndex >= 0 && msbIndex <= 3);
destination[curByte] = unchecked((byte)highDword);
if (msbIndex != 0)
{
bytes[curByte++] = unchecked((byte)highDword);
highDword >>= 8;
destination[++curByte] = unchecked((byte)(highDword >> 8));
if (msbIndex != 1)
{
destination[++curByte] = unchecked((byte)(highDword >> 16));
if (msbIndex != 2)
{
destination[++curByte] = unchecked((byte)(highDword >> 24));
}
}
}

Debug.Assert((!needExtraByte && curByte == length - 1) || (needExtraByte && curByte == length - 2));
if (needExtraByte)
{
bytes[bytes.Length - 1] = highByte;
destination[length - 1] = highByte;
}
return bytes;

return array;
}

/// <summary>
Expand Down
Loading

0 comments on commit 91f993e

Please sign in to comment.