Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 4c218db

Browse files
committed
Add double/float.TryFormat
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.
1 parent a8c8978 commit 4c218db

File tree

4 files changed

+95
-21
lines changed

4 files changed

+95
-21
lines changed

src/mscorlib/shared/System/Double.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace System
2222
[Serializable]
2323
[StructLayout(LayoutKind.Sequential)]
2424
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
25-
public struct Double : IComparable, IConvertible, IFormattable, IComparable<Double>, IEquatable<Double>
25+
public struct Double : IComparable, IConvertible, IFormattable, IComparable<Double>, IEquatable<Double>, ISpanFormattable
2626
{
2727
private double m_value; // Do not rename (binary serialization)
2828

@@ -253,6 +253,11 @@ public String ToString(String format, IFormatProvider provider)
253253
return Number.FormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider));
254254
}
255255

256+
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
257+
{
258+
return Number.TryFormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten);
259+
}
260+
256261
public static double Parse(String s)
257262
{
258263
if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);

src/mscorlib/shared/System/Number.Formatting.cs

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -366,13 +366,28 @@ private static unsafe void DecimalToNumber(decimal value, ref NumberBuffer numbe
366366

367367
public static string FormatDouble(double value, string format, NumberFormatInfo info)
368368
{
369-
ValueStringBuilder sb;
370-
unsafe
371-
{
372-
char* stackPtr = stackalloc char[CharStackBufferSize];
373-
sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
374-
}
369+
Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
370+
var sb = new ValueStringBuilder(stackBuffer);
371+
return FormatDouble(ref sb, value, format, info) ?? sb.ToString();
372+
}
373+
374+
public static bool TryFormatDouble(double value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
375+
{
376+
Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
377+
var sb = new ValueStringBuilder(stackBuffer);
378+
string s = FormatDouble(ref sb, value, format, info);
379+
return s != null ?
380+
TryCopyTo(s, destination, out charsWritten) :
381+
sb.TryCopyTo(destination, out charsWritten);
382+
}
375383

384+
/// <summary>Formats the specified value according to the specified format and info.</summary>
385+
/// <returns>
386+
/// Non-null if an existing string can be returned, in which case the builder will be unmodified.
387+
/// Null if no existing string was returned, in which case the formatted output is in the builder.
388+
/// </returns>
389+
private static string FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan<char> format, NumberFormatInfo info)
390+
{
376391
char fmt = ParseFormatSpecifier(format, out int digits);
377392
int precision = DoublePrecision;
378393
NumberBuffer number = default;
@@ -405,7 +420,7 @@ public static string FormatDouble(double value, string format, NumberFormatInfo
405420
NumberToString(ref sb, ref number, 'G', 17, info, isDecimal: false);
406421
}
407422

408-
return sb.ToString();
423+
return null;
409424
}
410425

411426
case 'E':
@@ -446,18 +461,33 @@ public static string FormatDouble(double value, string format, NumberFormatInfo
446461
NumberToStringFormat(ref sb, ref number, format, info);
447462
}
448463

449-
return sb.ToString();
464+
return null;
450465
}
451466

452467
public static string FormatSingle(float value, string format, NumberFormatInfo info)
453468
{
454-
ValueStringBuilder sb;
455-
unsafe
456-
{
457-
char* stackPtr = stackalloc char[CharStackBufferSize];
458-
sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
459-
}
469+
Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
470+
var sb = new ValueStringBuilder(stackBuffer);
471+
return FormatSingle(ref sb, value, format, info) ?? sb.ToString();
472+
}
473+
474+
public static bool TryFormatSingle(float value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
475+
{
476+
Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
477+
var sb = new ValueStringBuilder(stackBuffer);
478+
string s = FormatSingle(ref sb, value, format, info);
479+
return s != null ?
480+
TryCopyTo(s, destination, out charsWritten) :
481+
sb.TryCopyTo(destination, out charsWritten);
482+
}
460483

484+
/// <summary>Formats the specified value according to the specified format and info.</summary>
485+
/// <returns>
486+
/// Non-null if an existing string can be returned, in which case the builder will be unmodified.
487+
/// Null if no existing string was returned, in which case the formatted output is in the builder.
488+
/// </returns>
489+
private static string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan<char> format, NumberFormatInfo info)
490+
{
461491
char fmt = ParseFormatSpecifier(format, out int digits);
462492
int precision = FloatPrecision;
463493
NumberBuffer number = default;
@@ -489,8 +519,7 @@ public static string FormatSingle(float value, string format, NumberFormatInfo i
489519
DoubleToNumber(value, 9, ref number);
490520
NumberToString(ref sb, ref number, 'G', 9, info, isDecimal: false);
491521
}
492-
493-
return sb.ToString();
522+
return null;
494523
}
495524

496525
case 'E':
@@ -530,8 +559,21 @@ public static string FormatSingle(float value, string format, NumberFormatInfo i
530559
{
531560
NumberToStringFormat(ref sb, ref number, format, info);
532561
}
562+
return null;
563+
}
533564

534-
return sb.ToString();
565+
private static bool TryCopyTo(string source, Span<char> destination, out int charsWritten)
566+
{
567+
Debug.Assert(source != null);
568+
569+
if (source.AsReadOnlySpan().TryCopyTo(destination))
570+
{
571+
charsWritten = source.Length;
572+
return true;
573+
}
574+
575+
charsWritten = 0;
576+
return false;
535577
}
536578

537579
public static string FormatInt32(int value, ReadOnlySpan<char> format, NumberFormatInfo info)

src/mscorlib/shared/System/Single.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace System
2121
[Serializable]
2222
[StructLayout(LayoutKind.Sequential)]
2323
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
24-
public struct Single : IComparable, IConvertible, IFormattable, IComparable<Single>, IEquatable<Single>
24+
public struct Single : IComparable, IConvertible, IFormattable, IComparable<Single>, IEquatable<Single>, ISpanFormattable
2525
{
2626
private float m_value; // Do not rename (binary serialization)
2727

@@ -245,6 +245,11 @@ public String ToString(String format, IFormatProvider provider)
245245
return Number.FormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider));
246246
}
247247

248+
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null)
249+
{
250+
return Number.TryFormatSingle(m_value, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten);
251+
}
252+
248253
// Parses a float from a String in the given style. If
249254
// a NumberFormatInfo isn't specified, the current culture's
250255
// NumberFormatInfo is assumed.

src/mscorlib/shared/System/Text/StringBuilder.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,9 +1036,31 @@ public StringBuilder Append(long value)
10361036
}
10371037
}
10381038

1039-
public StringBuilder Append(float value) => Append(value.ToString());
1039+
public StringBuilder Append(float value)
1040+
{
1041+
if (value.TryFormat(RemainingCurrentChunk, out int charsWritten))
1042+
{
1043+
m_ChunkLength += charsWritten;
1044+
return this;
1045+
}
1046+
else
1047+
{
1048+
return Append(value.ToString());
1049+
}
1050+
}
10401051

1041-
public StringBuilder Append(double value) => Append(value.ToString());
1052+
public StringBuilder Append(double value)
1053+
{
1054+
if (value.TryFormat(RemainingCurrentChunk, out int charsWritten))
1055+
{
1056+
m_ChunkLength += charsWritten;
1057+
return this;
1058+
}
1059+
else
1060+
{
1061+
return Append(value.ToString());
1062+
}
1063+
}
10421064

10431065
public StringBuilder Append(decimal value)
10441066
{

0 commit comments

Comments
 (0)