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
Add Span-based APIs to BigInteger
Browse files Browse the repository at this point in the history
Adds:
```C#
public BigInteger(ReadOnlySpan<byte> value);
public int GetByteCount();
public static BigInteger Parse(ReadOnlySpan<char> value, NumberStyles style, IFormatProvider provider);
public static bool TryParse(ReadOnlySpan<char> value, out BigInteger result, NumberStyles style, IFormatProvider provider);
public bool TryWriteBytes(Span<byte> destination, out int bytesWritten);
```

Rather than duplicate a lot of logic, existing code paths were changed to use {ReadOnly}Span, with string and array-based overloads targeting them as well.
  • Loading branch information
stephentoub committed Aug 2, 2017
1 parent dd38e6f commit dccf73b
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 86 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
127 changes: 109 additions & 18 deletions src/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,13 @@ 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);

Expand Down Expand Up @@ -605,6 +606,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 +1035,75 @@ public int CompareTo(object obj)
/// <returns></returns>
public byte[] ToByteArray()
{
bool success = TryGetBytes(GetBytesMode.AllocateArray, default(Span<byte>), out byte[] array, out int bytesWritten);
Debug.Assert(success, "ToByteArray should always succeed");
Debug.Assert(array != null, "Expected non-null array");
Debug.Assert(bytesWritten == array.Length);
return array;
}

/// <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) =>
TryGetBytes(GetBytesMode.Span, destination, out byte[] _, out bytesWritten);

/// <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()
{
bool success = TryGetBytes(GetBytesMode.Count, default(Span<byte>), out byte[] _, out int count);
Debug.Assert(success);
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 { Count, AllocateArray, Span }

/// <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="array">The output array, if mode is <see cref="GetBytesMode.AllocateArray"/>.</param>
/// <param name="bytesWritten">
/// The number of bytes written to <paramref name="destination"/> or <paramref name="array"/>,
/// or if counting, the number of bytes that would have been written.
/// </param>
/// <returns>false if in <see cref="GetBytesMode.Span"/> mode and the destination isn't large enough; otherwise, true.</returns>
private bool TryGetBytes(GetBytesMode mode, Span<byte> destination, out byte[] array, out 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.");
array = null;
bytesWritten = 0;

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

byte highByte;
Expand Down Expand Up @@ -1096,17 +1172,29 @@ 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)
{
bytes = new byte[msbIndex + 1 + (needExtraByte ? 1 : 0)];
Debug.Assert(bytes.Length <= 4);
int length = bits == null ?
msbIndex + 1 + (needExtraByte ? 1 : 0) :
checked(4 * (bits.Length - 1) + msbIndex + 1 + (needExtraByte ? 1 : 0));
switch (mode)
{
case GetBytesMode.Count:
bytesWritten = length;
return true;
case GetBytesMode.AllocateArray:
destination = array = new byte[length];
break;
default: // case GetBytesMode.Span:
if (destination.Length < length)
{
bytesWritten = 0;
return false;
}
break;
}
else
{
bytes = new byte[checked(4 * (bits.Length - 1) + msbIndex + 1 + (needExtraByte ? 1 : 0))];

int curByte = 0;
if (bits != null)
{
for (int i = 0; i < bits.Length - 1; i++)
{
uint dword = bits[i];
Expand All @@ -1120,21 +1208,24 @@ public byte[] ToByteArray()
}
for (int j = 0; j < 4; j++)
{
bytes[curByte++] = unchecked((byte)dword);
destination[curByte++] = unchecked((byte)dword);
dword >>= 8;
}
}
}

for (int j = 0; j <= msbIndex; j++)
{
bytes[curByte++] = unchecked((byte)highDword);
destination[curByte++] = unchecked((byte)highDword);
highDword >>= 8;
}
if (needExtraByte)
{
bytes[bytes.Length - 1] = highByte;
destination[length - 1] = highByte;
}
return bytes;

bytesWritten = length;
return true;
}

/// <summary>
Expand Down
30 changes: 30 additions & 0 deletions src/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,18 @@ internal static bool TryValidateParseStyleInteger(NumberStyles style, out Argume

[SecuritySafeCritical]
internal static bool TryParseBigInteger(string value, NumberStyles style, NumberFormatInfo info, out BigInteger result)
{
if (value == null)
{
result = default(BigInteger);
return false;
}

return TryParseBigInteger(AsReadOnlySpan(value), style, info, out result);
}

[SecuritySafeCritical]
internal static bool TryParseBigInteger(ReadOnlySpan<char> value, NumberStyles style, NumberFormatInfo info, out BigInteger result)
{
unsafe
{
Expand Down Expand Up @@ -357,8 +369,26 @@ internal static bool TryParseBigInteger(string value, NumberStyles style, Number
internal static BigInteger ParseBigInteger(string value, NumberStyles style, NumberFormatInfo info)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}

return ParseBigInteger(AsReadOnlySpan(value), style, info);
}

// TODO #22688: Remove this and replace it with the real AsReadOnlySpan extension
// method from System.Memory once the System.Memory package is marked stable
// and the package validation system allows us to take a dependency on it.
private static unsafe ReadOnlySpan<char> AsReadOnlySpan(string s)
{
fixed (char* c = s)
{
return new ReadOnlySpan<char>(c, s.Length);
}
}

internal static BigInteger ParseBigInteger(ReadOnlySpan<char> value, NumberStyles style, NumberFormatInfo info)
{
ArgumentException e;
if (!TryValidateParseStyleInteger(style, out e))
throw e;
Expand Down
Loading

0 comments on commit dccf73b

Please sign in to comment.