Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add span-based methods to Decimal #32155

Merged
merged 3 commits into from
Feb 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,8 @@ public SqlDecimal(decimal value)
// m_data1 = *pInt++; // lo part
// m_data2 = *pInt++; // mid part

int[] bits = decimal.GetBits(value);
Span<int> bits = stackalloc int[4];
decimal.GetBits(value, bits);
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
uint sgnscl;

unchecked
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,9 @@ internal static void EmitArray(this ILGenerator il, Type arrayType)

private static void EmitDecimal(this ILGenerator il, decimal value)
{
int[] bits = decimal.GetBits(value);
Span<int> bits = stackalloc int[4];
decimal.GetBits(value, bits);

int scale = (bits[3] & int.MaxValue) >> 16;
if (scale == 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@
<value>Combination of arguments to the DateTime constructor is out of the legal range.</value>
</data>
<data name="Arg_DecBitCtor" xml:space="preserve">
<value>Decimal byte array constructor requires an array of length four containing valid decimal bytes.</value>
<value>Decimal constructor requires an array or span of four valid decimal bytes.</value>
</data>
<data name="Arg_DirectoryNotFoundException" xml:space="preserve">
<value>Attempted to access a path that is not on the disk.</value>
Expand Down
58 changes: 55 additions & 3 deletions src/libraries/System.Private.CoreLib/src/System/Decimal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,18 @@ public static long ToOACurrency(decimal value)
// The possible binary representations of a particular value are all
// equally valid, and all are numerically equivalent.
//
public Decimal(int[] bits)
public Decimal(int[] bits) :
this((ReadOnlySpan<int>)(bits ?? throw new ArgumentNullException(nameof(bits))))
{
}

/// <summary>
/// Initializes a new instance of <see cref="decimal"/> to a decimal value represented in binary and contained in the specified span.
/// </summary>
/// <param name="bits">A span of four <see cref="int"/>s containing a binary representation of a decimal value.</param>
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
/// <exception cref="ArgumentException">The length of <paramref name="bits"/> is not 4, or the representation of the decimal value in <paramref name="bits"/> is not valid.</exception>
public Decimal(ReadOnlySpan<int> bits)
{
if (bits == null)
throw new ArgumentNullException(nameof(bits));
if (bits.Length == 4)
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
{
int f = bits[3];
Expand Down Expand Up @@ -522,6 +530,50 @@ public static int[] GetBits(decimal d)
return new int[] { d.lo, d.mid, d.hi, d.flags };
}

/// <summary>
/// Converts the value of a specified instance of <see cref="decimal"/> to its equivalent binary representation.
/// </summary>
/// <param name="d">The value to convert.</param>
/// <param name="destination">The span into which to store the four-integer binary representation.</param>
/// <returns>Four, the number of integers in the binary representation.</returns>
/// <exception cref="ArgumentException">The destination span was not long enough to store the binary representation.</exception>
public static int GetBits(decimal d, Span<int> destination)
{
if ((uint)destination.Length <= 3)
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
{
ThrowHelper.ThrowArgumentException_DestinationTooShort();
}

destination[0] = d.lo;
destination[1] = d.mid;
destination[2] = d.hi;
destination[3] = d.flags;
return 4;
}

/// <summary>
/// Tries to convert the value of a specified instance of <see cref="decimal"/> to its equivalent binary representation.
/// </summary>
/// <param name="d">The value to convert.</param>
/// <param name="destination">The span into which to store the binary representation.</param>
/// <param name="valuesWritten">The number of integers written to the destination.</param>
/// <returns>true if the decimal's binary representation was written to the destination; false if the destination wasn't long enough.</returns>
public static bool TryGetBits(decimal d, Span<int> destination, out int valuesWritten)
{
if ((uint)destination.Length <= 3)
{
valuesWritten = 0;
return false;
}

destination[0] = d.lo;
destination[1] = d.mid;
destination[2] = d.hi;
destination[3] = d.flags;
valuesWritten = 4;
return true;
}

internal static void GetBytes(in decimal d, byte[] buffer)
{
Debug.Assert(buffer != null && buffer.Length >= 16, "[GetBytes]buffer != null && buffer.Length >= 16");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,23 @@ internal static class DecimalUtilities
{
public static int GetScale(this decimal value)
{
#if NETCOREAPP
Span<int> bits = stackalloc int[4];
decimal.GetBits(value, bits);
return unchecked((byte)(bits[3] >> 16));
#else
return unchecked((byte)(decimal.GetBits(value)[3] >> 16));
#endif
}

public static void GetBits(this decimal value, out bool isNegative, out byte scale, out uint low, out uint mid, out uint high)
{
#if NETCOREAPP
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
Span<int> bits = stackalloc int[4];
decimal.GetBits(value, bits);
#else
int[] bits = decimal.GetBits(value);
#endif

// The return value is a four-element array of 32-bit signed integers.
// The first, second, and third elements of the returned array contain the low, middle, and high 32 bits of the 96-bit integer number.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ public BigInteger(double value)
public BigInteger(decimal value)
{
// First truncate to get scale to 0 and extract bits
int[] bits = decimal.GetBits(decimal.Truncate(value));
Span<int> bits = stackalloc int[4];
decimal.GetBits(decimal.Truncate(value), bits);

Debug.Assert(bits.Length == 4 && (bits[3] & DecimalScaleFactorMask) == 0);

Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,7 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S
public Decimal(int value) { throw null; }
public Decimal(int lo, int mid, int hi, bool isNegative, byte scale) { throw null; }
public Decimal(int[] bits) { throw null; }
public Decimal(System.ReadOnlySpan<int> bits) { throw null; }
public Decimal(long value) { throw null; }
public Decimal(float value) { throw null; }
[System.CLSCompliantAttribute(false)]
Expand All @@ -901,6 +902,7 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S
public static System.Decimal Floor(System.Decimal d) { throw null; }
public static System.Decimal FromOACurrency(long cy) { throw null; }
public static int[] GetBits(System.Decimal d) { throw null; }
public static int GetBits(System.Decimal d, System.Span<int> destination) { throw null; }
public override int GetHashCode() { throw null; }
public System.TypeCode GetTypeCode() { throw null; }
public static System.Decimal Multiply(System.Decimal d1, System.Decimal d2) { throw null; }
Expand Down Expand Up @@ -998,6 +1000,7 @@ void System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(obj
public static ulong ToUInt64(System.Decimal d) { throw null; }
public static System.Decimal Truncate(System.Decimal d) { throw null; }
public bool TryFormat(System.Span<char> destination, out int charsWritten, System.ReadOnlySpan<char> format = default(System.ReadOnlySpan<char>), System.IFormatProvider? provider = null) { throw null; }
public static bool TryGetBits(System.Decimal d, System.Span<int> destination, out int valuesWritten) { throw null; }
public static bool TryParse(System.ReadOnlySpan<char> s, out System.Decimal result) { throw null; }
public static bool TryParse(System.ReadOnlySpan<char> s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Decimal result) { throw null; }
public static bool TryParse(string? s, out System.Decimal result) { throw null; }
Expand Down
56 changes: 56 additions & 0 deletions src/libraries/System.Runtime/tests/System/DecimalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ public void Ctor_IntArray(int[] value, decimal expected)
Assert.Equal(expected, new decimal(value));
}

[Theory]
[MemberData(nameof(Ctor_IntArray_TestData))]
public void Ctor_IntSpan(int[] value, decimal expected)
{
Assert.Equal(expected, new decimal(value.AsSpan()));
}

[Fact]
public void Ctor_NullBits_ThrowsArgumentNullException()
{
Expand All @@ -155,6 +162,7 @@ public void Ctor_NullBits_ThrowsArgumentNullException()
public void Ctor_InvalidBits_ThrowsArgumentException(int[] bits)
{
AssertExtensions.Throws<ArgumentException>(null, () => new decimal(bits));
AssertExtensions.Throws<ArgumentException>(null, () => new decimal(bits.AsSpan()));
}

[Theory]
Expand Down Expand Up @@ -619,6 +627,54 @@ public static void GetBits(decimal input, int[] expected)
Assert.Equal(input, newValue);
}

[Theory]
[MemberData(nameof(GetBits_TestData))]
public static void GetBitsSpan(decimal input, int[] expected)
{
Span<int> bits = new int[4];
int bitsWritten = decimal.GetBits(input, bits);
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

Assert.Equal(4, bitsWritten);
Assert.Equal(expected, bits.ToArray());

bool sign = (bits[3] & 0x80000000) != 0;
byte scale = (byte)((bits[3] >> 16) & 0x7F);
decimal newValue = new decimal(bits[0], bits[1], bits[2], sign, scale);

Assert.Equal(input, newValue);
}

[Fact]
public static void GetBitsSpan_TooShort_ThrowsArgumentException()
{
AssertExtensions.Throws<ArgumentException>("destination", () => decimal.GetBits(123, new int[3]));
}

[Theory]
[MemberData(nameof(GetBits_TestData))]
public static void TryGetBits(decimal input, int[] expected)
{
Span<int> bits;
int valuesWritten;

bits = new int[3] { 42, 43, 44 };
Assert.False(decimal.TryGetBits(input, bits, out valuesWritten));
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
Assert.Equal(0, valuesWritten);
Assert.Equal(new int[3] { 42, 43, 44 }, bits.ToArray());

bits = new int[4];
Assert.True(decimal.TryGetBits(input, bits, out valuesWritten));
Assert.Equal(4, valuesWritten);
Assert.Equal(expected, bits.ToArray());

bits = new int[5];
bits[4] = 42;
Assert.True(decimal.TryGetBits(input, bits, out valuesWritten));
Assert.Equal(4, valuesWritten);
Assert.Equal(expected, bits.Slice(0, 4).ToArray());
Assert.Equal(42, bits[4]);
}

[Fact]
public void GetTypeCode_Invoke_ReturnsDecimal()
{
Expand Down