Moving the Utf8Formatter and Utf8Parser into S.P.Corelib (dotnet/coreclr#20934)
authorTanner Gooding <tagoo@outlook.com>
Mon, 12 Nov 2018 23:46:55 +0000 (15:46 -0800)
committerGitHub <noreply@github.com>
Mon, 12 Nov 2018 23:46:55 +0000 (15:46 -0800)
* 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

Commit migrated from https://github.com/dotnet/coreclr/commit/80c0a0ea2446b665d13e1632422802f4bf208ae5

28 files changed:
src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx
src/coreclr/src/System.Private.CoreLib/src/System/ThrowHelper.cs
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Boolean.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.E.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.F.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.G.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Float.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Signed.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.TimeSpan.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs
src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs
src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs
src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs

index e439a77..42dcde3 100644 (file)
   <data name="Argument_MethodRedefined" xml:space="preserve">
     <value>Method has been already defined.</value>
   </data>
-</root>
+  <data name="Argument_CannotParsePrecision" xml:space="preserve">
+    <value>Characters following the format symbol must be a number of {0} or less.</value>
+  </data>
+  <data name="Argument_GWithPrecisionNotSupported" xml:space="preserve">
+    <value>The 'G' format combined with a precision is not supported.</value>
+  </data>
+  <data name="Argument_PrecisionTooLarge" xml:space="preserve">
+    <value>Precision cannot be larger than {0}.</value>
+  </data>
+</root>
\ No newline at end of file
index 6928f67..2a122ec 100644 (file)
@@ -35,6 +35,7 @@
 // multiple times for different instantiation.
 //
 
+using System.Buffers;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
@@ -295,6 +296,36 @@ namespace System
             throw GetArraySegmentCtorValidationFailedException(array, offset, count);
         }
 
+        internal static void ThrowFormatException_BadFormatSpecifier()
+        {
+            throw CreateFormatException_BadFormatSpecifier();
+        }
+
+        private static Exception CreateFormatException_BadFormatSpecifier()
+        {
+            return new FormatException(SR.Argument_BadFormatSpecifier);
+        }
+
+        internal static void ThrowArgumentOutOfRangeException_PrecisionTooLarge()
+        {
+            throw CreateArgumentOutOfRangeException_PrecisionTooLarge();
+        }
+
+        private static Exception CreateArgumentOutOfRangeException_PrecisionTooLarge()
+        {
+            return new ArgumentOutOfRangeException("precision", SR.Format(SR.Argument_PrecisionTooLarge, StandardFormat.MaxPrecision));
+        }
+
+        internal static void ThrowArgumentOutOfRangeException_SymbolDoesNotFit()
+        {
+            throw CreateArgumentOutOfRangeException_SymbolDoesNotFit();
+        }
+
+        private static Exception CreateArgumentOutOfRangeException_SymbolDoesNotFit()
+        {
+            return new ArgumentOutOfRangeException("symbol", SR.Argument_BadFormatSpecifier);
+        }
+
         private static Exception GetArraySegmentCtorValidationFailedException(Array array, int offset, int count)
         {
             if (array == null)
index c3067a5..79956c4 100644 (file)
@@ -52,6 +52,7 @@
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\IPinnable.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\MemoryHandle.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\MemoryManager.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\StandardFormat.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\TlsOverPerCoreLockedStacksArrayPool.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Utilities.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Binary\Reader.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Binary\WriterBigEndian.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Binary\WriterLittleEndian.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\FormattingHelpers.CountDigits.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Constants.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\FormattingHelpers.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Boolean.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.G.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.L.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.O.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.R.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.E.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.F.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.G.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Float.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Guid.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.D.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.Default.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Signed.N.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.D.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.Default.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.N.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Integer.Unsigned.X.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.TimeSpan.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\ParserHelpers.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Boolean.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Date.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Date.Default.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Date.G.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Date.Helpers.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Date.O.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Date.R.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Decimal.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Float.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Guid.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Integer.Signed.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Integer.Signed.D.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Integer.Signed.N.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Integer.Unsigned.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Integer.Unsigned.D.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Integer.Unsigned.N.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Integer.Unsigned.X.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.Number.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.TimeSpan.BigG.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.TimeSpan.C.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.TimeSpan.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.TimeSpan.LittleG.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Parser\Utf8Parser.TimeSpanSplitter.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Byte.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Char.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\CharEnumerator.cs" />
index d5aaf13..1b30d5f 100644 (file)
@@ -242,5 +242,15 @@ namespace System.Buffers.Text
         }
 
         #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;
+        }
     }
 }
index b6452d2..31bb6ca 100644 (file)
@@ -99,7 +99,7 @@ namespace System.Buffers.Text
             return false;
 
         BadFormat:
-            return ThrowHelper.TryFormatThrowFormatException(out bytesWritten);
+            return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten);
         }
     }
 }
index 5fbf3b7..046885d 100644 (file)
@@ -120,7 +120,7 @@ namespace System.Buffers.Text
                     return TryFormatDateTimeG(value.DateTime, offset, destination, out bytesWritten);
 
                 default:
-                    return ThrowHelper.TryFormatThrowFormatException(out bytesWritten);
+                    return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten);
             }
         }
 
@@ -164,7 +164,7 @@ namespace System.Buffers.Text
                     return TryFormatDateTimeG(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten);
 
                 default:
-                    return ThrowHelper.TryFormatThrowFormatException(out bytesWritten);
+                    return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten);
             }
         }
     }
index 6cde07f..89b54f2 100644 (file)
@@ -8,7 +8,7 @@ namespace System.Buffers.Text
 {
     public static partial class Utf8Formatter
     {
-        private static bool TryFormatDecimalE(ref NumberBuffer number, Span<byte> destination, out int bytesWritten, byte precision, byte exponentSymbol)
+        private static bool TryFormatDecimalE(ref Number.NumberBuffer number, Span<byte> destination, out int bytesWritten, byte precision, byte exponentSymbol)
         {
             const int NumExponentDigits = 3;
 
@@ -89,7 +89,7 @@ namespace System.Buffers.Text
                 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)
index e2409f9..51bc20b 100644 (file)
@@ -8,7 +8,7 @@ namespace System.Buffers.Text
 {
     public static partial class Utf8Formatter
     {
-        private static bool TryFormatDecimalF(ref NumberBuffer number, Span<byte> destination, out int bytesWritten, byte precision)
+        private static bool TryFormatDecimalF(ref Number.NumberBuffer number, Span<byte> destination, out int bytesWritten, byte precision)
         {
             int scale = number.Scale;
             ReadOnlySpan<byte> digits = number.Digits;
index e9149ad..b867eae 100644 (file)
@@ -8,11 +8,11 @@ namespace System.Buffers.Text
 {
     public static partial class Utf8Formatter
     {
-        private static bool TryFormatDecimalG(ref NumberBuffer number, Span<byte> destination, out int bytesWritten)
+        private static bool TryFormatDecimalG(ref Number.NumberBuffer number, Span<byte> destination, out int bytesWritten)
         {
             int scale = number.Scale;
             ReadOnlySpan<byte> digits = number.Digits;
-            int numDigits = number.NumDigits;
+            int numDigits = number.DigitsCount;
 
             bool isFraction = scale < numDigits;
             int numBytesNeeded;
index 872e2d4..e44589c 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Buffers.Text
         /// <exceptions>
         /// <cref>System.FormatException</cref> if the format is not valid for this data type.
         /// </exceptions>
-        public static bool TryFormat(decimal value, Span<byte> destination, out int bytesWritten, StandardFormat format = default)
+        public static unsafe bool TryFormat(decimal value, Span<byte> destination, out int bytesWritten, StandardFormat format = default)
         {
             if (format.IsDefault)
             {
@@ -42,8 +42,11 @@ namespace System.Buffers.Text
                     {
                         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 @@ namespace System.Buffers.Text
                         if (success)
                         {
                             Span<byte> 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 @@ namespace System.Buffers.Text
                                     numDigits--;
                                 }
 
+                                number.DigitsCount = numDigits;
                                 number.CheckConsistency();
 
                                 byte[] buffer2 = new byte[destination.Length];
@@ -81,8 +85,10 @@ namespace System.Buffers.Text
                 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 @@ namespace System.Buffers.Text
                 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);
             }
         }
     }
index 9459113..a32dd9d 100644 (file)
@@ -87,7 +87,7 @@ namespace System.Buffers.Text
                     break;
 
                 default:
-                    return ThrowHelper.TryFormatThrowFormatException(out bytesWritten);
+                    return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten);
             }
 
             string formatString = format.ToString();
index a631195..479f1dd 100644 (file)
@@ -81,7 +81,7 @@ namespace System.Buffers.Text
                     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
index 87966ca..fcd20b3 100644 (file)
@@ -45,7 +45,7 @@ namespace System.Buffers.Text
                     return TryFormatUInt64X((ulong)value & mask, format.Precision, false, destination, out bytesWritten);
 
                 default:
-                    return ThrowHelper.TryFormatThrowFormatException(out bytesWritten);
+                    return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten);
             }
         }
     }
index b143061..0040c50 100644 (file)
@@ -45,7 +45,7 @@ namespace System.Buffers.Text
                     return TryFormatUInt64X(value, format.Precision, false /* useLower */, destination, out bytesWritten);
 
                 default:
-                    return ThrowHelper.TryFormatThrowFormatException(out bytesWritten);
+                    return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten);
             }
         }
     }
index 1e29383..38bb35f 100644 (file)
@@ -45,7 +45,7 @@ namespace System.Buffers.Text
                     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.
index b527433..f440182 100644 (file)
@@ -51,5 +51,24 @@ namespace System.Buffers.Text
         {
             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<T>(out T value, out int bytesConsumed)
+        {
+            value = default;
+            return TryParseThrowFormatException(out bytesConsumed);
+        }
     }
 }
index 41c5714..3b039ba 100644 (file)
@@ -30,7 +30,7 @@ namespace System.Buffers.Text
         public static bool TryParse(ReadOnlySpan<byte> 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)
             {
index f103492..35ad716 100644 (file)
@@ -94,7 +94,7 @@ namespace System.Buffers.Text
                     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 @@ namespace System.Buffers.Text
                     return TryParseDateTimeG(source, out DateTime _, out value, out bytesConsumed);
 
                 default:
-                    return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+                    return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
             }
         }
 
index c0f1e0c..fa8bdc7 100644 (file)
@@ -26,7 +26,7 @@ namespace System.Buffers.Text
         /// <exceptions>
         /// <cref>System.FormatException</cref> if the format is not valid for this data type.
         /// </exceptions>
-        public static bool TryParse(ReadOnlySpan<byte> source, out decimal value, out int bytesConsumed, char standardFormat = default)
+        public static unsafe bool TryParse(ReadOnlySpan<byte> source, out decimal value, out int bytesConsumed, char standardFormat = default)
         {
             ParseNumberOptions options;
             switch (standardFormat)
@@ -45,10 +45,12 @@ namespace System.Buffers.Text
                     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 @@ namespace System.Buffers.Text
             }
 
             value = default;
-            if (!Number.NumberBufferToDecimal(ref number, ref value))
+            if (!Number.TryNumberToDecimal(ref number, ref value))
             {
                 value = default;
                 bytesConsumed = 0;
index 1bdc59d..e5e9e24 100644 (file)
@@ -26,11 +26,14 @@ namespace System.Buffers.Text
         /// <exceptions>
         /// <cref>System.FormatException</cref> if the format is not valid for this data type.
         /// </exceptions>
-        public static bool TryParse(ReadOnlySpan<byte> source, out float value, out int bytesConsumed, char standardFormat = default)
+        public static unsafe bool TryParse(ReadOnlySpan<byte> 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 @@ namespace System.Buffers.Text
         /// <exceptions>
         /// <cref>System.FormatException</cref> if the format is not valid for this data type.
         /// </exceptions>
-        public static bool TryParse(ReadOnlySpan<byte> source, out double value, out int bytesConsumed, char standardFormat = default)
+        public static unsafe bool TryParse(ReadOnlySpan<byte> 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 @@ namespace System.Buffers.Text
         //
         // Attempt to parse the regular floating points (the ones without names like "Infinity" and "NaN")
         //
-        private static bool TryParseNormalAsFloatingPoint(ReadOnlySpan<byte> source, out double value, out int bytesConsumed, char standardFormat)
+        private static bool TryParseNormalAsFloatingPoint(ReadOnlySpan<byte> source, ref Number.NumberBuffer number, out int bytesConsumed, char standardFormat)
         {
             ParseNumberOptions options;
             switch (standardFormat)
@@ -80,31 +89,22 @@ namespace System.Buffers.Text
                 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;
         }
 
index 17dec82..f0a99dd 100644 (file)
@@ -41,7 +41,7 @@ namespace System.Buffers.Text
                 case 'N':
                     return TryParseGuidN(source, out value, out bytesConsumed);
                 default:
-                    return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+                    return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
             }
         }
 
index b30291c..2e861b1 100644 (file)
@@ -54,7 +54,7 @@ namespace System.Buffers.Text
                     return TryParseByteX(source, out Unsafe.As<sbyte, byte>(ref value), out bytesConsumed);
 
                 default:
-                    return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+                    return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
             }
         }
 
@@ -100,7 +100,7 @@ namespace System.Buffers.Text
                     return TryParseUInt16X(source, out Unsafe.As<short, ushort>(ref value), out bytesConsumed);
 
                 default:
-                    return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+                    return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
             }
         }
 
@@ -146,7 +146,7 @@ namespace System.Buffers.Text
                     return TryParseUInt32X(source, out Unsafe.As<int, uint>(ref value), out bytesConsumed);
 
                 default:
-                    return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+                    return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
             }
         }
 
@@ -192,7 +192,7 @@ namespace System.Buffers.Text
                     return TryParseUInt64X(source, out Unsafe.As<long, ulong>(ref value), out bytesConsumed);
 
                 default:
-                    return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+                    return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
             }
         }
     }
index ae23c29..7c4e94e 100644 (file)
@@ -47,7 +47,7 @@ namespace System.Buffers.Text
                     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 @@ namespace System.Buffers.Text
                     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 @@ namespace System.Buffers.Text
                     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 @@ namespace System.Buffers.Text
                     return TryParseUInt64X(source, out value, out bytesConsumed);
 
                 default:
-                    return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+                    return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
             }
         }
     }
index 813a1f0..41fdc36 100644 (file)
@@ -14,10 +14,14 @@ namespace System.Buffers.Text
             AllowExponent = 0x00000001,
         }
 
-        private static bool TryParseNumber(ReadOnlySpan<byte> source, ref NumberBuffer number, out int bytesConsumed, ParseNumberOptions options, out bool textUsedExponentNotation)
+        private static bool TryParseNumber(ReadOnlySpan<byte> 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 @@ namespace System.Buffers.Text
             }
 
             int startIndexDigitsBeforeDecimal = srcIndex;
+            int digitCount = 0;
+            int maxDigitCount = digits.Length - 1;
 
             // Throw away any leading zeroes
             while (srcIndex != source.Length)
@@ -66,8 +72,6 @@ namespace System.Buffers.Text
 
             if (srcIndex == source.Length)
             {
-                digits[0] = 0;
-                number.Scale = 0;
                 number.IsNegative = false;
                 bytesConsumed = srcIndex;
                 number.CheckConsistency();
@@ -75,25 +79,47 @@ namespace System.Buffers.Text
             }
 
             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 @@ namespace System.Buffers.Text
 
                 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 @@ namespace System.Buffers.Text
                     }
                 }
 
-                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 @@ namespace System.Buffers.Text
                         return false;
                     }
 
+                    digits[dstIndex] = 0;
+                    number.DigitsCount = dstIndex;
                     bytesConsumed = srcIndex;
                     number.CheckConsistency();
                     return true;
@@ -161,6 +209,8 @@ namespace System.Buffers.Text
                     number.IsNegative = false;
                 }
 
+                digits[dstIndex] = 0;
+                number.DigitsCount = dstIndex;
                 bytesConsumed = srcIndex;
                 number.CheckConsistency();
                 return true;
@@ -238,6 +288,8 @@ namespace System.Buffers.Text
                 number.Scale += (int)absoluteExponent;
             }
 
+            digits[dstIndex] = 0;
+            number.DigitsCount = dstIndex;
             bytesConsumed = srcIndex;
             number.CheckConsistency();
             return true;
index 0ce810b..b49cccb 100644 (file)
@@ -45,7 +45,7 @@ namespace System.Buffers.Text
                     return TryParseTimeSpanLittleG(source, out value, out bytesConsumed);
 
                 default:
-                    return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+                    return ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed);
             }
         }
 
index a8c2df9..7392928 100644 (file)
@@ -333,7 +333,7 @@ namespace System
             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 @@ namespace System
             }
         }
 
-        private static unsafe void RoundNumber(ref NumberBuffer number, int pos)
+        internal static unsafe void RoundNumber(ref NumberBuffer number, int pos)
         {
             byte* dig = number.GetDigitsPointer();
 
index 9d053c6..268bdc7 100644 (file)
@@ -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
         {
index b814ba8..d0c42cc 100644 (file)
@@ -1537,7 +1537,7 @@ namespace System
             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 @@ namespace System
                (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 @@ namespace System
             return number.IsNegative ? -result : result;
         }
 
-        private static float NumberToSingle(ref NumberBuffer number)
+        internal static float NumberToSingle(ref NumberBuffer number)
         {
             number.CheckConsistency();