diff --git a/src/Common/src/CoreLib/System/Byte.cs b/src/Common/src/CoreLib/System/Byte.cs index da1f9147b904..4004cf13a5cf 100644 --- a/src/Common/src/CoreLib/System/Byte.cs +++ b/src/Common/src/CoreLib/System/Byte.cs @@ -160,7 +160,7 @@ private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFor { result = 0; int i; - if (!Number.TryParseInt32(s, style, info, out i)) + if (!Number.TryParseInt32(s, style, info, out i, out _)) { return false; } diff --git a/src/Common/src/CoreLib/System/Decimal.cs b/src/Common/src/CoreLib/System/Decimal.cs index a79a7c3e7fa0..2d3829792a50 100644 --- a/src/Common/src/CoreLib/System/Decimal.cs +++ b/src/Common/src/CoreLib/System/Decimal.cs @@ -488,12 +488,12 @@ public static bool TryParse(string s, out decimal result) return false; } - return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result, out _); } public static bool TryParse(ReadOnlySpan s, out decimal result) { - return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result, out _); } public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out decimal result) @@ -506,13 +506,13 @@ public static bool TryParse(string s, NumberStyles style, IFormatProvider provid return false; } - return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out decimal result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } // Returns a binary representation of a Decimal. The return value is an diff --git a/src/Common/src/CoreLib/System/Double.cs b/src/Common/src/CoreLib/System/Double.cs index d85b4bca9939..5a94551410c6 100644 --- a/src/Common/src/CoreLib/System/Double.cs +++ b/src/Common/src/CoreLib/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) { - bool success = Number.TryParseDouble(s, style, info, out result); + bool success = Number.TryParseDouble(s, style, info, out result, out _); if (!success) { ReadOnlySpan sTrim = s.Trim(); diff --git a/src/Common/src/CoreLib/System/Int16.cs b/src/Common/src/CoreLib/System/Int16.cs index 49732997639d..a88d477e4e58 100644 --- a/src/Common/src/CoreLib/System/Int16.cs +++ b/src/Common/src/CoreLib/System/Int16.cs @@ -201,7 +201,7 @@ private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFor { result = 0; int i; - if (!Number.TryParseInt32(s, style, info, out i)) + if (!Number.TryParseInt32(s, style, info, out i, out _)) { return false; } diff --git a/src/Common/src/CoreLib/System/Int32.cs b/src/Common/src/CoreLib/System/Int32.cs index 1d0aefe73cbf..5aa97537a904 100644 --- a/src/Common/src/CoreLib/System/Int32.cs +++ b/src/Common/src/CoreLib/System/Int32.cs @@ -152,12 +152,12 @@ public static bool TryParse(string s, out int result) return false; } - return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); } public static bool TryParse(ReadOnlySpan s, out int result) { - return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); } // Parses an integer from a String in the given style. Returns false rather @@ -173,13 +173,13 @@ public static bool TryParse(string s, NumberStyles style, IFormatProvider provid return false; } - return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out int result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } // diff --git a/src/Common/src/CoreLib/System/Int64.cs b/src/Common/src/CoreLib/System/Int64.cs index 62c9ffd4fe64..78e8352d2527 100644 --- a/src/Common/src/CoreLib/System/Int64.cs +++ b/src/Common/src/CoreLib/System/Int64.cs @@ -143,12 +143,12 @@ public static bool TryParse(string s, out long result) return false; } - return Number.TryParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); } public static bool TryParse(ReadOnlySpan s, out long result) { - return Number.TryParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); } public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out long result) @@ -161,13 +161,13 @@ public static bool TryParse(string s, NumberStyles style, IFormatProvider provid return false; } - return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out long result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } // diff --git a/src/Common/src/CoreLib/System/Number.Dragon4.cs b/src/Common/src/CoreLib/System/Number.Dragon4.cs index 3edefe37aa20..7c1d48061029 100644 --- a/src/Common/src/CoreLib/System/Number.Dragon4.cs +++ b/src/Common/src/CoreLib/System/Number.Dragon4.cs @@ -2,16 +2,17 @@ // 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.Runtime.CompilerServices; -using Internal.Runtime.CompilerServices; - namespace System { internal static partial class Number { private static unsafe void Dragon4(double value, int precision, ref NumberBuffer number) { + const double Log10V2 = 0.30102999566398119521373889472449; + + // DriftFactor = 1 - Log10V2 - epsilon (a small number account for drift of floating point multiplication) + const double DriftFactor = 0.69; + // ======================================================================================================================================== // This implementation is based on the paper: https://www.cs.indiana.edu/~dyb/pubs/FP-Printing-PLDI96.pdf // Besides the paper, some of the code and ideas are modified from http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ @@ -149,7 +150,7 @@ private static unsafe void Dragon4(double value, int precision, ref NumberBuffer r.Multiply10(); } - number.scale = (k - 1); + number.Scale = (k - 1); // This the prerequisite of calling BigInteger.HeuristicDivide(). BigInteger.PrepareHeuristicDivide(ref r, ref s); @@ -171,7 +172,7 @@ private static unsafe void Dragon4(double value, int precision, ref NumberBuffer break; } - number.digits[digitsNum] = (char)('0' + currentDigit); + number.Digits[digitsNum] = (char)('0' + currentDigit); digitsNum++; r.Multiply10(); @@ -199,7 +200,7 @@ private static unsafe void Dragon4(double value, int precision, ref NumberBuffer if (isRoundDown) { - number.digits[digitsNum] = (char)('0' + currentDigit); + number.Digits[digitsNum] = (char)('0' + currentDigit); digitsNum++; } else @@ -218,7 +219,7 @@ private static unsafe void Dragon4(double value, int precision, ref NumberBuffer // Output 1 at the next highest exponent *pCurrentDigit = '1'; digitsNum++; - number.scale += 1; + number.Scale += 1; break; } @@ -244,14 +245,14 @@ private static unsafe void Dragon4(double value, int precision, ref NumberBuffer while (digitsNum < precision) { - number.digits[digitsNum] = '0'; + number.Digits[digitsNum] = '0'; digitsNum++; } - number.digits[precision] = '\0'; + number.Digits[precision] = '\0'; - number.scale++; - number.sign = double.IsNegative(value); + number.Scale++; + number.Sign = double.IsNegative(value); } } } diff --git a/src/Common/src/CoreLib/System/Number.Formatting.cs b/src/Common/src/CoreLib/System/Number.Formatting.cs index 51ecee31a9df..76f0469b4b12 100644 --- a/src/Common/src/CoreLib/System/Number.Formatting.cs +++ b/src/Common/src/CoreLib/System/Number.Formatting.cs @@ -243,7 +243,7 @@ namespace System internal static partial class Number { internal const int DecimalPrecision = 29; // Decimal.DecCalc also uses this value - private const int FloatPrecision = 7; + private const int SinglePrecision = 7; private const int DoublePrecision = 15; private const int ScaleNAN = unchecked((int)0x80000000); private const int ScaleINF = 0x7FFFFFFF; @@ -285,11 +285,13 @@ internal static partial class Number "(#)", "-#", "- #", "#-", "# -", }; - public static string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info) + public static unsafe string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info) { char fmt = ParseFormatSpecifier(format, out int digits); - NumberBuffer number = default; + char* pDigits = stackalloc char[DecimalNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength); + DecimalToNumber(ref value, ref number); ValueStringBuilder sb; @@ -311,11 +313,13 @@ public static string FormatDecimal(decimal value, ReadOnlySpan format, Num return sb.ToString(); } - public static bool TryFormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) { char fmt = ParseFormatSpecifier(format, out int digits); - NumberBuffer number = default; + char* pDigits = stackalloc char[DecimalNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength); + DecimalToNumber(ref value, ref number); ValueStringBuilder sb; @@ -340,9 +344,8 @@ public static bool TryFormatDecimal(decimal value, ReadOnlySpan format, Nu private static unsafe void DecimalToNumber(ref decimal d, ref NumberBuffer number) { char* buffer = number.GetDigitsPointer(); - number.precision = DecimalPrecision; - number.sign = d.IsNegative; - number.kind = NumberBufferKind.Decimal; + number.Precision = DecimalPrecision; + number.Sign = d.IsNegative; char* p = buffer + DecimalPrecision; while ((d.Mid | d.High) != 0) @@ -352,7 +355,7 @@ private static unsafe void DecimalToNumber(ref decimal d, ref NumberBuffer numbe p = UInt32ToDecChars(p, d.Low, 0); int i = (int)((byte*)(buffer + DecimalPrecision) - (byte*)p) >> 1; - number.scale = i - d.Scale; + number.Scale = i - d.Scale; char* dst = number.GetDigitsPointer(); while (--i >= 0) @@ -384,44 +387,45 @@ public static bool TryFormatDouble(double value, ReadOnlySpan format, Numb /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static string FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan format, NumberFormatInfo info) + private static unsafe string FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan format, NumberFormatInfo info) { char fmt = ParseFormatSpecifier(format, out int digits); int precision = DoublePrecision; - NumberBuffer number = default; - number.kind = NumberBufferKind.Double; + + char* pDigits = stackalloc char[DoubleNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, DoubleNumberBufferLength); switch (fmt) { case 'R': case 'r': + { + // In order to give numbers that are both friendly to display and round-trippable, we parse the + // number using 15 digits and then determine if it round trips to the same value. If it does, we + // convert that NUMBER to a string, otherwise we reparse using 17 digits and display that. + DoubleToNumber(value, DoublePrecision, ref number); + if (number.Scale == ScaleNAN) { - // In order to give numbers that are both friendly to display and round-trippable, we parse the - // number using 15 digits and then determine if it round trips to the same value. If it does, we - // convert that NUMBER to a string, otherwise we reparse using 17 digits and display that. - DoubleToNumber(value, DoublePrecision, ref number); - if (number.scale == ScaleNAN) - { - return info.NaNSymbol; - } - else if (number.scale == ScaleINF) - { - return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; - } - - if (NumberToDouble(ref number) == value) - { - NumberToString(ref sb, ref number, 'G', DoublePrecision, info); - } - else - { - DoubleToNumber(value, 17, ref number); - NumberToString(ref sb, ref number, 'G', 17, info); - } + return info.NaNSymbol; + } + else if (number.Scale == ScaleINF) + { + return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + } - return null; + if (NumberToDouble(ref number) == value) + { + NumberToString(ref sb, ref number, 'G', DoublePrecision, info); + } + else + { + DoubleToNumber(value, 17, ref number); + NumberToString(ref sb, ref number, 'G', 17, info); } + return null; + } + case 'E': case 'e': // Round values less than E14 to 15 digits @@ -442,13 +446,13 @@ private static string FormatDouble(ref ValueStringBuilder sb, double value, Read } DoubleToNumber(value, precision, ref number); - if (number.scale == ScaleNAN) + if (number.Scale == ScaleNAN) { return info.NaNSymbol; } - else if (number.scale == ScaleINF) + else if (number.Scale == ScaleINF) { - return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } if (fmt != 0) @@ -485,42 +489,43 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format, Numbe /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan format, NumberFormatInfo info) + private static unsafe string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan format, NumberFormatInfo info) { char fmt = ParseFormatSpecifier(format, out int digits); - int precision = FloatPrecision; - NumberBuffer number = default; - number.kind = NumberBufferKind.Double; + int precision = SinglePrecision; + + char* pDigits = stackalloc char[SingleNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, SingleNumberBufferLength); switch (fmt) { case 'R': case 'r': + { + // In order to give numbers that are both friendly to display and round-trippable, we parse the + // number using 7 digits and then determine if it round trips to the same value. If it does, we + // convert that NUMBER to a string, otherwise we reparse using 9 digits and display that. + DoubleToNumber(value, SinglePrecision, ref number); + if (number.Scale == ScaleNAN) { - // In order to give numbers that are both friendly to display and round-trippable, we parse the - // number using 7 digits and then determine if it round trips to the same value. If it does, we - // convert that NUMBER to a string, otherwise we reparse using 9 digits and display that. - DoubleToNumber(value, FloatPrecision, ref number); - if (number.scale == ScaleNAN) - { - return info.NaNSymbol; - } - else if (number.scale == ScaleINF) - { - return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; - } + return info.NaNSymbol; + } + else if (number.Scale == ScaleINF) + { + return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + } - if ((float)NumberToDouble(ref number) == value) - { - NumberToString(ref sb, ref number, 'G', FloatPrecision, info); - } - else - { - DoubleToNumber(value, 9, ref number); - NumberToString(ref sb, ref number, 'G', 9, info); - } - return null; + if ((float)NumberToDouble(ref number) == value) + { + NumberToString(ref sb, ref number, 'G', SinglePrecision, info); } + else + { + DoubleToNumber(value, 9, ref number); + NumberToString(ref sb, ref number, 'G', 9, info); + } + return null; + } case 'E': case 'e': @@ -542,13 +547,13 @@ private static string FormatSingle(ref ValueStringBuilder sb, float value, ReadO } DoubleToNumber(value, precision, ref number); - if (number.scale == ScaleNAN) + if (number.Scale == ScaleNAN) { return info.NaNSymbol; } - else if (number.scale == ScaleINF) + else if (number.Scale == ScaleINF) { - return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } if (fmt != 0) @@ -576,7 +581,7 @@ private static bool TryCopyTo(string source, Span destination, out int cha return false; } - public static string FormatInt32(int value, ReadOnlySpan format, IFormatProvider provider) + public static unsafe string FormatInt32(int value, ReadOnlySpan format, IFormatProvider provider) { // Fast path for default format with a non-negative value if (value >= 0 && format.Length == 0) @@ -601,7 +606,10 @@ public static string FormatInt32(int value, ReadOnlySpan format, IFormatPr else { NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); - NumberBuffer number = default; + + char* pDigits = stackalloc char[Int32NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength); + Int32ToNumber(value, ref number); ValueStringBuilder sb; unsafe @@ -621,7 +629,7 @@ public static string FormatInt32(int value, ReadOnlySpan format, IFormatPr } } - public static bool TryFormatInt32(int value, ReadOnlySpan format, IFormatProvider provider, Span destination, out int charsWritten) + public static unsafe bool TryFormatInt32(int value, ReadOnlySpan format, IFormatProvider provider, Span destination, out int charsWritten) { // Fast path for default format with a non-negative value if (value >= 0 && format.Length == 0) @@ -646,7 +654,10 @@ public static bool TryFormatInt32(int value, ReadOnlySpan format, IFormatP else { NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); - NumberBuffer number = default; + + char* pDigits = stackalloc char[Int32NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength); + Int32ToNumber(value, ref number); ValueStringBuilder sb; unsafe @@ -666,7 +677,7 @@ public static bool TryFormatInt32(int value, ReadOnlySpan format, IFormatP } } - public static string FormatUInt32(uint value, ReadOnlySpan format, IFormatProvider provider) + public static unsafe string FormatUInt32(uint value, ReadOnlySpan format, IFormatProvider provider) { // Fast path for default format if (format.Length == 0) @@ -689,7 +700,10 @@ public static string FormatUInt32(uint value, ReadOnlySpan format, IFormat else { NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); - NumberBuffer number = default; + + char* pDigits = stackalloc char[UInt32NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength); + UInt32ToNumber(value, ref number); ValueStringBuilder sb; unsafe @@ -709,7 +723,7 @@ public static string FormatUInt32(uint value, ReadOnlySpan format, IFormat } } - public static bool TryFormatUInt32(uint value, ReadOnlySpan format, IFormatProvider provider, Span destination, out int charsWritten) + public static unsafe bool TryFormatUInt32(uint value, ReadOnlySpan format, IFormatProvider provider, Span destination, out int charsWritten) { // Fast path for default format if (format.Length == 0) @@ -732,7 +746,10 @@ public static bool TryFormatUInt32(uint value, ReadOnlySpan format, IForma else { NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); - NumberBuffer number = default; + + char* pDigits = stackalloc char[UInt32NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength); + UInt32ToNumber(value, ref number); ValueStringBuilder sb; unsafe @@ -752,7 +769,7 @@ public static bool TryFormatUInt32(uint value, ReadOnlySpan format, IForma } } - public static string FormatInt64(long value, ReadOnlySpan format, IFormatProvider provider) + public static unsafe string FormatInt64(long value, ReadOnlySpan format, IFormatProvider provider) { // Fast path for default format with a non-negative value if (value >= 0 && format.Length == 0) @@ -778,7 +795,10 @@ public static string FormatInt64(long value, ReadOnlySpan format, IFormatP else { NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); - NumberBuffer number = default; + + char* pDigits = stackalloc char[Int64NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength); + Int64ToNumber(value, ref number); ValueStringBuilder sb; unsafe @@ -798,7 +818,7 @@ public static string FormatInt64(long value, ReadOnlySpan format, IFormatP } } - public static bool TryFormatInt64(long value, ReadOnlySpan format, IFormatProvider provider, Span destination, out int charsWritten) + public static unsafe bool TryFormatInt64(long value, ReadOnlySpan format, IFormatProvider provider, Span destination, out int charsWritten) { // Fast path for default format with a non-negative value if (value >= 0 && format.Length == 0) @@ -824,7 +844,10 @@ public static bool TryFormatInt64(long value, ReadOnlySpan format, IFormat else { NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); - NumberBuffer number = default; + + char* pDigits = stackalloc char[Int64NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength); + Int64ToNumber(value, ref number); ValueStringBuilder sb; unsafe @@ -844,7 +867,7 @@ public static bool TryFormatInt64(long value, ReadOnlySpan format, IFormat } } - public static string FormatUInt64(ulong value, ReadOnlySpan format, IFormatProvider provider) + public static unsafe string FormatUInt64(ulong value, ReadOnlySpan format, IFormatProvider provider) { // Fast path for default format if (format.Length == 0) @@ -868,7 +891,10 @@ public static string FormatUInt64(ulong value, ReadOnlySpan format, IForma else { NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); - NumberBuffer number = default; + + char* pDigits = stackalloc char[UInt64NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength); + UInt64ToNumber(value, ref number); ValueStringBuilder sb; unsafe @@ -888,7 +914,7 @@ public static string FormatUInt64(ulong value, ReadOnlySpan format, IForma } } - public static bool TryFormatUInt64(ulong value, ReadOnlySpan format, IFormatProvider provider, Span destination, out int charsWritten) + public static unsafe bool TryFormatUInt64(ulong value, ReadOnlySpan format, IFormatProvider provider, Span destination, out int charsWritten) { // Fast path for default format if (format.Length == 0) @@ -912,7 +938,10 @@ public static bool TryFormatUInt64(ulong value, ReadOnlySpan format, IForm else { NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); - NumberBuffer number = default; + + char* pDigits = stackalloc char[UInt64NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength); + UInt64ToNumber(value, ref number); ValueStringBuilder sb; unsafe @@ -935,15 +964,15 @@ public static bool TryFormatUInt64(ulong value, ReadOnlySpan format, IForm [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location private static unsafe void Int32ToNumber(int value, ref NumberBuffer number) { - number.precision = Int32Precision; + number.Precision = Int32Precision; if (value >= 0) { - number.sign = false; + number.Sign = false; } else { - number.sign = true; + number.Sign = true; value = -value; } @@ -951,8 +980,7 @@ private static unsafe void Int32ToNumber(int value, ref NumberBuffer number) char* p = UInt32ToDecChars(buffer + Int32Precision, (uint)value, 0); int i = (int)(buffer + Int32Precision - p); - number.scale = i; - number.kind = NumberBufferKind.Integer; + number.Scale = i; char* dst = number.GetDigitsPointer(); while (--i >= 0) @@ -1062,14 +1090,13 @@ private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number) { - number.precision = UInt32Precision; - number.sign = false; + number.Precision = UInt32Precision; + number.Sign = false; char* buffer = number.GetDigitsPointer(); char* p = UInt32ToDecChars(buffer + UInt32Precision, value, 0); int i = (int)(buffer + UInt32Precision - p); - number.scale = i; - number.kind = NumberBufferKind.Integer; + number.Scale = i; char* dst = number.GetDigitsPointer(); while (--i >= 0) @@ -1174,9 +1201,9 @@ private static unsafe bool TryCopyTo(char* src, int length, Span destinati private static unsafe void Int64ToNumber(long input, ref NumberBuffer number) { ulong value = (ulong)input; - number.sign = input < 0; - number.precision = Int64Precision; - if (number.sign) + number.Sign = input < 0; + number.Precision = Int64Precision; + if (number.Sign) { value = (ulong)(-input); } @@ -1188,8 +1215,7 @@ private static unsafe void Int64ToNumber(long input, ref NumberBuffer number) p = UInt32ToDecChars(p, Low32(value), 0); int i = (int)(buffer + Int64Precision - p); - number.scale = i; - number.kind = NumberBufferKind.Integer; + number.Scale = i; char* dst = number.GetDigitsPointer(); while (--i >= 0) @@ -1319,8 +1345,8 @@ private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number) { - number.precision = UInt64Precision; - number.sign = false; + number.Precision = UInt64Precision; + number.Sign = false; char* buffer = number.GetDigitsPointer(); char* p = buffer + UInt64Precision; @@ -1330,8 +1356,7 @@ private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number) p = UInt32ToDecChars(p, Low32(value), 0); int i = (int)(buffer + UInt64Precision - p); - number.scale = i; - number.kind = NumberBufferKind.Integer; + number.Scale = i; char* dst = number.GetDigitsPointer(); while (--i >= 0) @@ -1456,117 +1481,117 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out // Default empty format to be "G"; custom format is signified with '\0'. digits = -1; return format.Length == 0 || c == '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it. - 'G' : + 'G' : '\0'; } internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info) { - Debug.Assert(number.kind != NumberBufferKind.Unknown); + Debug.Assert(number.Kind != NumberBufferKind.Unknown); switch (format) { case 'C': case 'c': - { - if (nMaxDigits < 0) - nMaxDigits = info.CurrencyDecimalDigits; + { + if (nMaxDigits < 0) + nMaxDigits = info.CurrencyDecimalDigits; - RoundNumber(ref number, number.scale + nMaxDigits); // Don't change this line to use digPos since digCount could have its sign changed. + RoundNumber(ref number, number.Scale + nMaxDigits); // Don't change this line to use digPos since digCount could have its sign changed. - FormatCurrency(ref sb, ref number, nMaxDigits, info); + FormatCurrency(ref sb, ref number, nMaxDigits, info); - break; - } + break; + } case 'F': case 'f': - { - if (nMaxDigits < 0) - nMaxDigits = info.NumberDecimalDigits; + { + if (nMaxDigits < 0) + nMaxDigits = info.NumberDecimalDigits; - RoundNumber(ref number, number.scale + nMaxDigits); + RoundNumber(ref number, number.Scale + nMaxDigits); - if (number.sign) - sb.Append(info.NegativeSign); + if (number.Sign) + sb.Append(info.NegativeSign); - FormatFixed(ref sb, ref number, nMaxDigits, info, null, info.NumberDecimalSeparator, null); + FormatFixed(ref sb, ref number, nMaxDigits, info, null, info.NumberDecimalSeparator, null); - break; - } + break; + } case 'N': case 'n': - { - if (nMaxDigits < 0) - nMaxDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation + { + if (nMaxDigits < 0) + nMaxDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation - RoundNumber(ref number, number.scale + nMaxDigits); + RoundNumber(ref number, number.Scale + nMaxDigits); - FormatNumber(ref sb, ref number, nMaxDigits, info); + FormatNumber(ref sb, ref number, nMaxDigits, info); - break; - } + break; + } case 'E': case 'e': - { - if (nMaxDigits < 0) - nMaxDigits = 6; - nMaxDigits++; + { + if (nMaxDigits < 0) + nMaxDigits = 6; + nMaxDigits++; - RoundNumber(ref number, nMaxDigits); + RoundNumber(ref number, nMaxDigits); - if (number.sign) - sb.Append(info.NegativeSign); + if (number.Sign) + sb.Append(info.NegativeSign); - FormatScientific(ref sb, ref number, nMaxDigits, info, format); + FormatScientific(ref sb, ref number, nMaxDigits, info, format); - break; - } + break; + } case 'G': case 'g': + { + bool noRounding = false; + if (nMaxDigits < 1) { - bool noRounding = false; - if (nMaxDigits < 1) + if ((number.Kind == NumberBufferKind.Decimal) && (nMaxDigits == -1)) { - if ((number.kind == NumberBufferKind.Decimal) && (nMaxDigits == -1)) - { - noRounding = true; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant - goto SkipRounding; - } - else - { - // This ensures that the PAL code pads out to the correct place even when we use the default precision - nMaxDigits = number.precision; - } + noRounding = true; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant + goto SkipRounding; } + else + { + // This ensures that the PAL code pads out to the correct place even when we use the default precision + nMaxDigits = number.Precision; + } + } - RoundNumber(ref number, nMaxDigits); + RoundNumber(ref number, nMaxDigits); -SkipRounding: - if (number.sign) - sb.Append(info.NegativeSign); + SkipRounding: + if (number.Sign) + sb.Append(info.NegativeSign); - FormatGeneral(ref sb, ref number, nMaxDigits, info, (char)(format - ('G' - 'E')), noRounding); + FormatGeneral(ref sb, ref number, nMaxDigits, info, (char)(format - ('G' - 'E')), noRounding); - break; - } + break; + } case 'P': case 'p': - { - if (nMaxDigits < 0) - nMaxDigits = info.PercentDecimalDigits; - number.scale += 2; + { + if (nMaxDigits < 0) + nMaxDigits = info.PercentDecimalDigits; + number.Scale += 2; - RoundNumber(ref number, number.scale + nMaxDigits); + RoundNumber(ref number, number.Scale + nMaxDigits); - FormatPercent(ref sb, ref number, nMaxDigits, info); + FormatPercent(ref sb, ref number, nMaxDigits, info); - break; - } + break; + } default: throw new FormatException(SR.Argument_BadFormatSpecifier); @@ -1575,7 +1600,7 @@ internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref Number internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref NumberBuffer number, ReadOnlySpan format, NumberFormatInfo info) { - Debug.Assert(number.kind != NumberBufferKind.Unknown); + Debug.Assert(number.Kind != NumberBufferKind.Unknown); int digitCount; int decimalPos; @@ -1594,7 +1619,7 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref char* dig = number.GetDigitsPointer(); char ch; - section = FindSection(format, dig[0] == 0 ? 2 : number.sign ? 1 : 0); + section = FindSection(format, dig[0] == 0 ? 2 : number.Sign ? 1 : 0); while (true) { @@ -1663,7 +1688,8 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref if ((src < format.Length && pFormat[src] == '0') || (src + 1 < format.Length && (pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0')) { - while (++src < format.Length && pFormat[src] == '0'); + while (++src < format.Length && pFormat[src] == '0') + ; scientific = true; } break; @@ -1684,8 +1710,8 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref if (dig[0] != 0) { - number.scale += scaleAdjust; - int pos = scientific ? digitCount : number.scale + digitCount - decimalPos; + number.Scale += scaleAdjust; + int pos = scientific ? digitCount : number.Scale + digitCount - decimalPos; RoundNumber(ref number, pos); if (dig[0] == 0) { @@ -1699,7 +1725,7 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref } else { - number.scale = 0; // Decimals with scale ('0.00') should be rounded. + number.Scale = 0; // Decimals with scale ('0.00') should be rounded. } break; @@ -1714,8 +1740,8 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref } else { - digPos = number.scale > decimalPos ? number.scale : decimalPos; - adjust = number.scale - decimalPos; + digPos = number.Scale > decimalPos ? number.Scale : decimalPos; + adjust = number.Scale - decimalPos; } src = section; @@ -1771,7 +1797,7 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref } } - if (number.sign && (section == 0) && (number.scale != 0)) + if (number.Sign && (section == 0) && (number.Scale != 0)) sb.Append(info.NegativeSign); bool decimalWritten = false; @@ -1813,47 +1839,47 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref { case '#': case '0': + { + if (adjust < 0) { - if (adjust < 0) - { - adjust++; - ch = digPos <= firstDigit ? '0' : '\0'; - } - else - { - ch = *cur != 0 ? *cur++ : digPos > lastDigit ? '0' : '\0'; - } - if (ch != 0) + adjust++; + ch = digPos <= firstDigit ? '0' : '\0'; + } + else + { + ch = *cur != 0 ? *cur++ : digPos > lastDigit ? '0' : '\0'; + } + if (ch != 0) + { + sb.Append(ch); + if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0) { - sb.Append(ch); - if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0) + if (digPos == thousandsSepPos[thousandsSepCtr] + 1) { - if (digPos == thousandsSepPos[thousandsSepCtr] + 1) - { - sb.Append(info.NumberGroupSeparator); - thousandsSepCtr--; - } + sb.Append(info.NumberGroupSeparator); + thousandsSepCtr--; } } - - digPos--; - break; } + + digPos--; + break; + } case '.': + { + if (digPos != 0 || decimalWritten) { - if (digPos != 0 || decimalWritten) - { - // For compatibility, don't echo repeated decimals - break; - } - // If the format has trailing zeros or the format has a decimal and digits remain - if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0)) - { - sb.Append(info.NumberDecimalSeparator); - decimalWritten = true; - } + // For compatibility, don't echo repeated decimals break; } + // If the format has trailing zeros or the format has a decimal and digits remain + if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0)) + { + sb.Append(info.NumberDecimalSeparator); + decimalWritten = true; + } + break; + } case '\x2030': sb.Append(info.PerMilleSymbol); break; @@ -1875,54 +1901,54 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref break; case 'E': case 'e': + { + bool positiveSign = false; + int i = 0; + if (scientific) { - bool positiveSign = false; - int i = 0; - if (scientific) + if (src < format.Length && pFormat[src] == '0') { - if (src < format.Length && pFormat[src] == '0') - { - // Handles E0, which should format the same as E-0 - i++; - } - else if (src+1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0') - { - // Handles E+0 - positiveSign = true; - } - else if (src+1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0') - { - // Handles E-0 - // Do nothing, this is just a place holder s.t. we don't break out of the loop. - } - else - { - sb.Append(ch); - break; - } - - while (++src < format.Length && pFormat[src] == '0') - i++; - if (i > 10) - i = 10; - - int exp = dig[0] == 0 ? 0 : number.scale - decimalPos; - FormatExponent(ref sb, info, exp, ch, i, positiveSign); - scientific = false; + // Handles E0, which should format the same as E-0 + i++; + } + else if (src + 1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0') + { + // Handles E+0 + positiveSign = true; + } + else if (src + 1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0') + { + // Handles E-0 + // Do nothing, this is just a place holder s.t. we don't break out of the loop. } else { - sb.Append(ch); // Copy E or e to output - if (src < format.Length) - { - if (pFormat[src] == '+' || pFormat[src] == '-') - sb.Append(pFormat[src++]); - while (src < format.Length && pFormat[src] == '0') - sb.Append(pFormat[src++]); - } + sb.Append(ch); + break; + } + + while (++src < format.Length && pFormat[src] == '0') + i++; + if (i > 10) + i = 10; + + int exp = dig[0] == 0 ? 0 : number.Scale - decimalPos; + FormatExponent(ref sb, info, exp, ch, i, positiveSign); + scientific = false; + } + else + { + sb.Append(ch); // Copy E or e to output + if (src < format.Length) + { + if (pFormat[src] == '+' || pFormat[src] == '-') + sb.Append(pFormat[src++]); + while (src < format.Length && pFormat[src] == '0') + sb.Append(pFormat[src++]); } - break; } + break; + } default: sb.Append(ch); break; @@ -1930,13 +1956,13 @@ internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref } } - if (number.sign && (section == 0) && (number.scale == 0) && (sb.Length > 0)) + if (number.Sign && (section == 0) && (number.Scale == 0) && (sb.Length > 0)) sb.Insert(0, info.NegativeSign); } private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) { - string fmt = number.sign ? + string fmt = number.Sign ? s_negCurrencyFormats[info.CurrencyNegativePattern] : s_posCurrencyFormats[info.CurrencyPositivePattern]; @@ -1962,7 +1988,7 @@ private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer n private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, int[] groupDigits, string sDecimal, string sGroup) { - int digPos = number.scale; + int digPos = number.Scale; char* dig = number.GetDigitsPointer(); if (digPos > 0) @@ -2064,7 +2090,7 @@ private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuff private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) { - string fmt = number.sign ? + string fmt = number.Sign ? s_negNumberFormats[info.NumberNegativePattern] : PosNumberFormat; @@ -2097,7 +2123,7 @@ private static unsafe void FormatScientific(ref ValueStringBuilder sb, ref Numbe while (--nMaxDigits > 0) sb.Append((*dig != 0) ? *dig++ : '0'); - int e = number.digits[0] == 0 ? 0 : number.scale - 1; + int e = number.Digits[0] == 0 ? 0 : number.Scale - 1; FormatExponent(ref sb, info, e, expChar, 3, true); } @@ -2124,7 +2150,7 @@ private static unsafe void FormatExponent(ref ValueStringBuilder sb, NumberForma private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool bSuppressScientific) { - int digPos = number.scale; + int digPos = number.Scale; bool scientific = false; if (!bSuppressScientific) @@ -2166,12 +2192,12 @@ private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBu } if (scientific) - FormatExponent(ref sb, info, number.scale - 1, expChar, 2, true); + FormatExponent(ref sb, info, number.Scale - 1, expChar, 2, true); } private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info) { - string fmt = number.sign ? + string fmt = number.Sign ? s_negPercentFormats[info.PercentNegativePattern] : s_posPercentFormats[info.PercentPositivePattern]; @@ -2214,7 +2240,7 @@ private static unsafe void RoundNumber(ref NumberBuffer number, int pos) } else { - number.scale++; + number.Scale++; dig[0] = '1'; i = 1; } @@ -2226,11 +2252,11 @@ private static unsafe void RoundNumber(ref NumberBuffer number, int pos) } if (i == 0) { - number.scale = 0; + number.Scale = 0; - if (number.kind == NumberBufferKind.Integer) + if (number.Kind == NumberBufferKind.Integer) { - number.sign = false; + number.Sign = false; } } dig[i] = '\0'; @@ -2247,7 +2273,7 @@ private static unsafe int FindSection(ReadOnlySpan format, int section) fixed (char* pFormat = &MemoryMarshal.GetReference(format)) { src = 0; - for (;;) + for (; ; ) { if (src >= format.Length) { @@ -2291,19 +2317,19 @@ private static uint Int64DivMod1E9(ref ulong value) private static unsafe void DoubleToNumber(double value, int precision, ref NumberBuffer number) { - number.precision = precision; + number.Precision = precision; if (!double.IsFinite(value)) { - number.scale = double.IsNaN(value) ? ScaleNAN : ScaleINF; - number.sign = double.IsNegative(value); - number.digits[0] = '\0'; + number.Scale = double.IsNaN(value) ? ScaleNAN : ScaleINF; + number.Sign = double.IsNegative(value); + number.Digits[0] = '\0'; } else if (value == 0.0) { - number.scale = 0; - number.sign = double.IsNegative(value); - number.digits[0] = '\0'; + number.Scale = 0; + number.Sign = double.IsNegative(value); + number.Digits[0] = '\0'; } else if (!Grisu3.Run(value, precision, ref number)) { diff --git a/src/Common/src/CoreLib/System/Number.Grisu3.cs b/src/Common/src/CoreLib/System/Number.Grisu3.cs index 60d629a14fab..0cbf2580c325 100644 --- a/src/Common/src/CoreLib/System/Number.Grisu3.cs +++ b/src/Common/src/CoreLib/System/Number.Grisu3.cs @@ -341,11 +341,11 @@ public static bool Run(double value, int precision, ref NumberBuffer number) if (double.IsNegative(value)) { value = -value; - number.sign = true; + number.Sign = true; } else { - number.sign = false; + number.Sign = false; } // Step 1: Determine the normalized DiyFp w. @@ -370,8 +370,8 @@ public static bool Run(double value, int precision, ref NumberBuffer number) if (isSuccess) { - number.digits[precision] = '\0'; - number.scale = (length - decimalExponent + kappa); + number.Digits[precision] = '\0'; + number.Scale = (length - decimalExponent + kappa); } return isSuccess; diff --git a/src/Common/src/CoreLib/System/Number.NumberBuffer.cs b/src/Common/src/CoreLib/System/Number.NumberBuffer.cs index b5ed19b54519..6636383c170f 100644 --- a/src/Common/src/CoreLib/System/Number.NumberBuffer.cs +++ b/src/Common/src/CoreLib/System/Number.NumberBuffer.cs @@ -9,26 +9,37 @@ namespace System { internal static partial class Number { - private const int NumberMaxDigits = 50; - - private const double Log10V2 = 0.30102999566398119521373889472449; - - // DriftFactor = 1 - Log10V2 - epsilon (a small number account for drift of floating point multiplication) - private const double DriftFactor = 0.69; + // 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 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 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 [StructLayout(LayoutKind.Sequential, Pack = 1)] internal unsafe ref struct NumberBuffer { - public int precision; - public int scale; - public bool sign; - public NumberBufferKind kind; - public fixed char digits[NumberMaxDigits + 1]; + public int Precision; + public int Scale; + public bool Sign; + public NumberBufferKind Kind; + public Span Digits; + + public NumberBuffer(NumberBufferKind kind, char* pDigits, int digitsLength) + { + Precision = 0; + Scale = 0; + Sign = false; + Kind = kind; + Digits = new Span(pDigits, digitsLength); + } public char* GetDigitsPointer() { // This is safe to do since we are a ref struct - return (char*)(Unsafe.AsPointer(ref digits[0])); + return (char*)(Unsafe.AsPointer(ref Digits[0])); } } @@ -37,7 +48,7 @@ internal enum NumberBufferKind : byte Unknown = 0, Integer = 1, Decimal = 2, - Double = 3 + Double = 3, } } } diff --git a/src/Common/src/CoreLib/System/Number.NumberToDouble.cs b/src/Common/src/CoreLib/System/Number.NumberToDouble.cs index 2df2b2aa49f1..974786d3de28 100644 --- a/src/Common/src/CoreLib/System/Number.NumberToDouble.cs +++ b/src/Common/src/CoreLib/System/Number.NumberToDouble.cs @@ -327,7 +327,7 @@ private static double NumberToDouble(ref NumberBuffer number) if (remaining == 0) { - return number.sign ? -0.0 : 0.0; + return number.Sign ? -0.0 : 0.0; } int count = Math.Min(remaining, 9); @@ -344,18 +344,18 @@ private static double NumberToDouble(ref NumberBuffer number) val = Mul32x32To64((uint)(val), mult) + DigitsToInt(src + 9, count); } - int scale = number.scale - (total - remaining); + 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; + return number.Sign ? double.NegativeInfinity : double.PositiveInfinity; } else { - return number.sign ? -0.0 : 0.0; + return number.Sign ? -0.0 : 0.0; } } @@ -438,7 +438,7 @@ private static double NumberToDouble(ref NumberBuffer number) val = ((ulong)(exp) << 52) + ((val >> 11) & 0x000FFFFFFFFFFFFF); } - if (number.sign) + if (number.Sign) { val |= 0x8000000000000000; } diff --git a/src/Common/src/CoreLib/System/Number.Parsing.cs b/src/Common/src/CoreLib/System/Number.Parsing.cs index 2f25e76c14d0..802f15745124 100644 --- a/src/Common/src/CoreLib/System/Number.Parsing.cs +++ b/src/Common/src/CoreLib/System/Number.Parsing.cs @@ -52,10 +52,10 @@ internal partial class Number 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 }; - private static unsafe bool NumberToInt32(ref NumberBuffer number, ref int value) + private static unsafe bool TryNumberToInt32(ref NumberBuffer number, ref int value) { - int i = number.scale; - if (i > Int32Precision || i < number.precision) + int i = number.Scale; + if (i > Int32Precision || i < number.Precision) { return false; } @@ -74,7 +74,7 @@ private static unsafe bool NumberToInt32(ref NumberBuffer number, ref int value) n += (*p++ - '0'); } } - if (number.sign) + if (number.Sign) { n = -n; if (n > 0) @@ -93,10 +93,10 @@ private static unsafe bool NumberToInt32(ref NumberBuffer number, ref int value) return true; } - private static unsafe bool NumberToInt64(ref NumberBuffer number, ref long value) + private static unsafe bool TryNumberToInt64(ref NumberBuffer number, ref long value) { - int i = number.scale; - if (i > Int64Precision || i < number.precision) + int i = number.Scale; + if (i > Int64Precision || i < number.Precision) { return false; } @@ -115,7 +115,7 @@ private static unsafe bool NumberToInt64(ref NumberBuffer number, ref long value n += (*p++ - '0'); } } - if (number.sign) + if (number.Sign) { n = -n; if (n > 0) @@ -134,10 +134,10 @@ private static unsafe bool NumberToInt64(ref NumberBuffer number, ref long value return true; } - private static unsafe bool NumberToUInt32(ref NumberBuffer number, ref uint value) + private static unsafe bool TryNumberToUInt32(ref NumberBuffer number, ref uint value) { - int i = number.scale; - if (i > UInt32Precision || i < number.precision || number.sign) + int i = number.Scale; + if (i > UInt32Precision || i < number.Precision || number.Sign) { return false; } @@ -166,10 +166,10 @@ private static unsafe bool NumberToUInt32(ref NumberBuffer number, ref uint valu return true; } - private static unsafe bool NumberToUInt64(ref NumberBuffer number, ref ulong value) + private static unsafe bool TryNumberToUInt64(ref NumberBuffer number, ref ulong value) { - int i = number.scale; - if (i > UInt64Precision || i < number.precision || number.sign) + int i = number.Scale; + if (i > UInt64Precision || i < number.Precision || number.Sign) { return false; } @@ -200,139 +200,45 @@ private static unsafe bool NumberToUInt64(ref NumberBuffer number, ref ulong val internal static int ParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if ((styles & ~NumberStyles.Integer) == 0) + if (!TryParseInt32(value, styles, info, out int result, out bool failureIsOverflow)) { - // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - if (!TryParseInt32IntegerStyle(value, styles, info, out int intResult, ref overflow)) - { - ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int32)); - } - return intResult; + ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Int32)); } - if ((styles & NumberStyles.AllowHexSpecifier) != 0) - { - bool overflow = false; - if (!TryParseUInt32HexNumberStyle(value, styles, out uint hexResult, ref overflow)) - { - ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int32)); - } - return (int)hexResult; - } - - NumberBuffer number = default; - int result = 0; - StringToNumber(value, styles, ref number, info, false); - if (!NumberToInt32(ref number, ref result)) - { - ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Int32)); - } return result; } internal static long ParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if ((styles & ~NumberStyles.Integer) == 0) - { - // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - if (!TryParseInt64IntegerStyle(value, styles, info, out long intResult, ref overflow)) - { - ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int64)); - } - return intResult; - } - - if ((styles & NumberStyles.AllowHexSpecifier) != 0) + if (!TryParseInt64(value, styles, info, out long result, out bool failureIsOverflow)) { - bool overflow = false; - if (!TryParseUInt64HexNumberStyle(value, styles, out ulong hexResult, ref overflow)) - { - ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_Int64)); - } - return (long)hexResult; + ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Int64)); } - NumberBuffer number = default; - long result = 0; - StringToNumber(value, styles, ref number, info, false); - if (!NumberToInt64(ref number, ref result)) - { - ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Int64)); - } return result; } internal static uint ParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - uint result = 0; - - if ((styles & ~NumberStyles.Integer) == 0) - { - // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - if (!TryParseUInt32IntegerStyle(value, styles, info, out result, ref overflow)) - { - ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt32)); - } - return result; - } - - if ((styles & NumberStyles.AllowHexSpecifier) != 0) + if (!TryParseUInt32(value, styles, info, out uint result, out bool failureIsOverflow)) { - bool overflow = false; - if (!TryParseUInt32HexNumberStyle(value, styles, out result, ref overflow)) - { - ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt32)); - } - return result; + ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_UInt32)); } - NumberBuffer number = default; - StringToNumber(value, styles, ref number, info, false); - if (!NumberToUInt32(ref number, ref result)) - { - ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_UInt32)); - } return result; } internal static ulong ParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - ulong result = 0; - - if ((styles & ~NumberStyles.Integer) == 0) - { - // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - if (!TryParseUInt64IntegerStyle(value, styles, info, out result, ref overflow)) - { - ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt64)); - } - return result; - } - - if ((styles & NumberStyles.AllowHexSpecifier) != 0) + if (!TryParseUInt64(value, styles, info, out ulong result, out bool failureIsOverflow)) { - bool overflow = false; - if (!TryParseUInt64HexNumberStyle(value, styles, out result, ref overflow)) - { - ThrowOverflowOrFormatException(overflow, nameof(SR.Overflow_UInt64)); - } - return result; + ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_UInt64)); } - NumberBuffer number = default; - StringToNumber(value, styles, ref number, info, false); - if (!NumberToUInt64(ref number, ref result)) - { - ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_UInt64)); - } return result; } - private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal) + private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) { Debug.Assert(str != null); Debug.Assert(strEnd != null); @@ -346,8 +252,8 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles const int StateDecimal = 0x0010; const int StateCurrency = 0x0020; - number.scale = 0; - number.sign = false; + number.Scale = 0; + number.Sign = false; string decSep; // decimal separator from NumberFormatInfo. string groupSep; // group separator from NumberFormatInfo. string currSymbol = null; // currency symbol from NumberFormatInfo. @@ -380,7 +286,7 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles // "-Kr 1231.47" is legal but "- 1231.47" is not. if (!IsWhite(ch) || (styles & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && info.NumberNegativePattern != 2))) { - if ((((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || ((next = MatchChars(p, strEnd, info.NegativeSign)) != null && (number.sign = true)))) + if ((((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || ((next = MatchChars(p, strEnd, info.NegativeSign)) != null && (number.Sign = true)))) { state |= StateSign; p = next - 1; @@ -388,7 +294,7 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles else if (ch == '(' && ((styles & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0)) { state |= StateSign | StateParens; - number.sign = true; + number.Sign = true; } else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) { @@ -405,8 +311,11 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles } ch = ++p < strEnd ? *p : '\0'; } + int digCount = 0; int digEnd = 0; + int maxDigCount = number.Digits.Length - 1; + while (true) { if (IsDigit(ch)) @@ -415,23 +324,23 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles if (ch != '0' || (state & StateNonZero) != 0) { - if (digCount < NumberMaxDigits) + if (digCount < maxDigCount) { - number.digits[digCount++] = ch; - if (ch != '0' || parseDecimal) + number.Digits[digCount++] = ch; + if (ch != '0' || number.Kind == NumberBufferKind.Decimal) { digEnd = digCount; } } if ((state & StateDecimal) == 0) { - number.scale++; + number.Scale++; } state |= StateNonZero; } else if ((state & StateDecimal) != 0) { - number.scale--; + number.Scale--; } } else if (((styles & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, info.NumberDecimalSeparator)) != null)) @@ -451,8 +360,8 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles } bool negExp = false; - number.precision = digEnd; - number.digits[digEnd] = '\0'; + number.Precision = digEnd; + number.Digits[digEnd] = '\0'; if ((state & StateDigits) != 0) { if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0)) @@ -488,7 +397,7 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles { exp = -exp; } - number.scale += exp; + number.Scale += exp; } else { @@ -500,7 +409,7 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles { if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0) { - if (((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0)) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || (((next = MatchChars(p, strEnd, info.NegativeSign)) != null) && (number.sign = true)))) + if (((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0)) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || (((next = MatchChars(p, strEnd, info.NegativeSign)) != null) && (number.Sign = true)))) { state |= StateSign; p = next - 1; @@ -525,13 +434,13 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles { if ((state & StateNonZero) == 0) { - if (!parseDecimal) + if (number.Kind != NumberBufferKind.Decimal) { - number.scale = 0; + number.Scale = 0; } if ((state & StateDecimal) == 0) { - number.sign = false; + number.Sign = false; } } str = p; @@ -542,27 +451,37 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles return false; } - internal static bool TryParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result) + internal static unsafe bool TryParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result, out bool failureIsOverflow) { + result = 0; + failureIsOverflow = false; + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - return TryParseInt32IntegerStyle(value, styles, info, out result, ref overflow); + return TryParseInt32IntegerStyle(value, styles, info, out result, ref failureIsOverflow); } - result = 0; - if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - bool overflow = false; - return TryParseUInt32HexNumberStyle(value, styles, out Unsafe.As(ref result), ref overflow); + return TryParseUInt32HexNumberStyle(value, styles, out Unsafe.As(ref result), ref failureIsOverflow); + } + + char* pDigits = stackalloc char[Int32NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength); + + if (!TryStringToNumber(value, styles, ref number, info)) + { + return false; + } + + if (!TryNumberToInt32(ref number, ref result)) + { + failureIsOverflow = true; + return false; } - NumberBuffer number = default; - return - TryStringToNumber(value, styles, ref number, info, false) && - NumberToInt32(ref number, ref result); + return true; } /// Parses int limited to styles that make up NumberStyles.Integer. @@ -571,7 +490,8 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) + goto FalseExit; bool overflow = false; int sign = 1; @@ -584,7 +504,8 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt do { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } while (IsWhite(num)); @@ -601,13 +522,15 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt { sign = -1; index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } else if (num == '+') { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } } @@ -618,19 +541,21 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { sign = -1; index += negativeSign.Length; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } } } - + int answer = 0; if (IsDigit(num)) @@ -641,28 +566,34 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt do { index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } while (num == '0'); - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; } - + // Parse most digits, up to the potential for overflow, which can't happen until after 9 digits. answer = num - '0'; // first digit index++; for (int i = 0; i < 8; i++) // next 8 digits can't overflow { - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } // Potential overflow now processing the 10th digit. - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; index++; if (answer > int.MaxValue / 10) { @@ -673,7 +604,8 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt { overflow = true; } - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. @@ -688,7 +620,7 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt } goto HasTrailingChars; } - + FalseExit: // parsing failed result = 0; return false; @@ -706,16 +638,20 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { - if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; + if ((styles & NumberStyles.AllowTrailingWhite) == 0) + goto FalseExit; for (index++; index < value.Length; index++) { - if (!IsWhite(value[index])) break; + if (!IsWhite(value[index])) + break; } - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(value, index)) goto FalseExit; - + if (!TrailingZeros(value, index)) + goto FalseExit; + goto DoneAtEndButPotentialOverflow; } @@ -726,7 +662,8 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) + goto FalseExit; bool overflow = false; int sign = 1; @@ -739,7 +676,8 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt do { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } while (IsWhite(num)); @@ -756,13 +694,15 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt { sign = -1; index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } else if (num == '+') { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } } @@ -773,19 +713,21 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { sign = -1; index += negativeSign.Length; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } } } - + long answer = 0; if (IsDigit(num)) @@ -796,28 +738,34 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt do { index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } while (num == '0'); - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; } - + // Parse most digits, up to the potential for overflow, which can't happen until after 18 digits. answer = num - '0'; // first digit index++; for (int i = 0; i < 17; i++) // next 17 digits can't overflow { - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } // Potential overflow now processing the 19th digit. - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; index++; if (answer > long.MaxValue / 10) { @@ -828,7 +776,8 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt { overflow = true; } - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. @@ -837,12 +786,13 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt { overflow = true; index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } goto HasTrailingChars; } - + FalseExit: // parsing failed result = 0; return false; @@ -860,62 +810,88 @@ private static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberSt // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { - if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; + if ((styles & NumberStyles.AllowTrailingWhite) == 0) + goto FalseExit; for (index++; index < value.Length; index++) { - if (!IsWhite(value[index])) break; + if (!IsWhite(value[index])) + break; } - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(value, index)) goto FalseExit; - + if (!TrailingZeros(value, index)) + goto FalseExit; + goto DoneAtEndButPotentialOverflow; } - internal static bool TryParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result) + internal static unsafe bool TryParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result, out bool failureIsOverflow) { + result = 0; + failureIsOverflow = false; + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - return TryParseInt64IntegerStyle(value, styles, info, out result, ref overflow); + return TryParseInt64IntegerStyle(value, styles, info, out result, ref failureIsOverflow); } - result = 0; - if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - bool overflow = false; - return TryParseUInt64HexNumberStyle(value, styles, out Unsafe.As(ref result), ref overflow); + return TryParseUInt64HexNumberStyle(value, styles, out Unsafe.As(ref result), ref failureIsOverflow); + } + + char* pDigits = stackalloc char[Int64NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength); + + if (!TryStringToNumber(value, styles, ref number, info)) + { + return false; } - NumberBuffer number = default; - return - TryStringToNumber(value, styles, ref number, info, false) && - NumberToInt64(ref number, ref result); + if (!TryNumberToInt64(ref number, ref result)) + { + failureIsOverflow = true; + return false; + } + + return true; } - internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result) + internal static unsafe bool TryParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result, out bool failureIsOverflow) { + result = 0; + failureIsOverflow = false; + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - return TryParseUInt32IntegerStyle(value, styles, info, out result, ref overflow); + return TryParseUInt32IntegerStyle(value, styles, info, out result, ref failureIsOverflow); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - bool overflow = false; - return TryParseUInt32HexNumberStyle(value, styles, out result, ref overflow); + return TryParseUInt32HexNumberStyle(value, styles, out result, ref failureIsOverflow); } - NumberBuffer number = default; - result = 0; - return - TryStringToNumber(value, styles, ref number, info, false) && - NumberToUInt32(ref number, ref result); + char* pDigits = stackalloc char[UInt32NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength); + + if (!TryStringToNumber(value, styles, ref number, info)) + { + return false; + } + + + if (!TryNumberToUInt32(ref number, ref result)) + { + failureIsOverflow = true; + return false; + } + + return true; } /// Parses uint limited to styles that make up NumberStyles.Integer. @@ -925,7 +901,8 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) + goto FalseExit; bool overflow = false; bool hasNegativeSign = false; @@ -938,7 +915,8 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style do { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } while (IsWhite(num)); @@ -954,14 +932,16 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style if (num == '+') { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } else if (num == '-') { hasNegativeSign = true; index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } } @@ -972,19 +952,21 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { hasNegativeSign = true; index += negativeSign.Length; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } } } - + int answer = 0; if (IsDigit(num)) @@ -995,35 +977,42 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style do { index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } while (num == '0'); - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; } - + // Parse most digits, up to the potential for overflow, which can't happen until after 9 digits. answer = num - '0'; // first digit index++; for (int i = 0; i < 8; i++) // next 8 digits can't overflow { - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } // Potential overflow now processing the 10th digit. - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; index++; if ((uint)answer > uint.MaxValue / 10 || ((uint)answer == uint.MaxValue / 10 && num > '5')) { overflow = true; } answer = answer * 10 + num - '0'; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. @@ -1032,12 +1021,13 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style { overflow = true; index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } goto HasTrailingChars; } - + FalseExit: // parsing failed result = 0; return false; @@ -1055,16 +1045,20 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { - if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; + if ((styles & NumberStyles.AllowTrailingWhite) == 0) + goto FalseExit; for (index++; index < value.Length; index++) { - if (!IsWhite(value[index])) break; + if (!IsWhite(value[index])) + break; } - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(value, index)) goto FalseExit; - + if (!TrailingZeros(value, index)) + goto FalseExit; + goto DoneAtEndButPotentialOverflow; } @@ -1075,7 +1069,8 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) + goto FalseExit; bool overflow = false; int index = 0; @@ -1088,7 +1083,8 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style do { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } while (IsWhite(num)); @@ -1105,31 +1101,38 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style do { index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEnd; + if ((uint)index >= (uint)value.Length) + goto DoneAtEnd; num = value[index]; } while (num == '0'); - if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) goto HasTrailingChars; + if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) + goto HasTrailingChars; } - + // Parse up through 8 digits, as no overflow is possible answer = charToHexLookup[num]; // first digit index++; for (int i = 0; i < 7; i++) // next 7 digits can't overflow { - if ((uint)index >= (uint)value.Length) goto DoneAtEnd; + if ((uint)index >= (uint)value.Length) + goto DoneAtEnd; num = value[index]; - if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; + if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) + goto HasTrailingChars; index++; answer = 16 * answer + numValue; } // If there's another digit, it's an overflow. - if ((uint)index >= (uint)value.Length) goto DoneAtEnd; + if ((uint)index >= (uint)value.Length) + goto DoneAtEnd; num = value[index]; - if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; + if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) + goto HasTrailingChars; index++; overflow = true; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. Read through any remaining digits. @@ -1137,12 +1140,13 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } goto HasTrailingChars; } - + FalseExit: // parsing failed result = 0; return false; @@ -1161,39 +1165,55 @@ internal static bool TryParseUInt32(ReadOnlySpan value, NumberStyles style // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { - if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; + if ((styles & NumberStyles.AllowTrailingWhite) == 0) + goto FalseExit; for (index++; index < value.Length; index++) { - if (!IsWhite(value[index])) break; + if (!IsWhite(value[index])) + break; } - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(value, index)) goto FalseExit; - + if (!TrailingZeros(value, index)) + goto FalseExit; + goto DoneAtEndButPotentialOverflow; } - - internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result) + + internal static unsafe bool TryParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result, out bool failureIsOverflow) { + result = 0; + failureIsOverflow = false; + if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - bool overflow = false; - return TryParseUInt64IntegerStyle(value, styles, info, out result, ref overflow); + return TryParseUInt64IntegerStyle(value, styles, info, out result, ref failureIsOverflow); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - bool overflow = false; - return TryParseUInt64HexNumberStyle(value, styles, out result, ref overflow); + return TryParseUInt64HexNumberStyle(value, styles, out result, ref failureIsOverflow); } - NumberBuffer number = default; - result = 0; - return - TryStringToNumber(value, styles, ref number, info, false) && - NumberToUInt64(ref number, ref result); + char* pDigits = stackalloc char[UInt64NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength); + + if (!TryStringToNumber(value, styles, ref number, info)) + { + return false; + } + + + if (!TryNumberToUInt64(ref number, ref result)) + { + failureIsOverflow = true; + return false; + } + + return true; } /// Parses ulong limited to styles that make up NumberStyles.Integer. @@ -1203,7 +1223,8 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) + goto FalseExit; bool overflow = false; bool hasNegativeSign = false; @@ -1216,7 +1237,8 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style do { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } while (IsWhite(num)); @@ -1232,14 +1254,16 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style if (num == '+') { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } else if (num == '-') { hasNegativeSign = true; index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } } @@ -1250,19 +1274,21 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { hasNegativeSign = true; index += negativeSign.Length; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } } } - + long answer = 0; if (IsDigit(num)) @@ -1273,35 +1299,42 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style do { index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } while (num == '0'); - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; } - + // Parse most digits, up to the potential for overflow, which can't happen until after 19 digits. answer = num - '0'; // first digit index++; for (int i = 0; i < 18; i++) // next 18 digits can't overflow { - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; index++; answer = 10 * answer + num - '0'; } // Potential overflow now processing the 20th digit. - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; - if (!IsDigit(num)) goto HasTrailingChars; + if (!IsDigit(num)) + goto HasTrailingChars; index++; if ((ulong)answer > ulong.MaxValue / 10 || ((ulong)answer == ulong.MaxValue / 10 && num > '5')) { overflow = true; } answer = answer * 10 + num - '0'; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. @@ -1310,12 +1343,13 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style { overflow = true; index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } goto HasTrailingChars; } - + FalseExit: // parsing failed result = 0; return false; @@ -1333,16 +1367,20 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { - if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; + if ((styles & NumberStyles.AllowTrailingWhite) == 0) + goto FalseExit; for (index++; index < value.Length; index++) { - if (!IsWhite(value[index])) break; + if (!IsWhite(value[index])) + break; } - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(value, index)) goto FalseExit; - + if (!TrailingZeros(value, index)) + goto FalseExit; + goto DoneAtEndButPotentialOverflow; } @@ -1353,7 +1391,8 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format"); Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) goto FalseExit; + if ((uint)value.Length < 1) + goto FalseExit; bool overflow = false; int index = 0; @@ -1366,7 +1405,8 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style do { index++; - if ((uint)index >= (uint)value.Length) goto FalseExit; + if ((uint)index >= (uint)value.Length) + goto FalseExit; num = value[index]; } while (IsWhite(num)); @@ -1383,31 +1423,38 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style do { index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEnd; + if ((uint)index >= (uint)value.Length) + goto DoneAtEnd; num = value[index]; } while (num == '0'); - if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) goto HasTrailingChars; + if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) + goto HasTrailingChars; } - + // Parse up through 16 digits, as no overflow is possible answer = charToHexLookup[num]; // first digit index++; for (int i = 0; i < 15; i++) // next 15 digits can't overflow { - if ((uint)index >= (uint)value.Length) goto DoneAtEnd; + if ((uint)index >= (uint)value.Length) + goto DoneAtEnd; num = value[index]; - if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; + if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) + goto HasTrailingChars; index++; answer = 16 * answer + numValue; } // If there's another digit, it's an overflow. - if ((uint)index >= (uint)value.Length) goto DoneAtEnd; + if ((uint)index >= (uint)value.Length) + goto DoneAtEnd; num = value[index]; - if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; + if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) + goto HasTrailingChars; index++; overflow = true; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. Read through any remaining digits. @@ -1415,12 +1462,13 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { index++; - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; num = value[index]; } goto HasTrailingChars; } - + FalseExit: // parsing failed result = 0; return false; @@ -1439,38 +1487,38 @@ internal static bool TryParseUInt64(ReadOnlySpan value, NumberStyles style // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { - if ((styles & NumberStyles.AllowTrailingWhite) == 0) goto FalseExit; + if ((styles & NumberStyles.AllowTrailingWhite) == 0) + goto FalseExit; for (index++; index < value.Length; index++) { - if (!IsWhite(value[index])) break; + if (!IsWhite(value[index])) + break; } - if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; + if ((uint)index >= (uint)value.Length) + goto DoneAtEndButPotentialOverflow; } - if (!TrailingZeros(value, index)) goto FalseExit; - + if (!TrailingZeros(value, index)) + goto FalseExit; + goto DoneAtEndButPotentialOverflow; } internal static decimal ParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - NumberBuffer number = default; - decimal result = 0; - - StringToNumber(value, styles, ref number, info, true); - - if (!NumberBufferToDecimal(ref number, ref result)) + if (!TryParseDecimal(value, styles, info, out decimal result, out bool failureIsOverflow)) { - ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Decimal)); + ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Decimal)); } + return result; } - private static unsafe bool NumberBufferToDecimal(ref NumberBuffer number, ref decimal value) + private static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value) { char* p = number.GetDigitsPointer(); - int e = number.scale; - bool sign = number.sign; + int e = number.Scale; + bool sign = number.Sign; uint c = *p; if (c == 0) { @@ -1569,154 +1617,123 @@ private static unsafe bool NumberBufferToDecimal(ref NumberBuffer number, ref de internal static double ParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - NumberBuffer number = default; - double d = 0; - - if (!TryStringToNumber(value, styles, ref number, info, false)) + if (!TryParseDouble(value, styles, info, out double result, out bool failureIsOverflow)) { - //If we failed TryStringToNumber, it may be from one of our special strings. - //Check the three with which we're concerned and rethrow if it's not one of - //those strings. - ReadOnlySpan sTrim = value.Trim(); - if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) - { - return double.PositiveInfinity; - } - if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) - { - return double.NegativeInfinity; - } - if (sTrim.EqualsOrdinal(info.NaNSymbol)) - { - return double.NaN; - } - ThrowOverflowOrFormatException(overflow: false, null); + ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Double)); } - if (!NumberBufferToDouble(ref number, ref d)) - { - ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Double)); - } - - return d; + return result; } internal static float ParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - NumberBuffer number = default; - double d = 0; - - if (!TryStringToNumber(value, styles, ref number, info, false)) + if (!TryParseSingle(value, styles, info, out float result, out bool failureIsOverflow)) { - //If we failed TryStringToNumber, it may be from one of our special strings. - //Check the three with which we're concerned and rethrow if it's not one of - //those strings. - ReadOnlySpan sTrim = value.Trim(); - if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) - { - return float.PositiveInfinity; - } - if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) - { - return float.NegativeInfinity; - } - if (sTrim.EqualsOrdinal(info.NaNSymbol)) - { - return float.NaN; - } - ThrowOverflowOrFormatException(overflow: false, null); + ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Single)); } - if (!NumberBufferToDouble(ref number, ref d)) - { - ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Single)); - } - float castSingle = (float)d; - if (float.IsInfinity(castSingle)) - { - ThrowOverflowOrFormatException(overflow: true, nameof(SR.Overflow_Single)); - } - return castSingle; + return result; } - internal static bool TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) + internal static unsafe bool TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result, out bool failureIsOverflow) { - NumberBuffer number = default; + char* pDigits = stackalloc char[DecimalNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength); + result = 0; + failureIsOverflow = false; - if (!TryStringToNumber(value, styles, ref number, info, true)) + if (!TryStringToNumber(value, styles, ref number, info)) { return false; } - if (!NumberBufferToDecimal(ref number, ref result)) + if (!TryNumberToDecimal(ref number, ref result)) { + failureIsOverflow = true; return false; } + return true; } - internal static bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result) + internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result, out bool failureIsOverflow) { - NumberBuffer number = default; + char* pDigits = stackalloc char[DoubleNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, DoubleNumberBufferLength); + result = 0; + failureIsOverflow = false; - if (!TryStringToNumber(value, styles, ref number, info, false)) + if (!TryStringToNumber(value, styles, ref number, info)) { - return false; + ReadOnlySpan valueTrim = value.Trim(); + + if (valueTrim.EqualsOrdinal(info.PositiveInfinitySymbol)) + { + result = double.PositiveInfinity; + } + else if (valueTrim.EqualsOrdinal(info.NegativeInfinitySymbol)) + { + result = double.NegativeInfinity; + } + else if (valueTrim.EqualsOrdinal(info.NaNSymbol)) + { + result = double.NaN; + } + else + { + return false; // We really failed + } + + return true; } - if (!NumberBufferToDouble(ref number, ref result)) + + if (!TryNumberToDouble(ref number, ref result)) { + failureIsOverflow = true; return false; } + return true; } - internal static bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result) + internal static bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result, out bool failureIsOverflow) { - NumberBuffer number = default; result = 0; - double d = 0; - if (!TryStringToNumber(value, styles, ref number, info, false)) - { - return false; - } - if (!NumberBufferToDouble(ref number, ref d)) + if (!TryParseDouble(value, styles, info, out double doubleResult, out failureIsOverflow)) { return false; } - float castSingle = (float)d; - if (float.IsInfinity(castSingle)) + + float singleResult = (float)(doubleResult); + + if (float.IsInfinity(singleResult) && double.IsFinite(doubleResult)) { + failureIsOverflow = true; return false; } - result = castSingle; + result = singleResult; return true; } - private static unsafe void StringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal) + private static unsafe void StringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) { - Debug.Assert(info != null); - fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) + if (!TryStringToNumber(value, styles, ref number, info)) { - char* p = stringPointer; - if (!ParseNumber(ref p, p + value.Length, styles, ref number, info, parseDecimal) - || (p - stringPointer < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) - { - ThrowOverflowOrFormatException(overflow: false, null); - } + ThrowOverflowOrFormatException(overflow: false, null); } } - internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, bool parseDecimal) + internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) { Debug.Assert(info != null); fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) { char* p = stringPointer; - if (!ParseNumber(ref p, p + value.Length, styles, ref number, info, parseDecimal) + if (!TryParseNumber(ref p, p + value.Length, styles, ref number, info) || (p - stringPointer < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) { return false; @@ -1760,7 +1777,8 @@ private static bool TrailingZeros(ReadOnlySpan value, int index) } p++; str++; - if (*str == '\0') return p; + if (*str == '\0') + return p; } } } @@ -1779,7 +1797,7 @@ private static void ThrowOverflowOrFormatException(bool overflow, string overflo (Exception)new FormatException(SR.Format_InvalidString); } - private static bool NumberBufferToDouble(ref NumberBuffer number, ref double value) + private static bool TryNumberToDouble(ref NumberBuffer number, ref double value) { double d = NumberToDouble(ref number); if (!double.IsFinite(d)) diff --git a/src/Common/src/CoreLib/System/SByte.cs b/src/Common/src/CoreLib/System/SByte.cs index e347e3b32301..d311484497e2 100644 --- a/src/Common/src/CoreLib/System/SByte.cs +++ b/src/Common/src/CoreLib/System/SByte.cs @@ -221,7 +221,7 @@ private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFor { result = 0; int i; - if (!Number.TryParseInt32(s, style, info, out i)) + if (!Number.TryParseInt32(s, style, info, out i, out _)) { return false; } diff --git a/src/Common/src/CoreLib/System/Single.cs b/src/Common/src/CoreLib/System/Single.cs index 8d1788f73197..dfe1af3f8c45 100644 --- a/src/Common/src/CoreLib/System/Single.cs +++ b/src/Common/src/CoreLib/System/Single.cs @@ -330,28 +330,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out float result) { - bool success = Number.TryParseSingle(s, style, info, out result); - 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.TryParseSingle(s, style, info, out result, out _); } // diff --git a/src/Common/src/CoreLib/System/UInt16.cs b/src/Common/src/CoreLib/System/UInt16.cs index f9ef1f6a624c..38c8b97b24c3 100644 --- a/src/Common/src/CoreLib/System/UInt16.cs +++ b/src/Common/src/CoreLib/System/UInt16.cs @@ -190,7 +190,7 @@ private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFor { result = 0; uint i; - if (!Number.TryParseUInt32(s, style, info, out i)) + if (!Number.TryParseUInt32(s, style, info, out i, out _)) { return false; } diff --git a/src/Common/src/CoreLib/System/UInt32.cs b/src/Common/src/CoreLib/System/UInt32.cs index 5ed193e95626..576b55f53569 100644 --- a/src/Common/src/CoreLib/System/UInt32.cs +++ b/src/Common/src/CoreLib/System/UInt32.cs @@ -148,13 +148,13 @@ public static bool TryParse(string s, out uint result) return false; } - return Number.TryParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); } [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan s, out uint result) { - return Number.TryParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); } [CLSCompliant(false)] @@ -168,14 +168,14 @@ public static bool TryParse(string s, NumberStyles style, IFormatProvider provid return false; } - return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out uint result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } // diff --git a/src/Common/src/CoreLib/System/UInt64.cs b/src/Common/src/CoreLib/System/UInt64.cs index 6abd76da21f2..0abe8d4f9a43 100644 --- a/src/Common/src/CoreLib/System/UInt64.cs +++ b/src/Common/src/CoreLib/System/UInt64.cs @@ -145,13 +145,13 @@ public static bool TryParse(string s, out ulong result) return false; } - return Number.TryParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); } [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan s, out ulong result) { - return Number.TryParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return Number.TryParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); } [CLSCompliant(false)] @@ -165,14 +165,14 @@ public static bool TryParse(string s, NumberStyles style, IFormatProvider provid return false; } - return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out ulong result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } //