diff --git a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs index 65c6740723aa2..06f9a24656d3c 100644 --- a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs +++ b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs @@ -486,7 +486,8 @@ public SqlDecimal(decimal value) // m_data1 = *pInt++; // lo part // m_data2 = *pInt++; // mid part - int[] bits = decimal.GetBits(value); + Span bits = stackalloc int[4]; + decimal.GetBits(value, bits); uint sgnscl; unchecked diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/ILGen.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/ILGen.cs index 17b289b1f58f4..a87dccf2c9f2c 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/ILGen.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/ILGen.cs @@ -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 bits = stackalloc int[4]; + decimal.GetBits(value, bits); + int scale = (bits[3] & int.MaxValue) >> 16; if (scale == 0) { diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 72cf637b496ce..346e3a8615046 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -263,7 +263,7 @@ Combination of arguments to the DateTime constructor is out of the legal range. - Decimal byte array constructor requires an array of length four containing valid decimal bytes. + Decimal constructor requires an array or span of four valid decimal bytes. Attempted to access a path that is not on the disk. diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index 5366e58a23cff..da61628abb064 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -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)(bits ?? throw new ArgumentNullException(nameof(bits)))) + { + } + + /// + /// Initializes a new instance of to a decimal value represented in binary and contained in the specified span. + /// + /// A span of four s containing a binary representation of a decimal value. + /// The length of is not 4, or the representation of the decimal value in is not valid. + public Decimal(ReadOnlySpan bits) { - if (bits == null) - throw new ArgumentNullException(nameof(bits)); if (bits.Length == 4) { int f = bits[3]; @@ -522,6 +530,50 @@ public static int[] GetBits(decimal d) return new int[] { d.lo, d.mid, d.hi, d.flags }; } + /// + /// Converts the value of a specified instance of to its equivalent binary representation. + /// + /// The value to convert. + /// The span into which to store the four-integer binary representation. + /// Four, the number of integers in the binary representation. + /// The destination span was not long enough to store the binary representation. + public static int GetBits(decimal d, Span 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; + } + + /// + /// Tries to convert the value of a specified instance of to its equivalent binary representation. + /// + /// The value to convert. + /// The span into which to store the binary representation. + /// The number of integers written to the destination. + /// true if the decimal's binary representation was written to the destination; false if the destination wasn't long enough. + public static bool TryGetBits(decimal d, Span 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"); diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/DecimalUtilities.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/DecimalUtilities.cs index ab8b30374f4c2..cd05cbfa1b1c3 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/DecimalUtilities.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/DecimalUtilities.cs @@ -8,12 +8,23 @@ internal static class DecimalUtilities { public static int GetScale(this decimal value) { +#if NETCOREAPP + Span 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 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. 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 42da6808e9890..7f673e2cde7da 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -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 bits = stackalloc int[4]; + decimal.GetBits(decimal.Truncate(value), bits); Debug.Assert(bits.Length == 4 && (bits[3] & DecimalScaleFactorMask) == 0); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 122e003723213..71262dfdb0597 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -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 bits) { throw null; } public Decimal(long value) { throw null; } public Decimal(float value) { throw null; } [System.CLSCompliantAttribute(false)] @@ -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 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; } @@ -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 destination, out int charsWritten, System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryGetBits(System.Decimal d, System.Span destination, out int valuesWritten) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.Decimal result) { throw null; } public static bool TryParse(System.ReadOnlySpan 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; } diff --git a/src/libraries/System.Runtime/tests/System/DecimalTests.cs b/src/libraries/System.Runtime/tests/System/DecimalTests.cs index f132752714c25..fa6744481c07b 100644 --- a/src/libraries/System.Runtime/tests/System/DecimalTests.cs +++ b/src/libraries/System.Runtime/tests/System/DecimalTests.cs @@ -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() { @@ -155,6 +162,7 @@ public void Ctor_NullBits_ThrowsArgumentNullException() public void Ctor_InvalidBits_ThrowsArgumentException(int[] bits) { AssertExtensions.Throws(null, () => new decimal(bits)); + AssertExtensions.Throws(null, () => new decimal(bits.AsSpan())); } [Theory] @@ -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 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("destination", () => decimal.GetBits(123, new int[3])); + } + + [Theory] + [MemberData(nameof(GetBits_TestData))] + public static void TryGetBits(decimal input, int[] expected) + { + Span 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() {