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.Buffers.Text;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.InteropServices;
14 // The Format methods provided by the numeric classes convert
15 // the numeric value to a string using the format string given by the
16 // format parameter. If the format parameter is null or
17 // an empty string, the number is formatted as if the string "G" (general
18 // format) was specified. The info parameter specifies the
19 // NumberFormatInfo instance to use when formatting the number. If the
20 // info parameter is null or omitted, the numeric formatting information
21 // is obtained from the current culture. The NumberFormatInfo supplies
22 // such information as the characters to use for decimal and thousand
23 // separators, and the spelling and placement of currency symbols in monetary
26 // Format strings fall into two categories: Standard format strings and
27 // user-defined format strings. A format string consisting of a single
28 // alphabetic character (A-Z or a-z), optionally followed by a sequence of
29 // digits (0-9), is a standard format string. All other format strings are
30 // used-defined format strings.
32 // A standard format string takes the form Axx, where A is an
33 // alphabetic character called the format specifier and xx is a
34 // sequence of digits called the precision specifier. The format
35 // specifier controls the type of formatting applied to the number and the
36 // precision specifier controls the number of significant digits or decimal
37 // places of the formatting operation. The following table describes the
38 // supported standard formats.
40 // C c - Currency format. The number is
41 // converted to a string that represents a currency amount. The conversion is
42 // controlled by the currency format information of the NumberFormatInfo
43 // used to format the number. The precision specifier indicates the desired
44 // number of decimal places. If the precision specifier is omitted, the default
45 // currency precision given by the NumberFormatInfo is used.
47 // D d - Decimal format. This format is
48 // supported for integral types only. The number is converted to a string of
49 // decimal digits, prefixed by a minus sign if the number is negative. The
50 // precision specifier indicates the minimum number of digits desired in the
51 // resulting string. If required, the number will be left-padded with zeros to
52 // produce the number of digits given by the precision specifier.
54 // E e Engineering (scientific) format.
55 // The number is converted to a string of the form
56 // "-d.ddd...E+ddd" or "-d.ddd...e+ddd", where each
57 // 'd' indicates a digit (0-9). The string starts with a minus sign if the
58 // number is negative, and one digit always precedes the decimal point. The
59 // precision specifier indicates the desired number of digits after the decimal
60 // point. If the precision specifier is omitted, a default of 6 digits after
61 // the decimal point is used. The format specifier indicates whether to prefix
62 // the exponent with an 'E' or an 'e'. The exponent is always consists of a
63 // plus or minus sign and three digits.
65 // F f Fixed point format. The number is
66 // converted to a string of the form "-ddd.ddd....", where each
67 // 'd' indicates a digit (0-9). The string starts with a minus sign if the
68 // number is negative. The precision specifier indicates the desired number of
69 // decimal places. If the precision specifier is omitted, the default numeric
70 // precision given by the NumberFormatInfo is used.
72 // G g - General format. The number is
73 // converted to the shortest possible decimal representation using fixed point
74 // or scientific format. The precision specifier determines the number of
75 // significant digits in the resulting string. If the precision specifier is
76 // omitted, the number of significant digits is determined by the type of the
77 // number being converted (10 for int, 19 for long, 7 for
78 // float, 15 for double, 19 for Currency, and 29 for
79 // Decimal). Trailing zeros after the decimal point are removed, and the
80 // resulting string contains a decimal point only if required. The resulting
81 // string uses fixed point format if the exponent of the number is less than
82 // the number of significant digits and greater than or equal to -4. Otherwise,
83 // the resulting string uses scientific format, and the case of the format
84 // specifier controls whether the exponent is prefixed with an 'E' or an 'e'.
86 // N n Number format. The number is
87 // converted to a string of the form "-d,ddd,ddd.ddd....", where
88 // each 'd' indicates a digit (0-9). The string starts with a minus sign if the
89 // number is negative. Thousand separators are inserted between each group of
90 // three digits to the left of the decimal point. The precision specifier
91 // indicates the desired number of decimal places. If the precision specifier
92 // is omitted, the default numeric precision given by the
93 // NumberFormatInfo is used.
95 // X x - Hexadecimal format. This format is
96 // supported for integral types only. The number is converted to a string of
97 // hexadecimal digits. The format specifier indicates whether to use upper or
98 // lower case characters for the hexadecimal digits above 9 ('X' for 'ABCDEF',
99 // and 'x' for 'abcdef'). The precision specifier indicates the minimum number
100 // of digits desired in the resulting string. If required, the number will be
101 // left-padded with zeros to produce the number of digits given by the
102 // precision specifier.
104 // Some examples of standard format strings and their results are shown in the
105 // table below. (The examples all assume a default NumberFormatInfo.)
107 // Value Format Result
108 // 12345.6789 C $12,345.68
109 // -12345.6789 C ($12,345.68)
112 // 12345.6789 E 1.234568E+004
113 // 12345.6789 E10 1.2345678900E+004
114 // 12345.6789 e4 1.2346e+004
115 // 12345.6789 F 12345.68
116 // 12345.6789 F0 12346
117 // 12345.6789 F6 12345.678900
118 // 12345.6789 G 12345.6789
119 // 12345.6789 G7 12345.68
120 // 123456789 G7 1.234568E8
121 // 12345.6789 N 12,345.68
122 // 123456789 N4 123,456,789.0000
125 // 0x2c45e X8 0002C45E
127 // Format strings that do not start with an alphabetic character, or that start
128 // with an alphabetic character followed by a non-digit, are called
129 // user-defined format strings. The following table describes the formatting
130 // characters that are supported in user defined format strings.
133 // 0 - Digit placeholder. If the value being
134 // formatted has a digit in the position where the '0' appears in the format
135 // string, then that digit is copied to the output string. Otherwise, a '0' is
136 // stored in that position in the output string. The position of the leftmost
137 // '0' before the decimal point and the rightmost '0' after the decimal point
138 // determines the range of digits that are always present in the output
141 // # - Digit placeholder. If the value being
142 // formatted has a digit in the position where the '#' appears in the format
143 // string, then that digit is copied to the output string. Otherwise, nothing
144 // is stored in that position in the output string.
146 // . - Decimal point. The first '.' character
147 // in the format string determines the location of the decimal separator in the
148 // formatted value; any additional '.' characters are ignored. The actual
149 // character used as a the decimal separator in the output string is given by
150 // the NumberFormatInfo used to format the number.
152 // , - Thousand separator and number scaling.
153 // The ',' character serves two purposes. First, if the format string contains
154 // a ',' character between two digit placeholders (0 or #) and to the left of
155 // the decimal point if one is present, then the output will have thousand
156 // separators inserted between each group of three digits to the left of the
157 // decimal separator. The actual character used as a the decimal separator in
158 // the output string is given by the NumberFormatInfo used to format the
159 // number. Second, if the format string contains one or more ',' characters
160 // immediately to the left of the decimal point, or after the last digit
161 // placeholder if there is no decimal point, then the number will be divided by
162 // 1000 times the number of ',' characters before it is formatted. For example,
163 // the format string '0,,' will represent 100 million as just 100. Use of the
164 // ',' character to indicate scaling does not also cause the formatted number
165 // to have thousand separators. Thus, to scale a number by 1 million and insert
166 // thousand separators you would use the format string '#,##0,,'.
168 // % - Percentage placeholder. The presence of
169 // a '%' character in the format string causes the number to be multiplied by
170 // 100 before it is formatted. The '%' character itself is inserted in the
171 // output string where it appears in the format string.
173 // E+ E- e+ e- - Scientific notation.
174 // If any of the strings 'E+', 'E-', 'e+', or 'e-' are present in the format
175 // string and are immediately followed by at least one '0' character, then the
176 // number is formatted using scientific notation with an 'E' or 'e' inserted
177 // between the number and the exponent. The number of '0' characters following
178 // the scientific notation indicator determines the minimum number of digits to
179 // output for the exponent. The 'E+' and 'e+' formats indicate that a sign
180 // character (plus or minus) should always precede the exponent. The 'E-' and
181 // 'e-' formats indicate that a sign character should only precede negative
184 // \ - Literal character. A backslash character
185 // causes the next character in the format string to be copied to the output
186 // string as-is. The backslash itself isn't copied, so to place a backslash
187 // character in the output string, use two backslashes (\\) in the format
190 // 'ABC' "ABC" - Literal string. Characters
191 // enclosed in single or double quotation marks are copied to the output string
192 // as-is and do not affect formatting.
194 // ; - Section separator. The ';' character is
195 // used to separate sections for positive, negative, and zero numbers in the
198 // Other - All other characters are copied to
199 // the output string in the position they appear.
201 // For fixed point formats (formats not containing an 'E+', 'E-', 'e+', or
202 // 'e-'), the number is rounded to as many decimal places as there are digit
203 // placeholders to the right of the decimal point. If the format string does
204 // not contain a decimal point, the number is rounded to the nearest
205 // integer. If the number has more digits than there are digit placeholders to
206 // the left of the decimal point, the extra digits are copied to the output
207 // string immediately before the first digit placeholder.
209 // For scientific formats, the number is rounded to as many significant digits
210 // as there are digit placeholders in the format string.
212 // To allow for different formatting of positive, negative, and zero values, a
213 // user-defined format string may contain up to three sections separated by
214 // semicolons. The results of having one, two, or three sections in the format
215 // string are described in the table below.
219 // One - The format string applies to all values.
221 // Two - The first section applies to positive values
222 // and zeros, and the second section applies to negative values. If the number
223 // to be formatted is negative, but becomes zero after rounding according to
224 // the format in the second section, then the resulting zero is formatted
225 // according to the first section.
227 // Three - The first section applies to positive
228 // values, the second section applies to negative values, and the third section
229 // applies to zeros. The second section may be left empty (by having no
230 // characters between the semicolons), in which case the first section applies
231 // to all non-zero values. If the number to be formatted is non-zero, but
232 // becomes zero after rounding according to the format in the first or second
233 // section, then the resulting zero is formatted according to the third
236 // For both standard and user-defined formatting operations on values of type
237 // float and double, if the value being formatted is a NaN (Not
238 // a Number) or a positive or negative infinity, then regardless of the format
239 // string, the resulting string is given by the NaNSymbol,
240 // PositiveInfinitySymbol, or NegativeInfinitySymbol property of
241 // the NumberFormatInfo used to format the number.
243 internal static partial class Number
245 internal const int DecimalPrecision = 29; // Decimal.DecCalc also uses this value
246 private const int FloatPrecision = 7;
247 private const int DoublePrecision = 15;
248 private const int ScaleNAN = unchecked((int)0x80000000);
249 private const int ScaleINF = 0x7FFFFFFF;
250 private const int MaxUInt32DecDigits = 10;
251 private const int CharStackBufferSize = 32;
252 private const string PosNumberFormat = "#";
254 private static readonly string[] s_singleDigitStringCache = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
256 private static readonly string[] s_posCurrencyFormats =
258 "$#", "#$", "$ #", "# $"
261 private static readonly string[] s_negCurrencyFormats =
263 "($#)", "-$#", "$-#", "$#-",
264 "(#$)", "-#$", "#-$", "#$-",
265 "-# $", "-$ #", "# $-", "$ #-",
266 "$ -#", "#- $", "($ #)", "(# $)"
269 private static readonly string[] s_posPercentFormats =
271 "# %", "#%", "%#", "% #"
274 private static readonly string[] s_negPercentFormats =
276 "-# %", "-#%", "-%#",
279 "-% #", "# %-", "% #-",
283 private static readonly string[] s_negNumberFormats =
285 "(#)", "-#", "- #", "#-", "# -",
288 public static string FormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info)
290 char fmt = ParseFormatSpecifier(format, out int digits);
292 NumberBuffer number = default;
293 DecimalToNumber(ref value, ref number);
295 ValueStringBuilder sb;
298 char* stackPtr = stackalloc char[CharStackBufferSize];
299 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
304 NumberToString(ref sb, ref number, fmt, digits, info);
308 NumberToStringFormat(ref sb, ref number, format, info);
311 return sb.ToString();
314 public static bool TryFormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
316 char fmt = ParseFormatSpecifier(format, out int digits);
318 NumberBuffer number = default;
319 DecimalToNumber(ref value, ref number);
321 ValueStringBuilder sb;
324 char* stackPtr = stackalloc char[CharStackBufferSize];
325 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
330 NumberToString(ref sb, ref number, fmt, digits, info);
334 NumberToStringFormat(ref sb, ref number, format, info);
337 return sb.TryCopyTo(destination, out charsWritten);
340 private static unsafe void DecimalToNumber(ref decimal d, ref NumberBuffer number)
342 char* buffer = number.GetDigitsPointer();
343 number.precision = DecimalPrecision;
344 number.sign = d.IsNegative;
345 number.kind = NumberBufferKind.Decimal;
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)((byte*)(buffer + DecimalPrecision) - (byte*)p) >> 1;
355 number.scale = i - d.Scale;
357 char* dst = number.GetDigitsPointer();
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;
392 number.kind = NumberBufferKind.Double;
399 // In order to give numbers that are both friendly to display and round-trippable, we parse the
400 // number using 15 digits and then determine if it round trips to the same value. If it does, we
401 // convert that NUMBER to a string, otherwise we reparse using 17 digits and display that.
402 DoubleToNumber(value, DoublePrecision, ref number);
403 if (number.scale == ScaleNAN)
405 return info.NaNSymbol;
407 else if (number.scale == ScaleINF)
409 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
412 if (NumberToDouble(ref number) == value)
414 NumberToString(ref sb, ref number, 'G', DoublePrecision, info);
418 DoubleToNumber(value, 17, ref number);
419 NumberToString(ref sb, ref number, 'G', 17, info);
427 // Round values less than E14 to 15 digits
436 // Round values less than G15 to 15 digits. G16 and G17 will not be touched.
444 DoubleToNumber(value, precision, ref number);
445 if (number.scale == ScaleNAN)
447 return info.NaNSymbol;
449 else if (number.scale == ScaleINF)
451 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
456 NumberToString(ref sb, ref number, fmt, digits, info);
460 NumberToStringFormat(ref sb, ref number, format, info);
466 public static string FormatSingle(float value, string format, NumberFormatInfo info)
468 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
469 var sb = new ValueStringBuilder(stackBuffer);
470 return FormatSingle(ref sb, value, format, info) ?? sb.ToString();
473 public static bool TryFormatSingle(float value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
475 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
476 var sb = new ValueStringBuilder(stackBuffer);
477 string s = FormatSingle(ref sb, value, format, info);
479 TryCopyTo(s, destination, out charsWritten) :
480 sb.TryCopyTo(destination, out charsWritten);
483 /// <summary>Formats the specified value according to the specified format and info.</summary>
485 /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
486 /// Null if no existing string was returned, in which case the formatted output is in the builder.
488 private static string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan<char> format, NumberFormatInfo info)
490 char fmt = ParseFormatSpecifier(format, out int digits);
491 int precision = FloatPrecision;
492 NumberBuffer number = default;
493 number.kind = NumberBufferKind.Double;
500 // In order to give numbers that are both friendly to display and round-trippable, we parse the
501 // number using 7 digits and then determine if it round trips to the same value. If it does, we
502 // convert that NUMBER to a string, otherwise we reparse using 9 digits and display that.
503 DoubleToNumber(value, FloatPrecision, ref number);
504 if (number.scale == ScaleNAN)
506 return info.NaNSymbol;
508 else if (number.scale == ScaleINF)
510 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
513 if ((float)NumberToDouble(ref number) == value)
515 NumberToString(ref sb, ref number, 'G', FloatPrecision, info);
519 DoubleToNumber(value, 9, ref number);
520 NumberToString(ref sb, ref number, 'G', 9, info);
527 // Round values less than E14 to 15 digits.
536 // Round values less than G15 to 15 digits. G16 and G17 will not be touched.
544 DoubleToNumber(value, precision, ref number);
545 if (number.scale == ScaleNAN)
547 return info.NaNSymbol;
549 else if (number.scale == ScaleINF)
551 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
556 NumberToString(ref sb, ref number, fmt, digits, info);
560 NumberToStringFormat(ref sb, ref number, format, info);
565 private static bool TryCopyTo(string source, Span<char> destination, out int charsWritten)
567 Debug.Assert(source != null);
569 if (source.AsSpan().TryCopyTo(destination))
571 charsWritten = source.Length;
579 public static string FormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider)
581 // Fast path for default format with a non-negative value
582 if (value >= 0 && format.Length == 0)
584 return UInt32ToDecStr((uint)value, digits: -1);
587 char fmt = ParseFormatSpecifier(format, out int digits);
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, NumberFormatInfo.GetInstance(provider).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 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
604 NumberBuffer number = default;
605 Int32ToNumber(value, ref number);
606 ValueStringBuilder sb;
609 char* stackPtr = stackalloc char[CharStackBufferSize];
610 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
614 NumberToString(ref sb, ref number, fmt, digits, info);
618 NumberToStringFormat(ref sb, ref number, format, info);
620 return sb.ToString();
624 public static bool TryFormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
626 // Fast path for default format with a non-negative value
627 if (value >= 0 && format.Length == 0)
629 return TryUInt32ToDecStr((uint)value, digits: -1, destination, out charsWritten);
632 char fmt = ParseFormatSpecifier(format, out int digits);
633 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
634 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
637 TryUInt32ToDecStr((uint)value, digits, destination, out charsWritten) :
638 TryNegativeInt32ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten);
640 else if (fmtUpper == 'X')
642 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
643 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
644 return TryInt32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
648 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
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);
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 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
679 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
681 return UInt32ToDecStr(value, digits);
683 else if (fmtUpper == 'X')
685 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
686 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
687 return Int32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits);
691 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
692 NumberBuffer number = default;
693 UInt32ToNumber(value, ref number);
694 ValueStringBuilder sb;
697 char* stackPtr = stackalloc char[CharStackBufferSize];
698 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
702 NumberToString(ref sb, ref number, fmt, digits, info);
706 NumberToStringFormat(ref sb, ref number, format, info);
708 return sb.ToString();
712 public static bool TryFormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
714 // Fast path for default format
715 if (format.Length == 0)
717 return TryUInt32ToDecStr(value, digits: -1, destination, out charsWritten);
720 char fmt = ParseFormatSpecifier(format, out int digits);
721 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
722 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
724 return TryUInt32ToDecStr(value, digits, destination, out charsWritten);
726 else if (fmtUpper == 'X')
728 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
729 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
730 return TryInt32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
734 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
735 NumberBuffer number = default;
736 UInt32ToNumber(value, ref number);
737 ValueStringBuilder sb;
740 char* stackPtr = stackalloc char[CharStackBufferSize];
741 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
745 NumberToString(ref sb, ref number, fmt, digits, info);
749 NumberToStringFormat(ref sb, ref number, format, info);
751 return sb.TryCopyTo(destination, out charsWritten);
755 public static string FormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider)
757 // Fast path for default format with a non-negative value
758 if (value >= 0 && format.Length == 0)
760 return UInt64ToDecStr((ulong)value, digits: -1);
763 char fmt = ParseFormatSpecifier(format, out int digits);
764 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
765 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
768 UInt64ToDecStr((ulong)value, digits) :
769 NegativeInt64ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign);
771 else if (fmtUpper == 'X')
773 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
774 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
775 // produces lowercase.
776 return Int64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
780 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
781 NumberBuffer number = default;
782 Int64ToNumber(value, ref number);
783 ValueStringBuilder sb;
786 char* stackPtr = stackalloc char[CharStackBufferSize];
787 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
791 NumberToString(ref sb, ref number, fmt, digits, info);
795 NumberToStringFormat(ref sb, ref number, format, info);
797 return sb.ToString();
801 public static bool TryFormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
803 // Fast path for default format with a non-negative value
804 if (value >= 0 && format.Length == 0)
806 return TryUInt64ToDecStr((ulong)value, digits: -1, destination, out charsWritten);
809 char fmt = ParseFormatSpecifier(format, out int digits);
810 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
811 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
814 TryUInt64ToDecStr((ulong)value, digits, destination, out charsWritten) :
815 TryNegativeInt64ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten);
817 else if (fmtUpper == 'X')
819 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
820 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
821 // produces lowercase.
822 return TryInt64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
826 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
827 NumberBuffer number = default;
828 Int64ToNumber(value, ref number);
829 ValueStringBuilder sb;
832 char* stackPtr = stackalloc char[CharStackBufferSize];
833 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
837 NumberToString(ref sb, ref number, fmt, digits, info);
841 NumberToStringFormat(ref sb, ref number, format, info);
843 return sb.TryCopyTo(destination, out charsWritten);
847 public static string FormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider)
849 // Fast path for default format
850 if (format.Length == 0)
852 return UInt64ToDecStr(value, digits: -1);
855 char fmt = ParseFormatSpecifier(format, out int digits);
856 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
857 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
859 return UInt64ToDecStr(value, digits);
861 else if (fmtUpper == 'X')
863 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
864 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
865 // produces lowercase.
866 return Int64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits);
870 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
871 NumberBuffer number = default;
872 UInt64ToNumber(value, ref number);
873 ValueStringBuilder sb;
876 char* stackPtr = stackalloc char[CharStackBufferSize];
877 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
881 NumberToString(ref sb, ref number, fmt, digits, info);
885 NumberToStringFormat(ref sb, ref number, format, info);
887 return sb.ToString();
891 public static bool TryFormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
893 // Fast path for default format
894 if (format.Length == 0)
896 return TryUInt64ToDecStr(value, digits: -1, destination, out charsWritten);
899 char fmt = ParseFormatSpecifier(format, out int digits);
900 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
901 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
903 return TryUInt64ToDecStr(value, digits, destination, out charsWritten);
905 else if (fmtUpper == 'X')
907 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
908 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
909 // produces lowercase.
910 return TryInt64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
914 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
915 NumberBuffer number = default;
916 UInt64ToNumber(value, ref number);
917 ValueStringBuilder sb;
920 char* stackPtr = stackalloc char[CharStackBufferSize];
921 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
925 NumberToString(ref sb, ref number, fmt, digits, info);
929 NumberToStringFormat(ref sb, ref number, format, info);
931 return sb.TryCopyTo(destination, out charsWritten);
935 [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
936 private static unsafe void Int32ToNumber(int value, ref NumberBuffer number)
938 number.precision = Int32Precision;
950 char* buffer = number.GetDigitsPointer();
951 char* p = UInt32ToDecChars(buffer + Int32Precision, (uint)value, 0);
952 int i = (int)(buffer + Int32Precision - p);
955 number.kind = NumberBufferKind.Integer;
957 char* dst = number.GetDigitsPointer();
963 private static unsafe string NegativeInt32ToDecStr(int value, int digits, string sNegative)
965 Debug.Assert(value < 0);
970 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length;
971 string result = string.FastAllocateString(bufferLength);
972 fixed (char* buffer = result)
974 char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
975 Debug.Assert(p == buffer + sNegative.Length);
977 for (int i = sNegative.Length - 1; i >= 0; i--)
979 *(--p) = sNegative[i];
981 Debug.Assert(p == buffer);
986 private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, string sNegative, Span<char> destination, out int charsWritten)
988 Debug.Assert(value < 0);
993 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length;
994 if (bufferLength > destination.Length)
1000 charsWritten = bufferLength;
1001 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1003 char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
1004 Debug.Assert(p == buffer + sNegative.Length);
1006 for (int i = sNegative.Length - 1; i >= 0; i--)
1008 *(--p) = sNegative[i];
1010 Debug.Assert(p == buffer);
1015 private static unsafe string Int32ToHexStr(int value, char hexBase, int digits)
1020 int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value));
1021 string result = string.FastAllocateString(bufferLength);
1022 fixed (char* buffer = result)
1024 char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
1025 Debug.Assert(p == buffer);
1030 private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span<char> destination, out int charsWritten)
1035 int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value));
1036 if (bufferLength > destination.Length)
1042 charsWritten = bufferLength;
1043 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1045 char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
1046 Debug.Assert(p == buffer);
1051 private static unsafe char* Int32ToHexChars(char* buffer, uint value, int hexBase, int digits)
1053 while (--digits >= 0 || value != 0)
1055 byte digit = (byte)(value & 0xF);
1056 *(--buffer) = (char)(digit + (digit < 10 ? (byte)'0' : hexBase));
1062 [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
1063 private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number)
1065 number.precision = UInt32Precision;
1066 number.sign = false;
1068 char* buffer = number.GetDigitsPointer();
1069 char* p = UInt32ToDecChars(buffer + UInt32Precision, value, 0);
1070 int i = (int)(buffer + UInt32Precision - p);
1072 number.kind = NumberBufferKind.Integer;
1074 char* dst = number.GetDigitsPointer();
1080 internal static unsafe char* UInt32ToDecChars(char* bufferEnd, uint value, int digits)
1082 while (--digits >= 0 || value != 0)
1084 // TODO https://github.com/dotnet/coreclr/issues/3439
1085 uint newValue = value / 10;
1086 *(--bufferEnd) = (char)(value - (newValue * 10) + '0');
1092 private static unsafe string UInt32ToDecStr(uint value, int digits)
1094 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
1096 // For single-digit values that are very common, especially 0 and 1, just return cached strings.
1097 if (bufferLength == 1)
1099 return s_singleDigitStringCache[value];
1102 string result = string.FastAllocateString(bufferLength);
1103 fixed (char* buffer = result)
1105 char* p = buffer + bufferLength;
1110 // TODO https://github.com/dotnet/coreclr/issues/3439
1111 uint div = value / 10;
1112 *(--p) = (char)('0' + value - (div * 10));
1119 p = UInt32ToDecChars(p, value, digits);
1121 Debug.Assert(p == buffer);
1126 private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span<char> destination, out int charsWritten)
1128 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
1129 if (bufferLength > destination.Length)
1135 charsWritten = bufferLength;
1136 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1138 char* p = buffer + bufferLength;
1143 // TODO https://github.com/dotnet/coreclr/issues/3439
1144 uint div = value / 10;
1145 *(--p) = (char)('0' + value - (div * 10));
1152 p = UInt32ToDecChars(p, value, digits);
1154 Debug.Assert(p == buffer);
1159 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1160 private static unsafe bool TryCopyTo(char* src, int length, Span<char> destination, out int charsWritten)
1162 if (new ReadOnlySpan<char>(src, length).TryCopyTo(destination))
1164 charsWritten = length;
1174 private static unsafe void Int64ToNumber(long input, ref NumberBuffer number)
1176 ulong value = (ulong)input;
1177 number.sign = input < 0;
1178 number.precision = Int64Precision;
1181 value = (ulong)(-input);
1184 char* buffer = number.GetDigitsPointer();
1185 char* p = buffer + Int64Precision;
1186 while (High32(value) != 0)
1187 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1188 p = UInt32ToDecChars(p, Low32(value), 0);
1189 int i = (int)(buffer + Int64Precision - p);
1192 number.kind = NumberBufferKind.Integer;
1194 char* dst = number.GetDigitsPointer();
1200 private static unsafe string NegativeInt64ToDecStr(long input, int digits, string sNegative)
1202 Debug.Assert(input < 0);
1209 ulong value = (ulong)(-input);
1211 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)) + sNegative.Length;
1212 string result = string.FastAllocateString(bufferLength);
1213 fixed (char* buffer = result)
1215 char* p = buffer + bufferLength;
1216 while (High32(value) != 0)
1218 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1221 p = UInt32ToDecChars(p, Low32(value), digits);
1222 Debug.Assert(p == buffer + sNegative.Length);
1224 for (int i = sNegative.Length - 1; i >= 0; i--)
1226 *(--p) = sNegative[i];
1228 Debug.Assert(p == buffer);
1233 private static unsafe bool TryNegativeInt64ToDecStr(long input, int digits, string sNegative, Span<char> destination, out int charsWritten)
1235 Debug.Assert(input < 0);
1242 ulong value = (ulong)(-input);
1244 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((ulong)(-input))) + sNegative.Length;
1245 if (bufferLength > destination.Length)
1251 charsWritten = bufferLength;
1252 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1254 char* p = buffer + bufferLength;
1255 while (High32(value) != 0)
1257 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1260 p = UInt32ToDecChars(p, Low32(value), digits);
1261 Debug.Assert(p == buffer + sNegative.Length);
1263 for (int i = sNegative.Length - 1; i >= 0; i--)
1265 *(--p) = sNegative[i];
1267 Debug.Assert(p == buffer);
1272 private static unsafe string Int64ToHexStr(long value, char hexBase, int digits)
1274 int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value));
1275 string result = string.FastAllocateString(bufferLength);
1276 fixed (char* buffer = result)
1278 char* p = buffer + bufferLength;
1279 if (High32((ulong)value) != 0)
1281 p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8);
1282 p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
1286 p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1));
1288 Debug.Assert(p == buffer);
1293 private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span<char> destination, out int charsWritten)
1295 int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value));
1296 if (bufferLength > destination.Length)
1302 charsWritten = bufferLength;
1303 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1305 char* p = buffer + bufferLength;
1306 if (High32((ulong)value) != 0)
1308 p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8);
1309 p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
1313 p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1));
1315 Debug.Assert(p == buffer);
1320 private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number)
1322 number.precision = UInt64Precision;
1323 number.sign = false;
1325 char* buffer = number.GetDigitsPointer();
1326 char* p = buffer + UInt64Precision;
1328 while (High32(value) != 0)
1329 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1330 p = UInt32ToDecChars(p, Low32(value), 0);
1331 int i = (int)(buffer + UInt64Precision - p);
1334 number.kind = NumberBufferKind.Integer;
1336 char* dst = number.GetDigitsPointer();
1342 private static unsafe string UInt64ToDecStr(ulong value, int digits)
1347 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
1349 // For single-digit values that are very common, especially 0 and 1, just return cached strings.
1350 if (bufferLength == 1)
1352 return s_singleDigitStringCache[value];
1355 string result = string.FastAllocateString(bufferLength);
1356 fixed (char* buffer = result)
1358 char* p = buffer + bufferLength;
1359 while (High32(value) != 0)
1361 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1364 p = UInt32ToDecChars(p, Low32(value), digits);
1365 Debug.Assert(p == buffer);
1370 private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span<char> destination, out int charsWritten)
1375 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
1376 if (bufferLength > destination.Length)
1382 charsWritten = bufferLength;
1383 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1385 char* p = buffer + bufferLength;
1386 while (High32(value) != 0)
1388 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1391 p = UInt32ToDecChars(p, Low32(value), digits);
1392 Debug.Assert(p == buffer);
1397 internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits)
1400 if (format.Length > 0)
1402 // If the format begins with a symbol, see if it's a standard format
1403 // with or without a specified number of digits.
1405 if ((uint)(c - 'A') <= 'Z' - 'A' ||
1406 (uint)(c - 'a') <= 'z' - 'a')
1408 // Fast path for sole symbol, e.g. "D"
1409 if (format.Length == 1)
1415 if (format.Length == 2)
1417 // Fast path for symbol and single digit, e.g. "X4"
1418 int d = format[1] - '0';
1425 else if (format.Length == 3)
1427 // Fast path for symbol and double digit, e.g. "F12"
1428 int d1 = format[1] - '0', d2 = format[2] - '0';
1429 if ((uint)d1 < 10 && (uint)d2 < 10)
1431 digits = d1 * 10 + d2;
1436 // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
1437 // but it can begin with any number of 0s, and thus we may need to check more than two
1438 // digits. Further, for compat, we need to stop when we hit a null char.
1441 while (i < format.Length && (((uint)format[i] - '0') < 10) && n < 10)
1443 n = (n * 10) + format[i++] - '0';
1446 // If we're at the end of the digits rather than having stopped because we hit something
1447 // other than a digit or overflowed, return the standard format info.
1448 if (i == format.Length || format[i] == '\0')
1456 // Default empty format to be "G"; custom format is signified with '\0'.
1458 return format.Length == 0 || c == '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it.
1463 internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info)
1465 Debug.Assert(number.kind != NumberBufferKind.Unknown);
1473 nMaxDigits = info.CurrencyDecimalDigits;
1475 RoundNumber(ref number, number.scale + nMaxDigits); // Don't change this line to use digPos since digCount could have its sign changed.
1477 FormatCurrency(ref sb, ref number, nMaxDigits, info);
1486 nMaxDigits = info.NumberDecimalDigits;
1488 RoundNumber(ref number, number.scale + nMaxDigits);
1491 sb.Append(info.NegativeSign);
1493 FormatFixed(ref sb, ref number, nMaxDigits, info, null, info.NumberDecimalSeparator, null);
1502 nMaxDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation
1504 RoundNumber(ref number, number.scale + nMaxDigits);
1506 FormatNumber(ref sb, ref number, nMaxDigits, info);
1518 RoundNumber(ref number, nMaxDigits);
1521 sb.Append(info.NegativeSign);
1523 FormatScientific(ref sb, ref number, nMaxDigits, info, format);
1531 bool noRounding = false;
1534 if ((number.kind == NumberBufferKind.Decimal) && (nMaxDigits == -1))
1536 noRounding = true; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant
1541 // This ensures that the PAL code pads out to the correct place even when we use the default precision
1542 nMaxDigits = number.precision;
1546 RoundNumber(ref number, nMaxDigits);
1550 sb.Append(info.NegativeSign);
1552 FormatGeneral(ref sb, ref number, nMaxDigits, info, (char)(format - ('G' - 'E')), noRounding);
1561 nMaxDigits = info.PercentDecimalDigits;
1564 RoundNumber(ref number, number.scale + nMaxDigits);
1566 FormatPercent(ref sb, ref number, nMaxDigits, info);
1572 throw new FormatException(SR.Argument_BadFormatSpecifier);
1576 internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref NumberBuffer number, ReadOnlySpan<char> format, NumberFormatInfo info)
1578 Debug.Assert(number.kind != NumberBufferKind.Unknown);
1587 int thousandCount = 0;
1594 char* dig = number.GetDigitsPointer();
1597 section = FindSection(format, dig[0] == 0 ? 2 : number.sign ? 1 : 0);
1603 firstDigit = 0x7FFFFFFF;
1607 thousandSeps = false;
1611 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
1613 while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
1621 if (firstDigit == 0x7FFFFFFF)
1622 firstDigit = digitCount;
1624 lastDigit = digitCount;
1628 decimalPos = digitCount;
1631 if (digitCount > 0 && decimalPos < 0)
1633 if (thousandPos >= 0)
1635 if (thousandPos == digitCount)
1640 thousandSeps = true;
1642 thousandPos = digitCount;
1654 while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
1658 if (src < format.Length && pFormat[src] != 0)
1663 if ((src < format.Length && pFormat[src] == '0') ||
1664 (src + 1 < format.Length && (pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0'))
1666 while (++src < format.Length && pFormat[src] == '0');
1675 decimalPos = digitCount;
1677 if (thousandPos >= 0)
1679 if (thousandPos == decimalPos)
1680 scaleAdjust -= thousandCount * 3;
1682 thousandSeps = true;
1687 number.scale += scaleAdjust;
1688 int pos = scientific ? digitCount : number.scale + digitCount - decimalPos;
1689 RoundNumber(ref number, pos);
1692 src = FindSection(format, 2);
1702 number.scale = 0; // Decimals with scale ('0.00') should be rounded.
1708 firstDigit = firstDigit < decimalPos ? decimalPos - firstDigit : 0;
1709 lastDigit = lastDigit > decimalPos ? decimalPos - lastDigit : 0;
1712 digPos = decimalPos;
1717 digPos = number.scale > decimalPos ? number.scale : decimalPos;
1718 adjust = number.scale - decimalPos;
1722 // Adjust can be negative, so we make this an int instead of an unsigned int.
1723 // Adjust represents the number of characters over the formatting e.g. format string is "0000" and you are trying to
1724 // format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
1725 // -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
1726 Span<int> thousandsSepPos = stackalloc int[4];
1727 int thousandsSepCtr = -1;
1731 // We need to precompute this outside the number formatting loop
1732 if (info.NumberGroupSeparator.Length > 0)
1734 // We need this array to figure out where to insert the thousands separator. We would have to traverse the string
1735 // backwards. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
1736 // the thousands separator so we can get away with traversing forwards. Note we only have to compute up to digPos.
1737 // The max is not bound since you can have formatting strings of the form "000,000..", and this
1738 // should handle that case too.
1740 int[] groupDigits = info.numberGroupSizes;
1742 int groupSizeIndex = 0; // Index into the groupDigits array.
1743 int groupTotalSizeCount = 0;
1744 int groupSizeLen = groupDigits.Length; // The length of groupDigits array.
1745 if (groupSizeLen != 0)
1746 groupTotalSizeCount = groupDigits[groupSizeIndex]; // The current running total of group size.
1747 int groupSize = groupTotalSizeCount;
1749 int totalDigits = digPos + ((adjust < 0) ? adjust : 0); // Actual number of digits in o/p
1750 int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits;
1751 while (numDigits > groupTotalSizeCount)
1756 if (thousandsSepCtr >= thousandsSepPos.Length)
1758 var newThousandsSepPos = new int[thousandsSepPos.Length * 2];
1759 thousandsSepPos.CopyTo(newThousandsSepPos);
1760 thousandsSepPos = newThousandsSepPos;
1763 thousandsSepPos[thousandsSepCtr] = groupTotalSizeCount;
1764 if (groupSizeIndex < groupSizeLen - 1)
1767 groupSize = groupDigits[groupSizeIndex];
1769 groupTotalSizeCount += groupSize;
1774 if (number.sign && (section == 0) && (number.scale != 0))
1775 sb.Append(info.NegativeSign);
1777 bool decimalWritten = false;
1779 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
1783 while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
1794 // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
1795 // the character after which the groupSeparator needs to be appended.
1796 sb.Append(*cur != 0 ? *cur++ : '0');
1797 if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
1799 if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
1801 sb.Append(info.NumberGroupSeparator);
1820 ch = digPos <= firstDigit ? '0' : '\0';
1824 ch = *cur != 0 ? *cur++ : digPos > lastDigit ? '0' : '\0';
1829 if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
1831 if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
1833 sb.Append(info.NumberGroupSeparator);
1844 if (digPos != 0 || decimalWritten)
1846 // For compatibility, don't echo repeated decimals
1849 // If the format has trailing zeros or the format has a decimal and digits remain
1850 if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0))
1852 sb.Append(info.NumberDecimalSeparator);
1853 decimalWritten = true;
1858 sb.Append(info.PerMilleSymbol);
1861 sb.Append(info.PercentSymbol);
1867 while (src < format.Length && pFormat[src] != 0 && pFormat[src] != ch)
1868 sb.Append(pFormat[src++]);
1869 if (src < format.Length && pFormat[src] != 0)
1873 if (src < format.Length && pFormat[src] != 0)
1874 sb.Append(pFormat[src++]);
1879 bool positiveSign = false;
1883 if (src < format.Length && pFormat[src] == '0')
1885 // Handles E0, which should format the same as E-0
1888 else if (src+1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0')
1891 positiveSign = true;
1893 else if (src+1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0')
1896 // Do nothing, this is just a place holder s.t. we don't break out of the loop.
1904 while (++src < format.Length && pFormat[src] == '0')
1909 int exp = dig[0] == 0 ? 0 : number.scale - decimalPos;
1910 FormatExponent(ref sb, info, exp, ch, i, positiveSign);
1915 sb.Append(ch); // Copy E or e to output
1916 if (src < format.Length)
1918 if (pFormat[src] == '+' || pFormat[src] == '-')
1919 sb.Append(pFormat[src++]);
1920 while (src < format.Length && pFormat[src] == '0')
1921 sb.Append(pFormat[src++]);
1933 if (number.sign && (section == 0) && (number.scale == 0) && (sb.Length > 0))
1934 sb.Insert(0, info.NegativeSign);
1937 private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
1939 string fmt = number.sign ?
1940 s_negCurrencyFormats[info.CurrencyNegativePattern] :
1941 s_posCurrencyFormats[info.CurrencyPositivePattern];
1943 foreach (char ch in fmt)
1948 FormatFixed(ref sb, ref number, nMaxDigits, info, info.currencyGroupSizes, info.CurrencyDecimalSeparator, info.CurrencyGroupSeparator);
1951 sb.Append(info.NegativeSign);
1954 sb.Append(info.CurrencySymbol);
1963 private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, int[] groupDigits, string sDecimal, string sGroup)
1965 int digPos = number.scale;
1966 char* dig = number.GetDigitsPointer();
1970 if (groupDigits != null)
1972 int groupSizeIndex = 0; // Index into the groupDigits array.
1973 int bufferSize = digPos; // The length of the result buffer string.
1974 int groupSize = 0; // The current group size.
1976 // Find out the size of the string buffer for the result.
1977 if (groupDigits.Length != 0) // You can pass in 0 length arrays
1979 int groupSizeCount = groupDigits[groupSizeIndex]; // The current total of group size.
1981 while (digPos > groupSizeCount)
1983 groupSize = groupDigits[groupSizeIndex];
1987 bufferSize += sGroup.Length;
1988 if (groupSizeIndex < groupDigits.Length - 1)
1991 groupSizeCount += groupDigits[groupSizeIndex];
1992 if (groupSizeCount < 0 || bufferSize < 0)
1993 throw new ArgumentOutOfRangeException(); // If we overflow
1996 groupSize = groupSizeCount == 0 ? 0 : groupDigits[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0
2001 int digLength = string.wcslen(dig);
2002 int digStart = (digPos < digLength) ? digPos : digLength;
2003 fixed (char* spanPtr = &MemoryMarshal.GetReference(sb.AppendSpan(bufferSize)))
2005 char* p = spanPtr + bufferSize - 1;
2006 for (int i = digPos - 1; i >= 0; i--)
2008 *(p--) = (i < digStart) ? dig[i] : '0';
2013 if ((digitCount == groupSize) && (i != 0))
2015 for (int j = sGroup.Length - 1; j >= 0; j--)
2018 if (groupSizeIndex < groupDigits.Length - 1)
2021 groupSize = groupDigits[groupSizeIndex];
2028 Debug.Assert(p >= spanPtr - 1, "Underflow");
2036 sb.Append(*dig != 0 ? *dig++ : '0');
2038 while (--digPos > 0);
2048 sb.Append(sDecimal);
2049 if ((digPos < 0) && (nMaxDigits > 0))
2051 int zeroes = Math.Min(-digPos, nMaxDigits);
2052 sb.Append('0', zeroes);
2054 nMaxDigits -= zeroes;
2057 while (nMaxDigits > 0)
2059 sb.Append((*dig != 0) ? *dig++ : '0');
2065 private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
2067 string fmt = number.sign ?
2068 s_negNumberFormats[info.NumberNegativePattern] :
2071 foreach (char ch in fmt)
2076 FormatFixed(ref sb, ref number, nMaxDigits, info, info.numberGroupSizes, info.NumberDecimalSeparator, info.NumberGroupSeparator);
2079 sb.Append(info.NegativeSign);
2088 private static unsafe void FormatScientific(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar)
2090 char* dig = number.GetDigitsPointer();
2092 sb.Append((*dig != 0) ? *dig++ : '0');
2094 if (nMaxDigits != 1) // For E0 we would like to suppress the decimal point
2095 sb.Append(info.NumberDecimalSeparator);
2097 while (--nMaxDigits > 0)
2098 sb.Append((*dig != 0) ? *dig++ : '0');
2100 int e = number.digits[0] == 0 ? 0 : number.scale - 1;
2101 FormatExponent(ref sb, info, e, expChar, 3, true);
2104 private static unsafe void FormatExponent(ref ValueStringBuilder sb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign)
2110 sb.Append(info.NegativeSign);
2116 sb.Append(info.PositiveSign);
2119 char* digits = stackalloc char[MaxUInt32DecDigits];
2120 char* p = UInt32ToDecChars(digits + MaxUInt32DecDigits, (uint)value, minDigits);
2121 int i = (int)(digits + MaxUInt32DecDigits - p);
2122 sb.Append(p, (int)(digits + MaxUInt32DecDigits - p));
2125 private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool bSuppressScientific)
2127 int digPos = number.scale;
2128 bool scientific = false;
2130 if (!bSuppressScientific)
2132 // Don't switch to scientific notation
2133 if (digPos > nMaxDigits || digPos < -3)
2140 char* dig = number.GetDigitsPointer();
2146 sb.Append((*dig != 0) ? *dig++ : '0');
2147 } while (--digPos > 0);
2154 if (*dig != 0 || digPos < 0)
2156 sb.Append(info.NumberDecimalSeparator);
2169 FormatExponent(ref sb, info, number.scale - 1, expChar, 2, true);
2172 private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
2174 string fmt = number.sign ?
2175 s_negPercentFormats[info.PercentNegativePattern] :
2176 s_posPercentFormats[info.PercentPositivePattern];
2178 foreach (char ch in fmt)
2183 FormatFixed(ref sb, ref number, nMaxDigits, info, info.percentGroupSizes, info.PercentDecimalSeparator, info.PercentGroupSeparator);
2186 sb.Append(info.NegativeSign);
2189 sb.Append(info.PercentSymbol);
2198 private static unsafe void RoundNumber(ref NumberBuffer number, int pos)
2200 char* dig = number.GetDigitsPointer();
2203 while (i < pos && dig[i] != 0)
2206 if (i == pos && dig[i] >= '5')
2208 while (i > 0 && dig[i - 1] == '9')
2224 while (i > 0 && dig[i - 1] == '0')
2231 if (number.kind == NumberBufferKind.Integer)
2233 number.sign = false;
2239 private static unsafe int FindSection(ReadOnlySpan<char> format, int section)
2247 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
2252 if (src >= format.Length)
2257 switch (ch = pFormat[src++])
2261 while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
2265 if (src < format.Length && pFormat[src] != 0)
2271 if (src < format.Length && pFormat[src] != 0 && pFormat[src] != ';')
2281 private static uint Low32(ulong value) => (uint)value;
2283 private static uint High32(ulong value) => (uint)((value & 0xFFFFFFFF00000000) >> 32);
2285 private static uint Int64DivMod1E9(ref ulong value)
2287 uint rem = (uint)(value % 1000000000);
2288 value /= 1000000000;
2292 private static unsafe void DoubleToNumber(double value, int precision, ref NumberBuffer number)
2294 number.precision = precision;
2296 if (!double.IsFinite(value))
2298 number.scale = double.IsNaN(value) ? ScaleNAN : ScaleINF;
2299 number.sign = double.IsNegative(value);
2300 number.digits[0] = '\0';
2302 else if (value == 0.0)
2305 number.sign = double.IsNegative(value);
2306 number.digits[0] = '\0';
2308 else if (!Grisu3.Run(value, precision, ref number))
2310 Dragon4(value, precision, ref number);
2314 private static long ExtractFractionAndBiasedExponent(double value, out int exponent)
2316 var bits = BitConverter.DoubleToInt64Bits(value);
2317 long fraction = (bits & 0xFFFFFFFFFFFFF);
2318 exponent = (int)((bits >> 52) & 0x7FF);
2322 // For normalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
2323 // value = 1.fraction * 2^(exp - 1023)
2324 // = (1 + mantissa / 2^52) * 2^(exp - 1023)
2325 // = (2^52 + mantissa) * 2^(exp - 1023 - 52)
2327 // So f = (2^52 + mantissa), e = exp - 1075;
2329 fraction |= ((long)(1) << 52);
2334 // For denormalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
2335 // value = 0.fraction * 2^(1 - 1023)
2336 // = (mantissa / 2^52) * 2^(-1022)
2337 // = mantissa * 2^(-1022 - 52)
2338 // = mantissa * 2^(-1074)
2339 // So f = mantissa, e = -1074