Add double/float.TryFormat
authorStephen Toub <stoub@microsoft.com>
Tue, 28 Nov 2017 17:58:36 +0000 (12:58 -0500)
committerStephen Toub <stoub@microsoft.com>
Wed, 29 Nov 2017 02:30:05 +0000 (21:30 -0500)
Also implement ISpanFormattable so that string.Format and StringBuilder.AppendFormat take optimized paths with double and float, and update StringBuilder.Append(double/float) to use the new TryFormat methods.

src/mscorlib/shared/System/Double.cs
src/mscorlib/shared/System/Number.Formatting.cs
src/mscorlib/shared/System/Single.cs
src/mscorlib/shared/System/Text/StringBuilder.cs

index 78c161c..3652963 100644 (file)
@@ -22,7 +22,7 @@ namespace System
     [Serializable]
     [StructLayout(LayoutKind.Sequential)]
     [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
-    public struct Double : IComparable, IConvertible, IFormattable, IComparable<Double>, IEquatable<Double>
+    public struct Double : IComparable, IConvertible, IFormattable, IComparable<Double>, IEquatable<Double>, ISpanFormattable
     {
         private double m_value; // Do not rename (binary serialization)
 
@@ -253,6 +253,11 @@ namespace System
             return Number.FormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider));
         }
 
+        public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+        {
+            return Number.TryFormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten);
+        }
+
         public static double Parse(String s)
         {
             if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
index 4958bd5..3050893 100644 (file)
@@ -366,13 +366,28 @@ namespace System
 
         public static string FormatDouble(double value, string format, NumberFormatInfo info)
         {
-            ValueStringBuilder sb;
-            unsafe
-            {
-                char* stackPtr = stackalloc char[CharStackBufferSize];
-                sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
-            }
+            Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
+            var sb = new ValueStringBuilder(stackBuffer);
+            return FormatDouble(ref sb, value, format, info) ?? sb.ToString();
+        }
+
+        public static bool TryFormatDouble(double value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
+        {
+            Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
+            var sb = new ValueStringBuilder(stackBuffer);
+            string s = FormatDouble(ref sb, value, format, info);
+            return s != null ?
+                TryCopyTo(s, destination, out charsWritten) :
+                sb.TryCopyTo(destination, out charsWritten);
+        }
 
+        /// <summary>Formats the specified value according to the specified format and info.</summary>
+        /// <returns>
+        /// 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.
+        /// </returns>
+        private static string FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan<char> format, NumberFormatInfo info)
+        {
             char fmt = ParseFormatSpecifier(format, out int digits);
             int precision = DoublePrecision;
             NumberBuffer number = default;
@@ -405,7 +420,7 @@ namespace System
                             NumberToString(ref sb, ref number, 'G', 17, info, isDecimal: false);
                         }
 
-                        return sb.ToString();
+                        return null;
                     }
 
                 case 'E':
@@ -446,18 +461,33 @@ namespace System
                 NumberToStringFormat(ref sb, ref number, format, info);
             }
 
-            return sb.ToString();
+            return null;
         }
 
         public static string FormatSingle(float value, string format, NumberFormatInfo info)
         {
-            ValueStringBuilder sb;
-            unsafe
-            {
-                char* stackPtr = stackalloc char[CharStackBufferSize];
-                sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
-            }
+            Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
+            var sb = new ValueStringBuilder(stackBuffer);
+            return FormatSingle(ref sb, value, format, info) ?? sb.ToString();
+        }
+
+        public static bool TryFormatSingle(float value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
+        {
+            Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
+            var sb = new ValueStringBuilder(stackBuffer);
+            string s = FormatSingle(ref sb, value, format, info);
+            return s != null ?
+                TryCopyTo(s, destination, out charsWritten) :
+                sb.TryCopyTo(destination, out charsWritten);
+        }
 
+        /// <summary>Formats the specified value according to the specified format and info.</summary>
+        /// <returns>
+        /// 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.
+        /// </returns>
+        private static string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan<char> format, NumberFormatInfo info)
+        {
             char fmt = ParseFormatSpecifier(format, out int digits);
             int precision = FloatPrecision;
             NumberBuffer number = default;
@@ -489,8 +519,7 @@ namespace System
                             DoubleToNumber(value, 9, ref number);
                             NumberToString(ref sb, ref number, 'G', 9, info, isDecimal: false);
                         }
-
-                        return sb.ToString();
+                        return null;
                     }
 
                 case 'E':
@@ -530,8 +559,21 @@ namespace System
             {
                 NumberToStringFormat(ref sb, ref number, format, info);
             }
+            return null;
+        }
 
-            return sb.ToString();
+        private static bool TryCopyTo(string source, Span<char> destination, out int charsWritten)
+        {
+            Debug.Assert(source != null);
+
+            if (source.AsReadOnlySpan().TryCopyTo(destination))
+            {
+                charsWritten = source.Length;
+                return true;
+            }
+
+            charsWritten = 0;
+            return false;
         }
 
         public static string FormatInt32(int value, ReadOnlySpan<char> format, NumberFormatInfo info)
index c357676..df97427 100644 (file)
@@ -21,7 +21,7 @@ namespace System
     [Serializable]
     [StructLayout(LayoutKind.Sequential)]
     [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
-    public struct Single : IComparable, IConvertible, IFormattable, IComparable<Single>, IEquatable<Single>
+    public struct Single : IComparable, IConvertible, IFormattable, IComparable<Single>, IEquatable<Single>, ISpanFormattable
     {
         private float m_value; // Do not rename (binary serialization)
 
@@ -245,6 +245,11 @@ namespace System
             return Number.FormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider));
         }
 
+        public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
+        {
+            return Number.TryFormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten);
+        }
+
         // Parses a float from a String in the given style.  If
         // a NumberFormatInfo isn't specified, the current culture's
         // NumberFormatInfo is assumed.
index ceeb911..85ebf41 100644 (file)
@@ -1036,9 +1036,31 @@ namespace System.Text
             }
         }
 
-        public StringBuilder Append(float value) => Append(value.ToString());
+        public StringBuilder Append(float value)
+        {
+            if (value.TryFormat(RemainingCurrentChunk, out int charsWritten))
+            {
+                m_ChunkLength += charsWritten;
+                return this;
+            }
+            else
+            {
+                return Append(value.ToString());
+            }
+        }
 
-        public StringBuilder Append(double value) => Append(value.ToString());
+        public StringBuilder Append(double value)
+        {
+            if (value.TryFormat(RemainingCurrentChunk, out int charsWritten))
+            {
+                m_ChunkLength += charsWritten;
+                return this;
+            }
+            else
+            {
+                return Append(value.ToString());
+            }
+        }
 
         public StringBuilder Append(decimal value)
         {