Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Number.Formatting.cs
blob131e3a0490eb4cd8c8d77b4486663003b317dd38
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;
10 using System.Text;
12 namespace System
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
24 // values.
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)
110 // 12345 D 12345
111 // 12345 D8 00012345
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
123 // 0x2c45e x 2c45e
124 // 0x2c45e X 2C45E
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
139 // string.
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
182 // exponents.
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
188 // string.
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
196 // format string.
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.
217 // Sections:
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
234 // section.
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
247 // SinglePrecision and DoublePrecision represent the maximum number of digits required
248 // to guarantee that any given Single or Double can roundtrip. Some numbers may require
249 // less, but none will require more.
250 private const int SinglePrecision = 9;
251 private const int DoublePrecision = 17;
253 // SinglePrecisionCustomFormat and DoublePrecisionCustomFormat are used to ensure that
254 // custom format strings return the same string as in previous releases when the format
255 // would return x digits or less (where x is the value of the corresponding constant).
256 // In order to support more digits, we would need to update ParseFormatSpecifier to pre-parse
257 // the format and determine exactly how many digits are being requested and whether they
258 // represent "significant digits" or "digits after the decimal point".
259 private const int SinglePrecisionCustomFormat = 7;
260 private const int DoublePrecisionCustomFormat = 15;
262 private const int DefaultPrecisionExponentialFormat = 6;
264 private const int MaxUInt32DecDigits = 10;
265 private const int CharStackBufferSize = 32;
266 private const string PosNumberFormat = "#";
268 private static readonly string[] s_singleDigitStringCache = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
270 private static readonly string[] s_posCurrencyFormats =
272 "$#", "#$", "$ #", "# $"
275 private static readonly string[] s_negCurrencyFormats =
277 "($#)", "-$#", "$-#", "$#-",
278 "(#$)", "-#$", "#-$", "#$-",
279 "-# $", "-$ #", "# $-", "$ #-",
280 "$ -#", "#- $", "($ #)", "(# $)"
283 private static readonly string[] s_posPercentFormats =
285 "# %", "#%", "%#", "% #"
288 private static readonly string[] s_negPercentFormats =
290 "-# %", "-#%", "-%#",
291 "%-#", "%#-",
292 "#-%", "#%-",
293 "-% #", "# %-", "% #-",
294 "% -#", "#- %"
297 private static readonly string[] s_negNumberFormats =
299 "(#)", "-#", "- #", "#-", "# -",
302 public static unsafe string FormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info)
304 char fmt = ParseFormatSpecifier(format, out int digits);
306 byte* pDigits = stackalloc byte[DecimalNumberBufferLength];
307 NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength);
309 DecimalToNumber(ref value, ref number);
311 char* stackPtr = stackalloc char[CharStackBufferSize];
312 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
314 if (fmt != 0)
316 NumberToString(ref sb, ref number, fmt, digits, info);
318 else
320 NumberToStringFormat(ref sb, ref number, format, info);
323 return sb.ToString();
326 public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
328 char fmt = ParseFormatSpecifier(format, out int digits);
330 byte* pDigits = stackalloc byte[DecimalNumberBufferLength];
331 NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength);
333 DecimalToNumber(ref value, ref number);
335 char* stackPtr = stackalloc char[CharStackBufferSize];
336 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
338 if (fmt != 0)
340 NumberToString(ref sb, ref number, fmt, digits, info);
342 else
344 NumberToStringFormat(ref sb, ref number, format, info);
347 return sb.TryCopyTo(destination, out charsWritten);
350 internal static unsafe void DecimalToNumber(ref decimal d, ref NumberBuffer number)
352 byte* buffer = number.GetDigitsPointer();
353 number.DigitsCount = DecimalPrecision;
354 number.IsNegative = d.IsNegative;
356 byte* p = buffer + DecimalPrecision;
357 while ((d.Mid | d.High) != 0)
359 p = UInt32ToDecChars(p, decimal.DecDivMod1E9(ref d), 9);
361 p = UInt32ToDecChars(p, d.Low, 0);
363 int i = (int)((buffer + DecimalPrecision) - p);
365 number.DigitsCount = i;
366 number.Scale = i - d.Scale;
368 byte* dst = number.GetDigitsPointer();
369 while (--i >= 0)
371 *dst++ = *p++;
373 *dst = (byte)('\0');
375 number.CheckConsistency();
378 public static string FormatDouble(double value, string? format, NumberFormatInfo info)
380 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
381 var sb = new ValueStringBuilder(stackBuffer);
382 return FormatDouble(ref sb, value, format, info) ?? sb.ToString();
385 public static bool TryFormatDouble(double value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
387 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
388 var sb = new ValueStringBuilder(stackBuffer);
389 string? s = FormatDouble(ref sb, value, format, info);
390 return s != null ?
391 TryCopyTo(s, destination, out charsWritten) :
392 sb.TryCopyTo(destination, out charsWritten);
395 private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int precision, NumberFormatInfo info, out bool isSignificantDigits)
397 if (fmt == 0)
399 isSignificantDigits = true;
400 return precision;
403 int maxDigits = precision;
405 switch (fmt)
407 case 'C':
408 case 'c':
410 // The currency format uses the precision specifier to indicate the number of
411 // decimal digits to format. This defaults to NumberFormatInfo.CurrencyDecimalDigits.
413 if (precision == -1)
415 precision = info.CurrencyDecimalDigits;
417 isSignificantDigits = false;
419 break;
422 case 'E':
423 case 'e':
425 // The exponential format uses the precision specifier to indicate the number of
426 // decimal digits to format. This defaults to 6. However, the exponential format
427 // also always formats a single integral digit, so we need to increase the precision
428 // specifier and treat it as the number of significant digits to account for this.
430 if (precision == -1)
432 precision = DefaultPrecisionExponentialFormat;
435 precision++;
436 isSignificantDigits = true;
438 break;
441 case 'F':
442 case 'f':
443 case 'N':
444 case 'n':
446 // The fixed-point and number formats use the precision specifier to indicate the number
447 // of decimal digits to format. This defaults to NumberFormatInfo.NumberDecimalDigits.
449 if (precision == -1)
451 precision = info.NumberDecimalDigits;
453 isSignificantDigits = false;
455 break;
458 case 'G':
459 case 'g':
461 // The general format uses the precision specifier to indicate the number of significant
462 // digits to format. This defaults to the shortest roundtrippable string. Additionally,
463 // given that we can't return zero significant digits, we treat 0 as returning the shortest
464 // roundtrippable string as well.
466 if (precision == 0)
468 precision = -1;
470 isSignificantDigits = true;
472 break;
475 case 'P':
476 case 'p':
478 // The percent format uses the precision specifier to indicate the number of
479 // decimal digits to format. This defaults to NumberFormatInfo.PercentDecimalDigits.
480 // However, the percent format also always multiplies the number by 100, so we need
481 // to increase the precision specifier to ensure we get the appropriate number of digits.
483 if (precision == -1)
485 precision = info.PercentDecimalDigits;
488 precision += 2;
489 isSignificantDigits = false;
491 break;
494 case 'R':
495 case 'r':
497 // The roundtrip format ignores the precision specifier and always returns the shortest
498 // roundtrippable string.
500 precision = -1;
501 isSignificantDigits = true;
503 break;
506 default:
508 throw new FormatException(SR.Argument_BadFormatSpecifier);
512 return maxDigits;
515 /// <summary>Formats the specified value according to the specified format and info.</summary>
516 /// <returns>
517 /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
518 /// Null if no existing string was returned, in which case the formatted output is in the builder.
519 /// </returns>
520 private static unsafe string? FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan<char> format, NumberFormatInfo info)
522 if (!double.IsFinite(value))
524 if (double.IsNaN(value))
526 return info.NaNSymbol;
529 return double.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
532 char fmt = ParseFormatSpecifier(format, out int precision);
533 byte* pDigits = stackalloc byte[DoubleNumberBufferLength];
535 if (fmt == '\0')
537 // For back-compat we currently specially treat the precision for custom
538 // format specifiers. The constant has more details as to why.
539 precision = DoublePrecisionCustomFormat;
542 NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength);
543 number.IsNegative = double.IsNegative(value);
545 // We need to track the original precision requested since some formats
546 // accept values like 0 and others may require additional fixups.
547 int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits);
549 if ((value != 0.0) && (!isSignificantDigits || !Grisu3.TryRunDouble(value, precision, ref number)))
551 Dragon4Double(value, precision, isSignificantDigits, ref number);
554 number.CheckConsistency();
556 // When the number is known to be roundtrippable (either because we requested it be, or
557 // because we know we have enough digits to satisfy roundtrippability), we should validate
558 // that the number actually roundtrips back to the original result.
560 Debug.Assert(((precision != -1) && (precision < DoublePrecision)) || (BitConverter.DoubleToInt64Bits(value) == BitConverter.DoubleToInt64Bits(NumberToDouble(ref number))));
562 if (fmt != 0)
564 if (precision == -1)
566 Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r'));
568 // For the roundtrip and general format specifiers, when returning the shortest roundtrippable
569 // string, we need to update the maximum number of digits to be the greater of number.DigitsCount
570 // or DoublePrecision. This ensures that we continue returning "pretty" strings for values with
571 // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01"
572 // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation.
574 nMaxDigits = Math.Max(number.DigitsCount, DoublePrecision);
576 NumberToString(ref sb, ref number, fmt, nMaxDigits, info);
578 else
580 Debug.Assert(precision == DoublePrecisionCustomFormat);
581 NumberToStringFormat(ref sb, ref number, format, info);
583 return null;
586 public static string FormatSingle(float value, string? format, NumberFormatInfo info)
588 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
589 var sb = new ValueStringBuilder(stackBuffer);
590 return FormatSingle(ref sb, value, format, info) ?? sb.ToString();
593 public static bool TryFormatSingle(float value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
595 Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
596 var sb = new ValueStringBuilder(stackBuffer);
597 string? s = FormatSingle(ref sb, value, format, info);
598 return s != null ?
599 TryCopyTo(s, destination, out charsWritten) :
600 sb.TryCopyTo(destination, out charsWritten);
603 /// <summary>Formats the specified value according to the specified format and info.</summary>
604 /// <returns>
605 /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
606 /// Null if no existing string was returned, in which case the formatted output is in the builder.
607 /// </returns>
608 private static unsafe string? FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan<char> format, NumberFormatInfo info)
610 if (!float.IsFinite(value))
612 if (float.IsNaN(value))
614 return info.NaNSymbol;
617 return float.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
620 char fmt = ParseFormatSpecifier(format, out int precision);
621 byte* pDigits = stackalloc byte[SingleNumberBufferLength];
623 if (fmt == '\0')
625 // For back-compat we currently specially treat the precision for custom
626 // format specifiers. The constant has more details as to why.
627 precision = SinglePrecisionCustomFormat;
630 NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength);
631 number.IsNegative = float.IsNegative(value);
633 // We need to track the original precision requested since some formats
634 // accept values like 0 and others may require additional fixups.
635 int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits);
637 if ((value != 0.0f) && (!isSignificantDigits || !Grisu3.TryRunSingle(value, precision, ref number)))
639 Dragon4Single(value, precision, isSignificantDigits, ref number);
642 number.CheckConsistency();
644 // When the number is known to be roundtrippable (either because we requested it be, or
645 // because we know we have enough digits to satisfy roundtrippability), we should validate
646 // that the number actually roundtrips back to the original result.
648 Debug.Assert(((precision != -1) && (precision < SinglePrecision)) || (BitConverter.SingleToInt32Bits(value) == BitConverter.SingleToInt32Bits(NumberToSingle(ref number))));
650 if (fmt != 0)
652 if (precision == -1)
654 Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r'));
656 // For the roundtrip and general format specifiers, when returning the shortest roundtrippable
657 // string, we need to update the maximum number of digits to be the greater of number.DigitsCount
658 // or SinglePrecision. This ensures that we continue returning "pretty" strings for values with
659 // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01"
660 // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation.
662 nMaxDigits = Math.Max(number.DigitsCount, SinglePrecision);
664 NumberToString(ref sb, ref number, fmt, nMaxDigits, info);
666 else
668 Debug.Assert(precision == SinglePrecisionCustomFormat);
669 NumberToStringFormat(ref sb, ref number, format, info);
671 return null;
674 private static bool TryCopyTo(string source, Span<char> destination, out int charsWritten)
676 Debug.Assert(source != null);
678 if (source.AsSpan().TryCopyTo(destination))
680 charsWritten = source.Length;
681 return true;
684 charsWritten = 0;
685 return false;
688 public static unsafe string FormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider? provider)
690 // Fast path for default format with a non-negative value
691 if (value >= 0 && format.Length == 0)
693 return UInt32ToDecStr((uint)value, digits: -1);
696 char fmt = ParseFormatSpecifier(format, out int digits);
697 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
698 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
700 return value >= 0 ?
701 UInt32ToDecStr((uint)value, digits) :
702 NegativeInt32ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign);
704 else if (fmtUpper == 'X')
706 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
707 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
708 return Int32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
710 else
712 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
714 byte* pDigits = stackalloc byte[Int32NumberBufferLength];
715 NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength);
717 Int32ToNumber(value, ref number);
719 char* stackPtr = stackalloc char[CharStackBufferSize];
720 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
722 if (fmt != 0)
724 NumberToString(ref sb, ref number, fmt, digits, info);
726 else
728 NumberToStringFormat(ref sb, ref number, format, info);
730 return sb.ToString();
734 public static unsafe bool TryFormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider? provider, Span<char> destination, out int charsWritten)
736 // Fast path for default format with a non-negative value
737 if (value >= 0 && format.Length == 0)
739 return TryUInt32ToDecStr((uint)value, digits: -1, destination, out charsWritten);
742 char fmt = ParseFormatSpecifier(format, out int digits);
743 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
744 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
746 return value >= 0 ?
747 TryUInt32ToDecStr((uint)value, digits, destination, out charsWritten) :
748 TryNegativeInt32ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten);
750 else if (fmtUpper == 'X')
752 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
753 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
754 return TryInt32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
756 else
758 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
760 byte* pDigits = stackalloc byte[Int32NumberBufferLength];
761 NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength);
763 Int32ToNumber(value, ref number);
765 char* stackPtr = stackalloc char[CharStackBufferSize];
766 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
768 if (fmt != 0)
770 NumberToString(ref sb, ref number, fmt, digits, info);
772 else
774 NumberToStringFormat(ref sb, ref number, format, info);
776 return sb.TryCopyTo(destination, out charsWritten);
780 public static unsafe string FormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider? provider)
782 // Fast path for default format
783 if (format.Length == 0)
785 return UInt32ToDecStr(value, digits: -1);
788 char fmt = ParseFormatSpecifier(format, out int digits);
789 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
790 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
792 return UInt32ToDecStr(value, digits);
794 else if (fmtUpper == 'X')
796 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
797 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
798 return Int32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits);
800 else
802 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
804 byte* pDigits = stackalloc byte[UInt32NumberBufferLength];
805 NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength);
807 UInt32ToNumber(value, ref number);
809 char* stackPtr = stackalloc char[CharStackBufferSize];
810 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
812 if (fmt != 0)
814 NumberToString(ref sb, ref number, fmt, digits, info);
816 else
818 NumberToStringFormat(ref sb, ref number, format, info);
820 return sb.ToString();
824 public static unsafe bool TryFormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider? provider, Span<char> destination, out int charsWritten)
826 // Fast path for default format
827 if (format.Length == 0)
829 return TryUInt32ToDecStr(value, digits: -1, destination, out charsWritten);
832 char fmt = ParseFormatSpecifier(format, out int digits);
833 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
834 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
836 return TryUInt32ToDecStr(value, digits, destination, out charsWritten);
838 else if (fmtUpper == 'X')
840 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
841 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
842 return TryInt32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
844 else
846 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
848 byte* pDigits = stackalloc byte[UInt32NumberBufferLength];
849 NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength);
851 UInt32ToNumber(value, ref number);
853 char* stackPtr = stackalloc char[CharStackBufferSize];
854 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
856 if (fmt != 0)
858 NumberToString(ref sb, ref number, fmt, digits, info);
860 else
862 NumberToStringFormat(ref sb, ref number, format, info);
864 return sb.TryCopyTo(destination, out charsWritten);
868 public static unsafe string FormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider? provider)
870 // Fast path for default format with a non-negative value
871 if (value >= 0 && format.Length == 0)
873 return UInt64ToDecStr((ulong)value, digits: -1);
876 char fmt = ParseFormatSpecifier(format, out int digits);
877 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
878 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
880 return value >= 0 ?
881 UInt64ToDecStr((ulong)value, digits) :
882 NegativeInt64ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign);
884 else if (fmtUpper == 'X')
886 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
887 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
888 // produces lowercase.
889 return Int64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
891 else
893 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
895 byte* pDigits = stackalloc byte[Int64NumberBufferLength];
896 NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength);
898 Int64ToNumber(value, ref number);
900 char* stackPtr = stackalloc char[CharStackBufferSize];
901 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
903 if (fmt != 0)
905 NumberToString(ref sb, ref number, fmt, digits, info);
907 else
909 NumberToStringFormat(ref sb, ref number, format, info);
911 return sb.ToString();
915 public static unsafe bool TryFormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider? provider, Span<char> destination, out int charsWritten)
917 // Fast path for default format with a non-negative value
918 if (value >= 0 && format.Length == 0)
920 return TryUInt64ToDecStr((ulong)value, digits: -1, destination, out charsWritten);
923 char fmt = ParseFormatSpecifier(format, out int digits);
924 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
925 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
927 return value >= 0 ?
928 TryUInt64ToDecStr((ulong)value, digits, destination, out charsWritten) :
929 TryNegativeInt64ToDecStr(value, digits, NumberFormatInfo.GetInstance(provider).NegativeSign, destination, out charsWritten);
931 else if (fmtUpper == 'X')
933 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
934 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
935 // produces lowercase.
936 return TryInt64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
938 else
940 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
942 byte* pDigits = stackalloc byte[Int64NumberBufferLength];
943 NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength);
945 Int64ToNumber(value, ref number);
947 char* stackPtr = stackalloc char[CharStackBufferSize];
948 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
950 if (fmt != 0)
952 NumberToString(ref sb, ref number, fmt, digits, info);
954 else
956 NumberToStringFormat(ref sb, ref number, format, info);
958 return sb.TryCopyTo(destination, out charsWritten);
962 public static unsafe string FormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider? provider)
964 // Fast path for default format
965 if (format.Length == 0)
967 return UInt64ToDecStr(value, digits: -1);
970 char fmt = ParseFormatSpecifier(format, out int digits);
971 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
972 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
974 return UInt64ToDecStr(value, digits);
976 else if (fmtUpper == 'X')
978 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
979 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
980 // produces lowercase.
981 return Int64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits);
983 else
985 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
987 byte* pDigits = stackalloc byte[UInt64NumberBufferLength];
988 NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength);
990 UInt64ToNumber(value, ref number);
992 char* stackPtr = stackalloc char[CharStackBufferSize];
993 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
995 if (fmt != 0)
997 NumberToString(ref sb, ref number, fmt, digits, info);
999 else
1001 NumberToStringFormat(ref sb, ref number, format, info);
1003 return sb.ToString();
1007 public static unsafe bool TryFormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider? provider, Span<char> destination, out int charsWritten)
1009 // Fast path for default format
1010 if (format.Length == 0)
1012 return TryUInt64ToDecStr(value, digits: -1, destination, out charsWritten);
1015 char fmt = ParseFormatSpecifier(format, out int digits);
1016 char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
1017 if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
1019 return TryUInt64ToDecStr(value, digits, destination, out charsWritten);
1021 else if (fmtUpper == 'X')
1023 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
1024 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
1025 // produces lowercase.
1026 return TryInt64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
1028 else
1030 NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
1032 byte* pDigits = stackalloc byte[UInt64NumberBufferLength];
1033 NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength);
1035 UInt64ToNumber(value, ref number);
1037 char* stackPtr = stackalloc char[CharStackBufferSize];
1038 ValueStringBuilder sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
1040 if (fmt != 0)
1042 NumberToString(ref sb, ref number, fmt, digits, info);
1044 else
1046 NumberToStringFormat(ref sb, ref number, format, info);
1048 return sb.TryCopyTo(destination, out charsWritten);
1052 [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
1053 private static unsafe void Int32ToNumber(int value, ref NumberBuffer number)
1055 number.DigitsCount = Int32Precision;
1057 if (value >= 0)
1059 number.IsNegative = false;
1061 else
1063 number.IsNegative = true;
1064 value = -value;
1067 byte* buffer = number.GetDigitsPointer();
1068 byte* p = UInt32ToDecChars(buffer + Int32Precision, (uint)value, 0);
1070 int i = (int)(buffer + Int32Precision - p);
1072 number.DigitsCount = i;
1073 number.Scale = i;
1075 byte* dst = number.GetDigitsPointer();
1076 while (--i >= 0)
1077 *dst++ = *p++;
1078 *dst = (byte)('\0');
1080 number.CheckConsistency();
1083 private static unsafe string NegativeInt32ToDecStr(int value, int digits, string sNegative)
1085 Debug.Assert(value < 0);
1087 if (digits < 1)
1088 digits = 1;
1090 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length;
1091 string result = string.FastAllocateString(bufferLength);
1092 fixed (char* buffer = result)
1094 char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
1095 Debug.Assert(p == buffer + sNegative.Length);
1097 for (int i = sNegative.Length - 1; i >= 0; i--)
1099 *(--p) = sNegative[i];
1101 Debug.Assert(p == buffer);
1103 return result;
1106 private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, string sNegative, Span<char> destination, out int charsWritten)
1108 Debug.Assert(value < 0);
1110 if (digits < 1)
1111 digits = 1;
1113 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((uint)(-value))) + sNegative.Length;
1114 if (bufferLength > destination.Length)
1116 charsWritten = 0;
1117 return false;
1120 charsWritten = bufferLength;
1121 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1123 char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
1124 Debug.Assert(p == buffer + sNegative.Length);
1126 for (int i = sNegative.Length - 1; i >= 0; i--)
1128 *(--p) = sNegative[i];
1130 Debug.Assert(p == buffer);
1132 return true;
1135 private static unsafe string Int32ToHexStr(int value, char hexBase, int digits)
1137 if (digits < 1)
1138 digits = 1;
1140 int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value));
1141 string result = string.FastAllocateString(bufferLength);
1142 fixed (char* buffer = result)
1144 char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
1145 Debug.Assert(p == buffer);
1147 return result;
1150 private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span<char> destination, out int charsWritten)
1152 if (digits < 1)
1153 digits = 1;
1155 int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((uint)value));
1156 if (bufferLength > destination.Length)
1158 charsWritten = 0;
1159 return false;
1162 charsWritten = bufferLength;
1163 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1165 char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
1166 Debug.Assert(p == buffer);
1168 return true;
1171 private static unsafe char* Int32ToHexChars(char* buffer, uint value, int hexBase, int digits)
1173 while (--digits >= 0 || value != 0)
1175 byte digit = (byte)(value & 0xF);
1176 *(--buffer) = (char)(digit + (digit < 10 ? (byte)'0' : hexBase));
1177 value >>= 4;
1179 return buffer;
1182 [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
1183 private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number)
1185 number.DigitsCount = UInt32Precision;
1186 number.IsNegative = false;
1188 byte* buffer = number.GetDigitsPointer();
1189 byte* p = UInt32ToDecChars(buffer + UInt32Precision, value, 0);
1191 int i = (int)(buffer + UInt32Precision - p);
1193 number.DigitsCount = i;
1194 number.Scale = i;
1196 byte* dst = number.GetDigitsPointer();
1197 while (--i >= 0)
1198 *dst++ = *p++;
1199 *dst = (byte)('\0');
1201 number.CheckConsistency();
1204 internal static unsafe byte* UInt32ToDecChars(byte* bufferEnd, uint value, int digits)
1206 while (--digits >= 0 || value != 0)
1208 // TODO https://github.com/dotnet/coreclr/issues/3439
1209 uint newValue = value / 10;
1210 *(--bufferEnd) = (byte)(value - (newValue * 10) + '0');
1211 value = newValue;
1213 return bufferEnd;
1216 internal static unsafe char* UInt32ToDecChars(char* bufferEnd, uint value, int digits)
1218 while (--digits >= 0 || value != 0)
1220 // TODO https://github.com/dotnet/coreclr/issues/3439
1221 uint newValue = value / 10;
1222 *(--bufferEnd) = (char)(value - (newValue * 10) + '0');
1223 value = newValue;
1225 return bufferEnd;
1228 private static unsafe string UInt32ToDecStr(uint value, int digits)
1230 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
1232 // For single-digit values that are very common, especially 0 and 1, just return cached strings.
1233 if (bufferLength == 1)
1235 return s_singleDigitStringCache[value];
1238 string result = string.FastAllocateString(bufferLength);
1239 fixed (char* buffer = result)
1241 char* p = buffer + bufferLength;
1242 if (digits <= 1)
1246 // TODO https://github.com/dotnet/coreclr/issues/3439
1247 uint div = value / 10;
1248 *(--p) = (char)('0' + value - (div * 10));
1249 value = div;
1251 while (value != 0);
1253 else
1255 p = UInt32ToDecChars(p, value, digits);
1257 Debug.Assert(p == buffer);
1259 return result;
1262 private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span<char> destination, out int charsWritten)
1264 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
1265 if (bufferLength > destination.Length)
1267 charsWritten = 0;
1268 return false;
1271 charsWritten = bufferLength;
1272 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1274 char* p = buffer + bufferLength;
1275 if (digits <= 1)
1279 // TODO https://github.com/dotnet/coreclr/issues/3439
1280 uint div = value / 10;
1281 *(--p) = (char)('0' + value - (div * 10));
1282 value = div;
1284 while (value != 0);
1286 else
1288 p = UInt32ToDecChars(p, value, digits);
1290 Debug.Assert(p == buffer);
1292 return true;
1295 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1296 private static unsafe bool TryCopyTo(char* src, int length, Span<char> destination, out int charsWritten)
1298 if (new ReadOnlySpan<char>(src, length).TryCopyTo(destination))
1300 charsWritten = length;
1301 return true;
1303 else
1305 charsWritten = 0;
1306 return false;
1310 private static unsafe void Int64ToNumber(long input, ref NumberBuffer number)
1312 ulong value = (ulong)input;
1313 number.IsNegative = input < 0;
1314 number.DigitsCount = Int64Precision;
1315 if (number.IsNegative)
1317 value = (ulong)(-input);
1320 byte* buffer = number.GetDigitsPointer();
1321 byte* p = buffer + Int64Precision;
1322 while (High32(value) != 0)
1323 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1324 p = UInt32ToDecChars(p, Low32(value), 0);
1326 int i = (int)(buffer + Int64Precision - p);
1328 number.DigitsCount = i;
1329 number.Scale = i;
1331 byte* dst = number.GetDigitsPointer();
1332 while (--i >= 0)
1333 *dst++ = *p++;
1334 *dst = (byte)('\0');
1336 number.CheckConsistency();
1339 private static unsafe string NegativeInt64ToDecStr(long input, int digits, string sNegative)
1341 Debug.Assert(input < 0);
1343 if (digits < 1)
1345 digits = 1;
1348 ulong value = (ulong)(-input);
1350 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value)) + sNegative.Length;
1351 string result = string.FastAllocateString(bufferLength);
1352 fixed (char* buffer = result)
1354 char* p = buffer + bufferLength;
1355 while (High32(value) != 0)
1357 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1358 digits -= 9;
1360 p = UInt32ToDecChars(p, Low32(value), digits);
1361 Debug.Assert(p == buffer + sNegative.Length);
1363 for (int i = sNegative.Length - 1; i >= 0; i--)
1365 *(--p) = sNegative[i];
1367 Debug.Assert(p == buffer);
1369 return result;
1372 private static unsafe bool TryNegativeInt64ToDecStr(long input, int digits, string sNegative, Span<char> destination, out int charsWritten)
1374 Debug.Assert(input < 0);
1376 if (digits < 1)
1378 digits = 1;
1381 ulong value = (ulong)(-input);
1383 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits((ulong)(-input))) + sNegative.Length;
1384 if (bufferLength > destination.Length)
1386 charsWritten = 0;
1387 return false;
1390 charsWritten = bufferLength;
1391 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1393 char* p = buffer + bufferLength;
1394 while (High32(value) != 0)
1396 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1397 digits -= 9;
1399 p = UInt32ToDecChars(p, Low32(value), digits);
1400 Debug.Assert(p == buffer + sNegative.Length);
1402 for (int i = sNegative.Length - 1; i >= 0; i--)
1404 *(--p) = sNegative[i];
1406 Debug.Assert(p == buffer);
1408 return true;
1411 private static unsafe string Int64ToHexStr(long value, char hexBase, int digits)
1413 int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value));
1414 string result = string.FastAllocateString(bufferLength);
1415 fixed (char* buffer = result)
1417 char* p = buffer + bufferLength;
1418 if (High32((ulong)value) != 0)
1420 p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8);
1421 p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
1423 else
1425 p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1));
1427 Debug.Assert(p == buffer);
1429 return result;
1432 private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span<char> destination, out int charsWritten)
1434 int bufferLength = Math.Max(digits, FormattingHelpers.CountHexDigits((ulong)value));
1435 if (bufferLength > destination.Length)
1437 charsWritten = 0;
1438 return false;
1441 charsWritten = bufferLength;
1442 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1444 char* p = buffer + bufferLength;
1445 if (High32((ulong)value) != 0)
1447 p = Int32ToHexChars(p, Low32((ulong)value), hexBase, 8);
1448 p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
1450 else
1452 p = Int32ToHexChars(p, Low32((ulong)value), hexBase, Math.Max(digits, 1));
1454 Debug.Assert(p == buffer);
1456 return true;
1459 private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number)
1461 number.DigitsCount = UInt64Precision;
1462 number.IsNegative = false;
1464 byte* buffer = number.GetDigitsPointer();
1465 byte* p = buffer + UInt64Precision;
1467 while (High32(value) != 0)
1468 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1469 p = UInt32ToDecChars(p, Low32(value), 0);
1471 int i = (int)(buffer + UInt64Precision - p);
1473 number.DigitsCount = i;
1474 number.Scale = i;
1476 byte* dst = number.GetDigitsPointer();
1477 while (--i >= 0)
1478 *dst++ = *p++;
1479 *dst = (byte)('\0');
1481 number.CheckConsistency();
1484 private static unsafe string UInt64ToDecStr(ulong value, int digits)
1486 if (digits < 1)
1487 digits = 1;
1489 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
1491 // For single-digit values that are very common, especially 0 and 1, just return cached strings.
1492 if (bufferLength == 1)
1494 return s_singleDigitStringCache[value];
1497 string result = string.FastAllocateString(bufferLength);
1498 fixed (char* buffer = result)
1500 char* p = buffer + bufferLength;
1501 while (High32(value) != 0)
1503 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1504 digits -= 9;
1506 p = UInt32ToDecChars(p, Low32(value), digits);
1507 Debug.Assert(p == buffer);
1509 return result;
1512 private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span<char> destination, out int charsWritten)
1514 if (digits < 1)
1515 digits = 1;
1517 int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
1518 if (bufferLength > destination.Length)
1520 charsWritten = 0;
1521 return false;
1524 charsWritten = bufferLength;
1525 fixed (char* buffer = &MemoryMarshal.GetReference(destination))
1527 char* p = buffer + bufferLength;
1528 while (High32(value) != 0)
1530 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1531 digits -= 9;
1533 p = UInt32ToDecChars(p, Low32(value), digits);
1534 Debug.Assert(p == buffer);
1536 return true;
1539 internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits)
1541 char c = default;
1542 if (format.Length > 0)
1544 // If the format begins with a symbol, see if it's a standard format
1545 // with or without a specified number of digits.
1546 c = format[0];
1547 if ((uint)(c - 'A') <= 'Z' - 'A' ||
1548 (uint)(c - 'a') <= 'z' - 'a')
1550 // Fast path for sole symbol, e.g. "D"
1551 if (format.Length == 1)
1553 digits = -1;
1554 return c;
1557 if (format.Length == 2)
1559 // Fast path for symbol and single digit, e.g. "X4"
1560 int d = format[1] - '0';
1561 if ((uint)d < 10)
1563 digits = d;
1564 return c;
1567 else if (format.Length == 3)
1569 // Fast path for symbol and double digit, e.g. "F12"
1570 int d1 = format[1] - '0', d2 = format[2] - '0';
1571 if ((uint)d1 < 10 && (uint)d2 < 10)
1573 digits = d1 * 10 + d2;
1574 return c;
1578 // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
1579 // but it can begin with any number of 0s, and thus we may need to check more than two
1580 // digits. Further, for compat, we need to stop when we hit a null char.
1581 int n = 0;
1582 int i = 1;
1583 while (i < format.Length && (((uint)format[i] - '0') < 10) && n < 10)
1585 n = (n * 10) + format[i++] - '0';
1588 // If we're at the end of the digits rather than having stopped because we hit something
1589 // other than a digit or overflowed, return the standard format info.
1590 if (i == format.Length || format[i] == '\0')
1592 digits = n;
1593 return c;
1598 // Default empty format to be "G"; custom format is signified with '\0'.
1599 digits = -1;
1600 return format.Length == 0 || c == '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it.
1601 'G' :
1602 '\0';
1605 internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info)
1607 number.CheckConsistency();
1608 bool isCorrectlyRounded = (number.Kind == NumberBufferKind.FloatingPoint);
1610 switch (format)
1612 case 'C':
1613 case 'c':
1615 if (nMaxDigits < 0)
1616 nMaxDigits = info.CurrencyDecimalDigits;
1618 RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded); // Don't change this line to use digPos since digCount could have its sign changed.
1620 FormatCurrency(ref sb, ref number, nMaxDigits, info);
1622 break;
1625 case 'F':
1626 case 'f':
1628 if (nMaxDigits < 0)
1629 nMaxDigits = info.NumberDecimalDigits;
1631 RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded);
1633 if (number.IsNegative)
1634 sb.Append(info.NegativeSign);
1636 FormatFixed(ref sb, ref number, nMaxDigits, null, info.NumberDecimalSeparator, null);
1638 break;
1641 case 'N':
1642 case 'n':
1644 if (nMaxDigits < 0)
1645 nMaxDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation
1647 RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded);
1649 FormatNumber(ref sb, ref number, nMaxDigits, info);
1651 break;
1654 case 'E':
1655 case 'e':
1657 if (nMaxDigits < 0)
1658 nMaxDigits = DefaultPrecisionExponentialFormat;
1659 nMaxDigits++;
1661 RoundNumber(ref number, nMaxDigits, isCorrectlyRounded);
1663 if (number.IsNegative)
1664 sb.Append(info.NegativeSign);
1666 FormatScientific(ref sb, ref number, nMaxDigits, info, format);
1668 break;
1671 case 'G':
1672 case 'g':
1674 bool noRounding = false;
1675 if (nMaxDigits < 1)
1677 if ((number.Kind == NumberBufferKind.Decimal) && (nMaxDigits == -1))
1679 noRounding = true; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant
1681 if (number.Digits[0] == 0)
1683 // -0 should be formatted as 0 for decimal. This is normally handled by RoundNumber (which we are skipping)
1684 goto SkipSign;
1687 goto SkipRounding;
1689 else
1691 // This ensures that the PAL code pads out to the correct place even when we use the default precision
1692 nMaxDigits = number.DigitsCount;
1696 RoundNumber(ref number, nMaxDigits, isCorrectlyRounded);
1698 SkipRounding:
1699 if (number.IsNegative)
1700 sb.Append(info.NegativeSign);
1702 SkipSign:
1703 FormatGeneral(ref sb, ref number, nMaxDigits, info, (char)(format - ('G' - 'E')), noRounding);
1705 break;
1708 case 'P':
1709 case 'p':
1711 if (nMaxDigits < 0)
1712 nMaxDigits = info.PercentDecimalDigits;
1713 number.Scale += 2;
1715 RoundNumber(ref number, number.Scale + nMaxDigits, isCorrectlyRounded);
1717 FormatPercent(ref sb, ref number, nMaxDigits, info);
1719 break;
1722 case 'R':
1723 case 'r':
1725 if (number.Kind != NumberBufferKind.FloatingPoint)
1727 goto default;
1730 format = (char)(format - ('R' - 'G'));
1731 Debug.Assert((format == 'G') || (format == 'g'));
1732 goto case 'G';
1735 default:
1736 throw new FormatException(SR.Argument_BadFormatSpecifier);
1740 internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref NumberBuffer number, ReadOnlySpan<char> format, NumberFormatInfo info)
1742 number.CheckConsistency();
1744 int digitCount;
1745 int decimalPos;
1746 int firstDigit;
1747 int lastDigit;
1748 int digPos;
1749 bool scientific;
1750 int thousandPos;
1751 int thousandCount = 0;
1752 bool thousandSeps;
1753 int scaleAdjust;
1754 int adjust;
1756 int section;
1757 int src;
1758 byte* dig = number.GetDigitsPointer();
1759 char ch;
1761 section = FindSection(format, dig[0] == 0 ? 2 : number.IsNegative ? 1 : 0);
1763 while (true)
1765 digitCount = 0;
1766 decimalPos = -1;
1767 firstDigit = 0x7FFFFFFF;
1768 lastDigit = 0;
1769 scientific = false;
1770 thousandPos = -1;
1771 thousandSeps = false;
1772 scaleAdjust = 0;
1773 src = section;
1775 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
1777 while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
1779 switch (ch)
1781 case '#':
1782 digitCount++;
1783 break;
1784 case '0':
1785 if (firstDigit == 0x7FFFFFFF)
1786 firstDigit = digitCount;
1787 digitCount++;
1788 lastDigit = digitCount;
1789 break;
1790 case '.':
1791 if (decimalPos < 0)
1792 decimalPos = digitCount;
1793 break;
1794 case ',':
1795 if (digitCount > 0 && decimalPos < 0)
1797 if (thousandPos >= 0)
1799 if (thousandPos == digitCount)
1801 thousandCount++;
1802 break;
1804 thousandSeps = true;
1806 thousandPos = digitCount;
1807 thousandCount = 1;
1809 break;
1810 case '%':
1811 scaleAdjust += 2;
1812 break;
1813 case '\x2030':
1814 scaleAdjust += 3;
1815 break;
1816 case '\'':
1817 case '"':
1818 while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
1820 break;
1821 case '\\':
1822 if (src < format.Length && pFormat[src] != 0)
1823 src++;
1824 break;
1825 case 'E':
1826 case 'e':
1827 if ((src < format.Length && pFormat[src] == '0') ||
1828 (src + 1 < format.Length && (pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0'))
1830 while (++src < format.Length && pFormat[src] == '0')
1832 scientific = true;
1834 break;
1839 if (decimalPos < 0)
1840 decimalPos = digitCount;
1842 if (thousandPos >= 0)
1844 if (thousandPos == decimalPos)
1845 scaleAdjust -= thousandCount * 3;
1846 else
1847 thousandSeps = true;
1850 if (dig[0] != 0)
1852 number.Scale += scaleAdjust;
1853 int pos = scientific ? digitCount : number.Scale + digitCount - decimalPos;
1854 RoundNumber(ref number, pos, isCorrectlyRounded: false);
1855 if (dig[0] == 0)
1857 src = FindSection(format, 2);
1858 if (src != section)
1860 section = src;
1861 continue;
1865 else
1867 if (number.Kind != NumberBufferKind.FloatingPoint)
1869 // The integer types don't have a concept of -0 and decimal always format -0 as 0
1870 number.IsNegative = false;
1872 number.Scale = 0; // Decimals with scale ('0.00') should be rounded.
1875 break;
1878 firstDigit = firstDigit < decimalPos ? decimalPos - firstDigit : 0;
1879 lastDigit = lastDigit > decimalPos ? decimalPos - lastDigit : 0;
1880 if (scientific)
1882 digPos = decimalPos;
1883 adjust = 0;
1885 else
1887 digPos = number.Scale > decimalPos ? number.Scale : decimalPos;
1888 adjust = number.Scale - decimalPos;
1890 src = section;
1892 // Adjust can be negative, so we make this an int instead of an unsigned int.
1893 // Adjust represents the number of characters over the formatting e.g. format string is "0000" and you are trying to
1894 // format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
1895 // -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
1896 Span<int> thousandsSepPos = stackalloc int[4];
1897 int thousandsSepCtr = -1;
1899 if (thousandSeps)
1901 // We need to precompute this outside the number formatting loop
1902 if (info.NumberGroupSeparator.Length > 0)
1904 // We need this array to figure out where to insert the thousands separator. We would have to traverse the string
1905 // backwards. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
1906 // the thousands separator so we can get away with traversing forwards. Note we only have to compute up to digPos.
1907 // The max is not bound since you can have formatting strings of the form "000,000..", and this
1908 // should handle that case too.
1910 int[] groupDigits = info._numberGroupSizes;
1912 int groupSizeIndex = 0; // Index into the groupDigits array.
1913 int groupTotalSizeCount = 0;
1914 int groupSizeLen = groupDigits.Length; // The length of groupDigits array.
1915 if (groupSizeLen != 0)
1916 groupTotalSizeCount = groupDigits[groupSizeIndex]; // The current running total of group size.
1917 int groupSize = groupTotalSizeCount;
1919 int totalDigits = digPos + ((adjust < 0) ? adjust : 0); // Actual number of digits in o/p
1920 int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits;
1921 while (numDigits > groupTotalSizeCount)
1923 if (groupSize == 0)
1924 break;
1925 ++thousandsSepCtr;
1926 if (thousandsSepCtr >= thousandsSepPos.Length)
1928 var newThousandsSepPos = new int[thousandsSepPos.Length * 2];
1929 thousandsSepPos.CopyTo(newThousandsSepPos);
1930 thousandsSepPos = newThousandsSepPos;
1933 thousandsSepPos[thousandsSepCtr] = groupTotalSizeCount;
1934 if (groupSizeIndex < groupSizeLen - 1)
1936 groupSizeIndex++;
1937 groupSize = groupDigits[groupSizeIndex];
1939 groupTotalSizeCount += groupSize;
1944 if (number.IsNegative && (section == 0) && (number.Scale != 0))
1945 sb.Append(info.NegativeSign);
1947 bool decimalWritten = false;
1949 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
1951 byte* cur = dig;
1953 while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
1955 if (adjust > 0)
1957 switch (ch)
1959 case '#':
1960 case '0':
1961 case '.':
1962 while (adjust > 0)
1964 // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
1965 // the character after which the groupSeparator needs to be appended.
1966 sb.Append(*cur != 0 ? (char)(*cur++) : '0');
1967 if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
1969 if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
1971 sb.Append(info.NumberGroupSeparator);
1972 thousandsSepCtr--;
1975 digPos--;
1976 adjust--;
1978 break;
1982 switch (ch)
1984 case '#':
1985 case '0':
1987 if (adjust < 0)
1989 adjust++;
1990 ch = digPos <= firstDigit ? '0' : '\0';
1992 else
1994 ch = *cur != 0 ? (char)(*cur++) : digPos > lastDigit ? '0' : '\0';
1996 if (ch != 0)
1998 sb.Append(ch);
1999 if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
2001 if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
2003 sb.Append(info.NumberGroupSeparator);
2004 thousandsSepCtr--;
2009 digPos--;
2010 break;
2012 case '.':
2014 if (digPos != 0 || decimalWritten)
2016 // For compatibility, don't echo repeated decimals
2017 break;
2019 // If the format has trailing zeros or the format has a decimal and digits remain
2020 if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0))
2022 sb.Append(info.NumberDecimalSeparator);
2023 decimalWritten = true;
2025 break;
2027 case '\x2030':
2028 sb.Append(info.PerMilleSymbol);
2029 break;
2030 case '%':
2031 sb.Append(info.PercentSymbol);
2032 break;
2033 case ',':
2034 break;
2035 case '\'':
2036 case '"':
2037 while (src < format.Length && pFormat[src] != 0 && pFormat[src] != ch)
2038 sb.Append(pFormat[src++]);
2039 if (src < format.Length && pFormat[src] != 0)
2040 src++;
2041 break;
2042 case '\\':
2043 if (src < format.Length && pFormat[src] != 0)
2044 sb.Append(pFormat[src++]);
2045 break;
2046 case 'E':
2047 case 'e':
2049 bool positiveSign = false;
2050 int i = 0;
2051 if (scientific)
2053 if (src < format.Length && pFormat[src] == '0')
2055 // Handles E0, which should format the same as E-0
2056 i++;
2058 else if (src + 1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0')
2060 // Handles E+0
2061 positiveSign = true;
2063 else if (src + 1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0')
2065 // Handles E-0
2066 // Do nothing, this is just a place holder s.t. we don't break out of the loop.
2068 else
2070 sb.Append(ch);
2071 break;
2074 while (++src < format.Length && pFormat[src] == '0')
2075 i++;
2076 if (i > 10)
2077 i = 10;
2079 int exp = dig[0] == 0 ? 0 : number.Scale - decimalPos;
2080 FormatExponent(ref sb, info, exp, ch, i, positiveSign);
2081 scientific = false;
2083 else
2085 sb.Append(ch); // Copy E or e to output
2086 if (src < format.Length)
2088 if (pFormat[src] == '+' || pFormat[src] == '-')
2089 sb.Append(pFormat[src++]);
2090 while (src < format.Length && pFormat[src] == '0')
2091 sb.Append(pFormat[src++]);
2094 break;
2096 default:
2097 sb.Append(ch);
2098 break;
2103 if (number.IsNegative && (section == 0) && (number.Scale == 0) && (sb.Length > 0))
2104 sb.Insert(0, info.NegativeSign);
2107 private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
2109 string fmt = number.IsNegative ?
2110 s_negCurrencyFormats[info.CurrencyNegativePattern] :
2111 s_posCurrencyFormats[info.CurrencyPositivePattern];
2113 foreach (char ch in fmt)
2115 switch (ch)
2117 case '#':
2118 FormatFixed(ref sb, ref number, nMaxDigits, info._currencyGroupSizes, info.CurrencyDecimalSeparator, info.CurrencyGroupSeparator);
2119 break;
2120 case '-':
2121 sb.Append(info.NegativeSign);
2122 break;
2123 case '$':
2124 sb.Append(info.CurrencySymbol);
2125 break;
2126 default:
2127 sb.Append(ch);
2128 break;
2133 private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, int[]? groupDigits, string? sDecimal, string? sGroup)
2135 int digPos = number.Scale;
2136 byte* dig = number.GetDigitsPointer();
2138 if (digPos > 0)
2140 if (groupDigits != null)
2142 Debug.Assert(sGroup != null, "Must be nulll when groupDigits != null");
2143 int groupSizeIndex = 0; // Index into the groupDigits array.
2144 int bufferSize = digPos; // The length of the result buffer string.
2145 int groupSize = 0; // The current group size.
2147 // Find out the size of the string buffer for the result.
2148 if (groupDigits.Length != 0) // You can pass in 0 length arrays
2150 int groupSizeCount = groupDigits[groupSizeIndex]; // The current total of group size.
2152 while (digPos > groupSizeCount)
2154 groupSize = groupDigits[groupSizeIndex];
2155 if (groupSize == 0)
2156 break;
2158 bufferSize += sGroup.Length;
2159 if (groupSizeIndex < groupDigits.Length - 1)
2160 groupSizeIndex++;
2162 groupSizeCount += groupDigits[groupSizeIndex];
2163 if (groupSizeCount < 0 || bufferSize < 0)
2164 throw new ArgumentOutOfRangeException(); // If we overflow
2167 groupSize = groupSizeCount == 0 ? 0 : groupDigits[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0
2170 groupSizeIndex = 0;
2171 int digitCount = 0;
2172 int digLength = number.DigitsCount;
2173 int digStart = (digPos < digLength) ? digPos : digLength;
2174 fixed (char* spanPtr = &MemoryMarshal.GetReference(sb.AppendSpan(bufferSize)))
2176 char* p = spanPtr + bufferSize - 1;
2177 for (int i = digPos - 1; i >= 0; i--)
2179 *(p--) = (i < digStart) ? (char)(dig[i]) : '0';
2181 if (groupSize > 0)
2183 digitCount++;
2184 if ((digitCount == groupSize) && (i != 0))
2186 for (int j = sGroup.Length - 1; j >= 0; j--)
2187 *(p--) = sGroup[j];
2189 if (groupSizeIndex < groupDigits.Length - 1)
2191 groupSizeIndex++;
2192 groupSize = groupDigits[groupSizeIndex];
2194 digitCount = 0;
2199 Debug.Assert(p >= spanPtr - 1, "Underflow");
2200 dig += digStart;
2203 else
2207 sb.Append(*dig != 0 ? (char)(*dig++) : '0');
2209 while (--digPos > 0);
2212 else
2214 sb.Append('0');
2217 if (nMaxDigits > 0)
2219 Debug.Assert(sDecimal != null);
2220 sb.Append(sDecimal);
2221 if ((digPos < 0) && (nMaxDigits > 0))
2223 int zeroes = Math.Min(-digPos, nMaxDigits);
2224 sb.Append('0', zeroes);
2225 digPos += zeroes;
2226 nMaxDigits -= zeroes;
2229 while (nMaxDigits > 0)
2231 sb.Append((*dig != 0) ? (char)(*dig++) : '0');
2232 nMaxDigits--;
2237 private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
2239 string fmt = number.IsNegative ?
2240 s_negNumberFormats[info.NumberNegativePattern] :
2241 PosNumberFormat;
2243 foreach (char ch in fmt)
2245 switch (ch)
2247 case '#':
2248 FormatFixed(ref sb, ref number, nMaxDigits, info._numberGroupSizes, info.NumberDecimalSeparator, info.NumberGroupSeparator);
2249 break;
2250 case '-':
2251 sb.Append(info.NegativeSign);
2252 break;
2253 default:
2254 sb.Append(ch);
2255 break;
2260 private static unsafe void FormatScientific(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar)
2262 byte* dig = number.GetDigitsPointer();
2264 sb.Append((*dig != 0) ? (char)(*dig++) : '0');
2266 if (nMaxDigits != 1) // For E0 we would like to suppress the decimal point
2267 sb.Append(info.NumberDecimalSeparator);
2269 while (--nMaxDigits > 0)
2270 sb.Append((*dig != 0) ? (char)(*dig++) : '0');
2272 int e = number.Digits[0] == 0 ? 0 : number.Scale - 1;
2273 FormatExponent(ref sb, info, e, expChar, 3, true);
2276 private static unsafe void FormatExponent(ref ValueStringBuilder sb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign)
2278 sb.Append(expChar);
2280 if (value < 0)
2282 sb.Append(info.NegativeSign);
2283 value = -value;
2285 else
2287 if (positiveSign)
2288 sb.Append(info.PositiveSign);
2291 char* digits = stackalloc char[MaxUInt32DecDigits];
2292 char* p = UInt32ToDecChars(digits + MaxUInt32DecDigits, (uint)value, minDigits);
2293 int i = (int)(digits + MaxUInt32DecDigits - p);
2294 sb.Append(p, (int)(digits + MaxUInt32DecDigits - p));
2297 private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info, char expChar, bool bSuppressScientific)
2299 int digPos = number.Scale;
2300 bool scientific = false;
2302 if (!bSuppressScientific)
2304 // Don't switch to scientific notation
2305 if (digPos > nMaxDigits || digPos < -3)
2307 digPos = 1;
2308 scientific = true;
2312 byte* dig = number.GetDigitsPointer();
2314 if (digPos > 0)
2318 sb.Append((*dig != 0) ? (char)(*dig++) : '0');
2319 } while (--digPos > 0);
2321 else
2323 sb.Append('0');
2326 if (*dig != 0 || digPos < 0)
2328 sb.Append(info.NumberDecimalSeparator);
2330 while (digPos < 0)
2332 sb.Append('0');
2333 digPos++;
2336 while (*dig != 0)
2337 sb.Append((char)(*dig++));
2340 if (scientific)
2341 FormatExponent(ref sb, info, number.Scale - 1, expChar, 2, true);
2344 private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer number, int nMaxDigits, NumberFormatInfo info)
2346 string fmt = number.IsNegative ?
2347 s_negPercentFormats[info.PercentNegativePattern] :
2348 s_posPercentFormats[info.PercentPositivePattern];
2350 foreach (char ch in fmt)
2352 switch (ch)
2354 case '#':
2355 FormatFixed(ref sb, ref number, nMaxDigits, info._percentGroupSizes, info.PercentDecimalSeparator, info.PercentGroupSeparator);
2356 break;
2357 case '-':
2358 sb.Append(info.NegativeSign);
2359 break;
2360 case '%':
2361 sb.Append(info.PercentSymbol);
2362 break;
2363 default:
2364 sb.Append(ch);
2365 break;
2370 internal static unsafe void RoundNumber(ref NumberBuffer number, int pos, bool isCorrectlyRounded)
2372 byte* dig = number.GetDigitsPointer();
2374 int i = 0;
2375 while (i < pos && dig[i] != '\0')
2376 i++;
2378 if ((i == pos) && ShouldRoundUp(dig, i, number.Kind, isCorrectlyRounded))
2380 while (i > 0 && dig[i - 1] == '9')
2381 i--;
2383 if (i > 0)
2385 dig[i - 1]++;
2387 else
2389 number.Scale++;
2390 dig[0] = (byte)('1');
2391 i = 1;
2394 else
2396 while (i > 0 && dig[i - 1] == '0')
2397 i--;
2400 if (i == 0)
2402 if (number.Kind != NumberBufferKind.FloatingPoint)
2404 // The integer types don't have a concept of -0 and decimal always format -0 as 0
2405 number.IsNegative = false;
2407 number.Scale = 0; // Decimals with scale ('0.00') should be rounded.
2410 dig[i] = (byte)('\0');
2411 number.DigitsCount = i;
2412 number.CheckConsistency();
2414 static bool ShouldRoundUp(byte* dig, int i, NumberBufferKind numberKind, bool isCorrectlyRounded)
2416 // We only want to round up if the digit is greater than or equal to 5 and we are
2417 // not rounding a floating-point number. If we are rounding a floating-point number
2418 // we have one of two cases.
2420 // In the case of a standard numeric-format specifier, the exact and correctly rounded
2421 // string will have been produced. In this scenario, pos will have pointed to the
2422 // terminating null for the buffer and so this will return false.
2424 // However, in the case of a custom numeric-format specifier, we currently fall back
2425 // to generating Single/DoublePrecisionCustomFormat digits and then rely on this
2426 // function to round correctly instead. This can unfortunately lead to double-rounding
2427 // bugs but is the best we have right now due to back-compat concerns.
2429 byte digit = dig[i];
2431 if ((digit == '\0') || isCorrectlyRounded)
2433 // Fast path for the common case with no rounding
2434 return false;
2437 // Values greater than or equal to 5 should round up, otherwise we round down. The IEEE
2438 // 754 spec actually dictates that ties (exactly 5) should round to the nearest even number
2439 // but that can have undesired behavior for custom numeric format strings. This probably
2440 // needs further thought for .NET 5 so that we can be spec compliant and so that users
2441 // can get the desired rounding behavior for their needs.
2443 return (digit >= '5');
2447 private static unsafe int FindSection(ReadOnlySpan<char> format, int section)
2449 int src;
2450 char ch;
2452 if (section == 0)
2453 return 0;
2455 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
2457 src = 0;
2458 for (; ; )
2460 if (src >= format.Length)
2462 return 0;
2465 switch (ch = pFormat[src++])
2467 case '\'':
2468 case '"':
2469 while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
2471 break;
2472 case '\\':
2473 if (src < format.Length && pFormat[src] != 0)
2474 src++;
2475 break;
2476 case ';':
2477 if (--section != 0)
2478 break;
2479 if (src < format.Length && pFormat[src] != 0 && pFormat[src] != ';')
2480 return src;
2481 goto case '\0';
2482 case '\0':
2483 return 0;
2489 private static uint Low32(ulong value) => (uint)value;
2491 private static uint High32(ulong value) => (uint)((value & 0xFFFFFFFF00000000) >> 32);
2493 private static uint Int64DivMod1E9(ref ulong value)
2495 uint rem = (uint)(value % 1000000000);
2496 value /= 1000000000;
2497 return rem;
2500 private static ulong ExtractFractionAndBiasedExponent(double value, out int exponent)
2502 ulong bits = (ulong)(BitConverter.DoubleToInt64Bits(value));
2503 ulong fraction = (bits & 0xFFFFFFFFFFFFF);
2504 exponent = ((int)(bits >> 52) & 0x7FF);
2506 if (exponent != 0)
2508 // For normalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
2509 // value = 1.fraction * 2^(exp - 1023)
2510 // = (1 + mantissa / 2^52) * 2^(exp - 1023)
2511 // = (2^52 + mantissa) * 2^(exp - 1023 - 52)
2513 // So f = (2^52 + mantissa), e = exp - 1075;
2515 fraction |= (1UL << 52);
2516 exponent -= 1075;
2518 else
2520 // For denormalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
2521 // value = 0.fraction * 2^(1 - 1023)
2522 // = (mantissa / 2^52) * 2^(-1022)
2523 // = mantissa * 2^(-1022 - 52)
2524 // = mantissa * 2^(-1074)
2525 // So f = mantissa, e = -1074
2526 exponent = -1074;
2529 return fraction;
2532 private static uint ExtractFractionAndBiasedExponent(float value, out int exponent)
2534 uint bits = (uint)(BitConverter.SingleToInt32Bits(value));
2535 uint fraction = (bits & 0x7FFFFF);
2536 exponent = ((int)(bits >> 23) & 0xFF);
2538 if (exponent != 0)
2540 // For normalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format
2541 // value = 1.fraction * 2^(exp - 127)
2542 // = (1 + mantissa / 2^23) * 2^(exp - 127)
2543 // = (2^23 + mantissa) * 2^(exp - 127 - 23)
2545 // So f = (2^23 + mantissa), e = exp - 150;
2547 fraction |= (1U << 23);
2548 exponent -= 150;
2550 else
2552 // For denormalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format
2553 // value = 0.fraction * 2^(1 - 127)
2554 // = (mantissa / 2^23) * 2^(-126)
2555 // = mantissa * 2^(-126 - 23)
2556 // = mantissa * 2^(-149)
2557 // So f = mantissa, e = -149
2558 exponent = -149;
2561 return fraction;