Optimize NumberBufferToDecimal (dotnet/coreclr#19072)
authorPent Ploompuu <kaalikas@gmail.com>
Thu, 16 Aug 2018 15:40:59 +0000 (18:40 +0300)
committerJan Kotas <jkotas@microsoft.com>
Thu, 16 Aug 2018 15:40:59 +0000 (08:40 -0700)
Commit migrated from https://github.com/dotnet/coreclr/commit/2f5e5d912751b1cec481ec3b3c35cb8c7a4adf0a

src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs
src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs

index 75ddbfc..22574c1 100644 (file)
@@ -36,16 +36,6 @@ namespace System
             return DecCalc.DecDivMod1E9(ref AsMutable(ref value));
         }
 
-        internal static void DecAddInt32(ref decimal value, uint i)
-        {
-            DecCalc.DecAddInt32(ref AsMutable(ref value), i);
-        }
-
-        internal static void DecMul10(ref decimal value)
-        {
-            DecCalc.DecMul10(ref AsMutable(ref value));
-        }
-
         #endregion
 
         /// <summary>
@@ -2471,8 +2461,6 @@ done:
                 return;
             }
 
-#region Number Formatting helpers
-
             internal static uint DecDivMod1E9(ref DecCalc value)
             {
                 ulong high64 = ((ulong)value.uhi << 32) + value.umid;
@@ -2486,57 +2474,6 @@ done:
                 return (uint)num - div * TenToPowerNine;
             }
 
-            internal static void DecAddInt32(ref DecCalc value, uint i)
-            {
-                if (D32AddCarry(ref value.ulo, i))
-                {
-                    if (D32AddCarry(ref value.umid, 1))
-                        D32AddCarry(ref value.uhi, 1);
-                }
-            }
-
-            private static bool D32AddCarry(ref uint value, uint i)
-            {
-                uint v = value;
-                uint sum = v + i;
-                value = sum;
-                return (sum < v) || (sum < i);
-            }
-
-            internal static void DecMul10(ref DecCalc value)
-            {
-                DecCalc d = value;
-                DecShiftLeft(ref value);
-                DecShiftLeft(ref value);
-                DecAdd(ref value, d);
-                DecShiftLeft(ref value);
-            }
-
-            private static void DecShiftLeft(ref DecCalc value)
-            {
-                uint c0 = (value.Low & 0x80000000) != 0 ? 1u : 0u;
-                uint c1 = (value.Mid & 0x80000000) != 0 ? 1u : 0u;
-                value.Low = value.Low << 1;
-                value.Mid = (value.Mid << 1) | c0;
-                value.High = (value.High << 1) | c1;
-            }
-
-            private static void DecAdd(ref DecCalc value, DecCalc d)
-            {
-                if (D32AddCarry(ref value.ulo, d.Low))
-                {
-                    if (D32AddCarry(ref value.umid, 1))
-                        D32AddCarry(ref value.uhi, 1);
-                }
-
-                if (D32AddCarry(ref value.umid, d.Mid))
-                    D32AddCarry(ref value.uhi, 1);
-
-                D32AddCarry(ref value.uhi, d.High);
-            }
-
-#endregion
-
             struct PowerOvfl
             {
                 public readonly uint Hi;
index 9b9bf66..4d85fc2 100644 (file)
@@ -1468,64 +1468,88 @@ namespace System
 
         private static unsafe bool NumberBufferToDecimal(ref NumberBuffer number, ref decimal value)
         {
-            decimal d = new decimal();
-
             char* p = number.digits;
             int e = number.scale;
-            if (*p == 0)
+            bool sign = number.sign;
+            uint c = *p;
+            if (c == 0)
             {
                 // To avoid risking an app-compat issue with pre 4.5 (where some app was illegally using Reflection to examine the internal scale bits), we'll only force
                 // the scale to 0 if the scale was previously positive (previously, such cases were unparsable to a bug.)
-                if (e > 0)
+                value = new decimal(0, 0, 0, sign, (byte)Math.Clamp(-e, 0, 28));
+                return true;
+            }
+
+            if (e > DecimalPrecision)
+                return false;
+
+            ulong low64 = 0;
+            while (e > -28)
+            {
+                e--;
+                low64 *= 10;
+                low64 += c - '0';
+                c = *++p;
+                if (low64 >= ulong.MaxValue / 10)
+                    break;
+                if (c == 0)
                 {
-                    e = 0;
+                    while (e > 0)
+                    {
+                        e--;
+                        low64 *= 10;
+                        if (low64 >= ulong.MaxValue / 10)
+                            break;
+                    }
+                    break;
                 }
             }
-            else
+
+            uint high = 0;
+            while ((e > 0 || (c != 0 && e > -28)) &&
+              (high < uint.MaxValue / 10 || (high == uint.MaxValue / 10 && (low64 < 0x99999999_99999999 || (low64 == 0x99999999_99999999 && c <= '5')))))
             {
-                if (e > DecimalPrecision)
-                    return false;
+                // multiply by 10
+                ulong tmpLow = (uint)low64 * 10UL;
+                ulong tmp64 = (uint)(low64 >> 32) * 10UL + (tmpLow >> 32);
+                low64 = (uint)tmpLow + (tmp64 << 32);
+                high = (uint)(tmp64 >> 32) + high * 10;
 
-                while (((e > 0) || ((*p != 0) && (e > -28))) &&
-                       ((d.High < 0x19999999) || ((d.High == 0x19999999) &&
-                                                  ((d.Mid < 0x99999999) || ((d.Mid == 0x99999999) &&
-                                                                            ((d.Low < 0x99999999) || ((d.Low == 0x99999999) &&
-                                                                                                      (*p <= '5'))))))))
+                if (c != 0)
                 {
-                    decimal.DecMul10(ref d);
-                    if (*p != 0)
-                        decimal.DecAddInt32(ref d, (uint)(*p++ - '0'));
-                    e--;
+                    c -= '0';
+                    low64 += c;
+                    if (low64 < c)
+                        high++;
+                    c = *++p;
                 }
+                e--;
+            }
 
-                if (*p++ >= '5')
+            if (c >= '5')
+            {
+                // If the next digit is 5, round up if the number is odd or any following digit is non-zero
+                if (c == '5' && (low64 & 1) == 0)
                 {
-                    bool round = true;
-                    if ((*(p - 1) == '5') && ((*(p - 2) % 2) == 0))
+                    c = *++p;
+                    int count = 20; // Look at the next 20 digits to check to round
+                    while (c == '0' && count != 0)
                     {
-                        // Check if previous digit is even, only if the when we are unsure whether hows to do
-                        // Banker's rounding. For digits > 5 we will be roundinp up anyway.
-                        int count = 20; // Look at the next 20 digits to check to round
-                        while ((*p == '0') && (count != 0))
-                        {
-                            p++;
-                            count--;
-                        }
-                        if ((*p == '\0') || (count == 0))
-                            round = false;// Do nothing
+                        c = *++p;
+                        count--;
                     }
+                    if (c == 0 || count == 0)
+                        goto NoRounding;// Do nothing
+                }
 
-                    if (round)
-                    {
-                        decimal.DecAddInt32(ref d, 1);
-                        if ((d.High | d.Mid | d.Low) == 0)
-                        {
-                            d = new decimal(unchecked((int)0x9999999A), unchecked((int)0x99999999), 0x19999999, false, 0);
-                            e++;
-                        }
-                    }
+                if (++low64 == 0 && ++high == 0)
+                {
+                    low64 = 0x99999999_9999999A;
+                    high = uint.MaxValue / 10;
+                    e++;
                 }
             }
+        NoRounding:
 
             if (e > 0)
                 return false;
@@ -1534,11 +1558,11 @@ namespace System
             {
                 // Parsing a large scale zero can give you more precision than fits in the decimal.
                 // This should only happen for actual zeros or very small numbers that round to zero.
-                value = new decimal(0, 0, 0, number.sign, DecimalPrecision - 1);
+                value = new decimal(0, 0, 0, sign, DecimalPrecision - 1);
             }
             else
             {
-                value = new decimal((int)d.Low, (int)d.Mid, (int)d.High, number.sign, (byte)-e);
+                value = new decimal((int)low64, (int)(low64 >> 32), (int)high, sign, (byte)-e);
             }
             return true;
         }