Skip to content

Commit

Permalink
Add span-based methods to Decimal (#32155)
Browse files Browse the repository at this point in the history
* Add span-based methods to Decimal

* Address PR feedback

Add missing tests

* Address PR feedback
  • Loading branch information
stephentoub committed Feb 13, 2020
1 parent ad7e1ff commit 6c30d91
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 7 deletions.
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);
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>
/// <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)
{
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)
{
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
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);

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));
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

0 comments on commit 6c30d91

Please sign in to comment.