From 4c5f9dbf6235bea6f1f6b1a86e612bf3664271a5 Mon Sep 17 00:00:00 2001 From: Pent Ploompuu Date: Thu, 16 Aug 2018 18:40:59 +0300 Subject: [PATCH] Optimize NumberBufferToDecimal (dotnet/coreclr#19072) Commit migrated from https://github.com/dotnet/coreclr/commit/2f5e5d912751b1cec481ec3b3c35cb8c7a4adf0a --- .../src/System/Decimal.DecCalc.cs | 63 ------------ .../src/System/Number.Parsing.cs | 106 +++++++++++++-------- 2 files changed, 65 insertions(+), 104 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs index 75ddbfc..22574c1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs @@ -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 /// @@ -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; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index 9b9bf66..4d85fc2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -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; } -- 2.7.4