Add span-based methods to Decimal (#32155)
authorStephen Toub <stoub@microsoft.com>
Thu, 13 Feb 2020 02:43:16 +0000 (18:43 -0800)
committerGitHub <noreply@github.com>
Thu, 13 Feb 2020 02:43:16 +0000 (18:43 -0800)
* Add span-based methods to Decimal

* Address PR feedback

Add missing tests

* Address PR feedback

src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs
src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/ILGen.cs
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System/Decimal.cs
src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/DecimalUtilities.cs
src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs
src/libraries/System.Runtime/ref/System.Runtime.cs
src/libraries/System.Runtime/tests/System/DecimalTests.cs

index 65c6740..06f9a24 100644 (file)
@@ -486,7 +486,8 @@ namespace System.Data.SqlTypes
             // 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
index 17b289b..a87dccf 100644 (file)
@@ -1079,7 +1079,9 @@ namespace System.Linq.Expressions.Compiler
 
         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)
             {
index 72cf637..346e3a8 100644 (file)
     <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>
index 5366e58..da61628 100644 (file)
@@ -243,10 +243,18 @@ namespace System
         // 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];
@@ -522,6 +530,50 @@ namespace System
             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");
index ab8b303..cd05cbf 100644 (file)
@@ -8,12 +8,23 @@ namespace System.Reflection.Internal
     {
         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.
index 42da680..7f673e2 100644 (file)
@@ -206,7 +206,8 @@ namespace System.Numerics
         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);
 
index 122e003..71262df 100644 (file)
@@ -883,6 +883,7 @@ namespace System
         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)]
@@ -901,6 +902,7 @@ namespace System
         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; }
@@ -998,6 +1000,7 @@ namespace System
         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; }
index f132752..fa67444 100644 (file)
@@ -140,6 +140,13 @@ namespace System.Tests
             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 @@ namespace System.Tests
         public void Ctor_InvalidBits_ThrowsArgumentException(int[] bits)
         {
             AssertExtensions.Throws<ArgumentException>(null, () => new decimal(bits));
+            AssertExtensions.Throws<ArgumentException>(null, () => new decimal(bits.AsSpan()));
         }
 
         [Theory]
@@ -619,6 +627,54 @@ namespace System.Tests
             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()
         {