From de6a1b99ac4da30f74f981b2109b134c7beb2804 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 12 Nov 2018 15:46:55 -0800 Subject: [PATCH] Moving the Utf8Formatter and Utf8Parser into S.P.Corelib (#20934) * Moving the Utf8Formatter and Utf8Parser into S.P.Corelib * Doing some minimal cleanup to lineup types and get the Utf8Parser/Utf8Formatter building * Updating the Utf8 Float Parser to have different buffers for Single vs Double * Fixing the Utf8Parser to track trailing zero digits and to properly mark the end of the buffer * Fixing a couple of issues in Utf8Parser.Number Signed-off-by: dotnet-bot --- .../System.Private.CoreLib.Shared.projitems | 50 ++++++++++++++ .../Text/Utf8Formatter/FormattingHelpers.cs | 10 +++ .../Utf8Formatter/Utf8Formatter.Boolean.cs | 2 +- .../Text/Utf8Formatter/Utf8Formatter.Date.cs | 4 +- .../Utf8Formatter/Utf8Formatter.Decimal.E.cs | 4 +- .../Utf8Formatter/Utf8Formatter.Decimal.F.cs | 2 +- .../Utf8Formatter/Utf8Formatter.Decimal.G.cs | 4 +- .../Utf8Formatter/Utf8Formatter.Decimal.cs | 26 ++++--- .../Text/Utf8Formatter/Utf8Formatter.Float.cs | 2 +- .../Text/Utf8Formatter/Utf8Formatter.Guid.cs | 2 +- .../Utf8Formatter.Integer.Signed.cs | 2 +- .../Utf8Formatter.Integer.Unsigned.cs | 2 +- .../Utf8Formatter/Utf8Formatter.TimeSpan.cs | 2 +- .../Buffers/Text/Utf8Parser/ParserHelpers.cs | 19 ++++++ .../Text/Utf8Parser/Utf8Parser.Boolean.cs | 2 +- .../Text/Utf8Parser/Utf8Parser.Date.cs | 4 +- .../Text/Utf8Parser/Utf8Parser.Decimal.cs | 10 +-- .../Text/Utf8Parser/Utf8Parser.Float.cs | 32 ++++----- .../Text/Utf8Parser/Utf8Parser.Guid.cs | 2 +- .../Utf8Parser/Utf8Parser.Integer.Signed.cs | 8 +-- .../Utf8Parser/Utf8Parser.Integer.Unsigned.cs | 8 +-- .../Text/Utf8Parser/Utf8Parser.Number.cs | 68 ++++++++++++++++--- .../Text/Utf8Parser/Utf8Parser.TimeSpan.cs | 2 +- .../src/CoreLib/System/Number.Formatting.cs | 4 +- .../src/CoreLib/System/Number.NumberBuffer.cs | 14 ++-- .../src/CoreLib/System/Number.Parsing.cs | 6 +- 26 files changed, 216 insertions(+), 75 deletions(-) diff --git a/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems b/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems index c3067a513dd2..79956c4342b0 100644 --- a/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems +++ b/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems @@ -52,6 +52,7 @@ + @@ -60,6 +61,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs index d5aaf13ce256..1b30d5f01336 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs @@ -242,5 +242,15 @@ public static uint DivMod(uint numerator, uint denominator, out uint modulo) } #endregion Math Helper methods + + // + // Enable use of ThrowHelper from TryFormat() routines without introducing dozens of non-code-coveraged "bytesWritten = 0; return false" boilerplate. + // + public static bool TryFormatThrowFormatException(out int bytesWritten) + { + bytesWritten = 0; + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + return false; + } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs index b6452d24c6e8..31bb6cae5559 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs @@ -99,7 +99,7 @@ public static bool TryFormat(bool value, Span destination, out int bytesWr return false; BadFormat: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs index 5fbf3b736209..046885d16270 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs @@ -120,7 +120,7 @@ public static bool TryFormat(DateTimeOffset value, Span destination, out i return TryFormatDateTimeG(value.DateTime, offset, destination, out bytesWritten); default: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } } @@ -164,7 +164,7 @@ public static bool TryFormat(DateTime value, Span destination, out int byt return TryFormatDateTimeG(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten); default: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs index 6cde07ff7fa1..89b54f2fd59d 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs @@ -8,7 +8,7 @@ namespace System.Buffers.Text { public static partial class Utf8Formatter { - private static bool TryFormatDecimalE(ref NumberBuffer number, Span destination, out int bytesWritten, byte precision, byte exponentSymbol) + private static bool TryFormatDecimalE(ref Number.NumberBuffer number, Span destination, out int bytesWritten, byte precision, byte exponentSymbol) { const int NumExponentDigits = 3; @@ -89,7 +89,7 @@ private static bool TryFormatDecimalE(ref NumberBuffer number, Span destin exponent = -exponent; } - Debug.Assert(exponent < Number.DECIMAL_PRECISION, "If you're trying to reuse this routine for double/float, you'll need to review the code carefully for Decimal-specific assumptions."); + Debug.Assert(exponent < Number.DecimalPrecision, "If you're trying to reuse this routine for double/float, you'll need to review the code carefully for Decimal-specific assumptions."); // Emit exactly three digits for the exponent. destination[dstIndex++] = (byte)'0'; // The exponent for Decimal can never exceed 28 (let alone 99) diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs index e2409f909b7c..51bc20b8d2d1 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs @@ -8,7 +8,7 @@ namespace System.Buffers.Text { public static partial class Utf8Formatter { - private static bool TryFormatDecimalF(ref NumberBuffer number, Span destination, out int bytesWritten, byte precision) + private static bool TryFormatDecimalF(ref Number.NumberBuffer number, Span destination, out int bytesWritten, byte precision) { int scale = number.Scale; ReadOnlySpan digits = number.Digits; diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs index e9149ad8a917..b867eaec1a1d 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs @@ -8,11 +8,11 @@ namespace System.Buffers.Text { public static partial class Utf8Formatter { - private static bool TryFormatDecimalG(ref NumberBuffer number, Span destination, out int bytesWritten) + private static bool TryFormatDecimalG(ref Number.NumberBuffer number, Span destination, out int bytesWritten) { int scale = number.Scale; ReadOnlySpan digits = number.Digits; - int numDigits = number.NumDigits; + int numDigits = number.DigitsCount; bool isFraction = scale < numDigits; int numBytesNeeded; diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs index 872e2d4f355d..e44589ccb474 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs @@ -28,7 +28,7 @@ public static partial class Utf8Formatter /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryFormat(decimal value, Span destination, out int bytesWritten, StandardFormat format = default) + public static unsafe bool TryFormat(decimal value, Span destination, out int bytesWritten, StandardFormat format = default) { if (format.IsDefault) { @@ -42,8 +42,11 @@ public static bool TryFormat(decimal value, Span destination, out int byte { if (format.Precision != StandardFormat.NoPrecision) throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported); - NumberBuffer number = default; - Number.DecimalToNumber(value, ref number); + + byte* pDigits = stackalloc byte[Number.DecimalNumberBufferLength]; + Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, Number.DecimalNumberBufferLength); + + Number.DecimalToNumber(ref value, ref number); bool success = TryFormatDecimalG(ref number, destination, out bytesWritten); #if DEBUG // This DEBUG segment exists to close a code coverage hole inside TryFormatDecimalG(). Because we don't call RoundNumber() on this path, we have no way to feed @@ -52,7 +55,7 @@ public static bool TryFormat(decimal value, Span destination, out int byte if (success) { Span digits = number.Digits; - int numDigits = number.NumDigits; + int numDigits = number.DigitsCount; if (numDigits != 0 && number.Scale == numDigits && digits[numDigits - 1] == '0') { while (numDigits != 0 && digits[numDigits - 1] == '0') @@ -61,6 +64,7 @@ public static bool TryFormat(decimal value, Span destination, out int byte numDigits--; } + number.DigitsCount = numDigits; number.CheckConsistency(); byte[] buffer2 = new byte[destination.Length]; @@ -81,8 +85,10 @@ public static bool TryFormat(decimal value, Span destination, out int byte case 'f': case 'F': { - NumberBuffer number = default; - Number.DecimalToNumber(value, ref number); + byte* pDigits = stackalloc byte[Number.DecimalNumberBufferLength]; + Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, Number.DecimalNumberBufferLength); + + Number.DecimalToNumber(ref value, ref number); byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)2 : format.Precision; Number.RoundNumber(ref number, number.Scale + precision); return TryFormatDecimalF(ref number, destination, out bytesWritten, precision); @@ -91,15 +97,17 @@ public static bool TryFormat(decimal value, Span destination, out int byte case 'e': case 'E': { - NumberBuffer number = default; - Number.DecimalToNumber(value, ref number); + byte* pDigits = stackalloc byte[Number.DecimalNumberBufferLength]; + Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, Number.DecimalNumberBufferLength); + + Number.DecimalToNumber(ref value, ref number); byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)6 : format.Precision; Number.RoundNumber(ref number, precision + 1); return TryFormatDecimalE(ref number, destination, out bytesWritten, precision, exponentSymbol: (byte)format.Symbol); } default: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs index 94591134c622..a32dd9d66afd 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs @@ -87,7 +87,7 @@ private static bool TryFormatFloatingPoint(T value, Span destination, o break; default: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } string formatString = format.ToString(); diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs index a6311954b649..479f1dd1cb19 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs @@ -81,7 +81,7 @@ public static bool TryFormat(Guid value, Span destination, out int bytesWr break; default: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } // At this point, the low byte of flags contains the minimum required length diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs index 87966ca358c4..fcd20b312d79 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs @@ -45,7 +45,7 @@ private static bool TryFormatInt64(long value, ulong mask, Span destinatio return TryFormatUInt64X((ulong)value & mask, format.Precision, false, destination, out bytesWritten); default: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs index b143061a586d..0040c5075a65 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs @@ -45,7 +45,7 @@ private static bool TryFormatUInt64(ulong value, Span destination, out int return TryFormatUInt64X(value, format.Precision, false /* useLower */, destination, out bytesWritten); default: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.TimeSpan.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.TimeSpan.cs index 1e29383bf7b9..38bb35f7dfa6 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.TimeSpan.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Formatter/Utf8Formatter.TimeSpan.cs @@ -45,7 +45,7 @@ public static bool TryFormat(TimeSpan value, Span destination, out int byt break; default: - return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); + return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten); } // First, calculate how large an output buffer is needed to hold the entire output. diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/ParserHelpers.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/ParserHelpers.cs index b527433a7d2f..f440182bf9d5 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/ParserHelpers.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/ParserHelpers.cs @@ -51,5 +51,24 @@ public static bool IsDigit(int i) { return (uint)(i - '0') <= ('9' - '0'); } + + // + // Enable use of ThrowHelper from TryParse() routines without introducing dozens of non-code-coveraged "value= default; bytesConsumed = 0; return false" boilerplate. + // + public static bool TryParseThrowFormatException(out int bytesConsumed) + { + bytesConsumed = 0; + ThrowHelper.ThrowFormatException_BadFormatSpecifier(); + return false; + } + + // + // Enable use of ThrowHelper from TryParse() routines without introducing dozens of non-code-coveraged "value= default; bytesConsumed = 0; return false" boilerplate. + // + public static bool TryParseThrowFormatException(out T value, out int bytesConsumed) + { + value = default; + return TryParseThrowFormatException(out bytesConsumed); + } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs index 41c57143a8e8..3b039bae2509 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs @@ -30,7 +30,7 @@ public static partial class Utf8Parser public static bool TryParse(ReadOnlySpan source, out bool value, out int bytesConsumed, char standardFormat = default) { if (!(standardFormat == default(char) || standardFormat == 'G' || standardFormat == 'l')) - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); if (source.Length >= 4) { diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs index f1034924615f..35ad71670595 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs @@ -94,7 +94,7 @@ public static bool TryParse(ReadOnlySpan source, out DateTime value, out i return TryParseDateTimeG(source, out value, out _, out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } @@ -139,7 +139,7 @@ public static bool TryParse(ReadOnlySpan source, out DateTimeOffset value, return TryParseDateTimeG(source, out DateTime _, out value, out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs index c0f1e0c0406f..fa8bdc792bc1 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs @@ -26,7 +26,7 @@ public static partial class Utf8Parser /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryParse(ReadOnlySpan source, out decimal value, out int bytesConsumed, char standardFormat = default) + public static unsafe bool TryParse(ReadOnlySpan source, out decimal value, out int bytesConsumed, char standardFormat = default) { ParseNumberOptions options; switch (standardFormat) @@ -45,10 +45,12 @@ public static bool TryParse(ReadOnlySpan source, out decimal value, out in break; default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } - NumberBuffer number = default; + byte* pDigits = stackalloc byte[Number.DecimalNumberBufferLength]; + Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, Number.DecimalNumberBufferLength); + if (!TryParseNumber(source, ref number, out bytesConsumed, options, out bool textUsedExponentNotation)) { value = default; @@ -69,7 +71,7 @@ public static bool TryParse(ReadOnlySpan source, out decimal value, out in } value = default; - if (!Number.NumberBufferToDecimal(ref number, ref value)) + if (!Number.TryNumberToDecimal(ref number, ref value)) { value = default; bytesConsumed = 0; diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs index 1bdc59d237cb..e5e9e240bb2d 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs @@ -26,11 +26,14 @@ public static partial class Utf8Parser /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryParse(ReadOnlySpan source, out float value, out int bytesConsumed, char standardFormat = default) + public static unsafe bool TryParse(ReadOnlySpan source, out float value, out int bytesConsumed, char standardFormat = default) { - if (TryParseNormalAsFloatingPoint(source, out double d, out bytesConsumed, standardFormat)) + byte* pDigits = stackalloc byte[Number.SingleNumberBufferLength]; + Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.FloatingPoint, pDigits, Number.SingleNumberBufferLength); + + if (TryParseNormalAsFloatingPoint(source, ref number, out bytesConsumed, standardFormat)) { - value = (float)(d); + value = Number.NumberToSingle(ref number); return true; } @@ -57,10 +60,16 @@ public static bool TryParse(ReadOnlySpan source, out float value, out int /// /// System.FormatException if the format is not valid for this data type. /// - public static bool TryParse(ReadOnlySpan source, out double value, out int bytesConsumed, char standardFormat = default) + public static unsafe bool TryParse(ReadOnlySpan source, out double value, out int bytesConsumed, char standardFormat = default) { - if (TryParseNormalAsFloatingPoint(source, out value, out bytesConsumed, standardFormat)) + byte* pDigits = stackalloc byte[Number.DoubleNumberBufferLength]; + Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.FloatingPoint, pDigits, Number.DoubleNumberBufferLength); + + if (TryParseNormalAsFloatingPoint(source, ref number, out bytesConsumed, standardFormat)) + { + value = Number.NumberToDouble(ref number); return true; + } return TryParseAsSpecialFloatingPoint(source, double.PositiveInfinity, double.NegativeInfinity, double.NaN, out value, out bytesConsumed); } @@ -68,7 +77,7 @@ public static bool TryParse(ReadOnlySpan source, out double value, out int // // Attempt to parse the regular floating points (the ones without names like "Infinity" and "NaN") // - private static bool TryParseNormalAsFloatingPoint(ReadOnlySpan source, out double value, out int bytesConsumed, char standardFormat) + private static bool TryParseNormalAsFloatingPoint(ReadOnlySpan source, ref Number.NumberBuffer number, out int bytesConsumed, char standardFormat) { ParseNumberOptions options; switch (standardFormat) @@ -80,31 +89,22 @@ private static bool TryParseNormalAsFloatingPoint(ReadOnlySpan source, out case 'e': options = ParseNumberOptions.AllowExponent; break; - case 'F': case 'f': options = default; break; - default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out bytesConsumed); } - - NumberBuffer number = default; if (!TryParseNumber(source, ref number, out bytesConsumed, options, out bool textUsedExponentNotation)) { - value = default; return false; } - if ((!textUsedExponentNotation) && (standardFormat == 'E' || standardFormat == 'e')) { - value = default; bytesConsumed = 0; return false; } - - value = Number.NumberBufferToDouble(ref number); return true; } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs index 17dec828bc2c..f0a99dd522f6 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs @@ -41,7 +41,7 @@ public static bool TryParse(ReadOnlySpan source, out Guid value, out int b case 'N': return TryParseGuidN(source, out value, out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs index b30291c6f209..2e861b1cfdd9 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs @@ -54,7 +54,7 @@ public static bool TryParse(ReadOnlySpan source, out sbyte value, out int return TryParseByteX(source, out Unsafe.As(ref value), out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } @@ -100,7 +100,7 @@ public static bool TryParse(ReadOnlySpan source, out short value, out int return TryParseUInt16X(source, out Unsafe.As(ref value), out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } @@ -146,7 +146,7 @@ public static bool TryParse(ReadOnlySpan source, out int value, out int by return TryParseUInt32X(source, out Unsafe.As(ref value), out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } @@ -192,7 +192,7 @@ public static bool TryParse(ReadOnlySpan source, out long value, out int b return TryParseUInt64X(source, out Unsafe.As(ref value), out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs index ae23c29d040e..7c4e94e56f12 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs @@ -47,7 +47,7 @@ public static bool TryParse(ReadOnlySpan source, out byte value, out int b return TryParseByteX(source, out value, out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } @@ -93,7 +93,7 @@ public static bool TryParse(ReadOnlySpan source, out ushort value, out int return TryParseUInt16X(source, out value, out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } @@ -139,7 +139,7 @@ public static bool TryParse(ReadOnlySpan source, out uint value, out int b return TryParseUInt32X(source, out value, out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } @@ -185,7 +185,7 @@ public static bool TryParse(ReadOnlySpan source, out ulong value, out int return TryParseUInt64X(source, out value, out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs index 813a1f0a6e92..41fdc36cf562 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs @@ -14,10 +14,14 @@ private enum ParseNumberOptions AllowExponent = 0x00000001, } - private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer number, out int bytesConsumed, ParseNumberOptions options, out bool textUsedExponentNotation) + private static bool TryParseNumber(ReadOnlySpan source, ref Number.NumberBuffer number, out int bytesConsumed, ParseNumberOptions options, out bool textUsedExponentNotation) { - Debug.Assert(number.Digits[0] == 0 && number.Scale == 0 && !number.IsNegative, "Number not initialized to default(NumberBuffer)"); + Debug.Assert(number.DigitsCount == 0); + Debug.Assert(number.Scale == 0); + Debug.Assert(number.IsNegative == false); + Debug.Assert(number.HasNonZeroTail == false); + number.CheckConsistency(); textUsedExponentNotation = false; if (source.Length == 0) @@ -54,6 +58,8 @@ private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer n } int startIndexDigitsBeforeDecimal = srcIndex; + int digitCount = 0; + int maxDigitCount = digits.Length - 1; // Throw away any leading zeroes while (srcIndex != source.Length) @@ -66,8 +72,6 @@ private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer n if (srcIndex == source.Length) { - digits[0] = 0; - number.Scale = 0; number.IsNegative = false; bytesConsumed = srcIndex; number.CheckConsistency(); @@ -75,25 +79,47 @@ private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer n } int startIndexNonLeadingDigitsBeforeDecimal = srcIndex; + + int hasNonZeroTail = 0; while (srcIndex != source.Length) { c = source[srcIndex]; - if ((c - 48u) > 9) + + if ((c -= (byte)('0')) > 9) + { break; + } + srcIndex++; + digitCount++; + + if (digitCount >= maxDigitCount) + { + // For decimal and binary floating-point numbers, we only + // need to store digits up to maxDigCount. However, we still + // need to keep track of whether any additional digits past + // maxDigCount were non-zero, as that can impact rounding + // for an input that falls evenly between two representable + // results. + + hasNonZeroTail |= c; + } } + number.HasNonZeroTail = (hasNonZeroTail != 0); int numDigitsBeforeDecimal = srcIndex - startIndexDigitsBeforeDecimal; int numNonLeadingDigitsBeforeDecimal = srcIndex - startIndexNonLeadingDigitsBeforeDecimal; Debug.Assert(dstIndex == 0); - int numNonLeadingDigitsBeforeDecimalToCopy = Math.Min(numNonLeadingDigitsBeforeDecimal, NumberBuffer.BufferSize - 1); + int numNonLeadingDigitsBeforeDecimalToCopy = Math.Min(numNonLeadingDigitsBeforeDecimal, maxDigitCount); source.Slice(startIndexNonLeadingDigitsBeforeDecimal, numNonLeadingDigitsBeforeDecimalToCopy).CopyTo(digits); dstIndex = numNonLeadingDigitsBeforeDecimalToCopy; number.Scale = numNonLeadingDigitsBeforeDecimal; if (srcIndex == source.Length) { + digits[dstIndex] = 0; + number.DigitsCount = dstIndex; bytesConsumed = srcIndex; number.CheckConsistency(); return true; @@ -108,13 +134,33 @@ private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer n srcIndex++; int startIndexDigitsAfterDecimal = srcIndex; + while (srcIndex != source.Length) { c = source[srcIndex]; - if ((c - 48u) > 9) + + if ((c -= (byte)('0')) > 9) + { break; + } + srcIndex++; + digitCount++; + + if (digitCount >= maxDigitCount) + { + // For decimal and binary floating-point numbers, we only + // need to store digits up to maxDigCount. However, we still + // need to keep track of whether any additional digits past + // maxDigCount were non-zero, as that can impact rounding + // for an input that falls evenly between two representable + // results. + + hasNonZeroTail |= c; + } } + number.HasNonZeroTail = (hasNonZeroTail != 0); + numDigitsAfterDecimal = srcIndex - startIndexDigitsAfterDecimal; int startIndexOfDigitsAfterDecimalToCopy = startIndexDigitsAfterDecimal; @@ -128,7 +174,7 @@ private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer n } } - int numDigitsAfterDecimalToCopy = Math.Min(srcIndex - startIndexOfDigitsAfterDecimalToCopy, NumberBuffer.BufferSize - dstIndex - 1); + int numDigitsAfterDecimalToCopy = Math.Min(srcIndex - startIndexOfDigitsAfterDecimalToCopy, maxDigitCount - dstIndex); source.Slice(startIndexOfDigitsAfterDecimalToCopy, numDigitsAfterDecimalToCopy).CopyTo(digits.Slice(dstIndex)); dstIndex += numDigitsAfterDecimalToCopy; // We "should" really NUL terminate, but there are multiple places we'd have to do this and it is a precondition that the caller pass in a fully zero=initialized Number. @@ -142,6 +188,8 @@ private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer n return false; } + digits[dstIndex] = 0; + number.DigitsCount = dstIndex; bytesConsumed = srcIndex; number.CheckConsistency(); return true; @@ -161,6 +209,8 @@ private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer n number.IsNegative = false; } + digits[dstIndex] = 0; + number.DigitsCount = dstIndex; bytesConsumed = srcIndex; number.CheckConsistency(); return true; @@ -238,6 +288,8 @@ private static bool TryParseNumber(ReadOnlySpan source, ref NumberBuffer n number.Scale += (int)absoluteExponent; } + digits[dstIndex] = 0; + number.DigitsCount = dstIndex; bytesConsumed = srcIndex; number.CheckConsistency(); return true; diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs index 0ce810b392a4..b49cccb6a2f5 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs @@ -45,7 +45,7 @@ public static bool TryParse(ReadOnlySpan source, out TimeSpan value, out i return TryParseTimeSpanLittleG(source, out value, out bytesConsumed); default: - return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed); + return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed); } } diff --git a/src/Common/src/CoreLib/System/Number.Formatting.cs b/src/Common/src/CoreLib/System/Number.Formatting.cs index a8c2df982be6..7392928959d2 100644 --- a/src/Common/src/CoreLib/System/Number.Formatting.cs +++ b/src/Common/src/CoreLib/System/Number.Formatting.cs @@ -333,7 +333,7 @@ public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan for return sb.TryCopyTo(destination, out charsWritten); } - private static unsafe void DecimalToNumber(ref decimal d, ref NumberBuffer number) + internal static unsafe void DecimalToNumber(ref decimal d, ref NumberBuffer number) { byte* buffer = number.GetDigitsPointer(); number.DigitsCount = DecimalPrecision; @@ -2238,7 +2238,7 @@ private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer nu } } - private static unsafe void RoundNumber(ref NumberBuffer number, int pos) + internal static unsafe void RoundNumber(ref NumberBuffer number, int pos) { byte* dig = number.GetDigitsPointer(); diff --git a/src/Common/src/CoreLib/System/Number.NumberBuffer.cs b/src/Common/src/CoreLib/System/Number.NumberBuffer.cs index 9d053c6d7f21..268bdc78b6cf 100644 --- a/src/Common/src/CoreLib/System/Number.NumberBuffer.cs +++ b/src/Common/src/CoreLib/System/Number.NumberBuffer.cs @@ -11,13 +11,13 @@ namespace System internal static partial class Number { // We need 1 additional byte, per length, for the terminating null - private const int DecimalNumberBufferLength = 29 + 1 + 1; // 29 for the longest input + 1 for rounding - private const int DoubleNumberBufferLength = 767 + 1 + 1; // 767 for the longest input + 1 for rounding: 4.9406564584124654E-324 - private const int Int32NumberBufferLength = 10 + 1; // 10 for the longest input: 2,147,483,647 - private const int Int64NumberBufferLength = 19 + 1; // 19 for the longest input: 9,223,372,036,854,775,807 - private const int SingleNumberBufferLength = 112 + 1 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45 - private const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295 - private const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615 + internal const int DecimalNumberBufferLength = 29 + 1 + 1; // 29 for the longest input + 1 for rounding + internal const int DoubleNumberBufferLength = 767 + 1 + 1; // 767 for the longest input + 1 for rounding: 4.9406564584124654E-324 + internal const int Int32NumberBufferLength = 10 + 1; // 10 for the longest input: 2,147,483,647 + internal const int Int64NumberBufferLength = 19 + 1; // 19 for the longest input: 9,223,372,036,854,775,807 + internal const int SingleNumberBufferLength = 112 + 1 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45 + internal const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295 + internal const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615 internal unsafe ref struct NumberBuffer { diff --git a/src/Common/src/CoreLib/System/Number.Parsing.cs b/src/Common/src/CoreLib/System/Number.Parsing.cs index b814ba827dbd..d0c42ccaf4fd 100644 --- a/src/Common/src/CoreLib/System/Number.Parsing.cs +++ b/src/Common/src/CoreLib/System/Number.Parsing.cs @@ -1537,7 +1537,7 @@ internal static decimal ParseDecimal(ReadOnlySpan value, NumberStyles styl return result; } - private static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value) + internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value) { number.CheckConsistency(); @@ -1834,7 +1834,7 @@ private static void ThrowOverflowOrFormatException(bool overflow, string overflo (Exception)new FormatException(SR.Format_InvalidString); } - private static double NumberToDouble(ref NumberBuffer number) + internal static double NumberToDouble(ref NumberBuffer number) { number.CheckConsistency(); @@ -1843,7 +1843,7 @@ private static double NumberToDouble(ref NumberBuffer number) return number.IsNegative ? -result : result; } - private static float NumberToSingle(ref NumberBuffer number) + internal static float NumberToSingle(ref NumberBuffer number) { number.CheckConsistency();