diff options
author | Tanner Gooding <tagoo@outlook.com> | 2018-11-07 20:23:49 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-07 20:23:49 -0800 |
commit | b30280d98e6c8e1980173a256609a0bde7f9c83d (patch) | |
tree | 43562a77917da61f08d08f893421ab24b129fe84 | |
parent | 002603e22fe881fc501b344910740a529bed0d9c (diff) | |
download | coreclr-b30280d98e6c8e1980173a256609a0bde7f9c83d.tar.gz coreclr-b30280d98e6c8e1980173a256609a0bde7f9c83d.tar.bz2 coreclr-b30280d98e6c8e1980173a256609a0bde7f9c83d.zip |
Fixing up the Double/Single parsing code to be correct (#20707)
* Don't normalize -0.0 to 0.0 when parsing
* Updating NumberBuffer to validate the constructor inputs
* Updating NumberToDouble to just get the precision
* Don't check for non-significant zero's in NumberToDouble
* Updating Number.BigInteger to carry additional space for the worst-case scenario
* Removing some dead code from double.TryParse
* Updating NumberToDouble to use the RealParser implementation from Roslyn
* Fixing TryNumberToDouble and TryNumberToSingle to apply the appropriate sign.
* Adding a fast path for double/single parsing when we have <= 15 digits and an absolute scale <= 22
* Update NumberBuffer to also track whether any input digits past maxDigCount were non-zero
* Renaming NumberToFloatingPointBitsRoslyn to NumberToFloatingPointBits
* Updating TryNumberToDouble and TryNumberToSingle to support Overflow to Infinity
* Fixing a Debug.Assert in TryParseNumber
* Fixing `DecimalNumberBufferLength` to 30
* Renaming NumberToFloatingPointBitsRoslyn to NumberToFloatingPointBits
* Clarifying the NumberBufferLength comments
* Fixing TryNumberToDecimal to check the last digit in the buffer, if it exists
* Disable some CoreFX tests due to the single/double/decimal parsing fixes
* Fix TryNumberToDecimal to not modify the value of p in the assert.
Co-Authored-By: tannergooding <tagoo@outlook.com>
* Updating NumberToFloatingPointBits to use single-precision arithmetic and extended-precision multiplication where possible
* Splitting the NumberToFloatingPointBits code into a fast and slow-path method
* Ensure Roslyn is properly attributed.
* Removing the 80-bit extended precision fast path for NumberToFloatingPointBits, due to a bug
* Fixing the double and single parser to ignore case for Infinity and NaN
* Add a clarifying comment to Number.NumberToFloatingPointBits that the code has been modified from the original source.
* Removing the remaining code that was used by the 80-bit extended precision fast-path in NumberToFloatingPointBits
* Adding a missing comma to the CoreFX.issues.json
* Remove licensing "glue" and re-release the Roslyn RealParser code under the MIT license.
* Some minor cleanup to the NumberToFloatingPointBits code.
12 files changed, 1407 insertions, 577 deletions
diff --git a/src/System.Private.CoreLib/Resources/Strings.resx b/src/System.Private.CoreLib/Resources/Strings.resx index 0349819347..b1f3b01786 100644 --- a/src/System.Private.CoreLib/Resources/Strings.resx +++ b/src/System.Private.CoreLib/Resources/Strings.resx @@ -3100,9 +3100,6 @@ <data name="Overflow_Decimal" xml:space="preserve"> <value>Value was either too large or too small for a Decimal.</value> </data> - <data name="Overflow_Double" xml:space="preserve"> - <value>Value was either too large or too small for a Double.</value> - </data> <data name="Overflow_Duration" xml:space="preserve"> <value>The duration cannot be returned for TimeSpan.MinValue because the absolute value of TimeSpan.MinValue exceeds the value of TimeSpan.MaxValue.</value> </data> @@ -3124,9 +3121,6 @@ <data name="Overflow_SByte" xml:space="preserve"> <value>Value was either too large or too small for a signed byte.</value> </data> - <data name="Overflow_Single" xml:space="preserve"> - <value>Value was either too large or too small for a Single.</value> - </data> <data name="Overflow_TimeSpanElementTooLarge" xml:space="preserve"> <value>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.</value> </data> 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 c3ef9a3211..050591fd91 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 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Number.Formatting.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Number.Grisu3.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Number.NumberBuffer.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)System\Number.NumberToDouble.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Number.NumberToFloatingPointBits.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Number.Parsing.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\NullReferenceException.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\ObjectDisposedException.cs" /> diff --git a/src/System.Private.CoreLib/shared/System/Double.cs b/src/System.Private.CoreLib/shared/System/Double.cs index 5a94551410..885f005a21 100644 --- a/src/System.Private.CoreLib/shared/System/Double.cs +++ b/src/System.Private.CoreLib/shared/System/Double.cs @@ -341,28 +341,7 @@ namespace System private static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out double result) { - bool success = Number.TryParseDouble(s, style, info, out result, out _); - if (!success) - { - ReadOnlySpan<char> 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); } // diff --git a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs index 2599823c44..1231b55bd6 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 @@ namespace System [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 @@ namespace System 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 @@ -35,6 +53,8 @@ namespace System 10, // 10^64 18, // 10^128 33, // 10^256 + 61, // 10^512 + 116, // 10^1024 }; private static readonly uint[] s_Pow10BigNumTable = new uint[] @@ -112,7 +132,175 @@ namespace System 0x5FDCEFCE, 0x000553F7, - // Trailing blocks to ensure MaxBlockCount + // 10^512 + 54, // _length + 0x00000000, // _blocks + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 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, + 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, + 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, 0x00000000, @@ -149,19 +337,39 @@ namespace System _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; + } - 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); + int lhsLength = lhs._length; + int index = 0; + uint carry = value; - uint index = (mask * 0x07C4ACDD) >> 27; - return s_MultiplyDeBruijnBitPosition[(int)(index)]; + while (index < lhsLength) + { + ulong sum = (ulong)(lhs._blocks[index]) + carry; + lhs._blocks[index] = (uint)(sum); + carry = (uint)(sum >> 32); + + 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) @@ -198,6 +406,217 @@ namespace System 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; @@ -278,10 +697,31 @@ namespace System return quotient; } + 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); - 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) @@ -314,14 +754,13 @@ namespace System 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++; } @@ -329,8 +768,8 @@ namespace System 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); } } @@ -410,12 +849,12 @@ namespace System } } - 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. @@ -438,6 +877,10 @@ namespace System // // 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 + 2) == s_Pow10BigNumTable.Length); + BigInteger temp1 = new BigInteger(s_Pow10UInt32Table[exponent & 0x7]); ref BigInteger lhs = ref temp1; @@ -453,7 +896,7 @@ namespace System 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 @@ -467,6 +910,7 @@ namespace System exponent >>= 1; } + result = new BigInteger(0); result.SetValue(ref lhs); } @@ -542,6 +986,100 @@ namespace System } } + 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; @@ -564,6 +1102,12 @@ namespace System _blocks[_length - 1] = blockValue; } + public uint GetBlock(uint index) + { + Debug.Assert(index < _length); + return _blocks[index]; + } + public bool IsOne() { return (_length == 1) @@ -618,6 +1162,20 @@ namespace System } } + 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; @@ -721,6 +1279,21 @@ namespace System } } + 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 7c1d480610..ec51104488 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 @@ namespace System 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 76f0469b4b..1c56546ed3 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 @@ namespace System 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,9 @@ namespace System return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } - if (NumberToDouble(ref number) == value) + double roundTrip = NumberToDouble(ref number); + + if (roundTrip == value) { NumberToString(ref sb, ref number, 'G', DoublePrecision, info); } @@ -495,7 +497,7 @@ namespace System 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 +517,9 @@ namespace System return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } - if ((float)NumberToDouble(ref number) == 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.NumberBuffer.cs b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs index f96687e43e..83af513458 100644 --- a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs +++ b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs @@ -2,7 +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.Runtime.InteropServices; +using System.Diagnostics; using Internal.Runtime.CompilerServices; namespace System @@ -10,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 = 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 + 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 @@ -23,16 +23,23 @@ namespace System public int Precision; public int Scale; public bool Sign; + public bool HasNonZeroTail; public NumberBufferKind Kind; public Span<char> 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; + HasNonZeroTail = false; Kind = kind; - Digits = new Span<char>(pDigits, digitsLength); + Digits = new Span<char>(digits, digitsLength); } public char* GetDigitsPointer() @@ -47,7 +54,7 @@ namespace System 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 974786d3de..0000000000 --- a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs +++ /dev/null @@ -1,449 +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<ulong>(s_Pow10MantissaTable, 0, 15), nameof(s_Pow10MantissaTable)); - CheckPow10ExponentTable(val, exp, new Span<short>(s_Pow10ExponentTable, 0, 15), nameof(s_Pow10ExponentTable)); - - val = 0x8E1BC9BF_04000000; - exp = 54; //10^16 - - CheckPow10MantissaTable(val, exp, new Span<ulong>(s_Pow10By16MantissaTable, 0, 21), nameof(s_Pow10By16MantissaTable)); - CheckPow10ExponentTable(val, exp, new Span<short>(s_Pow10By16ExponentTable, 0, 21), nameof(s_Pow10By16ExponentTable)); - - val = 0xCCCCCCCC_CCCCCCCD; - exp = -3; // 0.1 - - CheckPow10MantissaTable(val, exp, new Span<ulong>(s_Pow10MantissaTable, 15, 15), nameof(s_Pow10MantissaTable) + " - inv"); - - val = 0xE69594BE_C44DE160; - exp = -53; // 0.1^16 - - CheckPow10MantissaTable(val, exp, new Span<ulong>(s_Pow10By16MantissaTable, 21, 21), nameof(s_Pow10By16MantissaTable) + " - inv"); - } - - // debug-only verification of the precomputed tables - private static void CheckPow10MantissaTable(ulong val, int exp, Span<ulong> 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<short> 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; - } - - 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) - { - 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 = GetLength(src); - int remaining = total; - - // skip the leading zeros - while (src[0] == '0') - { - remaining--; - src++; - } - - if (remaining == 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.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs new file mode 100644 index 0000000000..16626bb7cb --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs @@ -0,0 +1,621 @@ +// 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 + { + public readonly struct FloatingPointInfo + { + public static readonly FloatingPointInfo Double = new FloatingPointInfo( + denormalMantissaBits: 52, + exponentBits: 11, + maxBinaryExponent: 1023, + exponentBias: 1023, + infinityBits: 0x7FF00000_00000000 + ); + + 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 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 + 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); + + char* src = number.GetDigitsPointer() + firstIndex; + uint remaining = lastIndex - firstIndex; + + while (remaining != 0) + { + uint count = Math.Min(remaining, 9); + uint value = DigitsToUInt32(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 DigitsToUInt32(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; + } + + // 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; + } + + private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in FloatingPointInfo info) + { + 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. + // We decompose the mantissa into two parts: an integer part and a fractional + // part. If the exponent is positive, then the integer part consists of the + // 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 fractionalDigitsPresent = totalDigits - integerDigitsPresent; + + uint fastExponent = (uint)(Math.Abs(number.Scale - integerDigitsPresent - fractionalDigitsPresent)); + + // 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 + // 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. + + char* src = number.GetDigitsPointer(); + + if (totalDigits == 0) + { + return info.ZeroBits; + } + + if ((info.DenormalMantissaBits == 23) && (totalDigits <= 7) && (fastExponent <= 10)) + { + // 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 (fractionalDigitsPresent != 0) + { + result /= scale; + } + else + { + result *= scale; + } + + return (uint)(BitConverter.SingleToInt32Bits(result)); + } + + if ((totalDigits <= 15) && (fastExponent <= 22)) + { + 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))); + } + } + + 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); + + if (integerDigitsMissing > 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 = !number.HasNonZeroTail && 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, roundBit, 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 802f157451..60185b44b5 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 @@ namespace System 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 == false); + 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. @@ -327,11 +331,23 @@ namespace System if (digCount < maxDigCount) { number.Digits[digCount++] = ch; - if (ch != '0' || number.Kind == NumberBufferKind.Decimal) + if ((ch != '0') || (number.Kind != NumberBufferKind.Integer)) { 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,20 @@ namespace System 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)) { c = *++p; - int count = 20; // Look at the next 20 digits to check to round - while (c == '0' && count != 0) + + // 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[1] == 0)); + + if (((c == 0) || c == '0') && !number.HasNonZeroTail) { - c = *++p; - count--; + // 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 (c == 0 || count == 0) - goto NoRounding;// Do nothing } if (++low64 == 0 && ++high == 0) @@ -1617,9 +1635,9 @@ namespace System internal static double ParseDouble(ReadOnlySpan<char> 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; @@ -1627,9 +1645,9 @@ namespace System internal static float ParseSingle(ReadOnlySpan<char> 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; @@ -1657,65 +1675,73 @@ namespace System return true; } - internal static unsafe bool TryParseDouble(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out double result, out bool failureIsOverflow) + internal static unsafe bool TryParseDouble(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out double result) { char* pDigits = stackalloc char[DoubleNumberBufferLength]; - NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, DoubleNumberBufferLength); - - result = 0; - failureIsOverflow = false; + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { ReadOnlySpan<char> 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; } else { + result = 0; return false; // We really failed } - - return true; } - - if (!TryNumberToDouble(ref number, ref result)) + else { - failureIsOverflow = true; - return false; + result = NumberToDouble(ref number); } return true; } - internal static bool TryParseSingle(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out float result, out bool failureIsOverflow) + internal static unsafe bool TryParseSingle(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out float result) { - result = 0; + char* pDigits = stackalloc char[SingleNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength); - if (!TryParseDouble(value, styles, info, out double doubleResult, out failureIsOverflow)) + if (!TryStringToNumber(value, styles, ref number, info)) { - return false; - } - - float singleResult = (float)(doubleResult); + ReadOnlySpan<char> valueTrim = value.Trim(); - if (float.IsInfinity(singleResult) && double.IsFinite(doubleResult)) + if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) + { + result = float.PositiveInfinity; + } + else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) + { + result = float.NegativeInfinity; + } + else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) + { + result = float.NaN; + } + else + { + result = 0; + return false; // We really failed + } + } + else { - failureIsOverflow = true; - return false; + result = NumberToSingle(ref number); } - result = singleResult; return true; } @@ -1723,7 +1749,7 @@ namespace System { if (!TryStringToNumber(value, styles, ref number, info)) { - ThrowOverflowOrFormatException(overflow: false, null); + ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null); } } @@ -1797,23 +1823,18 @@ namespace System (Exception)new FormatException(SR.Format_InvalidString); } - private static bool TryNumberToDouble(ref NumberBuffer number, ref double value) + private static double NumberToDouble(ref NumberBuffer number) { - double d = NumberToDouble(ref number); - if (!double.IsFinite(d)) - { - value = default; - return false; - } - - if (d == 0.0) - { - // normalize -0.0 to 0.0 - d = 0.0; - } + ulong bits = NumberToFloatingPointBits(ref number, in FloatingPointInfo.Double); + double result = BitConverter.Int64BitsToDouble((long)(bits)); + return number.Sign ? -result : result; + } - value = d; - return true; + private static float NumberToSingle(ref NumberBuffer number) + { + uint bits = (uint)(NumberToFloatingPointBits(ref number, in FloatingPointInfo.Single)); + float result = BitConverter.Int32BitsToSingle((int)(bits)); + 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 dfe1af3f8c..4a0a06f139 100644 --- a/src/System.Private.CoreLib/shared/System/Single.cs +++ b/src/System.Private.CoreLib/shared/System/Single.cs @@ -330,7 +330,7 @@ namespace System private static bool TryParse(ReadOnlySpan<char> 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); } // diff --git a/tests/CoreFX/CoreFX.issues.json b/tests/CoreFX/CoreFX.issues.json index 2f15282a7f..b784fbd635 100644 --- a/tests/CoreFX/CoreFX.issues.json +++ b/tests/CoreFX/CoreFX.issues.json @@ -494,7 +494,15 @@ { "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 +516,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 +700,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" + }, + ] + } + }, ] |