From 705d36896dc0154b90512606d4946cd47beab3b2 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 Oct 2018 09:49:12 -0700 Subject: [PATCH 01/29] Don't normalize -0.0 to 0.0 when parsing --- .../shared/System/Number.Parsing.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 802f15745124..a4191547fa62 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1799,20 +1799,15 @@ private static void ThrowOverflowOrFormatException(bool overflow, string overflo private static bool TryNumberToDouble(ref NumberBuffer number, ref double value) { - double d = NumberToDouble(ref number); - if (!double.IsFinite(d)) + double result = NumberToDouble(ref number); + + if (!double.IsFinite(result)) { value = default; return false; } - if (d == 0.0) - { - // normalize -0.0 to 0.0 - d = 0.0; - } - - value = d; + value = result; return true; } } From 380a9eda9887189c993a28301c4055b5ed1f1c95 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 Oct 2018 09:50:24 -0700 Subject: [PATCH 02/29] Updating NumberBuffer to validate the constructor inputs --- .../shared/System/Number.NumberBuffer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs index f96687e43e84..31b038c1f6c2 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using System.Runtime.InteropServices; using Internal.Runtime.CompilerServices; @@ -26,13 +27,18 @@ internal unsafe ref struct NumberBuffer public NumberBufferKind Kind; public Span Digits; - public NumberBuffer(NumberBufferKind kind, char* pDigits, int digitsLength) + public NumberBuffer(NumberBufferKind kind, char* digits, int digitsLength) { + Debug.Assert(Enum.IsDefined(typeof(NumberBufferKind), kind)); + Debug.Assert(kind != NumberBufferKind.Unknown); + Debug.Assert(digits != null); + Debug.Assert(digitsLength > 0); + Precision = 0; Scale = 0; Sign = false; Kind = kind; - Digits = new Span(pDigits, digitsLength); + Digits = new Span(digits, digitsLength); } public char* GetDigitsPointer() From 79b5d488c9dc9dfdb9ef77443c95cb2a2715121d Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 Oct 2018 09:51:07 -0700 Subject: [PATCH 03/29] Updating NumberToDouble to just get the precision --- .../shared/System/Number.NumberToDouble.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs index 974786d3de28..db7c9924887e 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs @@ -265,18 +265,6 @@ private static uint DigitsToInt(char* p, int count) return res; } - private static int GetLength(char* src) - { - int length = 0; - - while (src[length] != '\0') - { - length++; - } - - return length; - } - // helper function to multiply two 32-bit uints private static ulong Mul32x32To64(uint a, uint b) { @@ -315,7 +303,7 @@ private static double NumberToDouble(ref NumberBuffer number) #endif char* src = number.GetDigitsPointer(); - int total = GetLength(src); + int total = number.Precision; int remaining = total; // skip the leading zeros From 92bfc263cb8384f7bfcc6c27bb7d03c6b5bbd2b4 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 Oct 2018 10:15:04 -0700 Subject: [PATCH 04/29] Don't check for non-significant zero's in NumberToDouble --- .../shared/System/Number.NumberToDouble.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs index db7c9924887e..59f4ee94649e 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs @@ -306,14 +306,9 @@ private static double NumberToDouble(ref NumberBuffer number) int total = number.Precision; int remaining = total; - // skip the leading zeros - while (src[0] == '0') - { - remaining--; - src++; - } + Debug.Assert(src[0] != '0'); - if (remaining == 0) + if (total == 0) { return number.Sign ? -0.0 : 0.0; } From f1e6c170a1b967e677523fd6e1ef19b624745400 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 Oct 2018 11:01:53 -0700 Subject: [PATCH 05/29] Updating Number.BigInteger to carry additional space for the worst-case scenario --- .../shared/System/Number.BigInteger.cs | 111 +++++++++++++++++- 1 file changed, 107 insertions(+), 4 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs index 2599823c44cf..6aac739f90c1 100644 --- a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs +++ b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs @@ -13,7 +13,25 @@ internal static partial class Number [StructLayout(LayoutKind.Sequential, Pack = 1)] internal unsafe ref struct BigInteger { - private const int MaxBlockCount = 35; + // The longest binary mantissa requires: explicit mantissa bits + abs(min exponent) + // * Half: 10 + 14 = 24 + // * Single: 23 + 126 = 149 + // * Double: 52 + 1022 = 1074 + // * Quad: 112 + 16382 = 16494 + private const int BitsForLongestBinaryMantissa = 1074; + + // The longest digit sequence requires: ceil(log2(pow(10, max significant digits + 1 rounding digit))) + // * Half: ceil(log2(pow(10, 21 + 1))) = 74 + // * Single: ceil(log2(pow(10, 112 + 1))) = 376 + // * Double: ceil(log2(pow(10, 767 + 1))) = 2552 + // * Quad: ceil(log2(pow(10, 11563 + 1))) = 38415 + private const int BitsForLongestDigitSequence = 2552; + + // We require BitsPerBlock additional bits for shift space used during the pre-division preparation + private const int MaxBits = BitsForLongestBinaryMantissa + BitsForLongestDigitSequence + BitsPerBlock; + + private const int BitsPerBlock = sizeof(int) * 8; + private const int MaxBlockCount = (MaxBits + (BitsPerBlock - 1)) / BitsPerBlock; private static readonly uint[] s_Pow10UInt32Table = new uint[] { @@ -27,7 +45,7 @@ internal unsafe ref struct BigInteger 10000000, // 10^7 }; - private static readonly int[] s_s_Pow10BigNumTableIndices = new int[] + private static readonly int[] s_Pow10BigNumTableIndices = new int[] { 0, // 10^8 2, // 10^16 @@ -112,7 +130,88 @@ internal unsafe ref struct BigInteger 0x5FDCEFCE, 0x000553F7, - // Trailing blocks to ensure MaxBlockCount + // 88 Trailing blocks to ensure MaxBlockCount + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, @@ -438,6 +537,10 @@ public static void Pow10(uint exponent, ref BigInteger result) // // More details of this implementation can be found at: https://github.com/dotnet/coreclr/pull/12894#discussion_r128890596 + // Validate that `s_Pow10BigNumTable` has exactly enough trailing elements to fill a BigInteger (which contains MaxBlockCount + 1 elements) + // We validate here, since this is the only current consumer of the array + Debug.Assert((s_Pow10BigNumTableIndices[s_Pow10BigNumTableIndices.Length - 1] + MaxBlockCount + 1) == s_Pow10BigNumTable.Length); + BigInteger temp1 = new BigInteger(s_Pow10UInt32Table[exponent & 0x7]); ref BigInteger lhs = ref temp1; @@ -453,7 +556,7 @@ public static void Pow10(uint exponent, ref BigInteger result) if ((exponent & 1) != 0) { // Multiply into the next temporary - ref BigInteger rhs = ref *(BigInteger*)(Unsafe.AsPointer(ref s_Pow10BigNumTable[s_s_Pow10BigNumTableIndices[index]])); + ref BigInteger rhs = ref *(BigInteger*)(Unsafe.AsPointer(ref s_Pow10BigNumTable[s_Pow10BigNumTableIndices[index]])); Multiply(ref lhs, ref rhs, ref product); // Swap to the next temporary From b80353ca8f04692a78a8fae764831fbe5176a214 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 30 Oct 2018 19:30:59 -0700 Subject: [PATCH 06/29] Removing some dead code from double.TryParse --- .../shared/System/Double.cs | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Double.cs b/src/System.Private.CoreLib/shared/System/Double.cs index 5a94551410c6..232a3f7cfffe 100644 --- a/src/System.Private.CoreLib/shared/System/Double.cs +++ b/src/System.Private.CoreLib/shared/System/Double.cs @@ -341,28 +341,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out double result) { - bool success = Number.TryParseDouble(s, style, info, out result, out _); - if (!success) - { - ReadOnlySpan sTrim = s.Trim(); - if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) - { - result = PositiveInfinity; - } - else if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) - { - result = NegativeInfinity; - } - else if (sTrim.EqualsOrdinal(info.NaNSymbol)) - { - result = NaN; - } - else - { - return false; // We really failed - } - } - return true; + return Number.TryParseDouble(s, style, info, out result, out _); } // From b1f461146a88a9cf9283e3f8b4b3ffbaafea92bf Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 30 Oct 2018 20:05:40 -0700 Subject: [PATCH 07/29] Updating NumberToDouble to use the RealParser implementation from Roslyn --- .../System.Private.CoreLib.Shared.projitems | 2 +- .../shared/System/Number.BigInteger.cs | 573 ++++++++++++++++-- .../shared/System/Number.Dragon4.cs | 8 +- .../shared/System/Number.Formatting.cs | 8 +- .../shared/System/Number.NumberBuffer.cs | 2 +- .../shared/System/Number.NumberToDouble.cs | 432 ------------- .../Number.NumberToFloatingPointBitsRoslyn.cs | 499 +++++++++++++++ .../shared/System/Number.Parsing.cs | 62 +- 8 files changed, 1074 insertions(+), 512 deletions(-) delete mode 100644 src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs create mode 100644 src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index 078444fb8f48..cd7823c08841 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -294,7 +294,7 @@ - + diff --git a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs index 6aac739f90c1..8e49691fc44a 100644 --- a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs +++ b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs @@ -53,6 +53,8 @@ internal unsafe ref struct BigInteger 10, // 10^64 18, // 10^128 33, // 10^256 + 61, // 10^512 + 116, // 10^1024 }; private static readonly uint[] s_Pow10BigNumTable = new uint[] @@ -130,41 +132,9 @@ internal unsafe ref struct BigInteger 0x5FDCEFCE, 0x000553F7, - // 88 Trailing blocks to ensure MaxBlockCount - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, + // 10^512 + 54, // _length + 0x00000000, // _blocks 0x00000000, 0x00000000, 0x00000000, @@ -180,6 +150,48 @@ internal unsafe ref struct BigInteger 0x00000000, 0x00000000, 0x00000000, + 0xFC6CF801, + 0x77F27267, + 0x8F9546DC, + 0x5D96976F, + 0xB83A8A97, + 0xC31E1AD9, + 0x46C40513, + 0x94E65747, + 0xC88976C1, + 0x4475B579, + 0x28F8733B, + 0xAA1DA1BF, + 0x703ED321, + 0x1E25CFEA, + 0xB21A2F22, + 0xBC51FB2E, + 0x96E14F5D, + 0xBFA3EDAC, + 0x329C57AE, + 0xE7FC7153, + 0xC3FC0695, + 0x85A91924, + 0xF95F635E, + 0xB2908EE0, + 0x93ABADE4, + 0x1366732A, + 0x9449775C, + 0x69BE5B0E, + 0x7343AFAC, + 0xB099BC81, + 0x45A71D46, + 0xA2699748, + 0x8CB07303, + 0x8A0B1F13, + 0x8CAB8A97, + 0xC1D238D9, + 0x633415D4, + 0x0000001C, + + // 10^1024 + 107, // _length + 0x00000000, // _blocks 0x00000000, 0x00000000, 0x00000000, @@ -211,6 +223,83 @@ internal unsafe ref struct BigInteger 0x00000000, 0x00000000, 0x00000000, + 0x2919F001, + 0xF55B2B72, + 0x6E7C215B, + 0x1EC29F86, + 0x991C4E87, + 0x15C51A88, + 0x140AC535, + 0x4C7D1E1A, + 0xCC2CD819, + 0x0ED1440E, + 0x896634EE, + 0x7DE16CFB, + 0x1E43F61F, + 0x9FCE837D, + 0x231D2B9C, + 0x233E55C7, + 0x65DC60D7, + 0xF451218B, + 0x1C5CD134, + 0xC9635986, + 0x922BBB9F, + 0xA7E89431, + 0x9F9F2A07, + 0x62BE695A, + 0x8E1042C4, + 0x045B7A74, + 0x1ABE1DE3, + 0x8AD822A5, + 0xBA34C411, + 0xD814B505, + 0xBF3FDEB3, + 0x8FC51A16, + 0xB1B896BC, + 0xF56DEEEC, + 0x31FB6BFD, + 0xB6F4654B, + 0x101A3616, + 0x6B7595FB, + 0xDC1A47FE, + 0x80D98089, + 0x80BDA5A5, + 0x9A202882, + 0x31EB0F66, + 0xFC8F1F90, + 0x976A3310, + 0xE26A7B7E, + 0xDF68368A, + 0x3CE3A0B8, + 0x8E4262CE, + 0x75A351A2, + 0x6CB0B6C9, + 0x44597583, + 0x31B5653F, + 0xC356E38A, + 0x35FAABA6, + 0x0190FBA0, + 0x9FC4ED52, + 0x88BC491B, + 0x1640114A, + 0x005B8041, + 0xF4F3235E, + 0x1E8D4649, + 0x36A8DE06, + 0x73C55349, + 0xA7E6BD2A, + 0xC1A6970C, + 0x47187094, + 0xD2DB49EF, + 0x926C3F5B, + 0xAE6209D4, + 0x2D433949, + 0x34F4A3C6, + 0xD4305D94, + 0xD9D61A05, + 0x00000325, + + // 9 Trailing blocks to ensure MaxBlockCount 0x00000000, 0x00000000, 0x00000000, @@ -248,19 +337,39 @@ public BigInteger(ulong value) _length = (upper == 0) ? 1 : 2; } - public static uint BitScanReverse(uint mask) + public static void Add(ref BigInteger lhs, uint value, ref BigInteger result) { - // This comes from the Stanford Bit Widdling Hacks by Sean Eron Anderson: - // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn + if (lhs.IsZero()) + { + result.SetUInt32(value); + return; + } + + if (value == 0) + { + result.SetValue(ref lhs); + return; + } + + int lhsLength = lhs._length; + int index = 0; + uint carry = value; - mask |= (mask >> 1); // first round down to one less than a power of 2 - mask |= (mask >> 2); - mask |= (mask >> 4); - mask |= (mask >> 8); - mask |= (mask >> 16); + while (index < lhsLength) + { + ulong sum = (ulong)(lhs._blocks[index]) + carry; + lhs._blocks[index] = (uint)(sum); + carry = (uint)(sum >> 32); - uint index = (mask * 0x07C4ACDD) >> 27; - return s_MultiplyDeBruijnBitPosition[(int)(index)]; + index++; + } + + if (carry != 0) + { + Debug.Assert(unchecked((uint)(lhsLength)) + 1 <= MaxBlockCount); + result._blocks[index] = carry; + result._length = (lhsLength + 1); + } } public static int Compare(ref BigInteger lhs, ref BigInteger rhs) @@ -297,6 +406,217 @@ public static int Compare(ref BigInteger lhs, ref BigInteger rhs) return 0; } + public static uint CountSignificantBits(uint value) + { + return (value != 0) ? (1 + LogBase2(value)) : 0; + } + + public static uint CountSignificantBits(ulong value) + { + uint upper = (uint)(value >> 32); + + if (upper != 0) + { + return 32 + CountSignificantBits(upper); + } + + return CountSignificantBits((uint)(value)); + } + + public static uint CountSignificantBits(ref BigInteger value) + { + if (value.IsZero()) + { + return 0; + } + + // We don't track any unused blocks, so we only need to do a BSR on the + // last index and add that to the number of bits we skipped. + + uint lastIndex = (uint)(value._length - 1); + return (lastIndex * BitsPerBlock) + CountSignificantBits(value._blocks[lastIndex]); + } + + public static void DivRem(ref BigInteger lhs, ref BigInteger rhs, out BigInteger quo, out BigInteger rem) + { + // This is modified from the CoreFX BigIntegerCalculator.DivRem.cs implementation: + // https://github.com/dotnet/corefx/blob/0bb106232745aedfc0d0c5a84ff2b244bf190317/src/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs + + Debug.Assert(!rhs.IsZero()); + + quo = new BigInteger(0); + rem = new BigInteger(0); + + if (lhs.IsZero()) + { + return; + } + + int lhsLength = lhs._length; + int rhsLength = rhs._length; + + if ((lhsLength == 1) && (rhsLength == 1)) + { + uint quotient = Math.DivRem(lhs._blocks[0], rhs._blocks[0], out uint remainder); + quo = new BigInteger(quotient); + rem = new BigInteger(remainder); + return; + } + + if (rhsLength == 1) + { + // We can make the computation much simpler if the rhs is only one block + + int quoLength = lhsLength; + + ulong rhsValue = rhs._blocks[0]; + ulong carry = 0; + + for (int i = quoLength - 1; i >= 0; i--) + { + ulong value = (carry << 32) | lhs._blocks[i]; + ulong digit = Math.DivRem(value, rhsValue, out carry); + + if ((digit == 0) && (i == (quoLength - 1))) + { + quoLength--; + } + else + { + quo._blocks[i] = (uint)(digit); + } + } + + quo._length = quoLength; + rem.SetUInt32((uint)(carry)); + + return; + } + else if (rhsLength > lhsLength) + { + // Handle the case where we have no quotient + rem.SetValue(ref lhs); + return; + } + else + { + int quoLength = lhsLength - rhsLength + 1; + rem.SetValue(ref lhs); + int remLength = lhsLength; + + // Executes the "grammar-school" algorithm for computing q = a / b. + // Before calculating q_i, we get more bits into the highest bit + // block of the divisor. Thus, guessing digits of the quotient + // will be more precise. Additionally we'll get r = a % b. + + uint divHi = rhs._blocks[rhsLength - 1]; + uint divLo = rhs._blocks[rhsLength - 2]; + + // We measure the leading zeros of the divisor + int shiftLeft = (int)(LeadingZeroCount(divHi)); + int shiftRight = 32 - shiftLeft; + + // And, we make sure the most significant bit is set + if (shiftLeft > 0) + { + divHi = (divHi << shiftLeft) | (divLo >> shiftRight); + divLo = (divLo << shiftLeft); + + if (rhsLength > 2) + { + divLo |= (rhs._blocks[rhsLength - 3] >> shiftRight); + } + } + + // Then, we divide all of the bits as we would do it using + // pen and paper: guessing the next digit, subtracting, ... + for (int i = lhsLength; i >= rhsLength; i--) + { + int n = i - rhsLength; + uint t = i < lhsLength ? rem._blocks[i] : 0; + + ulong valHi = ((ulong)(t) << 32) | rem._blocks[i - 1]; + uint valLo = i > 1 ? rem._blocks[i - 2] : 0; + + // We shifted the divisor, we shift the dividend too + if (shiftLeft > 0) + { + valHi = (valHi << shiftLeft) | (valLo >> shiftRight); + valLo = (valLo << shiftLeft); + + if (i > 2) + { + valLo |= (rem._blocks[i - 3] >> shiftRight); + } + } + + // First guess for the current digit of the quotient, + // which naturally must have only 32 bits... + ulong digit = valHi / divHi; + + if (digit > uint.MaxValue) + { + digit = uint.MaxValue; + } + + // Our first guess may be a little bit to big + while (DivideGuessTooBig(digit, valHi, valLo, divHi, divLo)) + { + digit--; + } + + if (digit > 0) + { + // Now it's time to subtract our current quotient + uint carry = SubtractDivisor(ref rem, n, ref rhs, digit); + + if (carry != t) + { + Debug.Assert(carry == t + 1); + + // Our guess was still exactly one too high + carry = AddDivisor(ref rem, n, ref rhs); + digit--; + + Debug.Assert(carry == 1); + } + } + + // We have the digit! + if (quoLength != 0) + { + if ((digit == 0) && (n == (quoLength - 1))) + { + quoLength--; + } + else + { + quo._blocks[n] = (uint)(digit); + } + } + + if (i < remLength) + { + remLength--; + } + } + + quo._length = quoLength; + + // We need to check for the case where remainder is zero + + for (int i = remLength - 1; i >= 0; i--) + { + if (rem._blocks[i] == 0) + { + remLength--; + } + } + + rem._length = remLength; + } + } + public static uint HeuristicDivide(ref BigInteger dividend, ref BigInteger divisor) { int divisorLength = divisor._length; @@ -377,10 +697,26 @@ public static uint HeuristicDivide(ref BigInteger dividend, ref BigInteger divis return quotient; } + public static uint LeadingZeroCount(uint value) + { + return 32 - CountSignificantBits(value); + } + public static uint LogBase2(uint value) { Debug.Assert(value != 0); - return BitScanReverse(value); + + // This comes from the Stanford Bit Widdling Hacks by Sean Eron Anderson: + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn + + value |= (value >> 1); // first round down to one less than a power of 2 + value |= (value >> 2); + value |= (value >> 4); + value |= (value >> 8); + value |= (value >> 16); + + uint index = (value * 0x07C4ACDD) >> 27; + return s_MultiplyDeBruijnBitPosition[(int)(index)]; } public static uint LogBase2(ulong value) @@ -413,14 +749,13 @@ public static void Multiply(ref BigInteger lhs, uint value, ref BigInteger resul int lhsLength = lhs._length; int index = 0; - - ulong carry = 0; + uint carry = 0; while (index < lhsLength) { ulong product = ((ulong)(lhs._blocks[index]) * value) + carry; - carry = product >> 32; result._blocks[index] = (uint)(product); + carry = (uint)(product >> 32); index++; } @@ -428,8 +763,8 @@ public static void Multiply(ref BigInteger lhs, uint value, ref BigInteger resul if (carry != 0) { Debug.Assert(unchecked((uint)(lhsLength)) + 1 <= MaxBlockCount); - result._blocks[index] = (uint)(carry); - result._length += (lhsLength + 1); + result._blocks[index] = carry; + result._length = (lhsLength + 1); } } @@ -509,12 +844,12 @@ public static void Multiply(ref BigInteger lhs, ref BigInteger rhs, ref BigInteg } } - public static void Pow10(uint exponent, ref BigInteger result) + public static void Pow10(uint exponent, out BigInteger result) { // We leverage two arrays - s_Pow10UInt32Table and s_Pow10BigNumTable to speed up the Pow10 calculation. // // s_Pow10UInt32Table stores the results of 10^0 to 10^7. - // s_Pow10BigNumTable stores the results of 10^8, 10^16, 10^32, 10^64, 10^128 and 10^256. + // s_Pow10BigNumTable stores the results of 10^8, 10^16, 10^32, 10^64, 10^128, 10^256, and 10^512 // // For example, let's say exp = 0b111111. We can split the exp to two parts, one is small exp, // which 10^smallExp can be represented as uint, another part is 10^bigExp, which must be represented as BigNum. @@ -539,7 +874,7 @@ public static void Pow10(uint exponent, ref BigInteger result) // Validate that `s_Pow10BigNumTable` has exactly enough trailing elements to fill a BigInteger (which contains MaxBlockCount + 1 elements) // We validate here, since this is the only current consumer of the array - Debug.Assert((s_Pow10BigNumTableIndices[s_Pow10BigNumTableIndices.Length - 1] + MaxBlockCount + 1) == s_Pow10BigNumTable.Length); + Debug.Assert((s_Pow10BigNumTableIndices[s_Pow10BigNumTableIndices.Length - 1] + MaxBlockCount + 2) == s_Pow10BigNumTable.Length); BigInteger temp1 = new BigInteger(s_Pow10UInt32Table[exponent & 0x7]); ref BigInteger lhs = ref temp1; @@ -570,6 +905,7 @@ public static void Pow10(uint exponent, ref BigInteger result) exponent >>= 1; } + result = new BigInteger(0); result.SetValue(ref lhs); } @@ -645,6 +981,100 @@ public static void ShiftLeft(ulong input, uint shift, ref BigInteger output) } } + private static unsafe uint AddDivisor(ref BigInteger lhs, int lhsStartIndex, ref BigInteger rhs) + { + int lhsLength = lhs._length; + int rhsLength = rhs._length; + + Debug.Assert(lhsLength >= 0); + Debug.Assert(rhsLength >= 0); + Debug.Assert(lhsLength >= rhsLength); + + // Repairs the dividend, if the last subtract was too much + + ulong carry = 0UL; + + for (int i = 0; i < rhsLength; i++) + { + ref uint lhsValue = ref lhs._blocks[lhsStartIndex + i]; + + ulong digit = (lhsValue + carry) + rhs._blocks[i]; + lhsValue = unchecked((uint)digit); + carry = digit >> 32; + } + + return (uint)(carry); + } + + private static bool DivideGuessTooBig(ulong q, ulong valHi, uint valLo, uint divHi, uint divLo) + { + Debug.Assert(q <= 0xFFFFFFFF); + + // We multiply the two most significant limbs of the divisor + // with the current guess for the quotient. If those are bigger + // than the three most significant limbs of the current dividend + // we return true, which means the current guess is still too big. + + ulong chkHi = divHi * q; + ulong chkLo = divLo * q; + + chkHi = chkHi + (chkLo >> 32); + chkLo = chkLo & uint.MaxValue; + + if (chkHi < valHi) + return false; + + if (chkHi > valHi) + return true; + + if (chkLo < valLo) + return false; + + if (chkLo > valLo) + return true; + + return false; + } + + private static unsafe uint SubtractDivisor(ref BigInteger lhs, int lhsStartIndex, ref BigInteger rhs, ulong q) + { + int lhsLength = lhs._length - lhsStartIndex; + int rhsLength = rhs._length; + + Debug.Assert((lhsLength) >= 0); + Debug.Assert(rhsLength >= 0); + Debug.Assert(lhsLength >= rhsLength); + Debug.Assert(q <= uint.MaxValue); + + // Combines a subtract and a multiply operation, which is naturally + // more efficient than multiplying and then subtracting... + + ulong carry = 0; + + for (int i = 0; i < rhsLength; i++) + { + carry += rhs._blocks[i] * q; + uint digit = unchecked((uint)carry); + carry = carry >> 32; + + ref uint lhsValue = ref lhs._blocks[lhsStartIndex + i]; + + if (lhsValue < digit) + { + carry++; + } + + lhsValue = unchecked(lhsValue - digit); + } + + return (uint)(carry); + } + + public void Add(uint value) + { + Add(ref this, value, ref this); + } + public void ExtendBlock(uint blockValue) { _blocks[_length] = blockValue; @@ -667,6 +1097,12 @@ public void ExtendBlocks(uint blockValue, uint blockCount) _blocks[_length - 1] = blockValue; } + public uint GetBlock(uint index) + { + Debug.Assert(index < _length); + return _blocks[index]; + } + public bool IsOne() { return (_length == 1) @@ -721,6 +1157,20 @@ public void Multiply10() } } + public void MultiplyPow10(uint exponent) + { + Pow10(exponent, out BigInteger poweredValue); + + if (poweredValue._length == 1) + { + Multiply(poweredValue._blocks[0]); + } + else + { + Multiply(ref poweredValue); + } + } + public void SetUInt32(uint value) { _blocks[0] = value; @@ -824,6 +1274,21 @@ public void ShiftLeft(uint shift) } } + public ulong ToUInt64() + { + if (_length > 1) + { + return ((ulong)(_blocks[1]) << 32) + _blocks[0]; + } + + if (_length > 0) + { + return _blocks[0]; + } + + return 0; + } + private uint* GetBlocksPointer() { // This is safe to do since we are a ref struct diff --git a/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs b/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs index 7c1d48061029..ec5110448899 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs @@ -129,15 +129,11 @@ private static unsafe void Dragon4(double value, int precision, ref NumberBuffer if (k > 0) { - BigInteger poweredValue = new BigInteger(0); - BigInteger.Pow10((uint)(k), ref poweredValue); - s.Multiply(ref poweredValue); + s.MultiplyPow10((uint)(k)); } else if (k < 0) { - BigInteger poweredValue = new BigInteger(0); - BigInteger.Pow10((uint)(-k), ref poweredValue); - r.Multiply(ref poweredValue); + r.MultiplyPow10((uint)(-k)); } if (BigInteger.Compare(ref r, ref s) >= 0) diff --git a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs index 76f0469b4b12..57c5341d65e4 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs @@ -393,7 +393,7 @@ private static unsafe string FormatDouble(ref ValueStringBuilder sb, double valu int precision = DoublePrecision; char* pDigits = stackalloc char[DoubleNumberBufferLength]; - NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, DoubleNumberBufferLength); + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength); switch (fmt) { @@ -413,7 +413,7 @@ private static unsafe string FormatDouble(ref ValueStringBuilder sb, double valu return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } - if (NumberToDouble(ref number) == value) + if (TryNumberToDouble(ref number, out double roundTrip) && (roundTrip == value)) { NumberToString(ref sb, ref number, 'G', DoublePrecision, info); } @@ -495,7 +495,7 @@ private static unsafe string FormatSingle(ref ValueStringBuilder sb, float value int precision = SinglePrecision; char* pDigits = stackalloc char[SingleNumberBufferLength]; - NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, SingleNumberBufferLength); + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength); switch (fmt) { @@ -515,7 +515,7 @@ private static unsafe string FormatSingle(ref ValueStringBuilder sb, float value return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } - if ((float)NumberToDouble(ref number) == value) + if (TryNumberToSingle(ref number, out float roundTrip) && (roundTrip == value)) { NumberToString(ref sb, ref number, 'G', SinglePrecision, info); } diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs index 31b038c1f6c2..e5c986f983ea 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs @@ -53,7 +53,7 @@ internal enum NumberBufferKind : byte Unknown = 0, Integer = 1, Decimal = 2, - Double = 3, + FloatingPoint = 3, } } } diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs deleted file mode 100644 index 59f4ee94649e..000000000000 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs +++ /dev/null @@ -1,432 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; - -namespace System -{ - internal unsafe partial class Number - { - // precomputed tables with powers of 10. These allows us to do at most - // two Mul64 during the conversion. This is important not only - // for speed, but also for precision because of Mul64 computes with 1 bit error. - - private static readonly ulong[] s_Pow10MantissaTable = new ulong[] - { - // powers of 10 - 0XA0000000_00000000, // 1 - 0XC8000000_00000000, // 2 - 0XFA000000_00000000, // 3 - 0X9C400000_00000000, // 4 - 0XC3500000_00000000, // 5 - 0XF4240000_00000000, // 6 - 0X98968000_00000000, // 7 - 0XBEBC2000_00000000, // 8 - 0XEE6B2800_00000000, // 9 - 0X9502F900_00000000, // 10 - 0XBA43B740_00000000, // 11 - 0XE8D4A510_00000000, // 12 - 0X9184E72A_00000000, // 13 - 0XB5E620F4_80000000, // 14 - 0XE35FA931_A0000000, // 15 - - // powers of 0.1 - 0xCCCCCCCC_CCCCCCCD, // 1 - 0xA3D70A3D_70A3D70B, // 2 - 0x83126E97_8D4FDF3C, // 3 - 0xD1B71758_E219652E, // 4 - 0xA7C5AC47_1B478425, // 5 - 0x8637BD05_AF6C69B7, // 6 - 0xD6BF94D5_E57A42BE, // 7 - 0xABCC7711_8461CEFF, // 8 - 0x89705F41_36B4A599, // 9 - 0xDBE6FECE_BDEDD5C2, // 10 - 0xAFEBFF0B_CB24AB02, // 11 - 0x8CBCCC09_6F5088CF, // 12 - 0xE12E1342_4BB40E18, // 13 - 0xB424DC35_095CD813, // 14 - 0x901D7CF7_3AB0ACDC, // 15 - }; - - private static readonly short[] s_Pow10ExponentTable = new short[] - { - // exponents for both powers of 10 and 0.1 - 4, // 1 - 7, // 2 - 10, // 3 - 14, // 4 - 17, // 5 - 20, // 6 - 24, // 7 - 27, // 8 - 30, // 9 - 34, // 10 - 37, // 11 - 40, // 12 - 44, // 13 - 47, // 14 - 50, // 15 - }; - - private static readonly ulong[] s_Pow10By16MantissaTable = new ulong[] - { - // powers of 10^16 - 0x8E1BC9BF_04000000, // 1 - 0x9DC5ADA8_2B70B59E, // 2 - 0xAF298D05_0E4395D6, // 3 - 0xC2781F49_FFCFA6D4, // 4 - 0xD7E77A8F_87DAF7FA, // 5 - 0xEFB3AB16_C59B14A0, // 6 - 0x850FADC0_9923329C, // 7 - 0x93BA47C9_80E98CDE, // 8 - 0xA402B9C5_A8D3A6E6, // 9 - 0xB616A12B_7FE617A8, // 10 - 0xCA28A291_859BBF90, // 11 - 0xE070F78D_39275566, // 12 - 0xF92E0C35_37826140, // 13 - 0x8A5296FF_E33CC92C, // 14 - 0x9991A6F3_D6BF1762, // 15 - 0xAA7EEBFB_9DF9DE8A, // 16 - 0xBD49D14A_A79DBC7E, // 17 - 0xD226FC19_5C6A2F88, // 18 - 0xE950DF20_247C83F8, // 19 - 0x81842F29_F2CCE373, // 20 - 0x8FCAC257_558EE4E2, // 21 - - // powers of 0.1^16 - 0xE69594BE_C44DE160, // 1 - 0xCFB11EAD_453994C3, // 2 - 0xBB127C53_B17EC165, // 3 - 0xA87FEA27_A539E9B3, // 4 - 0x97C560BA_6B0919B5, // 5 - 0x88B402F7_FD7553AB, // 6 - 0xF64335BC_F065D3A0, // 7 - 0xDDD0467C_64BCE4C4, // 8 - 0xC7CABA6E_7C5382ED, // 9 - 0xB3F4E093_DB73A0B7, // 10 - 0xA21727DB_38CB0053, // 11 - 0x91FF8377_5423CC29, // 12 - 0x8380DEA9_3DA4BC82, // 13 - 0xECE53CEC_4A314F00, // 14 - 0xD5605FCD_CF32E217, // 15 - 0xC0314325_637A1978, // 16 - 0xAD1C8EAB_5EE43BA2, // 17 - 0x9BECCE62_836AC5B0, // 18 - 0x8C71DCD9_BA0B495C, // 19 - 0xFD00B897_47823938, // 20 - 0xE3E27A44_4D8D991A, // 21 - }; - - private static readonly short[] s_Pow10By16ExponentTable = new short[] - { - // exponents for both powers of 10^16 and 0.1^16 - 54, // 1 - 107, // 2 - 160, // 3 - 213, // 4 - 266, // 5 - 319, // 6 - 373, // 7 - 426, // 8 - 479, // 9 - 532, // 10 - 585, // 11 - 638, // 12 - 691, // 13 - 745, // 14 - 798, // 15 - 851, // 16 - 904, // 17 - 957, // 18 - 1010, // 19 - 1064, // 20 - 1117, // 21 - }; - -#if DEBUG - private static bool s_CheckedTables = false; - - private static void CheckTables() - { - ulong val; - int exp; - - val = 0xA0000000_00000000; - exp = 4; // 10 - - CheckPow10MantissaTable(val, exp, new Span(s_Pow10MantissaTable, 0, 15), nameof(s_Pow10MantissaTable)); - CheckPow10ExponentTable(val, exp, new Span(s_Pow10ExponentTable, 0, 15), nameof(s_Pow10ExponentTable)); - - val = 0x8E1BC9BF_04000000; - exp = 54; //10^16 - - CheckPow10MantissaTable(val, exp, new Span(s_Pow10By16MantissaTable, 0, 21), nameof(s_Pow10By16MantissaTable)); - CheckPow10ExponentTable(val, exp, new Span(s_Pow10By16ExponentTable, 0, 21), nameof(s_Pow10By16ExponentTable)); - - val = 0xCCCCCCCC_CCCCCCCD; - exp = -3; // 0.1 - - CheckPow10MantissaTable(val, exp, new Span(s_Pow10MantissaTable, 15, 15), nameof(s_Pow10MantissaTable) + " - inv"); - - val = 0xE69594BE_C44DE160; - exp = -53; // 0.1^16 - - CheckPow10MantissaTable(val, exp, new Span(s_Pow10By16MantissaTable, 21, 21), nameof(s_Pow10By16MantissaTable) + " - inv"); - } - - // debug-only verification of the precomputed tables - private static void CheckPow10MantissaTable(ulong val, int exp, Span table, string name) - { - ulong multval = val; - int mulexp = exp; - bool isBad = false; - - for (int i = 0; i < table.Length; i++) - { - if (table[i] != val) - { - if (!isBad) - { - Debug.WriteLine(name); - isBad = true; - } - Debug.WriteLine($"0x{val:X16}, // {i + 1}"); - } - - exp += mulexp; - val = Mul64Precise(val, multval, ref exp); - } - - Debug.Assert(!isBad, "NumberToDouble table not correct. Correct version dumped to debug output."); - } - - // debug-only verification of the precomputed tables - private static void CheckPow10ExponentTable(ulong val, int exp, Span table, string name) - { - ulong multval = val; - int mulexp = exp; - bool isBad = false; - - for (int i = 0; i < table.Length; i++) - { - if (table[i] != exp) - { - if (!isBad) - { - Debug.WriteLine(name); - isBad = true; - } - Debug.WriteLine($"{val}, // {i + 1}"); - } - - exp += mulexp; - val = Mul64Precise(val, multval, ref exp); - } - - Debug.Assert(!isBad, "NumberToDouble table not correct. Correct version dumped to debug output."); - } - - // slower high precision version of Mul64 for computation of the tables - private static ulong Mul64Precise(ulong a, ulong b, ref int exp) - { - ulong hilo = ((Mul32x32To64((uint)(a >> 32), (uint)(b)) >> 1) - + (Mul32x32To64((uint)(a), (uint)(b >> 32)) >> 1) - + (Mul32x32To64((uint)(a), (uint)(b)) >> 33)) >> 30; - - ulong val = Mul32x32To64((uint)(a >> 32), (uint)(b >> 32)) - + (hilo >> 1) - + (hilo & 1); - - // normalize - if ((val & 0x80000000_00000000) == 0) - { - val <<= 1; - exp--; - } - - return val; - } -#endif - - // get 32-bit integer from at most 9 digits - private static uint DigitsToInt(char* p, int count) - { - Debug.Assert((1 <= count) && (count <= 9)); - - char* end = (p + count); - uint res = (uint)(p[0] - '0'); - - for (p++; p < end; p++) - { - res = (10 * res) + p[0] - '0'; - } - - return res; - } - - // helper function to multiply two 32-bit uints - private static ulong Mul32x32To64(uint a, uint b) - { - return (ulong)(a) * b; - } - - // multiply two numbers in the internal integer representation - private static ulong Mul64Lossy(ulong a, ulong b, ref int exp) - { - // it's ok to lose some precision here - Mul64 will be called - // at most twice during the conversion, so the error won't propagate - // to any of the 53 significant bits of the result - ulong val = Mul32x32To64((uint)(a >> 32), (uint)(b >> 32)) - + (Mul32x32To64((uint)(a >> 32), (uint)(b)) >> 32) - + (Mul32x32To64((uint)(a), (uint)(b >> 32)) >> 32); - - // normalize - if ((val & 0x80000000_00000000) == 0) - { - val <<= 1; - exp--; - } - - return val; - } - - private static double NumberToDouble(ref NumberBuffer number) - { -#if DEBUG - - if (!s_CheckedTables) - { - CheckTables(); - s_CheckedTables = true; - } -#endif - - char* src = number.GetDigitsPointer(); - int total = number.Precision; - int remaining = total; - - Debug.Assert(src[0] != '0'); - - if (total == 0) - { - return number.Sign ? -0.0 : 0.0; - } - - int count = Math.Min(remaining, 9); - remaining -= count; - ulong val = DigitsToInt(src, count); - - if (remaining > 0) - { - count = Math.Min(remaining, 9); - remaining -= count; - - // get the denormalized power of 10 - uint mult = (uint)(s_Pow10MantissaTable[count - 1] >> (64 - s_Pow10ExponentTable[count - 1])); - val = Mul32x32To64((uint)(val), mult) + DigitsToInt(src + 9, count); - } - - int scale = number.Scale - (total - remaining); - int absscale = Math.Abs(scale); - if (absscale >= 22 * 16) - { - // overflow / underflow - if (scale > 0) - { - return number.Sign ? double.NegativeInfinity : double.PositiveInfinity; - } - else - { - return number.Sign ? -0.0 : 0.0; - } - } - - int exp = 64; - - // normalize the mantissa - if ((val & 0xFFFFFFFF_00000000) == 0) { val <<= 32; exp -= 32; } - if ((val & 0xFFFF0000_00000000) == 0) { val <<= 16; exp -= 16; } - if ((val & 0xFF000000_00000000) == 0) { val <<= 8; exp -= 8; } - if ((val & 0xF0000000_00000000) == 0) { val <<= 4; exp -= 4; } - if ((val & 0xC0000000_00000000) == 0) { val <<= 2; exp -= 2; } - if ((val & 0x80000000_00000000) == 0) { val <<= 1; exp -= 1; } - - int index = absscale & 15; - if (index != 0) - { - int multexp = s_Pow10ExponentTable[index - 1]; - // the exponents are shared between the inverted and regular table - exp += (scale < 0) ? (-multexp + 1) : multexp; - - ulong multval = s_Pow10MantissaTable[index + ((scale < 0) ? 15 : 0) - 1]; - val = Mul64Lossy(val, multval, ref exp); - } - - index = absscale >> 4; - if (index != 0) - { - int multexp = s_Pow10By16ExponentTable[index - 1]; - // the exponents are shared between the inverted and regular table - exp += (scale < 0) ? (-multexp + 1) : multexp; - - ulong multval = s_Pow10By16MantissaTable[index + ((scale < 0) ? 21 : 0) - 1]; - val = Mul64Lossy(val, multval, ref exp); - } - - // round & scale down - if (((uint)(val) & (1 << 10)) != 0) - { - // IEEE round to even - ulong tmp = val + ((1 << 10) - 1) + (((uint)(val) >> 11) & 1); - if (tmp < val) - { - // overflow - tmp = (tmp >> 1) | 0x8000000000000000; - exp += 1; - } - val = tmp; - } - - // return the exponent to a biased state - exp += 0x3FE; - - // handle overflow, underflow, "Epsilon - 1/2 Epsilon", denormalized, and the normal case - if (exp <= 0) - { - if (exp == -52 && (val >= 0x8000000000000058)) - { - // round X where {Epsilon > X >= 2.470328229206232730000000E-324} up to Epsilon (instead of down to zero) - val = 0x0000000000000001; - } - else if (exp <= -52) - { - // underflow - val = 0; - } - else - { - // denormalized - val >>= (-exp + 11 + 1); - } - } - else if (exp >= 0x7FF) - { - // overflow - val = 0x7FF0000000000000; - } - else - { - // normal postive exponent case - val = ((ulong)(exp) << 52) + ((val >> 11) & 0x000FFFFFFFFFFFFF); - } - - if (number.Sign) - { - val |= 0x8000000000000000; - } - - return BitConverter.Int64BitsToDouble((long)(val)); - } - } -} diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs new file mode 100644 index 000000000000..2fdfbbb17320 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs @@ -0,0 +1,499 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System +{ + internal unsafe partial class Number + { + // The below implementation comes from the Roslyn RealParser: https://github.com/dotnet/roslyn/blob/82b2f694b26e5933ff8dd0856a2cd775273ac138/src/Compilers/Core/Portable/RealParser.cs + // The original implementation is: Licensed under the Apache License, Version 2.0. + + public readonly struct FloatingPointInfo + { + public static readonly FloatingPointInfo Double = new FloatingPointInfo( + denormalMantissaBits: 52, + exponentBits: 11, + maxBinaryExponent: 1023, + exponentBias: 1023, + infinityBits: 0x7FF0000000000000 + ); + + public static readonly FloatingPointInfo Single = new FloatingPointInfo( + denormalMantissaBits: 23, + exponentBits: 8, + maxBinaryExponent: 127, + exponentBias: 127, + infinityBits: 0x7F800000 + ); + + public ulong ZeroBits { get; } + public ulong InfinityBits { get; } + + public ulong NormalMantissaMask { get; } + public ulong DenormalMantissaMask { get; } + + public int MinBinaryExponent { get; } + public int MaxBinaryExponent { get; } + + public int ExponentBias { get; } + public int OverflowDecimalExponent { get; } + + public ushort NormalMantissaBits { get; } + public ushort DenormalMantissaBits { get; } + + public ushort ExponentBits { get; } + + public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int maxBinaryExponent, int exponentBias, ulong infinityBits) + { + ExponentBits = exponentBits; + + DenormalMantissaBits = denormalMantissaBits; + NormalMantissaBits = (ushort)(denormalMantissaBits + 1); // we get an extra (hidden) bit for normal mantissas + + OverflowDecimalExponent = (maxBinaryExponent + 2 * NormalMantissaBits) / 3; + ExponentBias = exponentBias; + + MaxBinaryExponent = maxBinaryExponent; + MinBinaryExponent = 1 - maxBinaryExponent; + + DenormalMantissaMask = (1UL << denormalMantissaBits) - 1; + NormalMantissaMask = (1UL << NormalMantissaBits) - 1; + + InfinityBits = infinityBits; + ZeroBits = 0; + } + } + + private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result) + { + result = new BigInteger(0); + + char* src = number.GetDigitsPointer() + firstIndex; + uint remaining = lastIndex - firstIndex; + + while (remaining != 0) + { + uint count = Math.Min(remaining, 9); + uint value = DigitsToInt(src, (int)(count)); + + result.MultiplyPow10(count); + result.Add(value); + + src += count; + remaining -= count; + } + } + + private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong initialMantissa, int initialExponent, bool hasZeroTail) + { + // number of bits by which we must adjust the mantissa to shift it into the + // correct position, and compute the resulting base two exponent for the + // normalized mantissa: + uint initialMantissaBits = BigInteger.CountSignificantBits(initialMantissa); + int normalMantissaShift = info.NormalMantissaBits - (int)(initialMantissaBits); + int normalExponent = initialExponent - normalMantissaShift; + + ulong mantissa = initialMantissa; + int exponent = normalExponent; + + if (normalExponent > info.MaxBinaryExponent) + { + // The exponent is too large to be represented by the floating point + // type; report the overflow condition: + return info.InfinityBits; + } + else if (normalExponent < info.MinBinaryExponent) + { + // The exponent is too small to be represented by the floating point + // type as a normal value, but it may be representable as a denormal + // value. Compute the number of bits by which we need to shift the + // mantissa in order to form a denormal number. (The subtraction of + // an extra 1 is to account for the hidden bit of the mantissa that + // is not available for use when representing a denormal.) + int denormalMantissaShift = normalMantissaShift + normalExponent + info.ExponentBias - 1; + + // Denormal values have an exponent of zero, so the debiased exponent is + // the negation of the exponent bias: + exponent = -info.ExponentBias; + + if (denormalMantissaShift < 0) + { + // Use two steps for right shifts: for a shift of N bits, we first + // shift by N-1 bits, then shift the last bit and use its value to + // round the mantissa. + mantissa = RightShiftWithRounding(mantissa, -denormalMantissaShift, hasZeroTail); + + // If the mantissa is now zero, we have underflowed: + if (mantissa == 0) + { + return info.ZeroBits; + } + + // When we round the mantissa, the result may be so large that the + // number becomes a normal value. For example, consider the single + // precision case where the mantissa is 0x01ffffff and a right shift + // of 2 is required to shift the value into position. We perform the + // shift in two steps: we shift by one bit, then we shift again and + // round using the dropped bit. The initial shift yields 0x00ffffff. + // The rounding shift then yields 0x007fffff and because the least + // significant bit was 1, we add 1 to this number to round it. The + // final result is 0x00800000. + // + // 0x00800000 is 24 bits, which is more than the 23 bits available + // in the mantissa. Thus, we have rounded our denormal number into + // a normal number. + // + // We detect this case here and re-adjust the mantissa and exponent + // appropriately, to form a normal number: + if (mantissa > info.DenormalMantissaMask) + { + // We add one to the denormal_mantissa_shift to account for the + // hidden mantissa bit (we subtracted one to account for this bit + // when we computed the denormal_mantissa_shift above). + exponent = initialExponent - (denormalMantissaShift + 1) - normalMantissaShift; + } + } + else + { + mantissa <<= denormalMantissaShift; + } + } + else + { + if (normalMantissaShift < 0) + { + // Use two steps for right shifts: for a shift of N bits, we first + // shift by N-1 bits, then shift the last bit and use its value to + // round the mantissa. + mantissa = RightShiftWithRounding(mantissa, -normalMantissaShift, hasZeroTail); + + // When we round the mantissa, it may produce a result that is too + // large. In this case, we divide the mantissa by two and increment + // the exponent (this does not change the value). + if (mantissa > info.NormalMantissaMask) + { + mantissa >>= 1; + exponent++; + + // The increment of the exponent may have generated a value too + // large to be represented. In this case, report the overflow: + if (exponent > info.MaxBinaryExponent) + { + return info.InfinityBits; + } + } + } + else if (normalMantissaShift > 0) + { + mantissa <<= normalMantissaShift; + } + } + + // Unset the hidden bit in the mantissa and assemble the floating point value + // from the computed components: + mantissa &= info.DenormalMantissaMask; + + Debug.Assert((info.DenormalMantissaMask & (1UL << info.DenormalMantissaBits)) == 0); + ulong shiftedExponent = ((ulong)(exponent + info.ExponentBias)) << info.DenormalMantissaBits; + Debug.Assert((shiftedExponent & info.DenormalMantissaMask) == 0); + Debug.Assert((mantissa & ~info.DenormalMantissaMask) == 0); + Debug.Assert((shiftedExponent & ~(((1UL << info.ExponentBits) - 1) << info.DenormalMantissaBits)) == 0); // exponent fits in its place + + return (shiftedExponent | mantissa); + } + + private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger value, in FloatingPointInfo info, uint integerBitsOfPrecision, bool hasNonZeroFractionalPart) + { + int baseExponent = info.DenormalMantissaBits; + + // When we have 64-bits or less of precision, we can just get the mantissa directly + if (integerBitsOfPrecision <= 64) + { + return AssembleFloatingPointBits(in info, value.ToUInt64(), baseExponent, !hasNonZeroFractionalPart); + } + + uint topBlockIndex = Math.DivRem(integerBitsOfPrecision, 32, out uint topBlockBits); + uint middleBlockIndex = topBlockIndex - 1; + uint bottomBlockIndex = middleBlockIndex - 1; + + ulong mantissa = 0; + int exponent = baseExponent + ((int)(bottomBlockIndex) * 32); + bool hasZeroTail = !hasNonZeroFractionalPart; + + // When the top 64-bits perfectly span two blocks, we can get those blocks directly + if (topBlockBits == 0) + { + mantissa = ((ulong)(value.GetBlock(middleBlockIndex)) << 32) + value.GetBlock(bottomBlockIndex); + } + else + { + // Otherwise, we need to read three blocks and combine them into a 64-bit mantissa + + int bottomBlockShift = (int)(topBlockBits); + int topBlockShift = 64 - bottomBlockShift; + int middleBlockShift = topBlockShift - 32; + + exponent += (int)(topBlockBits); + + uint bottomBlock = value.GetBlock(bottomBlockIndex); + uint bottomBits = bottomBlock >> bottomBlockShift; + + ulong middleBits = (ulong)(value.GetBlock(middleBlockIndex)) << middleBlockShift; + ulong topBits = (ulong)(value.GetBlock(topBlockIndex)) << topBlockShift; + + mantissa = topBits + middleBits + bottomBits; + + uint unusedBottomBlockBitsMask = (1u << (int)(topBlockBits)) - 1; + hasZeroTail &= (bottomBlock & unusedBottomBlockBitsMask) == 0; + } + + for (uint i = 0; i != bottomBlockIndex; i++) + { + hasZeroTail &= (value.GetBlock(i) == 0); + } + + return AssembleFloatingPointBits(in info, mantissa, exponent, hasZeroTail); + } + + // get 32-bit integer from at most 9 digits + private static uint DigitsToInt(char* p, int count) + { + Debug.Assert((1 <= count) && (count <= 9)); + + char* end = (p + count); + uint res = (uint)(p[0] - '0'); + + for (p++; p 0) + { + if (integerDigitsMissing > info.OverflowDecimalExponent) + { + return info.InfinityBits; + } + + integerValue.MultiplyPow10(integerDigitsMissing); + } + + // At this point, the integer_value contains the value of the integer part + // of the mantissa. If either [1] this number has more than the required + // number of bits of precision or [2] the mantissa has no fractional part, + // then we can assemble the result immediately: + uint integerBitsOfPrecision = BigInteger.CountSignificantBits(ref integerValue); + + if ((integerBitsOfPrecision >= requiredBitsOfPrecision) || (fractionalDigitsPresent == 0)) + { + return ConvertBigIntegerToFloatingPointBits( + ref integerValue, + in info, + integerBitsOfPrecision, + fractionalDigitsPresent != 0 + ); + } + + // Otherwise, we did not get enough bits of precision from the integer part, + // and the mantissa has a fractional part. We parse the fractional part of + // the mantissa to obtain more bits of precision. To do this, we convert + // the fractional part into an actual fraction N/M, where the numerator N is + // computed from the digits of the fractional part, and the denominator M is + // computed as the power of 10 such that N/M is equal to the value of the + // fractional part of the mantissa. + + uint fractionalDenominatorExponent = fractionalDigitsPresent; + + if (number.Scale < 0) + { + fractionalDenominatorExponent += (uint)(-number.Scale); + } + + if ((integerBitsOfPrecision == 0) && (fractionalDenominatorExponent - (int)(totalDigits)) > info.OverflowDecimalExponent) + { + // If there were any digits in the integer part, it is impossible to + // underflow (because the exponent cannot possibly be small enough), + // so if we underflow here it is a true underflow and we return zero. + return info.ZeroBits; + } + + AccumulateDecimalDigitsIntoBigInteger(ref number, fractionalFirstIndex, fractionalLastIndex, out BigInteger fractionalNumerator); + Debug.Assert(!fractionalNumerator.IsZero()); + + BigInteger.Pow10(fractionalDenominatorExponent, out BigInteger fractionalDenominator); + + // Because we are using only the fractional part of the mantissa here, the + // numerator is guaranteed to be smaller than the denominator. We normalize + // the fraction such that the most significant bit of the numerator is in + // the same position as the most significant bit in the denominator. This + // ensures that when we later shift the numerator N bits to the left, we + // will produce N bits of precision. + uint fractionalNumeratorBits = BigInteger.CountSignificantBits(ref fractionalNumerator); + uint fractionalDenominatorBits = BigInteger.CountSignificantBits(ref fractionalDenominator); + + uint fractionalShift = 0; + + if (fractionalDenominatorBits > fractionalNumeratorBits) + { + fractionalShift = fractionalDenominatorBits - fractionalNumeratorBits; + } + + if (fractionalShift > 0) + { + fractionalNumerator.ShiftLeft(fractionalShift); + } + + uint requiredFractionalBitsOfPrecision = requiredBitsOfPrecision - integerBitsOfPrecision; + uint remainingBitsOfPrecisionRequired = requiredFractionalBitsOfPrecision; + + if (integerBitsOfPrecision > 0) + { + // If the fractional part of the mantissa provides no bits of precision + // and cannot affect rounding, we can just take whatever bits we got from + // the integer part of the mantissa. This is the case for numbers like + // 5.0000000000000000000001, where the significant digits of the fractional + // part start so far to the right that they do not affect the floating + // point representation. + // + // If the fractional shift is exactly equal to the number of bits of + // precision that we require, then no fractional bits will be part of the + // result, but the result may affect rounding. This is e.g. the case for + // large, odd integers with a fractional part greater than or equal to .5. + // Thus, we need to do the division to correctly round the result. + if (fractionalShift > remainingBitsOfPrecisionRequired) + { + return ConvertBigIntegerToFloatingPointBits( + ref integerValue, + in info, + integerBitsOfPrecision, + fractionalDigitsPresent != 0 + ); + } + + remainingBitsOfPrecisionRequired -= fractionalShift; + } + + // If there was no integer part of the mantissa, we will need to compute the + // exponent from the fractional part. The fractional exponent is the power + // of two by which we must multiply the fractional part to move it into the + // range [1.0, 2.0). This will either be the same as the shift we computed + // earlier, or one greater than that shift: + uint fractionalExponent = fractionalShift; + + if (BigInteger.Compare(ref fractionalNumerator, ref fractionalDenominator) < 0) + { + fractionalExponent++; + } + + fractionalNumerator.ShiftLeft(remainingBitsOfPrecisionRequired); + + BigInteger.DivRem(ref fractionalNumerator, ref fractionalDenominator, out BigInteger bigFractionalMantissa, out BigInteger fractionalRemainder); + ulong fractionalMantissa = bigFractionalMantissa.ToUInt64(); + bool hasZeroTail = fractionalRemainder.IsZero(); + + // We may have produced more bits of precision than were required. Check, + // and remove any "extra" bits: + uint fractionalMantissaBits = BigInteger.CountSignificantBits(fractionalMantissa); + + if (fractionalMantissaBits > requiredFractionalBitsOfPrecision) + { + int shift = (int)(fractionalMantissaBits - requiredFractionalBitsOfPrecision); + hasZeroTail = hasZeroTail && (fractionalMantissa & ((1UL << shift) - 1)) == 0; + fractionalMantissa >>= shift; + } + + // Compose the mantissa from the integer and fractional parts: + ulong integerMantissa = integerValue.ToUInt64(); + ulong completeMantissa = (integerMantissa << (int)(requiredFractionalBitsOfPrecision)) + fractionalMantissa; + + // Compute the final exponent: + // * If the mantissa had an integer part, then the exponent is one less than + // the number of bits we obtained from the integer part. (It's one less + // because we are converting to the form 1.11111, with one 1 to the left + // of the decimal point.) + // * If the mantissa had no integer part, then the exponent is the fractional + // exponent that we computed. + // Then, in both cases, we subtract an additional one from the exponent, to + // account for the fact that we've generated an extra bit of precision, for + // use in rounding. + int finalExponent = (integerBitsOfPrecision > 0) ? (int)(integerBitsOfPrecision) - 2 : -(int)(fractionalExponent) - 1; + + return AssembleFloatingPointBits(in info, completeMantissa, finalExponent, hasZeroTail); + } + + private static ulong RightShiftWithRounding(ulong value, int shift, bool hasZeroTail) + { + // If we'd need to shift further than it is possible to shift, the answer + // is always zero: + if (shift >= 64) + { + return 0; + } + + ulong extraBitsMask = (1UL << (shift - 1)) - 1; + ulong roundBitMask = (1UL << (shift - 1)); + ulong lsbBitMask = 1UL << shift; + + bool lsbBit = (value & lsbBitMask) != 0; + bool roundBit = (value & roundBitMask) != 0; + bool hasTailBits = !hasZeroTail || (value & extraBitsMask) != 0; + + return (value >> shift) + (ShouldRoundUp(lsbBit: lsbBit, roundBit: roundBit, hasTailBits: hasTailBits) ? 1UL : 0); + } + + private static bool ShouldRoundUp(bool lsbBit, bool roundBit, bool hasTailBits) + { + // If there are insignificant set bits, we need to round to the + // nearest; there are two cases: + // we round up if either [1] the value is slightly greater than the midpoint + // between two exactly representable values or [2] the value is exactly the + // midpoint between two exactly representable values and the greater of the + // two is even (this is "round-to-even"). + return roundBit && (hasTailBits || lsbBit); + } + } +} diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index a4191547fa62..72091e0c2cc2 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1660,9 +1660,7 @@ internal static unsafe bool TryParseDecimal(ReadOnlySpan value, NumberStyl internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result, out bool failureIsOverflow) { char* pDigits = stackalloc char[DoubleNumberBufferLength]; - NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, DoubleNumberBufferLength); - - result = 0; + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength); failureIsOverflow = false; if (!TryStringToNumber(value, styles, ref number, info)) @@ -1683,13 +1681,14 @@ internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyle } else { + result = 0; return false; // We really failed } return true; } - if (!TryNumberToDouble(ref number, ref result)) + if (!TryNumberToDouble(ref number, out result)) { failureIsOverflow = true; return false; @@ -1698,24 +1697,43 @@ internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyle return true; } - internal static bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result, out bool failureIsOverflow) + internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result, out bool failureIsOverflow) { - result = 0; + char* pDigits = stackalloc char[SingleNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength); + failureIsOverflow = false; - if (!TryParseDouble(value, styles, info, out double doubleResult, out failureIsOverflow)) + if (!TryStringToNumber(value, styles, ref number, info)) { - return false; - } + ReadOnlySpan valueTrim = value.Trim(); - float singleResult = (float)(doubleResult); + if (valueTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) + { + result = float.PositiveInfinity; + } + else if (valueTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) + { + result = float.NegativeInfinity; + } + else if (valueTrim.EqualsOrdinal(info.NaNSymbol)) + { + result = float.NaN; + } + else + { + result = 0; + return false; // We really failed + } - if (float.IsInfinity(singleResult) && double.IsFinite(doubleResult)) + return true; + } + + if (!TryNumberToSingle(ref number, out result)) { failureIsOverflow = true; return false; } - result = singleResult; return true; } @@ -1797,9 +1815,10 @@ private static void ThrowOverflowOrFormatException(bool overflow, string overflo (Exception)new FormatException(SR.Format_InvalidString); } - private static bool TryNumberToDouble(ref NumberBuffer number, ref double value) + private static bool TryNumberToDouble(ref NumberBuffer number, out double value) { - double result = NumberToDouble(ref number); + ulong bits = NumberToFloatingPointBitsRoslyn(ref number, in FloatingPointInfo.Double); + double result = BitConverter.Int64BitsToDouble((long)(bits)); if (!double.IsFinite(result)) { @@ -1810,5 +1829,20 @@ private static bool TryNumberToDouble(ref NumberBuffer number, ref double value) value = result; return true; } + + private static bool TryNumberToSingle(ref NumberBuffer number, out float value) + { + uint bits = (uint)(NumberToFloatingPointBitsRoslyn(ref number, in FloatingPointInfo.Single)); + float result = BitConverter.Int32BitsToSingle((int)(bits)); + + if (!float.IsFinite(result)) + { + value = default; + return false; + } + + value = result; + return true; + } } } From c48e9a8e81415d6edab903aa7faa8de4726a681b Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 30 Oct 2018 20:38:14 -0700 Subject: [PATCH 08/29] Fixing TryNumberToDouble and TryNumberToSingle to apply the appropriate sign. --- .../shared/System/Number.Parsing.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 72091e0c2cc2..cf2778332fad 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1827,6 +1827,12 @@ private static bool TryNumberToDouble(ref NumberBuffer number, out double value) } value = result; + + if (number.Sign) + { + value = -value; + } + return true; } @@ -1842,6 +1848,12 @@ private static bool TryNumberToSingle(ref NumberBuffer number, out float value) } value = result; + + if (number.Sign) + { + value = -value; + } + return true; } } From 57c8bf8c3e00214e79a0a89687047c4764a19131 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 31 Oct 2018 09:39:50 -0700 Subject: [PATCH 09/29] Adding a fast path for double/single parsing when we have <= 15 digits and an absolute scale <= 22 --- .../Number.NumberToFloatingPointBitsRoslyn.cs | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs index 2fdfbbb17320..fe16afd05e57 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs @@ -43,7 +43,7 @@ public readonly struct FloatingPointInfo public ushort NormalMantissaBits { get; } public ushort DenormalMantissaBits { get; } - + public ushort ExponentBits { get; } public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int maxBinaryExponent, int exponentBias, ulong infinityBits) @@ -67,6 +67,33 @@ public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int m } } + private static double[] s_Pow10Table = new double[] + { + 1e0, // 10^0 + 1e1, // 10^1 + 1e2, // 10^2 + 1e3, // 10^3 + 1e4, // 10^4 + 1e5, // 10^5 + 1e6, // 10^6 + 1e7, // 10^7 + 1e8, // 10^8 + 1e9, // 10^9 + 1e10, // 10^10 + 1e11, // 10^11 + 1e12, // 10^12 + 1e13, // 10^13 + 1e14, // 10^14 + 1e15, // 10^15 + 1e16, // 10^16 + 1e17, // 10^17 + 1e18, // 10^18 + 1e19, // 10^19 + 1e20, // 10^20 + 1e21, // 10^21 + 1e22, // 10^22 + }; + private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result) { result = new BigInteger(0); @@ -309,6 +336,60 @@ private static ulong NumberToFloatingPointBitsRoslyn(ref NumberBuffer number, in uint fractionalLastIndex = totalDigits; uint fractionalDigitsPresent = fractionalLastIndex - fractionalFirstIndex; + // When the number of significant digits is less than or equal to 15 and the + // scale is less than or equal to 22, we can take a shortcut and just rely + // on double-precision arithmetic to compute the correct result. This is + // because double-precision values allow us to exactly represent any whole + // integer that contains less then 15 digits, the same being true for powers + // of ten from 16-22. Additionally, IEEE operations dictate that the result is + // computed to the infinitely precise result and then rounded, which means that + // we can rely on it to produce the correct result when both inputs are exact. + + Debug.Assert(s_Pow10Table.Length == 23); + + uint fastExponent = (uint)(Math.Abs(number.Scale - integerDigitsPresent - fractionalDigitsPresent)); + + if ((totalDigits <= 15) && (fastExponent <= 22)) + { + uint remaining = totalDigits; + uint count = Math.Min(remaining, 9); + double result = DigitsToInt(src, (int)(count)); + + remaining -= count; + + if (remaining > 0) + { + Debug.Assert(remaining <= 6); + + // scale the value so we have space for the additional digits + // and add the remaining to get an exact mantissa. + + result *= s_Pow10Table[remaining]; + result += DigitsToInt(src + 9, (int)(remaining)); + } + + double scale = s_Pow10Table[fastExponent]; + + if (fractionalDigitsPresent != 0) + { + result /= scale; + } + else + { + result *= scale; + } + + if (info.DenormalMantissaBits == 52) + { + return (ulong)(BitConverter.DoubleToInt64Bits(result)); + } + else + { + Debug.Assert(info.DenormalMantissaBits == 23); + return (uint)(BitConverter.SingleToInt32Bits((float)(result))); + } + } + // First, we accumulate the integer part of the mantissa into a big_integer: AccumulateDecimalDigitsIntoBigInteger(ref number, integerFirstIndex, integerLastIndex, out BigInteger integerValue); From dcb6aaca4e457dec93ef4cb8f13323bc8d86552f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 31 Oct 2018 13:26:52 -0700 Subject: [PATCH 10/29] Update NumberBuffer to also track whether any input digits past maxDigCount were non-zero --- .../shared/System/Number.NumberBuffer.cs | 8 +++-- .../Number.NumberToFloatingPointBitsRoslyn.cs | 2 +- .../shared/System/Number.Parsing.cs | 35 ++++++++++++------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs index e5c986f983ea..3d18e711a25a 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs @@ -11,11 +11,11 @@ namespace System internal static partial class Number { // We need 1 additional byte, per length, for the terminating null - private const int DecimalNumberBufferLength = 50 + 1; - private const int DoubleNumberBufferLength = 768 + 1; // 767 for the longest input + 1 for rounding: 4.9406564584124654E-324 + private const int DecimalNumberBufferLength = 29 + 1; // 29 for the longest input + 1 for rounding + private const int DoubleNumberBufferLength = 768 + 1; // 768 for the longest input + 1 for rounding: 4.9406564584124654E-324 private const int Int32NumberBufferLength = 10 + 1; // 10 for the longest input: 2,147,483,647 private const int Int64NumberBufferLength = 19 + 1; // 19 for the longest input: 9,223,372,036,854,775,807 - private const int SingleNumberBufferLength = 113 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45 + private const int SingleNumberBufferLength = 113 + 1; // 113 for the longest input + 1 for rounding: 1.40129846E-45 private const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295 private const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615 @@ -24,6 +24,7 @@ internal unsafe ref struct NumberBuffer public int Precision; public int Scale; public bool Sign; + public bool HasNonZeroTail; public NumberBufferKind Kind; public Span Digits; @@ -37,6 +38,7 @@ public NumberBuffer(NumberBufferKind kind, char* digits, int digitsLength) Precision = 0; Scale = 0; Sign = false; + HasNonZeroTail = false; Kind = kind; Digits = new Span(digits, digitsLength); } diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs index fe16afd05e57..db0eb5599fc1 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs @@ -514,7 +514,7 @@ private static ulong NumberToFloatingPointBitsRoslyn(ref NumberBuffer number, in BigInteger.DivRem(ref fractionalNumerator, ref fractionalDenominator, out BigInteger bigFractionalMantissa, out BigInteger fractionalRemainder); ulong fractionalMantissa = bigFractionalMantissa.ToUInt64(); - bool hasZeroTail = fractionalRemainder.IsZero(); + bool hasZeroTail = !number.HasNonZeroTail && fractionalRemainder.IsZero(); // We may have produced more bits of precision than were required. Check, // and remove any "extra" bits: diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index cf2778332fad..373dd8fe7e8a 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -252,8 +252,12 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty const int StateDecimal = 0x0010; const int StateCurrency = 0x0020; - number.Scale = 0; - number.Sign = false; + Debug.Assert(number.Precision == 0); + Debug.Assert(number.Scale == 0); + Debug.Assert(number.Sign == false); + Debug.Assert(number.HasNonZeroTail == true); + Debug.Assert(number.Kind != NumberBufferKind.Unknown); + string decSep; // decimal separator from NumberFormatInfo. string groupSep; // group separator from NumberFormatInfo. string currSymbol = null; // currency symbol from NumberFormatInfo. @@ -332,6 +336,18 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty digEnd = digCount; } } + else if (ch != '0') + { + // For decimal and binary floating-point numbers, we only + // need to store digits up to maxDigCount. However, we still + // need to keep track of whether any additional digits past + // maxDigCount were non-zero, as that can impact rounding + // for an input that falls evenly between two representable + // results. + + number.HasNonZeroTail = true; + } + if ((state & StateDecimal) == 0) { number.Scale++; @@ -1576,18 +1592,11 @@ private static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decim 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) + if ((c == '5') && ((low64 & 1) == 0) && !number.HasNonZeroTail) { - c = *++p; - int count = 20; // Look at the next 20 digits to check to round - while (c == '0' && count != 0) - { - c = *++p; - count--; - } - if (c == 0 || count == 0) - goto NoRounding;// Do nothing + // When the next digit is 5, the number is even, and all following digits are zero + // we don't need to round. + goto NoRounding; } if (++low64 == 0 && ++high == 0) From 06650bebd2e45d7d3876e0a88308ed29d16d4ba9 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 31 Oct 2018 13:32:25 -0700 Subject: [PATCH 11/29] Renaming NumberToFloatingPointBitsRoslyn to NumberToFloatingPointBits --- .../shared/System.Private.CoreLib.Shared.projitems | 2 +- ...ngPointBitsRoslyn.cs => Number.NumberToFloatingPointBits.cs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/System.Private.CoreLib/shared/System/{Number.NumberToFloatingPointBitsRoslyn.cs => Number.NumberToFloatingPointBits.cs} (100%) diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index cd7823c08841..a19af657f69e 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -294,7 +294,7 @@ - + diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs similarity index 100% rename from src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBitsRoslyn.cs rename to src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs From 31357e2017b8acdc3154862a207ea6b4b5c4eaa0 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 31 Oct 2018 15:44:46 -0700 Subject: [PATCH 12/29] Updating TryNumberToDouble and TryNumberToSingle to support Overflow to Infinity --- .../shared/System/Number.Parsing.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 373dd8fe7e8a..08a15f3aecf5 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1829,12 +1829,6 @@ private static bool TryNumberToDouble(ref NumberBuffer number, out double value) ulong bits = NumberToFloatingPointBitsRoslyn(ref number, in FloatingPointInfo.Double); double result = BitConverter.Int64BitsToDouble((long)(bits)); - if (!double.IsFinite(result)) - { - value = default; - return false; - } - value = result; if (number.Sign) @@ -1850,12 +1844,6 @@ private static bool TryNumberToSingle(ref NumberBuffer number, out float value) uint bits = (uint)(NumberToFloatingPointBitsRoslyn(ref number, in FloatingPointInfo.Single)); float result = BitConverter.Int32BitsToSingle((int)(bits)); - if (!float.IsFinite(result)) - { - value = default; - return false; - } - value = result; if (number.Sign) From 0b9861e20d5df41fe93cc6c6426d370b081a7ea6 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 31 Oct 2018 15:49:55 -0700 Subject: [PATCH 13/29] Fixing a Debug.Assert in TryParseNumber --- src/System.Private.CoreLib/shared/System/Number.Parsing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 08a15f3aecf5..ea41ca71834c 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -255,7 +255,7 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty Debug.Assert(number.Precision == 0); Debug.Assert(number.Scale == 0); Debug.Assert(number.Sign == false); - Debug.Assert(number.HasNonZeroTail == true); + Debug.Assert(number.HasNonZeroTail == false); Debug.Assert(number.Kind != NumberBufferKind.Unknown); string decSep; // decimal separator from NumberFormatInfo. From 5d2c7976b9f29b3fbf36ffabc8656d6132ca80d3 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 31 Oct 2018 15:53:35 -0700 Subject: [PATCH 14/29] Fixing `DecimalNumberBufferLength` to 30 --- .../shared/System/Number.NumberBuffer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs index 3d18e711a25a..9bdb26da0fae 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs @@ -11,11 +11,11 @@ namespace System internal static partial class Number { // We need 1 additional byte, per length, for the terminating null - private const int DecimalNumberBufferLength = 29 + 1; // 29 for the longest input + 1 for rounding - private const int DoubleNumberBufferLength = 768 + 1; // 768 for the longest input + 1 for rounding: 4.9406564584124654E-324 + private const int DecimalNumberBufferLength = 30 + 1; // 29 for the longest input + 1 for rounding + private const int DoubleNumberBufferLength = 768 + 1; // 767 for the longest input + 1 for rounding: 4.9406564584124654E-324 private const int Int32NumberBufferLength = 10 + 1; // 10 for the longest input: 2,147,483,647 private const int Int64NumberBufferLength = 19 + 1; // 19 for the longest input: 9,223,372,036,854,775,807 - private const int SingleNumberBufferLength = 113 + 1; // 113 for the longest input + 1 for rounding: 1.40129846E-45 + private const int SingleNumberBufferLength = 113 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45 private const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295 private const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615 From 24e30d3007642507dd465c40fb84f14240416bef Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 1 Nov 2018 07:44:19 -0700 Subject: [PATCH 15/29] Renaming NumberToFloatingPointBitsRoslyn to NumberToFloatingPointBits --- .../Number.NumberToFloatingPointBits.cs | 226 +++++++++--------- .../shared/System/Number.Parsing.cs | 4 +- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index db0eb5599fc1..f342f0e3459e 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -105,7 +105,7 @@ private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer numbe { uint count = Math.Min(remaining, 9); uint value = DigitsToInt(src, (int)(count)); - + result.MultiplyPow10(count); result.Add(value); @@ -116,9 +116,9 @@ private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer numbe private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong initialMantissa, int initialExponent, bool hasZeroTail) { - // number of bits by which we must adjust the mantissa to shift it into the - // correct position, and compute the resulting base two exponent for the - // normalized mantissa: + // number of bits by which we must adjust the mantissa to shift it into the + // correct position, and compute the resulting base two exponent for the + // normalized mantissa: uint initialMantissaBits = BigInteger.CountSignificantBits(initialMantissa); int normalMantissaShift = info.NormalMantissaBits - (int)(initialMantissaBits); int normalExponent = initialExponent - normalMantissaShift; @@ -128,58 +128,58 @@ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong if (normalExponent > info.MaxBinaryExponent) { - // The exponent is too large to be represented by the floating point + // The exponent is too large to be represented by the floating point // type; report the overflow condition: return info.InfinityBits; } else if (normalExponent < info.MinBinaryExponent) { - // The exponent is too small to be represented by the floating point - // type as a normal value, but it may be representable as a denormal - // value. Compute the number of bits by which we need to shift the - // mantissa in order to form a denormal number. (The subtraction of - // an extra 1 is to account for the hidden bit of the mantissa that - // is not available for use when representing a denormal.) + // The exponent is too small to be represented by the floating point + // type as a normal value, but it may be representable as a denormal + // value. Compute the number of bits by which we need to shift the + // mantissa in order to form a denormal number. (The subtraction of + // an extra 1 is to account for the hidden bit of the mantissa that + // is not available for use when representing a denormal.) int denormalMantissaShift = normalMantissaShift + normalExponent + info.ExponentBias - 1; - // Denormal values have an exponent of zero, so the debiased exponent is - // the negation of the exponent bias: + // Denormal values have an exponent of zero, so the debiased exponent is + // the negation of the exponent bias: exponent = -info.ExponentBias; if (denormalMantissaShift < 0) { - // Use two steps for right shifts: for a shift of N bits, we first - // shift by N-1 bits, then shift the last bit and use its value to - // round the mantissa. + // Use two steps for right shifts: for a shift of N bits, we first + // shift by N-1 bits, then shift the last bit and use its value to + // round the mantissa. mantissa = RightShiftWithRounding(mantissa, -denormalMantissaShift, hasZeroTail); - // If the mantissa is now zero, we have underflowed: + // If the mantissa is now zero, we have underflowed: if (mantissa == 0) { return info.ZeroBits; } - // When we round the mantissa, the result may be so large that the - // number becomes a normal value. For example, consider the single - // precision case where the mantissa is 0x01ffffff and a right shift - // of 2 is required to shift the value into position. We perform the - // shift in two steps: we shift by one bit, then we shift again and - // round using the dropped bit. The initial shift yields 0x00ffffff. - // The rounding shift then yields 0x007fffff and because the least - // significant bit was 1, we add 1 to this number to round it. The - // final result is 0x00800000. - // - // 0x00800000 is 24 bits, which is more than the 23 bits available - // in the mantissa. Thus, we have rounded our denormal number into - // a normal number. - // - // We detect this case here and re-adjust the mantissa and exponent - // appropriately, to form a normal number: + // When we round the mantissa, the result may be so large that the + // number becomes a normal value. For example, consider the single + // precision case where the mantissa is 0x01ffffff and a right shift + // of 2 is required to shift the value into position. We perform the + // shift in two steps: we shift by one bit, then we shift again and + // round using the dropped bit. The initial shift yields 0x00ffffff. + // The rounding shift then yields 0x007fffff and because the least + // significant bit was 1, we add 1 to this number to round it. The + // final result is 0x00800000. + // + // 0x00800000 is 24 bits, which is more than the 23 bits available + // in the mantissa. Thus, we have rounded our denormal number into + // a normal number. + // + // We detect this case here and re-adjust the mantissa and exponent + // appropriately, to form a normal number: if (mantissa > info.DenormalMantissaMask) { - // We add one to the denormal_mantissa_shift to account for the - // hidden mantissa bit (we subtracted one to account for this bit - // when we computed the denormal_mantissa_shift above). + // We add one to the denormal_mantissa_shift to account for the + // hidden mantissa bit (we subtracted one to account for this bit + // when we computed the denormal_mantissa_shift above). exponent = initialExponent - (denormalMantissaShift + 1) - normalMantissaShift; } } @@ -192,21 +192,21 @@ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong { if (normalMantissaShift < 0) { - // Use two steps for right shifts: for a shift of N bits, we first - // shift by N-1 bits, then shift the last bit and use its value to - // round the mantissa. + // Use two steps for right shifts: for a shift of N bits, we first + // shift by N-1 bits, then shift the last bit and use its value to + // round the mantissa. mantissa = RightShiftWithRounding(mantissa, -normalMantissaShift, hasZeroTail); - // When we round the mantissa, it may produce a result that is too - // large. In this case, we divide the mantissa by two and increment - // the exponent (this does not change the value). + // When we round the mantissa, it may produce a result that is too + // large. In this case, we divide the mantissa by two and increment + // the exponent (this does not change the value). if (mantissa > info.NormalMantissaMask) { mantissa >>= 1; exponent++; - // The increment of the exponent may have generated a value too - // large to be represented. In this case, report the overflow: + // The increment of the exponent may have generated a value too + // large to be represented. In this case, report the overflow: if (exponent > info.MaxBinaryExponent) { return info.InfinityBits; @@ -219,8 +219,8 @@ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong } } - // Unset the hidden bit in the mantissa and assemble the floating point value - // from the computed components: + // Unset the hidden bit in the mantissa and assemble the floating point value + // from the computed components: mantissa &= info.DenormalMantissaMask; Debug.Assert((info.DenormalMantissaMask & (1UL << info.DenormalMantissaBits)) == 0); @@ -293,7 +293,7 @@ private static uint DigitsToInt(char* p, int count) char* end = (p + count); uint res = (uint)(p[0] - '0'); - for (p++; p info.OverflowDecimalExponent) { - // If there were any digits in the integer part, it is impossible to - // underflow (because the exponent cannot possibly be small enough), - // so if we underflow here it is a true underflow and we return zero. + // If there were any digits in the integer part, it is impossible to + // underflow (because the exponent cannot possibly be small enough), + // so if we underflow here it is a true underflow and we return zero. return info.ZeroBits; } @@ -447,12 +447,12 @@ private static ulong NumberToFloatingPointBitsRoslyn(ref NumberBuffer number, in BigInteger.Pow10(fractionalDenominatorExponent, out BigInteger fractionalDenominator); - // Because we are using only the fractional part of the mantissa here, the - // numerator is guaranteed to be smaller than the denominator. We normalize - // the fraction such that the most significant bit of the numerator is in - // the same position as the most significant bit in the denominator. This - // ensures that when we later shift the numerator N bits to the left, we - // will produce N bits of precision. + // Because we are using only the fractional part of the mantissa here, the + // numerator is guaranteed to be smaller than the denominator. We normalize + // the fraction such that the most significant bit of the numerator is in + // the same position as the most significant bit in the denominator. This + // ensures that when we later shift the numerator N bits to the left, we + // will produce N bits of precision. uint fractionalNumeratorBits = BigInteger.CountSignificantBits(ref fractionalNumerator); uint fractionalDenominatorBits = BigInteger.CountSignificantBits(ref fractionalDenominator); @@ -473,18 +473,18 @@ private static ulong NumberToFloatingPointBitsRoslyn(ref NumberBuffer number, in if (integerBitsOfPrecision > 0) { - // If the fractional part of the mantissa provides no bits of precision - // and cannot affect rounding, we can just take whatever bits we got from - // the integer part of the mantissa. This is the case for numbers like - // 5.0000000000000000000001, where the significant digits of the fractional - // part start so far to the right that they do not affect the floating - // point representation. - // - // If the fractional shift is exactly equal to the number of bits of - // precision that we require, then no fractional bits will be part of the - // result, but the result may affect rounding. This is e.g. the case for - // large, odd integers with a fractional part greater than or equal to .5. - // Thus, we need to do the division to correctly round the result. + // If the fractional part of the mantissa provides no bits of precision + // and cannot affect rounding, we can just take whatever bits we got from + // the integer part of the mantissa. This is the case for numbers like + // 5.0000000000000000000001, where the significant digits of the fractional + // part start so far to the right that they do not affect the floating + // point representation. + // + // If the fractional shift is exactly equal to the number of bits of + // precision that we require, then no fractional bits will be part of the + // result, but the result may affect rounding. This is e.g. the case for + // large, odd integers with a fractional part greater than or equal to .5. + // Thus, we need to do the division to correctly round the result. if (fractionalShift > remainingBitsOfPrecisionRequired) { return ConvertBigIntegerToFloatingPointBits( @@ -498,11 +498,11 @@ private static ulong NumberToFloatingPointBitsRoslyn(ref NumberBuffer number, in remainingBitsOfPrecisionRequired -= fractionalShift; } - // If there was no integer part of the mantissa, we will need to compute the - // exponent from the fractional part. The fractional exponent is the power - // of two by which we must multiply the fractional part to move it into the - // range [1.0, 2.0). This will either be the same as the shift we computed - // earlier, or one greater than that shift: + // If there was no integer part of the mantissa, we will need to compute the + // exponent from the fractional part. The fractional exponent is the power + // of two by which we must multiply the fractional part to move it into the + // range [1.0, 2.0). This will either be the same as the shift we computed + // earlier, or one greater than that shift: uint fractionalExponent = fractionalShift; if (BigInteger.Compare(ref fractionalNumerator, ref fractionalDenominator) < 0) @@ -516,8 +516,8 @@ private static ulong NumberToFloatingPointBitsRoslyn(ref NumberBuffer number, in ulong fractionalMantissa = bigFractionalMantissa.ToUInt64(); bool hasZeroTail = !number.HasNonZeroTail && fractionalRemainder.IsZero(); - // We may have produced more bits of precision than were required. Check, - // and remove any "extra" bits: + // We may have produced more bits of precision than were required. Check, + // and remove any "extra" bits: uint fractionalMantissaBits = BigInteger.CountSignificantBits(fractionalMantissa); if (fractionalMantissaBits > requiredFractionalBitsOfPrecision) @@ -527,20 +527,20 @@ private static ulong NumberToFloatingPointBitsRoslyn(ref NumberBuffer number, in fractionalMantissa >>= shift; } - // Compose the mantissa from the integer and fractional parts: + // Compose the mantissa from the integer and fractional parts: ulong integerMantissa = integerValue.ToUInt64(); ulong completeMantissa = (integerMantissa << (int)(requiredFractionalBitsOfPrecision)) + fractionalMantissa; - // Compute the final exponent: - // * If the mantissa had an integer part, then the exponent is one less than - // the number of bits we obtained from the integer part. (It's one less - // because we are converting to the form 1.11111, with one 1 to the left - // of the decimal point.) - // * If the mantissa had no integer part, then the exponent is the fractional - // exponent that we computed. - // Then, in both cases, we subtract an additional one from the exponent, to - // account for the fact that we've generated an extra bit of precision, for - // use in rounding. + // Compute the final exponent: + // * If the mantissa had an integer part, then the exponent is one less than + // the number of bits we obtained from the integer part. (It's one less + // because we are converting to the form 1.11111, with one 1 to the left + // of the decimal point.) + // * If the mantissa had no integer part, then the exponent is the fractional + // exponent that we computed. + // Then, in both cases, we subtract an additional one from the exponent, to + // account for the fact that we've generated an extra bit of precision, for + // use in rounding. int finalExponent = (integerBitsOfPrecision > 0) ? (int)(integerBitsOfPrecision) - 2 : -(int)(fractionalExponent) - 1; return AssembleFloatingPointBits(in info, completeMantissa, finalExponent, hasZeroTail); @@ -548,8 +548,8 @@ private static ulong NumberToFloatingPointBitsRoslyn(ref NumberBuffer number, in private static ulong RightShiftWithRounding(ulong value, int shift, bool hasZeroTail) { - // If we'd need to shift further than it is possible to shift, the answer - // is always zero: + // If we'd need to shift further than it is possible to shift, the answer + // is always zero: if (shift >= 64) { return 0; @@ -568,12 +568,12 @@ private static ulong RightShiftWithRounding(ulong value, int shift, bool hasZero private static bool ShouldRoundUp(bool lsbBit, bool roundBit, bool hasTailBits) { - // If there are insignificant set bits, we need to round to the - // nearest; there are two cases: - // we round up if either [1] the value is slightly greater than the midpoint - // between two exactly representable values or [2] the value is exactly the - // midpoint between two exactly representable values and the greater of the - // two is even (this is "round-to-even"). + // If there are insignificant set bits, we need to round to the + // nearest; there are two cases: + // we round up if either [1] the value is slightly greater than the midpoint + // between two exactly representable values or [2] the value is exactly the + // midpoint between two exactly representable values and the greater of the + // two is even (this is "round-to-even"). return roundBit && (hasTailBits || lsbBit); } } diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index ea41ca71834c..6e3bb12c0889 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1826,7 +1826,7 @@ private static void ThrowOverflowOrFormatException(bool overflow, string overflo private static bool TryNumberToDouble(ref NumberBuffer number, out double value) { - ulong bits = NumberToFloatingPointBitsRoslyn(ref number, in FloatingPointInfo.Double); + ulong bits = NumberToFloatingPointBits(ref number, in FloatingPointInfo.Double); double result = BitConverter.Int64BitsToDouble((long)(bits)); value = result; @@ -1841,7 +1841,7 @@ private static bool TryNumberToDouble(ref NumberBuffer number, out double value) private static bool TryNumberToSingle(ref NumberBuffer number, out float value) { - uint bits = (uint)(NumberToFloatingPointBitsRoslyn(ref number, in FloatingPointInfo.Single)); + uint bits = (uint)(NumberToFloatingPointBits(ref number, in FloatingPointInfo.Single)); float result = BitConverter.Int32BitsToSingle((int)(bits)); value = result; From cd87e0053f2a34b7bee15515b4c1fe6d31d9a10f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 1 Nov 2018 07:46:18 -0700 Subject: [PATCH 16/29] Clarifying the NumberBufferLength comments --- .../shared/System/Number.NumberBuffer.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs index 9bdb26da0fae..83af5134583c 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; -using System.Runtime.InteropServices; using Internal.Runtime.CompilerServices; namespace System @@ -11,11 +10,11 @@ namespace System internal static partial class Number { // We need 1 additional byte, per length, for the terminating null - private const int DecimalNumberBufferLength = 30 + 1; // 29 for the longest input + 1 for rounding - private const int DoubleNumberBufferLength = 768 + 1; // 767 for the longest input + 1 for rounding: 4.9406564584124654E-324 + private const int DecimalNumberBufferLength = 29 + 1 + 1; // 29 for the longest input + 1 for rounding + private const int DoubleNumberBufferLength = 767 + 1 + 1; // 767 for the longest input + 1 for rounding: 4.9406564584124654E-324 private const int Int32NumberBufferLength = 10 + 1; // 10 for the longest input: 2,147,483,647 private const int Int64NumberBufferLength = 19 + 1; // 19 for the longest input: 9,223,372,036,854,775,807 - private const int SingleNumberBufferLength = 113 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45 + private const int SingleNumberBufferLength = 112 + 1 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45 private const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295 private const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615 From 83a9738f916c155f57a54a25ee3c193dcd3244f6 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 1 Nov 2018 08:15:06 -0700 Subject: [PATCH 17/29] Fixing TryNumberToDecimal to check the last digit in the buffer, if it exists --- .../shared/System/Number.Parsing.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 6e3bb12c0889..3d2dc838bdf6 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1592,11 +1592,20 @@ private static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decim if (c >= '5') { - if ((c == '5') && ((low64 & 1) == 0) && !number.HasNonZeroTail) + if ((c == '5') && ((low64 & 1) == 0)) { - // When the next digit is 5, the number is even, and all following digits are zero - // we don't need to round. - goto NoRounding; + c = *++p; + + // At this point we should either be at the end of the buffer, or just + // have a single rounding digit left, and the next should be the end + Debug.Assert((c == 0) || (*++p == 0)); + + if (((c == 0) || c == '0') && !number.HasNonZeroTail) + { + // When the next digit is 5, the number is even, and all following digits are zero + // we don't need to round. + goto NoRounding; + } } if (++low64 == 0 && ++high == 0) From c71b90673d14073ea7eb508e1eea2dffd04dbab9 Mon Sep 17 00:00:00 2001 From: Pent Ploompuu Date: Thu, 1 Nov 2018 11:30:40 -0700 Subject: [PATCH 18/29] Fix TryNumberToDecimal to not modify the value of p in the assert. Co-Authored-By: tannergooding --- src/System.Private.CoreLib/shared/System/Number.Parsing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 3d2dc838bdf6..1e706291a027 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1598,7 +1598,7 @@ private static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decim // At this point we should either be at the end of the buffer, or just // have a single rounding digit left, and the next should be the end - Debug.Assert((c == 0) || (*++p == 0)); + Debug.Assert((c == 0) || (p[1] == 0)); if (((c == 0) || c == '0') && !number.HasNonZeroTail) { From c08f18036324f7638331622262f46b2cfadea502 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 1 Nov 2018 07:33:44 -0700 Subject: [PATCH 19/29] Disable some CoreFX tests due to the single/double/decimal parsing fixes --- tests/CoreFX/CoreFX.issues.json | 87 ++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/tests/CoreFX/CoreFX.issues.json b/tests/CoreFX/CoreFX.issues.json index 2f15282a7f85..d783eb33fb4b 100644 --- a/tests/CoreFX/CoreFX.issues.json +++ b/tests/CoreFX/CoreFX.issues.json @@ -495,6 +495,13 @@ "name": "System.SpanTests.MemoryMarshalTests.CreateFromPinnedArrayIntReadOnlyMemorySliceRemainsPinned", "reason": "https://github.com/dotnet/corefx/pull/32994" } + "name": "System.Buffers.Text.Tests.ParserTests.TestParserSingle", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + { + "name": "System.Buffers.Text.Tests.ParserTests.TestParserDouble", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, ] } }, @@ -508,7 +515,15 @@ { "name": "System.Tests.ConvertToStringTests.FromBoxedObject", "reason": "https://github.com/dotnet/coreclr/pull/19775" - } + }, + { + "name": "System.Tests.ConvertToDoubleTests.FromString", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + { + "name": "System.Tests.ConvertToSingleTests.FromString", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, ] } }, @@ -684,5 +699,73 @@ } ] } - } + }, + { + "name": "System.Xml.Linq.SDMSample.Tests", + "enabled": true, + "exclusions": { + "namespaces": null, + "classes": null, + "methods": [ + { + "name": "XDocumentTests.SDMSample.SDM_Attribute.AttributeExplicitToDouble", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + { + "name": "XDocumentTests.SDMSample.SDM_Attribute.AttributeExplicitToFloat", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + { + "name": "XDocumentTests.SDMSample.SDM_Element.ElementExplicitToFloat", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + { + "name": "XDocumentTests.SDMSample.SDM_Element.ElementExplicitToDouble", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + ] + } + }, + { + "name": "System.Json.Tests", + "enabled": true, + "exclusions": { + "namespaces": null, + "classes": null, + "methods": [ + { + "name": "System.Json.Tests.JsonValueTests.Parse_DoubleTooLarge_ThrowsOverflowException", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + ] + } + }, + { + "name": "System.Xml.RW.XmlWriterApi.Tests", + "enabled": true, + "exclusions": { + "namespaces": null, + "classes": null, + "methods": [ + { + "name": "System.Xml.Tests.TCFullEndElement+TCWriteValue.writeValue_27", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + ] + } + }, + { + "name": "System.ComponentModel.Annotations.Tests", + "enabled": true, + "exclusions": { + "namespaces": null, + "classes": null, + "methods": [ + { + "name": "System.ComponentModel.DataAnnotations.Tests.RangeAttributeTests.Validate_DoubleConversionOverflows_ThrowsOverflowException", + "reason": "https://github.com/dotnet/coreclr/pull/20707" + }, + ] + } + }, ] From b91d670b9162b65005179877fd57950c3b31c133 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 1 Nov 2018 18:58:54 -0700 Subject: [PATCH 20/29] Updating NumberToFloatingPointBits to use single-precision arithmetic and extended-precision multiplication where possible --- .../shared/System/Number.BigInteger.cs | 5 + .../Number.NumberToFloatingPointBits.cs | 224 ++++++++++++++++-- 2 files changed, 208 insertions(+), 21 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs index 8e49691fc44a..1231b55bd696 100644 --- a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs +++ b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs @@ -702,6 +702,11 @@ public static uint LeadingZeroCount(uint value) return 32 - CountSignificantBits(value); } + public static uint LeadingZeroCount(ulong value) + { + return 64 - CountSignificantBits(value); + } + public static uint LogBase2(uint value) { Debug.Assert(value != 0); diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index f342f0e3459e..a21c9eab6f30 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -18,7 +18,7 @@ public readonly struct FloatingPointInfo exponentBits: 11, maxBinaryExponent: 1023, exponentBias: 1023, - infinityBits: 0x7FF0000000000000 + infinityBits: 0x7FF00000_00000000 ); public static readonly FloatingPointInfo Single = new FloatingPointInfo( @@ -67,7 +67,22 @@ public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int m } } - private static double[] s_Pow10Table = new double[] + private static float[] s_Pow10SingleTable = new float[] + { + 1e0f, // 10^0 + 1e1f, // 10^1 + 1e2f, // 10^2 + 1e3f, // 10^3 + 1e4f, // 10^4 + 1e5f, // 10^5 + 1e6f, // 10^6 + 1e7f, // 10^7 + 1e8f, // 10^8 + 1e9f, // 10^9 + 1e10f, // 10^10 + }; + + private static double[] s_Pow10DoubleTable = new double[] { 1e0, // 10^0 1e1, // 10^1 @@ -94,6 +109,70 @@ public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int m 1e22, // 10^22 }; + private static ulong[] s_Pow10ExtendedMantissaTable = new ulong[] + { + 0x80000000_00000000, // 10^0 + 0xA0000000_00000000, // 10^1 + 0xC8000000_00000000, // 10^2 + 0xFA000000_00000000, // 10^3 + 0x9C400000_00000000, // 10^4 + 0xC3500000_00000000, // 10^5 + 0xF4240000_00000000, // 10^6 + 0x98968000_00000000, // 10^7 + 0xBEBC2000_00000000, // 10^8 + 0xEE6B2800_00000000, // 10^9 + 0x9502F900_00000000, // 10^10 + 0xBA43B740_00000000, // 10^11 + 0xE8D4A510_00000000, // 10^12 + 0x9184E72A_00000000, // 10^13 + 0xB5E620F4_80000000, // 10^14 + 0xE35FA931_A0000000, // 10^15 + 0x8E1BC9BF_04000000, // 10^16 + 0xB1A2BC2E_C5000000, // 10^17 + 0xDE0B6B3A_76400000, // 10^18 + 0x8AC72304_89E80000, // 10^19 + 0xAD78EBC5_AC620000, // 10^20 + 0xD8D726B7_177A8000, // 10^21 + 0x87867832_6EAC9000, // 10^22 + 0xA968163F_0A57B400, // 10^23 + 0xD3C21BCE_CCEDA100, // 10^24 + 0x84595161_401484A0, // 10^25 + 0xA56FA5B9_9019A5C8, // 10^26 + 0xCECB8F27_F4200F3A, // 10^27 + }; + + private static ushort[] s_Pow10ExtendedExponentTable = new ushort[] + { + 1, // 10^0 + 4, // 10^1 + 7, // 10^2 + 10, // 10^3 + 14, // 10^4 + 17, // 10^5 + 20, // 10^6 + 24, // 10^7 + 27, // 10^8 + 30, // 10^9 + 34, // 10^10 + 37, // 10^11 + 40, // 10^12 + 44, // 10^13 + 47, // 10^14 + 50, // 10^15 + 54, // 10^16 + 57, // 10^17 + 60, // 10^18 + 64, // 10^19 + 67, // 10^20 + 70, // 10^21 + 74, // 10^22 + 77, // 10^23 + 80, // 10^24 + 84, // 10^25 + 87, // 10^26 + 90, // 10^27 + }; + private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result) { result = new BigInteger(0); @@ -104,7 +183,7 @@ private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer numbe while (remaining != 0) { uint count = Math.Min(remaining, 9); - uint value = DigitsToInt(src, (int)(count)); + uint value = DigitsToUInt32(src, (int)(count)); result.MultiplyPow10(count); result.Add(value); @@ -114,14 +193,19 @@ private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer numbe } } - private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong initialMantissa, int initialExponent, bool hasZeroTail) + private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong initialMantissa, int initialExponent, bool hasZeroTail, bool exponentNormalized = false) { // number of bits by which we must adjust the mantissa to shift it into the // correct position, and compute the resulting base two exponent for the // normalized mantissa: uint initialMantissaBits = BigInteger.CountSignificantBits(initialMantissa); int normalMantissaShift = info.NormalMantissaBits - (int)(initialMantissaBits); - int normalExponent = initialExponent - normalMantissaShift; + int normalExponent = initialExponent; + + if (!exponentNormalized) + { + normalExponent -= normalMantissaShift; + } ulong mantissa = initialMantissa; int exponent = normalExponent; @@ -286,7 +370,7 @@ private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger value, } // get 32-bit integer from at most 9 digits - private static uint DigitsToInt(char* p, int count) + private static uint DigitsToUInt32(char* p, int count) { Debug.Assert((1 <= count) && (count <= 9)); @@ -301,6 +385,52 @@ private static uint DigitsToInt(char* p, int count) return res; } + // get 64-bit integer from at most 19 digits + private static ulong DigitsToUInt64(char* p, int count) + { + Debug.Assert((1 <= count) && (count <= 19)); + + char* end = (p + count); + ulong res = (ulong)(p[0] - '0'); + + for (p++; p < end; p++) + { + res = (10 * res) + p[0] - '0'; + } + + return res; + } + + // Get the 64-bit product of two 32-bit integers + private static ulong Multiply32x32To64(uint lhs, uint rhs) + { + return (ulong)(lhs) * rhs; + } + + // Get the high 64-bits of the product of two 64-bit integers + private static ulong Multiply64x64To128(ulong lhs, ulong rhs, out ulong lower) + { + uint al = (uint)(lhs); + uint au = (uint)(lhs >> 32); + + uint bl = (uint)(rhs); + uint bu = (uint)(rhs >> 32); + + ulong c = Multiply32x32To64(al, bl); + uint cl = (uint)(c); + uint cu = (uint)(c >> 32); + + ulong d = Multiply32x32To64(au, bl) + cu; + uint dl = (uint)(d); + uint du = (uint)(d >> 32); + + ulong e = Multiply32x32To64(al, bu) + dl; + uint eu = (uint)(e >> 32); + + lower = (e << 32) + eu; + return Multiply32x32To64(au, bu) + du + eu; + } + private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in FloatingPointInfo info) { char* src = number.GetDigitsPointer(); @@ -345,30 +475,39 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float // computed to the infinitely precise result and then rounded, which means that // we can rely on it to produce the correct result when both inputs are exact. - Debug.Assert(s_Pow10Table.Length == 23); + Debug.Assert(s_Pow10DoubleTable.Length == 23); uint fastExponent = (uint)(Math.Abs(number.Scale - integerDigitsPresent - fractionalDigitsPresent)); - if ((totalDigits <= 15) && (fastExponent <= 22)) + if ((totalDigits <= 7) && (fastExponent <= 10)) { - uint remaining = totalDigits; - uint count = Math.Min(remaining, 9); - double result = DigitsToInt(src, (int)(count)); - - remaining -= count; + float result = DigitsToUInt32(src, (int)(totalDigits)); + float scale = s_Pow10SingleTable[fastExponent]; - if (remaining > 0) + if (fractionalDigitsPresent != 0) { - Debug.Assert(remaining <= 6); - - // scale the value so we have space for the additional digits - // and add the remaining to get an exact mantissa. + result /= scale; + } + else + { + result *= scale; + } - result *= s_Pow10Table[remaining]; - result += DigitsToInt(src + 9, (int)(remaining)); + if (info.DenormalMantissaBits == 52) + { + return (ulong)(BitConverter.DoubleToInt64Bits(result)); + } + else + { + Debug.Assert(info.DenormalMantissaBits == 23); + return (uint)(BitConverter.SingleToInt32Bits(result)); } + } - double scale = s_Pow10Table[fastExponent]; + if ((totalDigits <= 15) && (fastExponent <= 22)) + { + double result = DigitsToUInt64(src, (int)(totalDigits)); + double scale = s_Pow10DoubleTable[fastExponent]; if (fractionalDigitsPresent != 0) { @@ -390,6 +529,49 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float } } + if ((totalDigits <= 19) && (fastExponent <= 27)) + { + // Adjust the mantissa and exponent to the extended precision format + ulong mantissa = DigitsToUInt64(src, (int)(totalDigits)); + int shift = (int)(BigInteger.LeadingZeroCount(mantissa)); + + mantissa <<= shift; + int exponent = 63 - shift; + + ulong scaleMantissa = s_Pow10ExtendedMantissaTable[fastExponent]; + int scaleExponent = s_Pow10ExtendedExponentTable[fastExponent]; + + bool hasNonZeroTail = number.HasNonZeroTail; + + if (fractionalDigitsPresent == 0) + { + mantissa = Multiply64x64To128(mantissa, scaleMantissa, out ulong tail); + exponent += scaleExponent; + + if ((uint)(mantissa >> 32) < 0x80000000) + { + // We need to normalize the mantissa by multiplying by two + // and the exponent by subtracting one. + + ulong carry = tail + tail; + mantissa += mantissa; + + if (carry < tail) + { + mantissa += 1; + } + + tail = carry; + + exponent--; + } + + hasNonZeroTail |= (tail != 0); + + return AssembleFloatingPointBits(in info, mantissa, exponent, !hasNonZeroTail, exponentNormalized: true); + } + } + // First, we accumulate the integer part of the mantissa into a big_integer: AccumulateDecimalDigitsIntoBigInteger(ref number, integerFirstIndex, integerLastIndex, out BigInteger integerValue); From c33427756e287d32986326130965681140158f43 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 1 Nov 2018 21:29:44 -0700 Subject: [PATCH 21/29] Splitting the NumberToFloatingPointBits code into a fast and slow-path method --- .../Number.NumberToFloatingPointBits.cs | 146 +++++++++--------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index a21c9eab6f30..8ab0e55b4b49 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -433,21 +433,7 @@ private static ulong Multiply64x64To128(ulong lhs, ulong rhs, out ulong lower) private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in FloatingPointInfo info) { - char* src = number.GetDigitsPointer(); - uint totalDigits = (uint)(number.Precision); - - Debug.Assert(src[0] != '0'); - - if (totalDigits == 0) - { - return info.ZeroBits; - } - - // To generate an N bit mantissa we require N + 1 bits of precision. The - // extra bit is used to correctly round the mantissa (if there are fewer bits - // than this available, then that's totally okay; in that case we use what we - // have and we don't need to round). - uint requiredBitsOfPrecision = (uint)(info.NormalMantissaBits + 1); + Debug.Assert(number.GetDigitsPointer()[0] != '0'); // The input is of the form 0.Mantissa x 10^Exponent, where 'Mantissa' are // the decimal digits of the mantissa and 'Exponent' is the decimal exponent. @@ -456,81 +442,79 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float // first 'exponent' digits, or all present digits if there are fewer digits. // If the exponent is zero or negative, then the integer part is empty. In // either case, the remaining digits form the fractional part of the mantissa. + + uint totalDigits = (uint)(number.Precision); uint positiveExponent = (uint)(Math.Max(0, number.Scale)); + uint integerDigitsPresent = Math.Min(positiveExponent, totalDigits); - uint integerDigitsMissing = positiveExponent - integerDigitsPresent; - uint integerFirstIndex = 0; - uint integerLastIndex = integerDigitsPresent; + uint fractionalDigitsPresent = totalDigits - integerDigitsPresent; - uint fractionalFirstIndex = integerLastIndex; - uint fractionalLastIndex = totalDigits; - uint fractionalDigitsPresent = fractionalLastIndex - fractionalFirstIndex; - - // When the number of significant digits is less than or equal to 15 and the - // scale is less than or equal to 22, we can take a shortcut and just rely - // on double-precision arithmetic to compute the correct result. This is - // because double-precision values allow us to exactly represent any whole - // integer that contains less then 15 digits, the same being true for powers - // of ten from 16-22. Additionally, IEEE operations dictate that the result is + uint fastExponent = (uint)(Math.Abs(number.Scale - integerDigitsPresent - fractionalDigitsPresent)); + + // When the number of significant digits is less than or equal to 19 and the + // scale is less than or equal to 27, we can take some shortcuts and just rely + // on floating-point arithmetic to compute the correct result. This is + // because each floating-point precision values allows us to exactly represent + // different whole integers and certain powers of 10, depending on the underlying + // formats exact range. Additionally, IEEE operations dictate that the result is // computed to the infinitely precise result and then rounded, which means that // we can rely on it to produce the correct result when both inputs are exact. - Debug.Assert(s_Pow10DoubleTable.Length == 23); - - uint fastExponent = (uint)(Math.Abs(number.Scale - integerDigitsPresent - fractionalDigitsPresent)); - - if ((totalDigits <= 7) && (fastExponent <= 10)) + if ((totalDigits <= 19) && (fastExponent <= 27)) { - float result = DigitsToUInt32(src, (int)(totalDigits)); - float scale = s_Pow10SingleTable[fastExponent]; + char* src = number.GetDigitsPointer(); - if (fractionalDigitsPresent != 0) + if (totalDigits == 0) { - result /= scale; - } - else - { - result *= scale; + return info.ZeroBits; } - if (info.DenormalMantissaBits == 52) - { - return (ulong)(BitConverter.DoubleToInt64Bits(result)); - } - else + if ((info.DenormalMantissaBits == 23) && (totalDigits <= 7) && (fastExponent <= 10)) { - Debug.Assert(info.DenormalMantissaBits == 23); - return (uint)(BitConverter.SingleToInt32Bits(result)); - } - } + // It is only valid to do this optimization for single-precision floating-point + // values since we can lose some of the mantissa bits and would return the + // wrong value when upcasting to double. - if ((totalDigits <= 15) && (fastExponent <= 22)) - { - double result = DigitsToUInt64(src, (int)(totalDigits)); - double scale = s_Pow10DoubleTable[fastExponent]; + float result = DigitsToUInt32(src, (int)(totalDigits)); + float scale = s_Pow10SingleTable[fastExponent]; - if (fractionalDigitsPresent != 0) - { - result /= scale; - } - else - { - result *= scale; - } + if (fractionalDigitsPresent != 0) + { + result /= scale; + } + else + { + result *= scale; + } - if (info.DenormalMantissaBits == 52) - { - return (ulong)(BitConverter.DoubleToInt64Bits(result)); + return (uint)(BitConverter.SingleToInt32Bits(result)); } - else + + if ((totalDigits <= 15) && (fastExponent <= 22)) { - Debug.Assert(info.DenormalMantissaBits == 23); - return (uint)(BitConverter.SingleToInt32Bits((float)(result))); + double result = DigitsToUInt64(src, (int)(totalDigits)); + double scale = s_Pow10DoubleTable[fastExponent]; + + if (fractionalDigitsPresent != 0) + { + result /= scale; + } + else + { + result *= scale; + } + + if (info.DenormalMantissaBits == 52) + { + return (ulong)(BitConverter.DoubleToInt64Bits(result)); + } + else + { + Debug.Assert(info.DenormalMantissaBits == 23); + return (uint)(BitConverter.SingleToInt32Bits((float)(result))); + } } - } - if ((totalDigits <= 19) && (fastExponent <= 27)) - { // Adjust the mantissa and exponent to the extended precision format ulong mantissa = DigitsToUInt64(src, (int)(totalDigits)); int shift = (int)(BigInteger.LeadingZeroCount(mantissa)); @@ -572,6 +556,26 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float } } + return NumberToFloatingPointBitsSlow(ref number, in info, positiveExponent, integerDigitsPresent, fractionalDigitsPresent); + } + + private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in FloatingPointInfo info, uint positiveExponent, uint integerDigitsPresent, uint fractionalDigitsPresent) + { + // To generate an N bit mantissa we require N + 1 bits of precision. The + // extra bit is used to correctly round the mantissa (if there are fewer bits + // than this available, then that's totally okay; in that case we use what we + // have and we don't need to round). + uint requiredBitsOfPrecision = (uint)(info.NormalMantissaBits + 1); + + uint totalDigits = (uint)(number.Precision); + uint integerDigitsMissing = positiveExponent - integerDigitsPresent; + + uint integerFirstIndex = 0; + uint integerLastIndex = integerDigitsPresent; + + uint fractionalFirstIndex = integerLastIndex; + uint fractionalLastIndex = totalDigits; + // First, we accumulate the integer part of the mantissa into a big_integer: AccumulateDecimalDigitsIntoBigInteger(ref number, integerFirstIndex, integerLastIndex, out BigInteger integerValue); From e7e939a2cb1e7ac831a4a1ef85912945908ade93 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 2 Nov 2018 14:28:30 -0700 Subject: [PATCH 22/29] Ensure Roslyn is properly attributed. --- THIRD-PARTY-NOTICES.TXT | 205 ++++++++++++++++++ .../Number.NumberToFloatingPointBits.cs | 12 +- 2 files changed, 211 insertions(+), 6 deletions(-) diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 4a8002db2514..d6b2677eb96d 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -249,3 +249,208 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License notice for Roslyn +------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index 8ab0e55b4b49..5a8390212768 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -1,6 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See THIRD-PARTY-NOTICES.TXT in the project root for license information. + +// LICENSING NOTE: The license for this file is from the originating source +// and not the general CoreCLR License. +// +// See https://github.com/dotnet/roslyn/blob/82b2f694b26e5933ff8dd0856a2cd775273ac138/src/Compilers/Core/Portable/RealParser.cs using System.Diagnostics; @@ -8,9 +11,6 @@ namespace System { internal unsafe partial class Number { - // The below implementation comes from the Roslyn RealParser: https://github.com/dotnet/roslyn/blob/82b2f694b26e5933ff8dd0856a2cd775273ac138/src/Compilers/Core/Portable/RealParser.cs - // The original implementation is: Licensed under the Apache License, Version 2.0. - public readonly struct FloatingPointInfo { public static readonly FloatingPointInfo Double = new FloatingPointInfo( From 31719b933b6d0e0146d74e2a2334ad098735bb77 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 2 Nov 2018 16:11:56 -0700 Subject: [PATCH 23/29] Removing the 80-bit extended precision fast path for NumberToFloatingPointBits, due to a bug --- .../Number.NumberToFloatingPointBits.cs | 121 ++++++------------ .../shared/System/Number.Parsing.cs | 2 +- 2 files changed, 40 insertions(+), 83 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index 5a8390212768..3fce97ac33da 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -451,8 +451,8 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float uint fastExponent = (uint)(Math.Abs(number.Scale - integerDigitsPresent - fractionalDigitsPresent)); - // When the number of significant digits is less than or equal to 19 and the - // scale is less than or equal to 27, we can take some shortcuts and just rely + // When the number of significant digits is less than or equal to 15 and the + // scale is less than or equal to 22, we can take some shortcuts and just rely // on floating-point arithmetic to compute the correct result. This is // because each floating-point precision values allows us to exactly represent // different whole integers and certain powers of 10, depending on the underlying @@ -460,99 +460,56 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float // computed to the infinitely precise result and then rounded, which means that // we can rely on it to produce the correct result when both inputs are exact. - if ((totalDigits <= 19) && (fastExponent <= 27)) + char* src = number.GetDigitsPointer(); + + if (totalDigits == 0) + { + return info.ZeroBits; + } + + if ((info.DenormalMantissaBits == 23) && (totalDigits <= 7) && (fastExponent <= 10)) { - char* src = number.GetDigitsPointer(); + // It is only valid to do this optimization for single-precision floating-point + // values since we can lose some of the mantissa bits and would return the + // wrong value when upcasting to double. + + float result = DigitsToUInt32(src, (int)(totalDigits)); + float scale = s_Pow10SingleTable[fastExponent]; - if (totalDigits == 0) + if (fractionalDigitsPresent != 0) { - return info.ZeroBits; + result /= scale; } - - if ((info.DenormalMantissaBits == 23) && (totalDigits <= 7) && (fastExponent <= 10)) + else { - // It is only valid to do this optimization for single-precision floating-point - // values since we can lose some of the mantissa bits and would return the - // wrong value when upcasting to double. + result *= scale; + } - float result = DigitsToUInt32(src, (int)(totalDigits)); - float scale = s_Pow10SingleTable[fastExponent]; + return (uint)(BitConverter.SingleToInt32Bits(result)); + } - if (fractionalDigitsPresent != 0) - { - result /= scale; - } - else - { - result *= scale; - } + if ((totalDigits <= 15) && (fastExponent <= 22)) + { + double result = DigitsToUInt64(src, (int)(totalDigits)); + double scale = s_Pow10DoubleTable[fastExponent]; - return (uint)(BitConverter.SingleToInt32Bits(result)); + if (fractionalDigitsPresent != 0) + { + result /= scale; } - - if ((totalDigits <= 15) && (fastExponent <= 22)) + else { - double result = DigitsToUInt64(src, (int)(totalDigits)); - double scale = s_Pow10DoubleTable[fastExponent]; - - if (fractionalDigitsPresent != 0) - { - result /= scale; - } - else - { - result *= scale; - } - - if (info.DenormalMantissaBits == 52) - { - return (ulong)(BitConverter.DoubleToInt64Bits(result)); - } - else - { - Debug.Assert(info.DenormalMantissaBits == 23); - return (uint)(BitConverter.SingleToInt32Bits((float)(result))); - } + result *= scale; } - // Adjust the mantissa and exponent to the extended precision format - ulong mantissa = DigitsToUInt64(src, (int)(totalDigits)); - int shift = (int)(BigInteger.LeadingZeroCount(mantissa)); - - mantissa <<= shift; - int exponent = 63 - shift; - - ulong scaleMantissa = s_Pow10ExtendedMantissaTable[fastExponent]; - int scaleExponent = s_Pow10ExtendedExponentTable[fastExponent]; - - bool hasNonZeroTail = number.HasNonZeroTail; - - if (fractionalDigitsPresent == 0) + if (info.DenormalMantissaBits == 52) { - mantissa = Multiply64x64To128(mantissa, scaleMantissa, out ulong tail); - exponent += scaleExponent; - - if ((uint)(mantissa >> 32) < 0x80000000) - { - // We need to normalize the mantissa by multiplying by two - // and the exponent by subtracting one. - - ulong carry = tail + tail; - mantissa += mantissa; - - if (carry < tail) - { - mantissa += 1; - } - - tail = carry; - - exponent--; - } - - hasNonZeroTail |= (tail != 0); - - return AssembleFloatingPointBits(in info, mantissa, exponent, !hasNonZeroTail, exponentNormalized: true); + return (ulong)(BitConverter.DoubleToInt64Bits(result)); + } + else + { + Debug.Assert(info.DenormalMantissaBits == 23); + return (uint)(BitConverter.SingleToInt32Bits((float)(result))); } } diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 1e706291a027..628c057dcbf2 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -331,7 +331,7 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty if (digCount < maxDigCount) { number.Digits[digCount++] = ch; - if (ch != '0' || number.Kind == NumberBufferKind.Decimal) + if ((ch != '0') || (number.Kind != NumberBufferKind.Integer)) { digEnd = digCount; } From 90b82d90c51603644f9a83d0930c14fb279552ce Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 2 Nov 2018 16:13:08 -0700 Subject: [PATCH 24/29] Fixing the double and single parser to ignore case for Infinity and NaN --- .../shared/System/Number.Parsing.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 628c057dcbf2..6c8c74be6236 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1685,15 +1685,15 @@ internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyle { ReadOnlySpan valueTrim = value.Trim(); - if (valueTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) + if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) { result = double.PositiveInfinity; } - else if (valueTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) + else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) { result = double.NegativeInfinity; } - else if (valueTrim.EqualsOrdinal(info.NaNSymbol)) + else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) { result = double.NaN; } @@ -1725,15 +1725,15 @@ internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyle { ReadOnlySpan valueTrim = value.Trim(); - if (valueTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) + if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) { result = float.PositiveInfinity; } - else if (valueTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) + else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) { result = float.NegativeInfinity; } - else if (valueTrim.EqualsOrdinal(info.NaNSymbol)) + else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) { result = float.NaN; } From 7cf4b92769eb1a99dbcdfc65ee90b72d5a0ad2c3 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 5 Nov 2018 07:39:42 -0800 Subject: [PATCH 25/29] Add a clarifying comment to Number.NumberToFloatingPointBits that the code has been modified from the original source. --- .../shared/System/Number.NumberToFloatingPointBits.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index 3fce97ac33da..06bcc10194f0 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -5,6 +5,9 @@ // // See https://github.com/dotnet/roslyn/blob/82b2f694b26e5933ff8dd0856a2cd775273ac138/src/Compilers/Core/Portable/RealParser.cs +// This file is a port of the original code and has been modified to have certain performance improvements +// and to better fit in with the requirements of CoreCLR and existing code in System.Private.Corelib. + using System.Diagnostics; namespace System From 5a3c0532890f2886098aa5f41d6ab76d7caf5941 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 5 Nov 2018 13:22:10 -0800 Subject: [PATCH 26/29] Removing the remaining code that was used by the 80-bit extended precision fast-path in NumberToFloatingPointBits --- .../Number.NumberToFloatingPointBits.cs | 103 +----------------- 1 file changed, 2 insertions(+), 101 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index 06bcc10194f0..95ed24859c2e 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -112,70 +112,6 @@ public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int m 1e22, // 10^22 }; - private static ulong[] s_Pow10ExtendedMantissaTable = new ulong[] - { - 0x80000000_00000000, // 10^0 - 0xA0000000_00000000, // 10^1 - 0xC8000000_00000000, // 10^2 - 0xFA000000_00000000, // 10^3 - 0x9C400000_00000000, // 10^4 - 0xC3500000_00000000, // 10^5 - 0xF4240000_00000000, // 10^6 - 0x98968000_00000000, // 10^7 - 0xBEBC2000_00000000, // 10^8 - 0xEE6B2800_00000000, // 10^9 - 0x9502F900_00000000, // 10^10 - 0xBA43B740_00000000, // 10^11 - 0xE8D4A510_00000000, // 10^12 - 0x9184E72A_00000000, // 10^13 - 0xB5E620F4_80000000, // 10^14 - 0xE35FA931_A0000000, // 10^15 - 0x8E1BC9BF_04000000, // 10^16 - 0xB1A2BC2E_C5000000, // 10^17 - 0xDE0B6B3A_76400000, // 10^18 - 0x8AC72304_89E80000, // 10^19 - 0xAD78EBC5_AC620000, // 10^20 - 0xD8D726B7_177A8000, // 10^21 - 0x87867832_6EAC9000, // 10^22 - 0xA968163F_0A57B400, // 10^23 - 0xD3C21BCE_CCEDA100, // 10^24 - 0x84595161_401484A0, // 10^25 - 0xA56FA5B9_9019A5C8, // 10^26 - 0xCECB8F27_F4200F3A, // 10^27 - }; - - private static ushort[] s_Pow10ExtendedExponentTable = new ushort[] - { - 1, // 10^0 - 4, // 10^1 - 7, // 10^2 - 10, // 10^3 - 14, // 10^4 - 17, // 10^5 - 20, // 10^6 - 24, // 10^7 - 27, // 10^8 - 30, // 10^9 - 34, // 10^10 - 37, // 10^11 - 40, // 10^12 - 44, // 10^13 - 47, // 10^14 - 50, // 10^15 - 54, // 10^16 - 57, // 10^17 - 60, // 10^18 - 64, // 10^19 - 67, // 10^20 - 70, // 10^21 - 74, // 10^22 - 77, // 10^23 - 80, // 10^24 - 84, // 10^25 - 87, // 10^26 - 90, // 10^27 - }; - private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result) { result = new BigInteger(0); @@ -196,19 +132,14 @@ private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer numbe } } - private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong initialMantissa, int initialExponent, bool hasZeroTail, bool exponentNormalized = false) + private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong initialMantissa, int initialExponent, bool hasZeroTail) { // number of bits by which we must adjust the mantissa to shift it into the // correct position, and compute the resulting base two exponent for the // normalized mantissa: uint initialMantissaBits = BigInteger.CountSignificantBits(initialMantissa); int normalMantissaShift = info.NormalMantissaBits - (int)(initialMantissaBits); - int normalExponent = initialExponent; - - if (!exponentNormalized) - { - normalExponent -= normalMantissaShift; - } + int normalExponent = initialExponent - normalMantissaShift; ulong mantissa = initialMantissa; int exponent = normalExponent; @@ -404,36 +335,6 @@ private static ulong DigitsToUInt64(char* p, int count) return res; } - // Get the 64-bit product of two 32-bit integers - private static ulong Multiply32x32To64(uint lhs, uint rhs) - { - return (ulong)(lhs) * rhs; - } - - // Get the high 64-bits of the product of two 64-bit integers - private static ulong Multiply64x64To128(ulong lhs, ulong rhs, out ulong lower) - { - uint al = (uint)(lhs); - uint au = (uint)(lhs >> 32); - - uint bl = (uint)(rhs); - uint bu = (uint)(rhs >> 32); - - ulong c = Multiply32x32To64(al, bl); - uint cl = (uint)(c); - uint cu = (uint)(c >> 32); - - ulong d = Multiply32x32To64(au, bl) + cu; - uint dl = (uint)(d); - uint du = (uint)(d >> 32); - - ulong e = Multiply32x32To64(al, bu) + dl; - uint eu = (uint)(e >> 32); - - lower = (e << 32) + eu; - return Multiply32x32To64(au, bu) + du + eu; - } - private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in FloatingPointInfo info) { Debug.Assert(number.GetDigitsPointer()[0] != '0'); From 4af4a9600e89c1061cd45aadd0e0264dc8574e9c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 7 Nov 2018 10:45:29 -0800 Subject: [PATCH 27/29] Adding a missing comma to the CoreFX.issues.json --- tests/CoreFX/CoreFX.issues.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CoreFX/CoreFX.issues.json b/tests/CoreFX/CoreFX.issues.json index d783eb33fb4b..b784fbd63509 100644 --- a/tests/CoreFX/CoreFX.issues.json +++ b/tests/CoreFX/CoreFX.issues.json @@ -494,7 +494,8 @@ { "name": "System.SpanTests.MemoryMarshalTests.CreateFromPinnedArrayIntReadOnlyMemorySliceRemainsPinned", "reason": "https://github.com/dotnet/corefx/pull/32994" - } + }, + { "name": "System.Buffers.Text.Tests.ParserTests.TestParserSingle", "reason": "https://github.com/dotnet/coreclr/pull/20707" }, From 9f488acdf3f5057b20b28c92779ce2ec5511f7e5 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 7 Nov 2018 12:33:29 -0800 Subject: [PATCH 28/29] Remove licensing "glue" and re-release the Roslyn RealParser code under the MIT license. --- THIRD-PARTY-NOTICES.TXT | 205 ------------------ .../Number.NumberToFloatingPointBits.cs | 12 +- 2 files changed, 3 insertions(+), 214 deletions(-) diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index d6b2677eb96d..4a8002db2514 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -249,208 +249,3 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -License notice for Roslyn -------------------------- - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index 95ed24859c2e..575c4896166b 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -1,12 +1,6 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See THIRD-PARTY-NOTICES.TXT in the project root for license information. - -// LICENSING NOTE: The license for this file is from the originating source -// and not the general CoreCLR License. -// -// See https://github.com/dotnet/roslyn/blob/82b2f694b26e5933ff8dd0856a2cd775273ac138/src/Compilers/Core/Portable/RealParser.cs - -// This file is a port of the original code and has been modified to have certain performance improvements -// and to better fit in with the requirements of CoreCLR and existing code in System.Private.Corelib. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. using System.Diagnostics; From f90e2ba1bb8ed19909bcfe34049409a97834a4b5 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 7 Nov 2018 12:52:38 -0800 Subject: [PATCH 29/29] Some minor cleanup to the NumberToFloatingPointBits code. --- .../Resources/Strings.resx | 6 -- .../shared/System/Double.cs | 2 +- .../shared/System/Number.Formatting.cs | 8 ++- .../Number.NumberToFloatingPointBits.cs | 2 +- .../shared/System/Number.Parsing.cs | 56 +++++-------------- .../shared/System/Single.cs | 2 +- 6 files changed, 24 insertions(+), 52 deletions(-) diff --git a/src/System.Private.CoreLib/Resources/Strings.resx b/src/System.Private.CoreLib/Resources/Strings.resx index 0349819347e3..b1f3b017867d 100644 --- a/src/System.Private.CoreLib/Resources/Strings.resx +++ b/src/System.Private.CoreLib/Resources/Strings.resx @@ -3100,9 +3100,6 @@ Value was either too large or too small for a Decimal. - - Value was either too large or too small for a Double. - The duration cannot be returned for TimeSpan.MinValue because the absolute value of TimeSpan.MinValue exceeds the value of TimeSpan.MaxValue. @@ -3124,9 +3121,6 @@ Value was either too large or too small for a signed byte. - - Value was either too large or too small for a Single. - The TimeSpan string '{0}' could not be parsed because at least one of the numeric components is out of range or contains too many digits. diff --git a/src/System.Private.CoreLib/shared/System/Double.cs b/src/System.Private.CoreLib/shared/System/Double.cs index 232a3f7cfffe..885f005a219a 100644 --- a/src/System.Private.CoreLib/shared/System/Double.cs +++ b/src/System.Private.CoreLib/shared/System/Double.cs @@ -341,7 +341,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out double result) { - return Number.TryParseDouble(s, style, info, out result, out _); + return Number.TryParseDouble(s, style, info, out result); } // diff --git a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs index 57c5341d65e4..1c56546ed303 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs @@ -413,7 +413,9 @@ private static unsafe string FormatDouble(ref ValueStringBuilder sb, double valu return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } - if (TryNumberToDouble(ref number, out double roundTrip) && (roundTrip == value)) + double roundTrip = NumberToDouble(ref number); + + if (roundTrip == value) { NumberToString(ref sb, ref number, 'G', DoublePrecision, info); } @@ -515,7 +517,9 @@ private static unsafe string FormatSingle(ref ValueStringBuilder sb, float value return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } - if (TryNumberToSingle(ref number, out float roundTrip) && (roundTrip == value)) + float roundTrip = NumberToSingle(ref number); + + if (roundTrip == value) { NumberToString(ref sb, ref number, 'G', SinglePrecision, info); } diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs index 575c4896166b..16626bb7cb0b 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -604,7 +604,7 @@ private static ulong RightShiftWithRounding(ulong value, int shift, bool hasZero bool roundBit = (value & roundBitMask) != 0; bool hasTailBits = !hasZeroTail || (value & extraBitsMask) != 0; - return (value >> shift) + (ShouldRoundUp(lsbBit: lsbBit, roundBit: roundBit, hasTailBits: hasTailBits) ? 1UL : 0); + return (value >> shift) + (ShouldRoundUp(lsbBit, roundBit, hasTailBits) ? 1UL : 0); } private static bool ShouldRoundUp(bool lsbBit, bool roundBit, bool hasTailBits) diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs index 6c8c74be6236..60185b44b5cf 100644 --- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs +++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs @@ -1635,9 +1635,9 @@ private static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decim internal static double ParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if (!TryParseDouble(value, styles, info, out double result, out bool failureIsOverflow)) + if (!TryParseDouble(value, styles, info, out double result)) { - ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Double)); + ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null); } return result; @@ -1645,9 +1645,9 @@ internal static double ParseDouble(ReadOnlySpan value, NumberStyles styles internal static float ParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if (!TryParseSingle(value, styles, info, out float result, out bool failureIsOverflow)) + if (!TryParseSingle(value, styles, info, out float result)) { - ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Single)); + ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null); } return result; @@ -1675,11 +1675,10 @@ internal static unsafe bool TryParseDecimal(ReadOnlySpan value, NumberStyl return true; } - internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result, out bool failureIsOverflow) + internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result) { char* pDigits = stackalloc char[DoubleNumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength); - failureIsOverflow = false; if (!TryStringToNumber(value, styles, ref number, info)) { @@ -1702,24 +1701,19 @@ internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyle result = 0; return false; // We really failed } - - return true; } - - if (!TryNumberToDouble(ref number, out result)) + else { - failureIsOverflow = true; - return false; + result = NumberToDouble(ref number); } return true; } - internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result, out bool failureIsOverflow) + internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result) { char* pDigits = stackalloc char[SingleNumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength); - failureIsOverflow = false; if (!TryStringToNumber(value, styles, ref number, info)) { @@ -1742,14 +1736,10 @@ internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyle result = 0; return false; // We really failed } - - return true; } - - if (!TryNumberToSingle(ref number, out result)) + else { - failureIsOverflow = true; - return false; + result = NumberToSingle(ref number); } return true; @@ -1759,7 +1749,7 @@ private static unsafe void StringToNumber(ReadOnlySpan value, NumberStyles { if (!TryStringToNumber(value, styles, ref number, info)) { - ThrowOverflowOrFormatException(overflow: false, null); + ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null); } } @@ -1833,34 +1823,18 @@ private static void ThrowOverflowOrFormatException(bool overflow, string overflo (Exception)new FormatException(SR.Format_InvalidString); } - private static bool TryNumberToDouble(ref NumberBuffer number, out double value) + private static double NumberToDouble(ref NumberBuffer number) { ulong bits = NumberToFloatingPointBits(ref number, in FloatingPointInfo.Double); double result = BitConverter.Int64BitsToDouble((long)(bits)); - - value = result; - - if (number.Sign) - { - value = -value; - } - - return true; + return number.Sign ? -result : result; } - private static bool TryNumberToSingle(ref NumberBuffer number, out float value) + private static float NumberToSingle(ref NumberBuffer number) { uint bits = (uint)(NumberToFloatingPointBits(ref number, in FloatingPointInfo.Single)); float result = BitConverter.Int32BitsToSingle((int)(bits)); - - value = result; - - if (number.Sign) - { - value = -value; - } - - return true; + return number.Sign ? -result : result; } } } diff --git a/src/System.Private.CoreLib/shared/System/Single.cs b/src/System.Private.CoreLib/shared/System/Single.cs index dfe1af3f8c45..4a0a06f13984 100644 --- a/src/System.Private.CoreLib/shared/System/Single.cs +++ b/src/System.Private.CoreLib/shared/System/Single.cs @@ -330,7 +330,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out float result) { - return Number.TryParseSingle(s, style, info, out result, out _); + return Number.TryParseSingle(s, style, info, out result); } //