1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Diagnostics;
6 using System.Globalization;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
13 // The Format methods provided by the numeric classes convert
14 // the numeric value to a string using the format string given by the
15 // format parameter. If the format parameter is null or
16 // an empty string, the number is formatted as if the string "G" (general
17 // format) was specified. The info parameter specifies the
18 // NumberFormatInfo instance to use when formatting the number. If the
19 // info parameter is null or omitted, the numeric formatting information
20 // is obtained from the current culture. The NumberFormatInfo supplies
21 // such information as the characters to use for decimal and thousand
22 // separators, and the spelling and placement of currency symbols in monetary
25 // Format strings fall into two categories: Standard format strings and
26 // user-defined format strings. A format string consisting of a single
27 // alphabetic character (A-Z or a-z), optionally followed by a sequence of
28 // digits (0-9), is a standard format string. All other format strings are
29 // used-defined format strings.
31 // A standard format string takes the form Axx, where A is an
32 // alphabetic character called the format specifier and xx is a
33 // sequence of digits called the precision specifier. The format
34 // specifier controls the type of formatting applied to the number and the
35 // precision specifier controls the number of significant digits or decimal
36 // places of the formatting operation. The following table describes the
37 // supported standard formats.
39 // C c - Currency format. The number is
40 // converted to a string that represents a currency amount. The conversion is
41 // controlled by the currency format information of the NumberFormatInfo
42 // used to format the number. The precision specifier indicates the desired
43 // number of decimal places. If the precision specifier is omitted, the default
44 // currency precision given by the NumberFormatInfo is used.
46 // D d - Decimal format. This format is
47 // supported for integral types only. The number is converted to a string of
48 // decimal digits, prefixed by a minus sign if the number is negative. The
49 // precision specifier indicates the minimum number of digits desired in the
50 // resulting string. If required, the number will be left-padded with zeros to
51 // produce the number of digits given by the precision specifier.
53 // E e Engineering (scientific) format.
54 // The number is converted to a string of the form
55 // "-d.ddd...E+ddd" or "-d.ddd...e+ddd", where each
56 // 'd' indicates a digit (0-9). The string starts with a minus sign if the
57 // number is negative, and one digit always precedes the decimal point. The
58 // precision specifier indicates the desired number of digits after the decimal
59 // point. If the precision specifier is omitted, a default of 6 digits after
60 // the decimal point is used. The format specifier indicates whether to prefix
61 // the exponent with an 'E' or an 'e'. The exponent is always consists of a
62 // plus or minus sign and three digits.
64 // F f Fixed point format. The number is
65 // converted to a string of the form "-ddd.ddd....", where each
66 // 'd' indicates a digit (0-9). The string starts with a minus sign if the
67 // number is negative. The precision specifier indicates the desired number of
68 // decimal places. If the precision specifier is omitted, the default numeric
69 // precision given by the NumberFormatInfo is used.
71 // G g - General format. The number is
72 // converted to the shortest possible decimal representation using fixed point
73 // or scientific format. The precision specifier determines the number of
74 // significant digits in the resulting string. If the precision specifier is
75 // omitted, the number of significant digits is determined by the type of the
76 // number being converted (10 for int, 19 for long, 7 for
77 // float, 15 for double, 19 for Currency, and 29 for
78 // Decimal). Trailing zeros after the decimal point are removed, and the
79 // resulting string contains a decimal point only if required. The resulting
80 // string uses fixed point format if the exponent of the number is less than
81 // the number of significant digits and greater than or equal to -4. Otherwise,
82 // the resulting string uses scientific format, and the case of the format
83 // specifier controls whether the exponent is prefixed with an 'E' or an 'e'.
85 // N n Number format. The number is
86 // converted to a string of the form "-d,ddd,ddd.ddd....", where
87 // each 'd' indicates a digit (0-9). The string starts with a minus sign if the
88 // number is negative. Thousand separators are inserted between each group of
89 // three digits to the left of the decimal point. The precision specifier
90 // indicates the desired number of decimal places. If the precision specifier
91 // is omitted, the default numeric precision given by the
92 // NumberFormatInfo is used.
94 // X x - Hexadecimal format. This format is
95 // supported for integral types only. The number is converted to a string of
96 // hexadecimal digits. The format specifier indicates whether to use upper or
97 // lower case characters for the hexadecimal digits above 9 ('X' for 'ABCDEF',
98 // and 'x' for 'abcdef'). The precision specifier indicates the minimum number
99 // of digits desired in the resulting string. If required, the number will be
100 // left-padded with zeros to produce the number of digits given by the
101 // precision specifier.
103 // Some examples of standard format strings and their results are shown in the
104 // table below. (The examples all assume a default NumberFormatInfo.)
106 // Value Format Result
107 // 12345.6789 C $12,345.68
108 // -12345.6789 C ($12,345.68)
111 // 12345.6789 E 1.234568E+004
112 // 12345.6789 E10 1.2345678900E+004
113 // 12345.6789 e4 1.2346e+004
114 // 12345.6789 F 12345.68
115 // 12345.6789 F0 12346
116 // 12345.6789 F6 12345.678900
117 // 12345.6789 G 12345.6789
118 // 12345.6789 G7 12345.68
119 // 123456789 G7 1.234568E8
120 // 12345.6789 N 12,345.68
121 // 123456789 N4 123,456,789.0000
124 // 0x2c45e X8 0002C45E
126 // Format strings that do not start with an alphabetic character, or that start
127 // with an alphabetic character followed by a non-digit, are called
128 // user-defined format strings. The following table describes the formatting
129 // characters that are supported in user defined format strings.
132 // 0 - Digit placeholder. If the value being
133 // formatted has a digit in the position where the '0' appears in the format
134 // string, then that digit is copied to the output string. Otherwise, a '0' is
135 // stored in that position in the output string. The position of the leftmost
136 // '0' before the decimal point and the rightmost '0' after the decimal point
137 // determines the range of digits that are always present in the output
140 // # - Digit placeholder. If the value being
141 // formatted has a digit in the position where the '#' appears in the format
142 // string, then that digit is copied to the output string. Otherwise, nothing
143 // is stored in that position in the output string.
145 // . - Decimal point. The first '.' character
146 // in the format string determines the location of the decimal separator in the
147 // formatted value; any additional '.' characters are ignored. The actual
148 // character used as a the decimal separator in the output string is given by
149 // the NumberFormatInfo used to format the number.
151 // , - Thousand separator and number scaling.
152 // The ',' character serves two purposes. First, if the format string contains
153 // a ',' character between two digit placeholders (0 or #) and to the left of
154 // the decimal point if one is present, then the output will have thousand
155 // separators inserted between each group of three digits to the left of the
156 // decimal separator. The actual character used as a the decimal separator in
157 // the output string is given by the NumberFormatInfo used to format the
158 // number. Second, if the format string contains one or more ',' characters
159 // immediately to the left of the decimal point, or after the last digit
160 // placeholder if there is no decimal point, then the number will be divided by
161 // 1000 times the number of ',' characters before it is formatted. For example,
162 // the format string '0,,' will represent 100 million as just 100. Use of the
163 // ',' character to indicate scaling does not also cause the formatted number
164 // to have thousand separators. Thus, to scale a number by 1 million and insert
165 // thousand separators you would use the format string '#,##0,,'.
167 // % - Percentage placeholder. The presence of
168 // a '%' character in the format string causes the number to be multiplied by
169 // 100 before it is formatted. The '%' character itself is inserted in the
170 // output string where it appears in the format string.
172 // E+ E- e+ e- - Scientific notation.
173 // If any of the strings 'E+', 'E-', 'e+', or 'e-' are present in the format
174 // string and are immediately followed by at least one '0' character, then the
175 // number is formatted using scientific notation with an 'E' or 'e' inserted
176 // between the number and the exponent. The number of '0' characters following
177 // the scientific notation indicator determines the minimum number of digits to
178 // output for the exponent. The 'E+' and 'e+' formats indicate that a sign
179 // character (plus or minus) should always precede the exponent. The 'E-' and
180 // 'e-' formats indicate that a sign character should only precede negative
183 // \ - Literal character. A backslash character
184 // causes the next character in the format string to be copied to the output
185 // string as-is. The backslash itself isn't copied, so to place a backslash
186 // character in the output string, use two backslashes (\\) in the format
189 // 'ABC' "ABC" - Literal string. Characters
190 // enclosed in single or double quotation marks are copied to the output string
191 // as-is and do not affect formatting.
193 // ; - Section separator. The ';' character is
194 // used to separate sections for positive, negative, and zero numbers in the
197 // Other - All other characters are copied to
198 // the output string in the position they appear.
200 // For fixed point formats (formats not containing an 'E+', 'E-', 'e+', or
201 // 'e-'), the number is rounded to as many decimal places as there are digit
202 // placeholders to the right of the decimal point. If the format string does
203 // not contain a decimal point, the number is rounded to the nearest
204 // integer. If the number has more digits than there are digit placeholders to
205 // the left of the decimal point, the extra digits are copied to the output
206 // string immediately before the first digit placeholder.
208 // For scientific formats, the number is rounded to as many significant digits
209 // as there are digit placeholders in the format string.
211 // To allow for different formatting of positive, negative, and zero values, a
212 // user-defined format string may contain up to three sections separated by
213 // semicolons. The results of having one, two, or three sections in the format
214 // string are described in the table below.
218 // One - The format string applies to all values.
220 // Two - The first section applies to positive values
221 // and zeros, and the second section applies to negative values. If the number
222 // to be formatted is negative, but becomes zero after rounding according to
223 // the format in the second section, then the resulting zero is formatted
224 // according to the first section.
226 // Three - The first section applies to positive
227 // values, the second section applies to negative values, and the third section
228 // applies to zeros. The second section may be left empty (by having no
229 // characters between the semicolons), in which case the first section applies
230 // to all non-zero values. If the number to be formatted is non-zero, but
231 // becomes zero after rounding according to the format in the first or second
232 // section, then the resulting zero is formatted according to the third
235 // For both standard and user-defined formatting operations on values of type
236 // float and double, if the value being formatted is a NaN (Not
237 // a Number) or a positive or negative infinity, then regardless of the format
238 // string, the resulting string is given by the NaNSymbol,
239 // PositiveInfinitySymbol, or NegativeInfinitySymbol property of
240 // the NumberFormatInfo used to format the number.
242 internal static partial class Number
244 internal const int DecimalPrecision = 29; // Decimal.DecCalc also uses this value
245 private const int FloatPrecision = 7;
246 private const int DoublePrecision = 15;
247 private const int ScaleNAN = unchecked((int)0x80000000);
248 private const int ScaleINF = 0x7FFFFFFF;
249 private const int MaxUInt32HexDigits = 8;
250 private const int MaxUInt32DecDigits = 10;
251 private const int MaxUInt64DecDigits = 20;
252 private const int CharStackBufferSize = 32;
253 private const string PosNumberFormat = "#";
255 private static readonly string[] s_posCurrencyFormats =
257 "$#", "#$", "$ #", "# $"
260 private static readonly string[] s_negCurrencyFormats =
262 "($#)", "-$#", "$-#", "$#-",
263 "(#$)", "-#$", "#-$", "#$-",
264 "-# $", "-$ #", "# $-", "$ #-",
265 "$ -#", "#- $", "($ #)", "(# $)"
268 private static readonly string[] s_posPercentFormats =
270 "# %", "#%", "%#", "% #"
273 private static readonly string[] s_negPercentFormats =
275 "-# %", "-#%", "-%#",
278 "-% #", "# %-", "% #-",
282 private static readonly string[] s_negNumberFormats =
284 "(#)", "-#", "- #", "#-", "# -",
287 public static string FormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info)
289 char fmt = ParseFormatSpecifier(format, out int digits);
291 NumberBuffer number = default;
292 DecimalToNumber(value, ref number);
294 ValueStringBuilder sb;
297 char* stackPtr = stackalloc char[CharStackBufferSize];
298 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
303 NumberToString(ref sb, ref number, fmt, digits, info, isDecimal:true);
307 NumberToStringFormat(ref sb, ref number, format, info);
310 return sb.ToString();
313 public static bool TryFormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
315 char fmt = ParseFormatSpecifier(format, out int digits);
317 NumberBuffer number = default;
318 DecimalToNumber(value, ref number);
320 ValueStringBuilder sb;
323 char* stackPtr = stackalloc char[CharStackBufferSize];
324 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
329 NumberToString(ref sb, ref number, fmt, digits, info, isDecimal: true);
333 NumberToStringFormat(ref sb, ref number, format, info);
336 return sb.TryCopyTo(destination, out charsWritten);
339 private static unsafe void DecimalToNumber(decimal value, ref NumberBuffer number)
343 char* buffer = number.digits;
344 number.precision = DecimalPrecision;
345 number.sign = d.IsNegative;
347 char* p = buffer + DecimalPrecision;
348 while ((d.Mid | d.High) != 0)
350 p = UInt32ToDecChars(p, decimal.DecDivMod1E9(ref d), 9);
352 p = UInt32ToDecChars(p, d.Low, 0);
354 int i = (int)(buffer + DecimalPrecision - p);
355 number.scale = i - d.Scale;
357 char* dst = number.digits;
365 public static string FormatDouble(double value, string format, NumberFormatInfo info)
367 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
368 var sb = new ValueStringBuilder(stackBuffer);
369 return FormatDouble(ref sb, value, format, info) ?? sb.ToString();
372 public static bool TryFormatDouble(double value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
374 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
375 var sb = new ValueStringBuilder(stackBuffer);
376 string s = FormatDouble(ref sb, value, format, info);
378 TryCopyTo(s, destination, out charsWritten) :
379 sb.TryCopyTo(destination, out charsWritten);
382 /// <summary>Formats the specified value according to the specified format and info.</summary>
384 /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
385 /// Null if no existing string was returned, in which case the formatted output is in the builder.
387 private static string FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan<char> format, NumberFormatInfo info)
389 char fmt = ParseFormatSpecifier(format, out int digits);
390 int precision = DoublePrecision;
391 NumberBuffer number = default;
398 // In order to give numbers that are both friendly to display and round-trippable, we parse the
399 // number using 15 digits and then determine if it round trips to the same value. If it does, we
400 // convert that NUMBER to a string, otherwise we reparse using 17 digits and display that.
401 DoubleToNumber(value, DoublePrecision, ref number);
402 if (number.scale == ScaleNAN)
404 return info.NaNSymbol;
406 else if (number.scale == ScaleINF)
408 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
411 if (NumberToDouble(ref number) == value)
413 NumberToString(ref sb, ref number, 'G', DoublePrecision, info, isDecimal: false);
417 DoubleToNumber(value, 17, ref number);
418 NumberToString(ref sb, ref number, 'G', 17, info, isDecimal: false);
426 // Round values less than E14 to 15 digits
435 // Round values less than G15 to 15 digits. G16 and G17 will not be touched.
443 DoubleToNumber(value, precision, ref number);
444 if (number.scale == ScaleNAN)
446 return info.NaNSymbol;
448 else if (number.scale == ScaleINF)
450 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
455 NumberToString(ref sb, ref number, fmt, digits, info, isDecimal: false);
459 NumberToStringFormat(ref sb, ref number, format, info);
465 public static string FormatSingle(float value, string format, NumberFormatInfo info)
467 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
468 var sb = new ValueStringBuilder(stackBuffer);
469 return FormatSingle(ref sb, value, format, info) ?? sb.ToString();
472 public static bool TryFormatSingle(float value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
474 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
475 var sb = new ValueStringBuilder(stackBuffer);
476 string s = FormatSingle(ref sb, value, format, info);
478 TryCopyTo(s, destination, out charsWritten) :
479 sb.TryCopyTo(destination, out charsWritten);
482 /// <summary>Formats the specified value according to the specified format and info.</summary>
484 /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
485 /// Null if no existing string was returned, in which case the formatted output is in the builder.
487 private static string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan<char> format, NumberFormatInfo info)
489 char fmt = ParseFormatSpecifier(format, out int digits);
490 int precision = FloatPrecision;
491 NumberBuffer number = default;
498 // In order to give numbers that are both friendly to display and round-trippable, we parse the
499 // number using 7 digits and then determine if it round trips to the same value. If it does, we
500 // convert that NUMBER to a string, otherwise we reparse using 9 digits and display that.
501 DoubleToNumber(value, FloatPrecision, ref number);
502 if (number.scale == ScaleNAN)
504 return info.NaNSymbol;
506 else if (number.scale == ScaleINF)
508 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
511 if ((float)NumberToDouble(ref number) == value)
513 NumberToString(ref sb, ref number, 'G', FloatPrecision, info, isDecimal: false);
517 DoubleToNumber(value, 9, ref number);
518 NumberToString(ref sb, ref number, 'G', 9, info, isDecimal: false);
525 // Round values less than E14 to 15 digits.
534 // Round values less than G15 to 15 digits. G16 and G17 will not be touched.
542 DoubleToNumber(value, precision, ref number);
543 if (number.scale == ScaleNAN)
545 return info.NaNSymbol;
547 else if (number.scale == ScaleINF)
549 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
554 NumberToString(ref sb, ref number, fmt, digits, info, false);
558 NumberToStringFormat(ref sb, ref number, format, info);
563 private static bool TryCopyTo(string source, Span<char> destination, out int charsWritten)
565 Debug.Assert(source != null);
567 if (source.AsSpan().TryCopyTo(destination))
569 charsWritten = source.Length;
577 public static string FormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider)
579 // Fast path for default format with a non-negative value
580 if (value >= 0 && format.Length == 0)
582 return UInt32ToDecStr((uint)value, digits: -1);
585 char fmt = ParseFormatSpecifier(format, out int digits);
586 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
588 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
589 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
592 UInt32ToDecStr((uint)value, digits) :
593 NegativeInt32ToDecStr(value, digits, info.NegativeSign);
595 else if (fmtUpper == 'X')
597 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
598 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
599 return Int32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
603 NumberBuffer number = default;
604 Int32ToNumber(value, ref number);
605 ValueStringBuilder sb;
608 char* stackPtr = stackalloc char[CharStackBufferSize];
609 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
613 NumberToString(ref sb, ref number, fmt, digits, info, false);
617 NumberToStringFormat(ref sb, ref number, format, info);
619 return sb.ToString();
623 public static bool TryFormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
625 // Fast path for default format with a non-negative value
626 if (value >= 0 && format.Length == 0)
628 return TryUInt32ToDecStr((uint)value, digits: -1, destination, out charsWritten);
631 char fmt = ParseFormatSpecifier(format, out int digits);
632 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
634 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
635 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
638 TryUInt32ToDecStr((uint)value, digits, destination, out charsWritten) :
639 TryNegativeInt32ToDecStr(value, digits, info.NegativeSign, destination, out charsWritten);
641 else if (fmtUpper == 'X')
643 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
644 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
645 return TryInt32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
649 NumberBuffer number = default;
650 Int32ToNumber(value, ref number);
651 ValueStringBuilder sb;
654 char* stackPtr = stackalloc char[CharStackBufferSize];
655 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
659 NumberToString(ref sb, ref number, fmt, digits, info, false);
663 NumberToStringFormat(ref sb, ref number, format, info);
665 return sb.TryCopyTo(destination, out charsWritten);
669 public static string FormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider)
671 // Fast path for default format
672 if (format.Length == 0)
674 return UInt32ToDecStr(value, digits: -1);
677 char fmt = ParseFormatSpecifier(format, out int digits);
678 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
680 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
681 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
683 return UInt32ToDecStr(value, digits);
685 else if (fmtUpper == 'X')
687 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
688 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
689 return Int32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits);
693 NumberBuffer number = default;
694 UInt32ToNumber(value, ref number);
695 ValueStringBuilder sb;
698 char* stackPtr = stackalloc char[CharStackBufferSize];
699 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
703 NumberToString(ref sb, ref number, fmt, digits, info, false);
707 NumberToStringFormat(ref sb, ref number, format, info);
709 return sb.ToString();
713 public static bool TryFormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
715 // Fast path for default format
716 if (format.Length == 0)
718 return TryUInt32ToDecStr(value, digits: -1, destination, out charsWritten);
721 char fmt = ParseFormatSpecifier(format, out int digits);
722 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
724 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
725 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
727 return TryUInt32ToDecStr(value, digits, destination, out charsWritten);
729 else if (fmtUpper == 'X')
731 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
732 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
733 return TryInt32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
737 NumberBuffer number = default;
738 UInt32ToNumber(value, ref number);
739 ValueStringBuilder sb;
742 char* stackPtr = stackalloc char[CharStackBufferSize];
743 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
747 NumberToString(ref sb, ref number, fmt, digits, info, false);
751 NumberToStringFormat(ref sb, ref number, format, info);
753 return sb.TryCopyTo(destination, out charsWritten);
757 public static string FormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider)
759 // Fast path for default format with a non-negative value
760 if (value >= 0 && format.Length == 0)
762 return UInt64ToDecStr((ulong)value, digits: -1);
765 char fmt = ParseFormatSpecifier(format, out int digits);
766 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
768 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
769 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
772 UInt64ToDecStr((ulong)value, digits) :
773 NegativeInt64ToDecStr(value, digits, info.NegativeSign);
775 else if (fmtUpper == 'X')
777 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
778 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
779 // produces lowercase.
780 return Int64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
784 NumberBuffer number = default;
785 Int64ToNumber(value, ref number);
786 ValueStringBuilder sb;
789 char* stackPtr = stackalloc char[CharStackBufferSize];
790 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
794 NumberToString(ref sb, ref number, fmt, digits, info, false);
798 NumberToStringFormat(ref sb, ref number, format, info);
800 return sb.ToString();
804 public static bool TryFormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
806 // Fast path for default format with a non-negative value
807 if (value >= 0 && format.Length == 0)
809 return TryUInt64ToDecStr((ulong)value, digits: -1, destination, out charsWritten);
812 char fmt = ParseFormatSpecifier(format, out int digits);
813 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
815 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
816 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
819 TryUInt64ToDecStr((ulong)value, digits, destination, out charsWritten) :
820 TryNegativeInt64ToDecStr(value, digits, info.NegativeSign, destination, out charsWritten);
822 else if (fmtUpper == 'X')
824 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
825 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
826 // produces lowercase.
827 return TryInt64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
831 NumberBuffer number = default;
832 Int64ToNumber(value, ref number);
833 ValueStringBuilder sb;
836 char* stackPtr = stackalloc char[CharStackBufferSize];
837 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
841 NumberToString(ref sb, ref number, fmt, digits, info, false);
845 NumberToStringFormat(ref sb, ref number, format, info);
847 return sb.TryCopyTo(destination, out charsWritten);
851 public static string FormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider)
853 // Fast path for default format
854 if (format.Length == 0)
856 return UInt64ToDecStr(value, digits: -1);
859 char fmt = ParseFormatSpecifier(format, out int digits);
860 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
862 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
863 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
865 return UInt64ToDecStr(value, digits);
867 else if (fmtUpper == 'X')
869 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
870 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
871 // produces lowercase.
872 return Int64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits);
876 NumberBuffer number = default;
877 UInt64ToNumber(value, ref number);
878 ValueStringBuilder sb;
881 char* stackPtr = stackalloc char[CharStackBufferSize];
882 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
886 NumberToString(ref sb, ref number, fmt, digits, info, false);
890 NumberToStringFormat(ref sb, ref number, format, info);
892 return sb.ToString();
896 public static bool TryFormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
898 // Fast path for default format
899 if (format.Length == 0)
901 return TryUInt64ToDecStr(value, digits: -1, destination, out charsWritten);
904 char fmt = ParseFormatSpecifier(format, out int digits);
905 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
907 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
908 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
910 return TryUInt64ToDecStr(value, digits, destination, out charsWritten);
912 else if (fmtUpper == 'X')
914 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
915 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
916 // produces lowercase.
917 return TryInt64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
921 NumberBuffer number = default;
922 UInt64ToNumber(value, ref number);
923 ValueStringBuilder sb;
926 char* stackPtr = stackalloc char[CharStackBufferSize];
927 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
931 NumberToString(ref sb, ref number, fmt, digits, info, false);
935 NumberToStringFormat(ref sb, ref number, format, info);
937 return sb.TryCopyTo(destination, out charsWritten);
941 [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
942 private static unsafe void Int32ToNumber(int value, ref NumberBuffer number)
944 number.precision = Int32Precision;
956 char* buffer = number.digits;
957 char* p = UInt32ToDecChars(buffer + Int32Precision, (uint)value, 0);
958 int i = (int)(buffer + Int32Precision - p);
962 char* dst = number.digits;
968 private static unsafe string NegativeInt32ToDecStr(int value, int digits, string sNegative)
970 Debug.Assert(value < 0);
975 int bufferLength = Math.Max(digits, MaxUInt32DecDigits) + sNegative.Length;
976 int index = bufferLength;
978 char* buffer = stackalloc char[bufferLength];
979 char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
980 for (int i = sNegative.Length - 1; i >= 0; i--)
982 *(--p) = sNegative[i];
985 Debug.Assert(buffer + bufferLength - p >= 0 && buffer <= p);
986 return new string(p, 0, (int)(buffer + bufferLength - p));
989 private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, string sNegative, Span<char> destination, out int charsWritten)
991 Debug.Assert(value < 0);
996 int bufferLength = Math.Max(digits, MaxUInt32DecDigits) + sNegative.Length;
997 int index = bufferLength;
999 char* buffer = stackalloc char[bufferLength];
1000 char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
1001 for (int i = sNegative.Length - 1; i >= 0; i--)
1003 *(--p) = sNegative[i];
1006 Debug.Assert(buffer + bufferLength - p >= 0 && buffer <= p);
1007 return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten);
1010 private static unsafe string Int32ToHexStr(int value, char hexBase, int digits)
1015 int bufferLength = Math.Max(digits, MaxUInt32HexDigits);
1016 char* buffer = stackalloc char[bufferLength];
1018 char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
1019 return new string(p, 0, (int)(buffer + bufferLength - p));
1022 private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span<char> destination, out int charsWritten)
1027 int bufferLength = Math.Max(digits, MaxUInt32HexDigits);
1028 char* buffer = stackalloc char[bufferLength];
1030 char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
1031 return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten);
1034 private static unsafe char* Int32ToHexChars(char* buffer, uint value, int hexBase, int digits)
1036 while (--digits >= 0 || value != 0)
1038 byte digit = (byte)(value & 0xF);
1039 *(--buffer) = (char)(digit + (digit < 10 ? (byte)'0' : hexBase));
1045 [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
1046 private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number)
1048 number.precision = UInt32Precision;
1049 number.sign = false;
1051 char* buffer = number.digits;
1052 char* p = UInt32ToDecChars(buffer + UInt32Precision, value, 0);
1053 int i = (int)(buffer + UInt32Precision - p);
1056 char* dst = number.digits;
1062 internal static unsafe char* UInt32ToDecChars(char* bufferEnd, uint value, int digits)
1064 while (--digits >= 0 || value != 0)
1066 // TODO https://github.com/dotnet/coreclr/issues/3439
1067 uint newValue = value / 10;
1068 *(--bufferEnd) = (char)(value - (newValue * 10) + '0');
1074 private static unsafe string UInt32ToDecStr(uint value, int digits)
1078 char* buffer = stackalloc char[MaxUInt32DecDigits];
1080 char* start = buffer + MaxUInt32DecDigits;
1084 // TODO https://github.com/dotnet/coreclr/issues/3439
1085 uint div = value / 10;
1086 *(--p) = (char)('0' + value - (div * 10));
1091 return new string(p, 0, (int)(start - p));
1095 int bufferSize = Math.Max(digits, MaxUInt32DecDigits);
1096 char* buffer = stackalloc char[bufferSize];
1097 char* p = UInt32ToDecChars(buffer + bufferSize, value, digits);
1098 return new string(p, 0, (int)(buffer + bufferSize - p));
1102 private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span<char> destination, out int charsWritten)
1106 char* buffer = stackalloc char[MaxUInt32DecDigits];
1107 char* start = buffer + MaxUInt32DecDigits;
1111 // TODO https://github.com/dotnet/coreclr/issues/3439
1112 uint div = value / 10;
1113 *(--p) = (char)('0' + value - (div * 10));
1117 return TryCopyTo(p, (int)(start - p), destination, out charsWritten);
1121 int bufferSize = Math.Max(digits, MaxUInt32DecDigits);
1122 char* buffer = stackalloc char[bufferSize];
1123 char* p = UInt32ToDecChars(buffer + bufferSize, value, digits);
1124 return TryCopyTo(p, (int)(buffer + bufferSize - p), destination, out charsWritten);
1128 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1129 private static unsafe bool TryCopyTo(char* src, int length, Span<char> destination, out int charsWritten)
1131 if (new ReadOnlySpan<char>(src, length).TryCopyTo(destination))
1133 charsWritten = length;
1143 private static unsafe void Int64ToNumber(long input, ref NumberBuffer number)
1145 ulong value = (ulong)input;
1146 number.sign = input < 0;
1147 number.precision = Int64Precision;
1150 value = (ulong)(-input);
1153 char* buffer = number.digits;
1154 char* p = buffer + Int64Precision;
1155 while (High32(value) != 0)
1156 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1157 p = UInt32ToDecChars(p, Low32(value), 0);
1158 int i = (int)(buffer + Int64Precision - p);
1162 char* dst = number.digits;
1168 private static unsafe string NegativeInt64ToDecStr(long input, int digits, string sNegative)
1170 Debug.Assert(input < 0);
1177 ulong value = (ulong)(-input);
1179 int bufferLength = Math.Max(digits, MaxUInt64DecDigits) + sNegative.Length;
1180 int index = bufferLength;
1182 char* buffer = stackalloc char[bufferLength];
1183 char* p = buffer + bufferLength;
1184 while (High32(value) != 0)
1186 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1189 p = UInt32ToDecChars(p, Low32(value), digits);
1191 for (int i = sNegative.Length - 1; i >= 0; i--)
1193 *(--p) = sNegative[i];
1196 return new string(p, 0, (int)(buffer + bufferLength - p));
1199 private static unsafe bool TryNegativeInt64ToDecStr(long input, int digits, string sNegative, Span<char> destination, out int charsWritten)
1201 Debug.Assert(input < 0);
1208 ulong value = (ulong)(-input);
1210 int bufferLength = Math.Max(digits, MaxUInt64DecDigits) + sNegative.Length;
1211 int index = bufferLength;
1213 char* buffer = stackalloc char[bufferLength];
1214 char* p = buffer + bufferLength;
1215 while (High32(value) != 0)
1217 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1220 p = UInt32ToDecChars(p, Low32(value), digits);
1222 for (int i = sNegative.Length - 1; i >= 0; i--)
1224 *(--p) = sNegative[i];
1227 return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten);
1230 private static unsafe string Int64ToHexStr(long value, char hexBase, int digits)
1232 int bufferLength = Math.Max(digits, MaxUInt32HexDigits * 2);
1233 char* buffer = stackalloc char[bufferLength];
1234 int index = bufferLength;
1237 if (High32((ulong)value) != 0)
1239 p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, 8);
1240 p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
1244 p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, Math.Max(digits, 1));
1247 return new string(p, 0, (int)(buffer + bufferLength - p));
1250 private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span<char> destination, out int charsWritten)
1252 int bufferLength = Math.Max(digits, MaxUInt32HexDigits * 2);
1253 char* buffer = stackalloc char[bufferLength];
1254 int index = bufferLength;
1257 if (High32((ulong)value) != 0)
1259 p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, 8);
1260 p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
1264 p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, Math.Max(digits, 1));
1267 return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten);
1270 private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number)
1272 number.precision = UInt64Precision;
1273 number.sign = false;
1275 char* buffer = number.digits;
1276 char* p = buffer + UInt64Precision;
1278 while (High32(value) != 0)
1279 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1280 p = UInt32ToDecChars(p, Low32(value), 0);
1281 int i = (int)(buffer + UInt64Precision - p);
1285 char* dst = number.digits;
1291 private static unsafe string UInt64ToDecStr(ulong value, int digits)
1296 int bufferSize = Math.Max(digits, MaxUInt64DecDigits);
1297 char* buffer = stackalloc char[bufferSize];
1298 char* p = buffer + bufferSize;
1299 while (High32(value) != 0)
1301 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1304 p = UInt32ToDecChars(p, Low32(value), digits);
1306 return new string(p, 0, (int)(buffer + bufferSize - p));
1309 private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span<char> destination, out int charsWritten)
1314 int bufferSize = Math.Max(digits, MaxUInt64DecDigits);
1315 char* buffer = stackalloc char[bufferSize];
1316 char* p = buffer + bufferSize;
1317 while (High32(value) != 0)
1319 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1322 p = UInt32ToDecChars(p, Low32(value), digits);
1324 return TryCopyTo(p, (int)(buffer + bufferSize - p), destination, out charsWritten);
1327 internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits)
1330 if (format.Length > 0)
1332 // If the format begins with a symbol, see if it's a standard format
1333 // with or without a specified number of digits.
1335 if ((uint)(c - 'A') <= 'Z' - 'A' ||
1336 (uint)(c - 'a') <= 'z' - 'a')
1338 // Fast path for sole symbol, e.g. "D"
1339 if (format.Length == 1)
1345 if (format.Length == 2)
1347 // Fast path for symbol and single digit, e.g. "X4"
1348 int d = format[1] - '0';
1355 else if (format.Length == 3)
1357 // Fast path for symbol and double digit, e.g. "F12"
1358 int d1 = format[1] - '0', d2 = format[2] - '0';
1359 if ((uint)d1 < 10 && (uint)d2 < 10)
1361 digits = d1 * 10 + d2;
1366 // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
1367 // but it can begin with any number of 0s, and thus we may need to check more than two
1368 // digits. Further, for compat, we need to stop when we hit a null char.
1371 while (i < format.Length && (((uint)format[i] - '0') < 10) && n < 10)
1373 n = (n * 10) + format[i++] - '0';
1376 // If we're at the end of the digits rather than having stopped because we hit something
1377 // other than a digit or overflowed, return the standard format info.
1378 if (i == format.Length || format[i] == '\0')
1386 // Default empty format to be "G"; custom format is signified with '\0'.
1388 return format.Length == 0 || c == '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it.
1393 internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info, bool isDecimal)
1395 int nMinDigits = -1;
1402 nMinDigits = nMaxDigits >= 0 ? nMaxDigits : info.CurrencyDecimalDigits;
1404 nMaxDigits = info.CurrencyDecimalDigits;
1406 RoundNumber(ref number, number.scale + nMaxDigits); // Don't change this line to use digPos since digCount could have its sign changed.
1408 FormatCurrency(ref sb, ref number, nMinDigits, nMaxDigits, info);
1417 nMaxDigits = nMinDigits = info.NumberDecimalDigits;
1419 nMinDigits = nMaxDigits;
1421 RoundNumber(ref number, number.scale + nMaxDigits);
1424 sb.Append(info.NegativeSign);
1426 FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, null, info.NumberDecimalSeparator, null);
1435 nMaxDigits = nMinDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation
1437 nMinDigits = nMaxDigits;
1439 RoundNumber(ref number, number.scale + nMaxDigits);
1441 FormatNumber(ref sb, ref number, nMinDigits, nMaxDigits, info);
1450 nMaxDigits = nMinDigits = 6;
1452 nMinDigits = nMaxDigits;
1455 RoundNumber(ref number, nMaxDigits);
1458 sb.Append(info.NegativeSign);
1460 FormatScientific(ref sb, ref number, nMinDigits, nMaxDigits, info, format);
1468 bool enableRounding = true;
1471 if (isDecimal && (nMaxDigits == -1))
1473 // Default to 29 digits precision only for G formatting without a precision specifier
1474 // This ensures that the PAL code pads out to the correct place even when we use the default precision
1475 nMaxDigits = nMinDigits = DecimalPrecision;
1476 enableRounding = false; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant
1480 // This ensures that the PAL code pads out to the correct place even when we use the default precision
1481 nMaxDigits = nMinDigits = number.precision;
1485 nMinDigits = nMaxDigits;
1487 if (enableRounding) // Don't round for G formatting without precision
1488 RoundNumber(ref number, nMaxDigits); // This also fixes up the minus zero case
1491 if (isDecimal && (number.digits[0] == 0))
1493 // Minus zero should be formatted as 0
1494 number.sign = false;
1499 sb.Append(info.NegativeSign);
1501 FormatGeneral(ref sb, ref number, nMinDigits, nMaxDigits, info, (char)(format - ('G' - 'E')), !enableRounding);
1510 nMaxDigits = nMinDigits = info.PercentDecimalDigits;
1512 nMinDigits = nMaxDigits;
1515 RoundNumber(ref number, number.scale + nMaxDigits);
1517 FormatPercent(ref sb, ref number, nMinDigits, nMaxDigits, info);
1523 throw new FormatException(SR.Argument_BadFormatSpecifier);
1527 internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref NumberBuffer number, ReadOnlySpan<char> format, NumberFormatInfo info)
1536 int thousandCount = 0;
1543 char* dig = number.digits;
1546 section = FindSection(format, dig[0] == 0 ? 2 : number.sign ? 1 : 0);
1552 firstDigit = 0x7FFFFFFF;
1556 thousandSeps = false;
1560 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
1562 while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
1570 if (firstDigit == 0x7FFFFFFF)
1571 firstDigit = digitCount;
1573 lastDigit = digitCount;
1577 decimalPos = digitCount;
1580 if (digitCount > 0 && decimalPos < 0)
1582 if (thousandPos >= 0)
1584 if (thousandPos == digitCount)
1589 thousandSeps = true;
1591 thousandPos = digitCount;
1603 while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
1607 if (src < format.Length && pFormat[src] != 0)
1612 if ((src < format.Length && pFormat[src] == '0') ||
1613 (src + 1 < format.Length && (pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0'))
1615 while (++src < format.Length && pFormat[src] == '0');
1624 decimalPos = digitCount;
1626 if (thousandPos >= 0)
1628 if (thousandPos == decimalPos)
1629 scaleAdjust -= thousandCount * 3;
1631 thousandSeps = true;
1636 number.scale += scaleAdjust;
1637 int pos = scientific ? digitCount : number.scale + digitCount - decimalPos;
1638 RoundNumber(ref number, pos);
1641 src = FindSection(format, 2);
1651 number.sign = false; // We need to format -0 without the sign set.
1652 number.scale = 0; // Decimals with scale ('0.00') should be rounded.
1658 firstDigit = firstDigit < decimalPos ? decimalPos - firstDigit : 0;
1659 lastDigit = lastDigit > decimalPos ? decimalPos - lastDigit : 0;
1662 digPos = decimalPos;
1667 digPos = number.scale > decimalPos ? number.scale : decimalPos;
1668 adjust = number.scale - decimalPos;
1672 // Adjust can be negative, so we make this an int instead of an unsigned int.
1673 // Adjust represents the number of characters over the formatting e.g. format string is "0000" and you are trying to
1674 // format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
1675 // -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
1676 Span<int> thousandsSepPos = stackalloc int[4];
1677 int thousandsSepCtr = -1;
1681 // We need to precompute this outside the number formatting loop
1682 if (info.NumberGroupSeparator.Length > 0)
1684 // We need this array to figure out where to insert the thousands separator. We would have to traverse the string
1685 // backwards. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
1686 // the thousands separator so we can get away with traversing forwards. Note we only have to compute up to digPos.
1687 // The max is not bound since you can have formatting strings of the form "000,000..", and this
1688 // should handle that case too.
1690 int[] groupDigits = info.numberGroupSizes;
1692 int groupSizeIndex = 0; // Index into the groupDigits array.
1693 int groupTotalSizeCount = 0;
1694 int groupSizeLen = groupDigits.Length; // The length of groupDigits array.
1695 if (groupSizeLen != 0)
1696 groupTotalSizeCount = groupDigits[groupSizeIndex]; // The current running total of group size.
1697 int groupSize = groupTotalSizeCount;
1699 int totalDigits = digPos + ((adjust < 0) ? adjust : 0); // Actual number of digits in o/p
1700 int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits;
1701 while (numDigits > groupTotalSizeCount)
1706 if (thousandsSepCtr >= thousandsSepPos.Length)
1708 var newThousandsSepPos = new int[thousandsSepPos.Length * 2];
1709 thousandsSepPos.CopyTo(newThousandsSepPos);
1710 thousandsSepPos = newThousandsSepPos;
1713 thousandsSepPos[thousandsSepCtr] = groupTotalSizeCount;
1714 if (groupSizeIndex < groupSizeLen - 1)
1717 groupSize = groupDigits[groupSizeIndex];
1719 groupTotalSizeCount += groupSize;
1724 if (number.sign && section == 0)
1725 sb.Append(info.NegativeSign);
1727 bool decimalWritten = false;
1729 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
1733 while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
1744 // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
1745 // the character after which the groupSeparator needs to be appended.
1746 sb.Append(*cur != 0 ? *cur++ : '0');
1747 if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
1749 if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
1751 sb.Append(info.NumberGroupSeparator);
1770 ch = digPos <= firstDigit ? '0' : '\0';
1774 ch = *cur != 0 ? *cur++ : digPos > lastDigit ? '0' : '\0';
1779 if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
1781 if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
1783 sb.Append(info.NumberGroupSeparator);
1794 if (digPos != 0 || decimalWritten)
1796 // For compatibility, don't echo repeated decimals
1799 // If the format has trailing zeros or the format has a decimal and digits remain
1800 if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0))
1802 sb.Append(info.NumberDecimalSeparator);
1803 decimalWritten = true;
1808 sb.Append(info.PerMilleSymbol);
1811 sb.Append(info.PercentSymbol);
1817 while (src < format.Length && pFormat[src] != 0 && pFormat[src] != ch)
1818 sb.Append(pFormat[src++]);
1819 if (src < format.Length && pFormat[src] != 0)
1823 if (src < format.Length && pFormat[src] != 0)
1824 sb.Append(pFormat[src++]);
1829 bool positiveSign = false;
1833 if (src < format.Length && pFormat[src] == '0')
1835 // Handles E0, which should format the same as E-0
1838 else if (src+1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0')
1841 positiveSign = true;
1843 else if (src+1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0')
1846 // Do nothing, this is just a place holder s.t. we don't break out of the loop.
1854 while (++src < format.Length && pFormat[src] == '0')
1859 int exp = dig[0] == 0 ? 0 : number.scale - decimalPos;
1860 FormatExponent(ref sb, info, exp, ch, i, positiveSign);
1865 sb.Append(ch); // Copy E or e to output
1866 if (src < format.Length)
1868 if (pFormat[src] == '+' || pFormat[src] == '-')
1869 sb.Append(pFormat[src++]);
1870 while (src < format.Length && pFormat[src] == '0')
1871 sb.Append(pFormat[src++]);
1884 private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
1886 string fmt = number.sign ?
1887 s_negCurrencyFormats[info.CurrencyNegativePattern] :
1888 s_posCurrencyFormats[info.CurrencyPositivePattern];
1890 foreach (char ch in fmt)
1895 FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.currencyGroupSizes, info.CurrencyDecimalSeparator, info.CurrencyGroupSeparator);
1898 sb.Append(info.NegativeSign);
1901 sb.Append(info.CurrencySymbol);
1910 private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, int[] groupDigits, string sDecimal, string sGroup)
1912 int digPos = number.scale;
1913 char* dig = number.digits;
1917 if (groupDigits != null)
1919 int groupSizeIndex = 0; // Index into the groupDigits array.
1920 int groupSizeCount = groupDigits[groupSizeIndex]; // The current total of group size.
1921 int bufferSize = digPos; // The length of the result buffer string.
1922 int groupSize = 0; // The current group size.
1924 // Find out the size of the string buffer for the result.
1925 if (groupDigits.Length != 0) // You can pass in 0 length arrays
1927 while (digPos > groupSizeCount)
1929 groupSize = groupDigits[groupSizeIndex];
1933 bufferSize += sGroup.Length;
1934 if (groupSizeIndex < groupDigits.Length - 1)
1937 groupSizeCount += groupDigits[groupSizeIndex];
1938 if (groupSizeCount < 0 || bufferSize < 0)
1939 throw new ArgumentOutOfRangeException(); // If we overflow
1942 groupSize = groupSizeCount == 0 ? 0 : groupDigits[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0
1947 int digLength = string.wcslen(dig);
1948 int digStart = (digPos < digLength) ? digPos : digLength;
1949 fixed (char* spanPtr = &MemoryMarshal.GetReference(sb.AppendSpan(bufferSize)))
1951 char* p = spanPtr + bufferSize - 1;
1952 for (int i = digPos - 1; i >= 0; i--)
1954 *(p--) = (i < digStart) ? dig[i] : '0';
1959 if ((digitCount == groupSize) && (i != 0))
1961 for (int j = sGroup.Length - 1; j >= 0; j--)
1964 if (groupSizeIndex < groupDigits.Length - 1)
1967 groupSize = groupDigits[groupSizeIndex];
1974 Debug.Assert(p >= spanPtr - 1, "Underflow");
1982 sb.Append(*dig != 0 ? *dig++ : '0');
1984 while (--digPos > 0);
1994 sb.Append(sDecimal);
1995 if ((digPos < 0) && (nMaxDigits > 0))
1997 int zeroes = Math.Min(-digPos, nMaxDigits);
1998 sb.Append('0', zeroes);
2000 nMaxDigits -= zeroes;
2003 while (nMaxDigits > 0)
2005 sb.Append((*dig != 0) ? *dig++ : '0');
2011 private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
2013 string fmt = number.sign ?
2014 s_negNumberFormats[info.NumberNegativePattern] :
2017 foreach (char ch in fmt)
2022 FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.numberGroupSizes, info.NumberDecimalSeparator, info.NumberGroupSeparator);
2025 sb.Append(info.NegativeSign);
2034 private static unsafe void FormatScientific(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, char expChar)
2036 char* dig = number.digits;
2038 sb.Append((*dig != 0) ? *dig++ : '0');
2040 if (nMaxDigits != 1) // For E0 we would like to suppress the decimal point
2041 sb.Append(info.NumberDecimalSeparator);
2043 while (--nMaxDigits > 0)
2044 sb.Append((*dig != 0) ? *dig++ : '0');
2046 int e = number.digits[0] == 0 ? 0 : number.scale - 1;
2047 FormatExponent(ref sb, info, e, expChar, 3, true);
2050 private static unsafe void FormatExponent(ref ValueStringBuilder sb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign)
2056 sb.Append(info.NegativeSign);
2062 sb.Append(info.PositiveSign);
2065 char* digits = stackalloc char[MaxUInt32DecDigits];
2066 char* p = UInt32ToDecChars(digits + MaxUInt32DecDigits, (uint)value, minDigits);
2067 int i = (int)(digits + MaxUInt32DecDigits - p);
2068 sb.Append(p, (int)(digits + MaxUInt32DecDigits - p));
2071 private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, char expChar, bool bSuppressScientific)
2073 int digPos = number.scale;
2074 bool scientific = false;
2076 if (!bSuppressScientific)
2078 // Don't switch to scientific notation
2079 if (digPos > nMaxDigits || digPos < -3)
2086 char* dig = number.digits;
2092 sb.Append((*dig != 0) ? *dig++ : '0');
2093 } while (--digPos > 0);
2100 if (*dig != 0 || digPos < 0)
2102 sb.Append(info.NumberDecimalSeparator);
2115 FormatExponent(ref sb, info, number.scale - 1, expChar, 2, true);
2118 private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
2120 string fmt = number.sign ?
2121 s_negPercentFormats[info.PercentNegativePattern] :
2122 s_posPercentFormats[info.PercentPositivePattern];
2124 foreach (char ch in fmt)
2129 FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.percentGroupSizes, info.PercentDecimalSeparator, info.PercentGroupSeparator);
2132 sb.Append(info.NegativeSign);
2135 sb.Append(info.PercentSymbol);
2144 private static unsafe void RoundNumber(ref NumberBuffer number, int pos)
2146 char* dig = number.digits;
2149 while (i < pos && dig[i] != 0)
2152 if (i == pos && dig[i] >= '5')
2154 while (i > 0 && dig[i - 1] == '9')
2170 while (i > 0 && dig[i - 1] == '0')
2176 number.sign = false;
2181 private static unsafe int FindSection(ReadOnlySpan<char> format, int section)
2189 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
2194 if (src >= format.Length)
2199 switch (ch = pFormat[src++])
2203 while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
2207 if (src < format.Length && pFormat[src] != 0)
2213 if (src < format.Length && pFormat[src] != 0 && pFormat[src] != ';')
2223 private static uint Low32(ulong value) => (uint)value;
2225 private static uint High32(ulong value) => (uint)((value & 0xFFFFFFFF00000000) >> 32);
2227 private static uint Int64DivMod1E9(ref ulong value)
2229 uint rem = (uint)(value % 1000000000);
2230 value /= 1000000000;