/
Utf8Formatter.Float.cs
121 lines (109 loc) · 5.29 KB
/
Utf8Formatter.Float.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using System.Text;
namespace System.Buffers.Text
{
public static partial class Utf8Formatter
{
/// <summary>
/// Formats a Double as a UTF8 string.
/// </summary>
/// <param name="value">Value to format</param>
/// <param name="destination">Buffer to write the UTF8-formatted value to</param>
/// <param name="bytesWritten">Receives the length of the formatted text in bytes</param>
/// <param name="format">The standard format to use</param>
/// <returns>
/// true for success. "bytesWritten" contains the length of the formatted text in bytes.
/// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds.
/// </returns>
/// <remarks>
/// Formats supported:
/// G/g (default)
/// F/f 12.45 Fixed point
/// E/e 1.245000e1 Exponential
/// </remarks>
/// <exceptions>
/// <cref>System.FormatException</cref> if the format is not valid for this data type.
/// </exceptions>
public static bool TryFormat(double value, Span<byte> destination, out int bytesWritten, StandardFormat format = default)
{
return TryFormatFloatingPoint<double>(value, destination, out bytesWritten, format);
}
/// <summary>
/// Formats a Single as a UTF8 string.
/// </summary>
/// <param name="value">Value to format</param>
/// <param name="destination">Buffer to write the UTF8-formatted value to</param>
/// <param name="bytesWritten">Receives the length of the formatted text in bytes</param>
/// <param name="format">The standard format to use</param>
/// <returns>
/// true for success. "bytesWritten" contains the length of the formatted text in bytes.
/// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds.
/// </returns>
/// <remarks>
/// Formats supported:
/// G/g (default)
/// F/f 12.45 Fixed point
/// E/e 1.245000e1 Exponential
/// </remarks>
/// <exceptions>
/// <cref>System.FormatException</cref> if the format is not valid for this data type.
/// </exceptions>
public static bool TryFormat(float value, Span<byte> destination, out int bytesWritten, StandardFormat format = default)
{
return TryFormatFloatingPoint<float>(value, destination, out bytesWritten, format);
}
private static bool TryFormatFloatingPoint<T>(T value, Span<byte> destination, out int bytesWritten, StandardFormat format) where T : ISpanFormattable
{
scoped Span<char> formatText = default;
if (!format.IsDefault)
{
formatText = stackalloc char[StandardFormat.FormatStringLength];
int formatTextLength = format.Format(formatText);
formatText = formatText.Slice(0, formatTextLength);
}
// We first try to format into a stack-allocated buffer, and if it succeeds, we can avoid
// all allocation. If that fails, we fall back to allocating strings. If it proves impactful,
// that allocation (as well as roundtripping from byte to char and back to byte) could be avoided by
// calling into a refactored Number.FormatSingle/Double directly.
const int StackBufferLength = 128; // large enough to handle the majority cases
Span<char> stackBuffer = stackalloc char[StackBufferLength];
scoped ReadOnlySpan<char> utf16Text;
// Try to format into the stack buffer. If we're successful, we can avoid all allocations.
if (value.TryFormat(stackBuffer, out int formattedLength, formatText, CultureInfo.InvariantCulture))
{
utf16Text = stackBuffer.Slice(0, formattedLength);
}
else
{
// The stack buffer wasn't large enough. If the destination buffer isn't at least as
// big as the stack buffer, we know the whole operation will eventually fail, so we
// can just fail now.
if (destination.Length <= StackBufferLength)
{
bytesWritten = 0;
return false;
}
// Fall back to using a string format and allocating a string for the resulting formatted value.
utf16Text = value.ToString(new string(formatText), CultureInfo.InvariantCulture);
}
// Copy the value to the destination, if it's large enough.
if (utf16Text.Length > destination.Length)
{
bytesWritten = 0;
return false;
}
try
{
bytesWritten = Encoding.UTF8.GetBytes(utf16Text, destination);
return true;
}
catch
{
bytesWritten = 0;
return false;
}
}
}
}