AsReadOnlySpan -> AsSpan rename to fix build breaks
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Number.Formatting.cs
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.
4
5 using System.Diagnostics;
6 using System.Globalization;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
9 using System.Text;
10
11 namespace System
12 {
13     // The Format methods provided by the numeric classes convert
14     // the numeric value to a string using the format string given by the
15     // format parameter. If the format parameter is null or
16     // an empty string, the number is formatted as if the string "G" (general
17     // format) was specified. The info parameter specifies the
18     // NumberFormatInfo instance to use when formatting the number. If the
19     // info parameter is null or omitted, the numeric formatting information
20     // is obtained from the current culture. The NumberFormatInfo supplies
21     // such information as the characters to use for decimal and thousand
22     // separators, and the spelling and placement of currency symbols in monetary
23     // values.
24     //
25     // Format strings fall into two categories: Standard format strings and
26     // user-defined format strings. A format string consisting of a single
27     // alphabetic character (A-Z or a-z), optionally followed by a sequence of
28     // digits (0-9), is a standard format string. All other format strings are
29     // used-defined format strings.
30     //
31     // A standard format string takes the form Axx, where A is an
32     // alphabetic character called the format specifier and xx is a
33     // sequence of digits called the precision specifier. The format
34     // specifier controls the type of formatting applied to the number and the
35     // precision specifier controls the number of significant digits or decimal
36     // places of the formatting operation. The following table describes the
37     // supported standard formats.
38     //
39     // C c - Currency format. The number is
40     // converted to a string that represents a currency amount. The conversion is
41     // controlled by the currency format information of the NumberFormatInfo
42     // used to format the number. The precision specifier indicates the desired
43     // number of decimal places. If the precision specifier is omitted, the default
44     // currency precision given by the NumberFormatInfo is used.
45     //
46     // D d - Decimal format. This format is
47     // supported for integral types only. The number is converted to a string of
48     // decimal digits, prefixed by a minus sign if the number is negative. The
49     // precision specifier indicates the minimum number of digits desired in the
50     // resulting string. If required, the number will be left-padded with zeros to
51     // produce the number of digits given by the precision specifier.
52     //
53     // E e Engineering (scientific) format.
54     // The number is converted to a string of the form
55     // "-d.ddd...E+ddd" or "-d.ddd...e+ddd", where each
56     // 'd' indicates a digit (0-9). The string starts with a minus sign if the
57     // number is negative, and one digit always precedes the decimal point. The
58     // precision specifier indicates the desired number of digits after the decimal
59     // point. If the precision specifier is omitted, a default of 6 digits after
60     // the decimal point is used. The format specifier indicates whether to prefix
61     // the exponent with an 'E' or an 'e'. The exponent is always consists of a
62     // plus or minus sign and three digits.
63     //
64     // F f Fixed point format. The number is
65     // converted to a string of the form "-ddd.ddd....", where each
66     // 'd' indicates a digit (0-9). The string starts with a minus sign if the
67     // number is negative. The precision specifier indicates the desired number of
68     // decimal places. If the precision specifier is omitted, the default numeric
69     // precision given by the NumberFormatInfo is used.
70     //
71     // G g - General format. The number is
72     // converted to the shortest possible decimal representation using fixed point
73     // or scientific format. The precision specifier determines the number of
74     // significant digits in the resulting string. If the precision specifier is
75     // omitted, the number of significant digits is determined by the type of the
76     // number being converted (10 for int, 19 for long, 7 for
77     // float, 15 for double, 19 for Currency, and 29 for
78     // Decimal). Trailing zeros after the decimal point are removed, and the
79     // resulting string contains a decimal point only if required. The resulting
80     // string uses fixed point format if the exponent of the number is less than
81     // the number of significant digits and greater than or equal to -4. Otherwise,
82     // the resulting string uses scientific format, and the case of the format
83     // specifier controls whether the exponent is prefixed with an 'E' or an 'e'.
84     //
85     // N n Number format. The number is
86     // converted to a string of the form "-d,ddd,ddd.ddd....", where
87     // each 'd' indicates a digit (0-9). The string starts with a minus sign if the
88     // number is negative. Thousand separators are inserted between each group of
89     // three digits to the left of the decimal point. The precision specifier
90     // indicates the desired number of decimal places. If the precision specifier
91     // is omitted, the default numeric precision given by the
92     // NumberFormatInfo is used.
93     //
94     // X x - Hexadecimal format. This format is
95     // supported for integral types only. The number is converted to a string of
96     // hexadecimal digits. The format specifier indicates whether to use upper or
97     // lower case characters for the hexadecimal digits above 9 ('X' for 'ABCDEF',
98     // and 'x' for 'abcdef'). The precision specifier indicates the minimum number
99     // of digits desired in the resulting string. If required, the number will be
100     // left-padded with zeros to produce the number of digits given by the
101     // precision specifier.
102     //
103     // Some examples of standard format strings and their results are shown in the
104     // table below. (The examples all assume a default NumberFormatInfo.)
105     //
106     // Value        Format  Result
107     // 12345.6789   C       $12,345.68
108     // -12345.6789  C       ($12,345.68)
109     // 12345        D       12345
110     // 12345        D8      00012345
111     // 12345.6789   E       1.234568E+004
112     // 12345.6789   E10     1.2345678900E+004
113     // 12345.6789   e4      1.2346e+004
114     // 12345.6789   F       12345.68
115     // 12345.6789   F0      12346
116     // 12345.6789   F6      12345.678900
117     // 12345.6789   G       12345.6789
118     // 12345.6789   G7      12345.68
119     // 123456789    G7      1.234568E8
120     // 12345.6789   N       12,345.68
121     // 123456789    N4      123,456,789.0000
122     // 0x2c45e      x       2c45e
123     // 0x2c45e      X       2C45E
124     // 0x2c45e      X8      0002C45E
125     //
126     // Format strings that do not start with an alphabetic character, or that start
127     // with an alphabetic character followed by a non-digit, are called
128     // user-defined format strings. The following table describes the formatting
129     // characters that are supported in user defined format strings.
130     //
131     // 
132     // 0 - Digit placeholder. If the value being
133     // formatted has a digit in the position where the '0' appears in the format
134     // string, then that digit is copied to the output string. Otherwise, a '0' is
135     // stored in that position in the output string. The position of the leftmost
136     // '0' before the decimal point and the rightmost '0' after the decimal point
137     // determines the range of digits that are always present in the output
138     // string.
139     //
140     // # - Digit placeholder. If the value being
141     // formatted has a digit in the position where the '#' appears in the format
142     // string, then that digit is copied to the output string. Otherwise, nothing
143     // is stored in that position in the output string.
144     //
145     // . - Decimal point. The first '.' character
146     // in the format string determines the location of the decimal separator in the
147     // formatted value; any additional '.' characters are ignored. The actual
148     // character used as a the decimal separator in the output string is given by
149     // the NumberFormatInfo used to format the number.
150     //
151     // , - Thousand separator and number scaling.
152     // The ',' character serves two purposes. First, if the format string contains
153     // a ',' character between two digit placeholders (0 or #) and to the left of
154     // the decimal point if one is present, then the output will have thousand
155     // separators inserted between each group of three digits to the left of the
156     // decimal separator. The actual character used as a the decimal separator in
157     // the output string is given by the NumberFormatInfo used to format the
158     // number. Second, if the format string contains one or more ',' characters
159     // immediately to the left of the decimal point, or after the last digit
160     // placeholder if there is no decimal point, then the number will be divided by
161     // 1000 times the number of ',' characters before it is formatted. For example,
162     // the format string '0,,' will represent 100 million as just 100. Use of the
163     // ',' character to indicate scaling does not also cause the formatted number
164     // to have thousand separators. Thus, to scale a number by 1 million and insert
165     // thousand separators you would use the format string '#,##0,,'.
166     //
167     // % - Percentage placeholder. The presence of
168     // a '%' character in the format string causes the number to be multiplied by
169     // 100 before it is formatted. The '%' character itself is inserted in the
170     // output string where it appears in the format string.
171     //
172     // E+ E- e+ e-   - Scientific notation.
173     // If any of the strings 'E+', 'E-', 'e+', or 'e-' are present in the format
174     // string and are immediately followed by at least one '0' character, then the
175     // number is formatted using scientific notation with an 'E' or 'e' inserted
176     // between the number and the exponent. The number of '0' characters following
177     // the scientific notation indicator determines the minimum number of digits to
178     // output for the exponent. The 'E+' and 'e+' formats indicate that a sign
179     // character (plus or minus) should always precede the exponent. The 'E-' and
180     // 'e-' formats indicate that a sign character should only precede negative
181     // exponents.
182     //
183     // \ - Literal character. A backslash character
184     // causes the next character in the format string to be copied to the output
185     // string as-is. The backslash itself isn't copied, so to place a backslash
186     // character in the output string, use two backslashes (\\) in the format
187     // string.
188     //
189     // 'ABC' "ABC" - Literal string. Characters
190     // enclosed in single or double quotation marks are copied to the output string
191     // as-is and do not affect formatting.
192     //
193     // ; - Section separator. The ';' character is
194     // used to separate sections for positive, negative, and zero numbers in the
195     // format string.
196     //
197     // Other - All other characters are copied to
198     // the output string in the position they appear.
199     //
200     // For fixed point formats (formats not containing an 'E+', 'E-', 'e+', or
201     // 'e-'), the number is rounded to as many decimal places as there are digit
202     // placeholders to the right of the decimal point. If the format string does
203     // not contain a decimal point, the number is rounded to the nearest
204     // integer. If the number has more digits than there are digit placeholders to
205     // the left of the decimal point, the extra digits are copied to the output
206     // string immediately before the first digit placeholder.
207     //
208     // For scientific formats, the number is rounded to as many significant digits
209     // as there are digit placeholders in the format string.
210     //
211     // To allow for different formatting of positive, negative, and zero values, a
212     // user-defined format string may contain up to three sections separated by
213     // semicolons. The results of having one, two, or three sections in the format
214     // string are described in the table below.
215     //
216     // Sections:
217     //
218     // One - The format string applies to all values.
219     //
220     // Two - The first section applies to positive values
221     // and zeros, and the second section applies to negative values. If the number
222     // to be formatted is negative, but becomes zero after rounding according to
223     // the format in the second section, then the resulting zero is formatted
224     // according to the first section.
225     //
226     // Three - The first section applies to positive
227     // values, the second section applies to negative values, and the third section
228     // applies to zeros. The second section may be left empty (by having no
229     // characters between the semicolons), in which case the first section applies
230     // to all non-zero values. If the number to be formatted is non-zero, but
231     // becomes zero after rounding according to the format in the first or second
232     // section, then the resulting zero is formatted according to the third
233     // section.
234     //
235     // For both standard and user-defined formatting operations on values of type
236     // float and double, if the value being formatted is a NaN (Not
237     // a Number) or a positive or negative infinity, then regardless of the format
238     // string, the resulting string is given by the NaNSymbol,
239     // PositiveInfinitySymbol, or NegativeInfinitySymbol property of
240     // the NumberFormatInfo used to format the number.
241
242     internal static partial class Number
243     {
244         internal const int DecimalPrecision = 29; // Decimal.DecCalc also uses this value
245         private const int FloatPrecision = 7;
246         private const int DoublePrecision = 15;
247         private const int ScaleNAN = unchecked((int)0x80000000);
248         private const int ScaleINF = 0x7FFFFFFF;
249         private const int MaxUInt32HexDigits = 8;
250         private const int MaxUInt32DecDigits = 10;
251         private const int MaxUInt64DecDigits = 20;
252         private const int CharStackBufferSize = 32;
253         private const string PosNumberFormat = "#";
254
255         private static readonly string[] s_posCurrencyFormats =
256         {
257             "$#", "#$", "$ #", "# $"
258         };
259
260         private static readonly string[] s_negCurrencyFormats =
261         {
262             "($#)", "-$#", "$-#", "$#-",
263             "(#$)", "-#$", "#-$", "#$-",
264             "-# $", "-$ #", "# $-", "$ #-",
265             "$ -#", "#- $", "($ #)", "(# $)"
266         };
267
268         private static readonly string[] s_posPercentFormats =
269         {
270             "# %", "#%", "%#", "% #"
271         };
272
273         private static readonly string[] s_negPercentFormats =
274         {
275             "-# %", "-#%", "-%#",
276             "%-#", "%#-",
277             "#-%", "#%-",
278             "-% #", "# %-", "% #-",
279             "% -#", "#- %"
280         };
281
282         private static readonly string[] s_negNumberFormats =
283         {
284             "(#)", "-#", "- #", "#-", "# -",
285         };
286
287         public static string FormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info)
288         {
289             char fmt = ParseFormatSpecifier(format, out int digits);
290
291             NumberBuffer number = default;
292             DecimalToNumber(value, ref number);
293
294             ValueStringBuilder sb;
295             unsafe
296             {
297                 char* stackPtr = stackalloc char[CharStackBufferSize];
298                 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
299             }
300
301             if (fmt != 0)
302             {
303                 NumberToString(ref sb, ref number, fmt, digits, info, isDecimal:true);
304             }
305             else
306             {
307                 NumberToStringFormat(ref sb, ref number, format, info);
308             }
309
310             return sb.ToString();
311         }
312
313         public static bool TryFormatDecimal(decimal value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
314         {
315             char fmt = ParseFormatSpecifier(format, out int digits);
316
317             NumberBuffer number = default;
318             DecimalToNumber(value, ref number);
319
320             ValueStringBuilder sb;
321             unsafe
322             {
323                 char* stackPtr = stackalloc char[CharStackBufferSize];
324                 sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
325             }
326
327             if (fmt != 0)
328             {
329                 NumberToString(ref sb, ref number, fmt, digits, info, isDecimal: true);
330             }
331             else
332             {
333                 NumberToStringFormat(ref sb, ref number, format, info);
334             }
335
336             return sb.TryCopyTo(destination, out charsWritten);
337         }
338
339         private static unsafe void DecimalToNumber(decimal value, ref NumberBuffer number)
340         {
341             decimal d = value;
342
343             char* buffer = number.digits;
344             number.precision = DecimalPrecision;
345             number.sign = d.IsNegative;
346
347             char* p = buffer + DecimalPrecision;
348             while ((d.Mid | d.High) != 0)
349             {
350                 p = UInt32ToDecChars(p, decimal.DecDivMod1E9(ref d), 9);
351             }
352             p = UInt32ToDecChars(p, d.Low, 0);
353
354             int i = (int)(buffer + DecimalPrecision - p);
355             number.scale = i - d.Scale;
356
357             char* dst = number.digits;
358             while (--i >= 0)
359             {
360                 *dst++ = *p++;
361             }
362             *dst = '\0';
363         }
364
365         public static string FormatDouble(double value, string format, NumberFormatInfo info)
366         {
367             Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
368             var sb = new ValueStringBuilder(stackBuffer);
369             return FormatDouble(ref sb, value, format, info) ?? sb.ToString();
370         }
371
372         public static bool TryFormatDouble(double value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
373         {
374             Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
375             var sb = new ValueStringBuilder(stackBuffer);
376             string s = FormatDouble(ref sb, value, format, info);
377             return s != null ?
378                 TryCopyTo(s, destination, out charsWritten) :
379                 sb.TryCopyTo(destination, out charsWritten);
380         }
381
382         /// <summary>Formats the specified value according to the specified format and info.</summary>
383         /// <returns>
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.
386         /// </returns>
387         private static string FormatDouble(ref ValueStringBuilder sb, double value, ReadOnlySpan<char> format, NumberFormatInfo info)
388         {
389             char fmt = ParseFormatSpecifier(format, out int digits);
390             int precision = DoublePrecision;
391             NumberBuffer number = default;
392
393             switch (fmt)
394             {
395                 case 'R':
396                 case 'r':
397                     {
398                         // In order to give numbers that are both friendly to display and round-trippable, we parse the
399                         // number using 15 digits and then determine if it round trips to the same value. If it does, we
400                         // convert that NUMBER to a string, otherwise we reparse using 17 digits and display that.
401                         DoubleToNumber(value, DoublePrecision, ref number);
402                         if (number.scale == ScaleNAN)
403                         {
404                             return info.NaNSymbol;
405                         }
406                         else if (number.scale == ScaleINF)
407                         {
408                             return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
409                         }
410
411                         if (NumberToDouble(ref number) == value)
412                         {
413                             NumberToString(ref sb, ref number, 'G', DoublePrecision, info, isDecimal: false);
414                         }
415                         else
416                         {
417                             DoubleToNumber(value, 17, ref number);
418                             NumberToString(ref sb, ref number, 'G', 17, info, isDecimal: false);
419                         }
420
421                         return null;
422                     }
423
424                 case 'E':
425                 case 'e':
426                     // Round values less than E14 to 15 digits
427                     if (digits > 14)
428                     {
429                         precision = 17;
430                     }
431                     break;
432
433                 case 'G':
434                 case 'g':
435                     // Round values less than G15 to 15 digits. G16 and G17 will not be touched.
436                     if (digits > 15)
437                     {
438                         precision = 17;
439                     }
440                     break;
441             }
442
443             DoubleToNumber(value, precision, ref number);
444             if (number.scale == ScaleNAN)
445             {
446                 return info.NaNSymbol;
447             }
448             else if (number.scale == ScaleINF)
449             {
450                 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
451             }
452
453             if (fmt != 0)
454             {
455                 NumberToString(ref sb, ref number, fmt, digits, info, isDecimal: false);
456             }
457             else
458             {
459                 NumberToStringFormat(ref sb, ref number, format, info);
460             }
461
462             return null;
463         }
464
465         public static string FormatSingle(float value, string format, NumberFormatInfo info)
466         {
467             Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
468             var sb = new ValueStringBuilder(stackBuffer);
469             return FormatSingle(ref sb, value, format, info) ?? sb.ToString();
470         }
471
472         public static bool TryFormatSingle(float value, ReadOnlySpan<char> format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
473         {
474             Span<char> stackBuffer = stackalloc char[CharStackBufferSize];
475             var sb = new ValueStringBuilder(stackBuffer);
476             string s = FormatSingle(ref sb, value, format, info);
477             return s != null ?
478                 TryCopyTo(s, destination, out charsWritten) :
479                 sb.TryCopyTo(destination, out charsWritten);
480         }
481
482         /// <summary>Formats the specified value according to the specified format and info.</summary>
483         /// <returns>
484         /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
485         /// Null if no existing string was returned, in which case the formatted output is in the builder.
486         /// </returns>
487         private static string FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan<char> format, NumberFormatInfo info)
488         {
489             char fmt = ParseFormatSpecifier(format, out int digits);
490             int precision = FloatPrecision;
491             NumberBuffer number = default;
492
493             switch (fmt)
494             {
495                 case 'R':
496                 case 'r':
497                     {
498                         // In order to give numbers that are both friendly to display and round-trippable, we parse the
499                         // number using 7 digits and then determine if it round trips to the same value. If it does, we
500                         // convert that NUMBER to a string, otherwise we reparse using 9 digits and display that.
501                         DoubleToNumber(value, FloatPrecision, ref number);
502                         if (number.scale == ScaleNAN)
503                         {
504                             return info.NaNSymbol;
505                         }
506                         else if (number.scale == ScaleINF)
507                         {
508                             return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
509                         }
510
511                         if ((float)NumberToDouble(ref number) == value)
512                         {
513                             NumberToString(ref sb, ref number, 'G', FloatPrecision, info, isDecimal: false);
514                         }
515                         else
516                         {
517                             DoubleToNumber(value, 9, ref number);
518                             NumberToString(ref sb, ref number, 'G', 9, info, isDecimal: false);
519                         }
520                         return null;
521                     }
522
523                 case 'E':
524                 case 'e':
525                     // Round values less than E14 to 15 digits.
526                     if (digits > 6)
527                     {
528                         precision = 9;
529                     }
530                     break;
531
532                 case 'G':
533                 case 'g':
534                     // Round values less than G15 to 15 digits. G16 and G17 will not be touched.
535                     if (digits > 7)
536                     {
537                         precision = 9;
538                     }
539                     break;
540             }
541
542             DoubleToNumber(value, precision, ref number);
543             if (number.scale == ScaleNAN)
544             {
545                 return info.NaNSymbol;
546             }
547             else if (number.scale == ScaleINF)
548             {
549                 return number.sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
550             }
551
552             if (fmt != 0)
553             {
554                 NumberToString(ref sb, ref number, fmt, digits, info, false);
555             }
556             else
557             {
558                 NumberToStringFormat(ref sb, ref number, format, info);
559             }
560             return null;
561         }
562
563         private static bool TryCopyTo(string source, Span<char> destination, out int charsWritten)
564         {
565             Debug.Assert(source != null);
566
567             if (source.AsSpan().TryCopyTo(destination))
568             {
569                 charsWritten = source.Length;
570                 return true;
571             }
572
573             charsWritten = 0;
574             return false;
575         }
576
577         public static string FormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider)
578         {
579             // Fast path for default format with a non-negative value
580             if (value >= 0 && format.Length == 0)
581             {
582                 return UInt32ToDecStr((uint)value, digits: -1);
583             }
584
585             char fmt = ParseFormatSpecifier(format, out int digits);
586             NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
587
588             char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
589             if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
590             {
591                 return value >= 0 ?
592                     UInt32ToDecStr((uint)value, digits) :
593                     NegativeInt32ToDecStr(value, digits, info.NegativeSign);
594             }
595             else if (fmtUpper == 'X')
596             {
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);
600             }
601             else
602             {
603                 NumberBuffer number = default;
604                 Int32ToNumber(value, ref number);
605                 ValueStringBuilder sb;
606                 unsafe
607                 {
608                     char* stackPtr = stackalloc char[CharStackBufferSize];
609                     sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
610                 }
611                 if (fmt != 0)
612                 {
613                     NumberToString(ref sb, ref number, fmt, digits, info, false);
614                 }
615                 else
616                 {
617                     NumberToStringFormat(ref sb, ref number, format, info);
618                 }
619                 return sb.ToString();
620             }
621         }
622
623         public static bool TryFormatInt32(int value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
624         {
625             // Fast path for default format with a non-negative value
626             if (value >= 0 && format.Length == 0)
627             {
628                 return TryUInt32ToDecStr((uint)value, digits: -1, destination, out charsWritten);
629             }
630
631             char fmt = ParseFormatSpecifier(format, out int digits);
632             NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
633
634             char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
635             if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
636             {
637                 return value >= 0 ?
638                     TryUInt32ToDecStr((uint)value, digits, destination, out charsWritten) :
639                     TryNegativeInt32ToDecStr(value, digits, info.NegativeSign, destination, out charsWritten);
640             }
641             else if (fmtUpper == 'X')
642             {
643                 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
644                 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
645                 return TryInt32ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
646             }
647             else
648             {
649                 NumberBuffer number = default;
650                 Int32ToNumber(value, ref number);
651                 ValueStringBuilder sb;
652                 unsafe
653                 {
654                     char* stackPtr = stackalloc char[CharStackBufferSize];
655                     sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
656                 }
657                 if (fmt != 0)
658                 {
659                     NumberToString(ref sb, ref number, fmt, digits, info, false);
660                 }
661                 else
662                 {
663                     NumberToStringFormat(ref sb, ref number, format, info);
664                 }
665                 return sb.TryCopyTo(destination, out charsWritten);
666             }
667         }
668
669         public static string FormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider)
670         {
671             // Fast path for default format
672             if (format.Length == 0)
673             {
674                 return UInt32ToDecStr(value, digits: -1);
675             }
676
677             char fmt = ParseFormatSpecifier(format, out int digits);
678             NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
679
680             char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
681             if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
682             {
683                 return UInt32ToDecStr(value, digits);
684             }
685             else if (fmtUpper == 'X')
686             {
687                 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
688                 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
689                 return Int32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits);
690             }
691             else
692             {
693                 NumberBuffer number = default;
694                 UInt32ToNumber(value, ref number);
695                 ValueStringBuilder sb;
696                 unsafe
697                 {
698                     char* stackPtr = stackalloc char[CharStackBufferSize];
699                     sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
700                 }
701                 if (fmt != 0)
702                 {
703                     NumberToString(ref sb, ref number, fmt, digits, info, false);
704                 }
705                 else
706                 {
707                     NumberToStringFormat(ref sb, ref number, format, info);
708                 }
709                 return sb.ToString();
710             }
711         }
712
713         public static bool TryFormatUInt32(uint value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
714         {
715             // Fast path for default format
716             if (format.Length == 0)
717             {
718                 return TryUInt32ToDecStr(value, digits: -1, destination, out charsWritten);
719             }
720
721             char fmt = ParseFormatSpecifier(format, out int digits);
722             NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
723
724             char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
725             if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
726             {
727                 return TryUInt32ToDecStr(value, digits, destination, out charsWritten);
728             }
729             else if (fmtUpper == 'X')
730             {
731                 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
732                 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
733                 return TryInt32ToHexStr((int)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
734             }
735             else
736             {
737                 NumberBuffer number = default;
738                 UInt32ToNumber(value, ref number);
739                 ValueStringBuilder sb;
740                 unsafe
741                 {
742                     char* stackPtr = stackalloc char[CharStackBufferSize];
743                     sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
744                 }
745                 if (fmt != 0)
746                 {
747                     NumberToString(ref sb, ref number, fmt, digits, info, false);
748                 }
749                 else
750                 {
751                     NumberToStringFormat(ref sb, ref number, format, info);
752                 }
753                 return sb.TryCopyTo(destination, out charsWritten);
754             }
755         }
756
757         public static string FormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider)
758         {
759             // Fast path for default format with a non-negative value
760             if (value >= 0 && format.Length == 0)
761             {
762                 return UInt64ToDecStr((ulong)value, digits: -1);
763             }
764
765             char fmt = ParseFormatSpecifier(format, out int digits);
766             NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
767
768             char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
769             if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
770             {
771                 return value >= 0 ?
772                     UInt64ToDecStr((ulong)value, digits) :
773                     NegativeInt64ToDecStr(value, digits, info.NegativeSign);
774             }
775             else if (fmtUpper == 'X')
776             {
777                 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
778                 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
779                 // produces lowercase.
780                 return Int64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits);
781             }
782             else
783             {
784                 NumberBuffer number = default;
785                 Int64ToNumber(value, ref number);
786                 ValueStringBuilder sb;
787                 unsafe
788                 {
789                     char* stackPtr = stackalloc char[CharStackBufferSize];
790                     sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
791                 }
792                 if (fmt != 0)
793                 {
794                     NumberToString(ref sb, ref number, fmt, digits, info, false);
795                 }
796                 else
797                 {
798                     NumberToStringFormat(ref sb, ref number, format, info);
799                 }
800                 return sb.ToString();
801             }
802         }
803
804         public static bool TryFormatInt64(long value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
805         {
806             // Fast path for default format with a non-negative value
807             if (value >= 0 && format.Length == 0)
808             {
809                 return TryUInt64ToDecStr((ulong)value, digits: -1, destination, out charsWritten);
810             }
811
812             char fmt = ParseFormatSpecifier(format, out int digits);
813             NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
814
815             char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
816             if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
817             {
818                 return value >= 0 ?
819                     TryUInt64ToDecStr((ulong)value, digits, destination, out charsWritten) :
820                     TryNegativeInt64ToDecStr(value, digits, info.NegativeSign, destination, out charsWritten);
821             }
822             else if (fmtUpper == 'X')
823             {
824                 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
825                 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
826                 // produces lowercase.
827                 return TryInt64ToHexStr(value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
828             }
829             else
830             {
831                 NumberBuffer number = default;
832                 Int64ToNumber(value, ref number);
833                 ValueStringBuilder sb;
834                 unsafe
835                 {
836                     char* stackPtr = stackalloc char[CharStackBufferSize];
837                     sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
838                 }
839                 if (fmt != 0)
840                 {
841                     NumberToString(ref sb, ref number, fmt, digits, info, false);
842                 }
843                 else
844                 {
845                     NumberToStringFormat(ref sb, ref number, format, info);
846                 }
847                 return sb.TryCopyTo(destination, out charsWritten);
848             }
849         }
850
851         public static string FormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider)
852         {
853             // Fast path for default format
854             if (format.Length == 0)
855             {
856                 return UInt64ToDecStr(value, digits: -1);
857             }
858
859             char fmt = ParseFormatSpecifier(format, out int digits);
860             NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
861
862             char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
863             if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
864             {
865                 return UInt64ToDecStr(value, digits);
866             }
867             else if (fmtUpper == 'X')
868             {
869                 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
870                 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
871                 // produces lowercase.
872                 return Int64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits);
873             }
874             else
875             {
876                 NumberBuffer number = default;
877                 UInt64ToNumber(value, ref number);
878                 ValueStringBuilder sb;
879                 unsafe
880                 {
881                     char* stackPtr = stackalloc char[CharStackBufferSize];
882                     sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
883                 }
884                 if (fmt != 0)
885                 {
886                     NumberToString(ref sb, ref number, fmt, digits, info, false);
887                 }
888                 else
889                 {
890                     NumberToStringFormat(ref sb, ref number, format, info);
891                 }
892                 return sb.ToString();
893             }
894         }
895
896         public static bool TryFormatUInt64(ulong value, ReadOnlySpan<char> format, IFormatProvider provider, Span<char> destination, out int charsWritten)
897         {
898             // Fast path for default format
899             if (format.Length == 0)
900             {
901                 return TryUInt64ToDecStr(value, digits: -1, destination, out charsWritten);
902             }
903
904             char fmt = ParseFormatSpecifier(format, out int digits);
905             NumberFormatInfo info = NumberFormatInfo.GetInstance(provider);
906
907             char fmtUpper = (char)(fmt & 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
908             if ((fmtUpper == 'G' && digits < 1) || fmtUpper == 'D')
909             {
910                 return TryUInt64ToDecStr(value, digits, destination, out charsWritten);
911             }
912             else if (fmtUpper == 'X')
913             {
914                 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
915                 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
916                 // produces lowercase.
917                 return TryInt64ToHexStr((long)value, (char)(fmt - ('X' - 'A' + 10)), digits, destination, out charsWritten);
918             }
919             else
920             {
921                 NumberBuffer number = default;
922                 UInt64ToNumber(value, ref number);
923                 ValueStringBuilder sb;
924                 unsafe
925                 {
926                     char* stackPtr = stackalloc char[CharStackBufferSize];
927                     sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
928                 }
929                 if (fmt != 0)
930                 {
931                     NumberToString(ref sb, ref number, fmt, digits, info, false);
932                 }
933                 else
934                 {
935                     NumberToStringFormat(ref sb, ref number, format, info);
936                 }
937                 return sb.TryCopyTo(destination, out charsWritten);
938             }
939         }
940
941         [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
942         private static unsafe void Int32ToNumber(int value, ref NumberBuffer number)
943         {
944             number.precision = Int32Precision;
945
946             if (value >= 0)
947             {
948                 number.sign = false;
949             }
950             else
951             {
952                 number.sign = true;
953                 value = -value;
954             }
955
956             char* buffer = number.digits;
957             char* p = UInt32ToDecChars(buffer + Int32Precision, (uint)value, 0);
958             int i = (int)(buffer + Int32Precision - p);
959
960             number.scale = i;
961
962             char* dst = number.digits;
963             while (--i >= 0)
964                 *dst++ = *p++;
965             *dst = '\0';
966         }
967
968         private static unsafe string NegativeInt32ToDecStr(int value, int digits, string sNegative)
969         {
970             Debug.Assert(value < 0);
971
972             if (digits < 1)
973                 digits = 1;
974
975             int bufferLength = Math.Max(digits, MaxUInt32DecDigits) + sNegative.Length;
976             int index = bufferLength;
977
978             char* buffer = stackalloc char[bufferLength];
979             char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
980             for (int i = sNegative.Length - 1; i >= 0; i--)
981             {
982                 *(--p) = sNegative[i];
983             }
984
985             Debug.Assert(buffer + bufferLength - p >= 0 && buffer <= p);
986             return new string(p, 0, (int)(buffer + bufferLength - p));
987         }
988
989         private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits, string sNegative, Span<char> destination, out int charsWritten)
990         {
991             Debug.Assert(value < 0);
992
993             if (digits < 1)
994                 digits = 1;
995
996             int bufferLength = Math.Max(digits, MaxUInt32DecDigits) + sNegative.Length;
997             int index = bufferLength;
998
999             char* buffer = stackalloc char[bufferLength];
1000             char* p = UInt32ToDecChars(buffer + bufferLength, (uint)(-value), digits);
1001             for (int i = sNegative.Length - 1; i >= 0; i--)
1002             {
1003                 *(--p) = sNegative[i];
1004             }
1005
1006             Debug.Assert(buffer + bufferLength - p >= 0 && buffer <= p);
1007             return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten);
1008         }
1009
1010         private static unsafe string Int32ToHexStr(int value, char hexBase, int digits)
1011         {
1012             if (digits < 1)
1013                 digits = 1;
1014
1015             int bufferLength = Math.Max(digits, MaxUInt32HexDigits);
1016             char* buffer = stackalloc char[bufferLength];
1017
1018             char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
1019             return new string(p, 0, (int)(buffer + bufferLength - p));
1020         }
1021
1022         private static unsafe bool TryInt32ToHexStr(int value, char hexBase, int digits, Span<char> destination, out int charsWritten)
1023         {
1024             if (digits < 1)
1025                 digits = 1;
1026
1027             int bufferLength = Math.Max(digits, MaxUInt32HexDigits);
1028             char* buffer = stackalloc char[bufferLength];
1029
1030             char* p = Int32ToHexChars(buffer + bufferLength, (uint)value, hexBase, digits);
1031             return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten);
1032         }
1033
1034         private static unsafe char* Int32ToHexChars(char* buffer, uint value, int hexBase, int digits)
1035         {
1036             while (--digits >= 0 || value != 0)
1037             {
1038                 byte digit = (byte)(value & 0xF);
1039                 *(--buffer) = (char)(digit + (digit < 10 ? (byte)'0' : hexBase));
1040                 value >>= 4;
1041             }
1042             return buffer;
1043         }
1044
1045         [MethodImpl(MethodImplOptions.AggressiveInlining)] // called from only one location
1046         private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number)
1047         {
1048             number.precision = UInt32Precision;
1049             number.sign = false;
1050
1051             char* buffer = number.digits;
1052             char* p = UInt32ToDecChars(buffer + UInt32Precision, value, 0);
1053             int i = (int)(buffer + UInt32Precision - p);
1054             number.scale = i;
1055
1056             char* dst = number.digits;
1057             while (--i >= 0)
1058                 *dst++ = *p++;
1059             *dst = '\0';
1060         }
1061
1062         internal static unsafe char* UInt32ToDecChars(char* bufferEnd, uint value, int digits)
1063         {
1064             while (--digits >= 0 || value != 0)
1065             {
1066                 // TODO https://github.com/dotnet/coreclr/issues/3439
1067                 uint newValue = value / 10;
1068                 *(--bufferEnd) = (char)(value - (newValue * 10) + '0');
1069                 value = newValue;
1070             }
1071             return bufferEnd;
1072         }
1073
1074         private static unsafe string UInt32ToDecStr(uint value, int digits)
1075         {
1076             if (digits <= 1)
1077             {
1078                 char* buffer = stackalloc char[MaxUInt32DecDigits];
1079
1080                 char* start = buffer + MaxUInt32DecDigits;
1081                 char* p = start;
1082                 do
1083                 {
1084                     // TODO https://github.com/dotnet/coreclr/issues/3439
1085                     uint div = value / 10;
1086                     *(--p) = (char)('0' + value - (div * 10));
1087                     value = div;
1088                 }
1089                 while (value != 0);
1090
1091                 return new string(p, 0, (int)(start - p));
1092             }
1093             else
1094             {
1095                 int bufferSize = Math.Max(digits, MaxUInt32DecDigits);
1096                 char* buffer = stackalloc char[bufferSize];
1097                 char* p = UInt32ToDecChars(buffer + bufferSize, value, digits);
1098                 return new string(p, 0, (int)(buffer + bufferSize - p));
1099             }
1100         }
1101
1102         private static unsafe bool TryUInt32ToDecStr(uint value, int digits, Span<char> destination, out int charsWritten)
1103         {
1104             if (digits <= 1)
1105             {
1106                 char* buffer = stackalloc char[MaxUInt32DecDigits];
1107                 char* start = buffer + MaxUInt32DecDigits;
1108                 char* p = start;
1109                 do
1110                 {
1111                     // TODO https://github.com/dotnet/coreclr/issues/3439
1112                     uint div = value / 10;
1113                     *(--p) = (char)('0' + value - (div * 10));
1114                     value = div;
1115                 }
1116                 while (value != 0);
1117                 return TryCopyTo(p, (int)(start - p), destination, out charsWritten);
1118             }
1119             else
1120             {
1121                 int bufferSize = Math.Max(digits, MaxUInt32DecDigits);
1122                 char* buffer = stackalloc char[bufferSize];
1123                 char* p = UInt32ToDecChars(buffer + bufferSize, value, digits);
1124                 return TryCopyTo(p, (int)(buffer + bufferSize - p), destination, out charsWritten);
1125             }
1126         }
1127
1128         [MethodImpl(MethodImplOptions.AggressiveInlining)]
1129         private static unsafe bool TryCopyTo(char* src, int length, Span<char> destination, out int charsWritten)
1130         {
1131             if (new ReadOnlySpan<char>(src, length).TryCopyTo(destination))
1132             {
1133                 charsWritten = length;
1134                 return true;
1135             }
1136             else
1137             {
1138                 charsWritten = 0;
1139                 return false;
1140             }
1141         }
1142
1143         private static unsafe void Int64ToNumber(long input, ref NumberBuffer number)
1144         {
1145             ulong value = (ulong)input;
1146             number.sign = input < 0;
1147             number.precision = Int64Precision;
1148             if (number.sign)
1149             {
1150                 value = (ulong)(-input);
1151             }
1152
1153             char* buffer = number.digits;
1154             char* p = buffer + Int64Precision;
1155             while (High32(value) != 0)
1156                 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1157             p = UInt32ToDecChars(p, Low32(value), 0);
1158             int i = (int)(buffer + Int64Precision - p);
1159
1160             number.scale = i;
1161
1162             char* dst = number.digits;
1163             while (--i >= 0)
1164                 *dst++ = *p++;
1165             *dst = '\0';
1166         }
1167
1168         private static unsafe string NegativeInt64ToDecStr(long input, int digits, string sNegative)
1169         {
1170             Debug.Assert(input < 0);
1171
1172             if (digits < 1)
1173             {
1174                 digits = 1;
1175             }
1176
1177             ulong value = (ulong)(-input);
1178
1179             int bufferLength = Math.Max(digits, MaxUInt64DecDigits) + sNegative.Length;
1180             int index = bufferLength;
1181
1182             char* buffer = stackalloc char[bufferLength];
1183             char* p = buffer + bufferLength;
1184             while (High32(value) != 0)
1185             {
1186                 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1187                 digits -= 9;
1188             }
1189             p = UInt32ToDecChars(p, Low32(value), digits);
1190
1191             for (int i = sNegative.Length - 1; i >= 0; i--)
1192             {
1193                 *(--p) = sNegative[i];
1194             }
1195
1196             return new string(p, 0, (int)(buffer + bufferLength - p));
1197         }
1198
1199         private static unsafe bool TryNegativeInt64ToDecStr(long input, int digits, string sNegative, Span<char> destination, out int charsWritten)
1200         {
1201             Debug.Assert(input < 0);
1202
1203             if (digits < 1)
1204             {
1205                 digits = 1;
1206             }
1207
1208             ulong value = (ulong)(-input);
1209
1210             int bufferLength = Math.Max(digits, MaxUInt64DecDigits) + sNegative.Length;
1211             int index = bufferLength;
1212
1213             char* buffer = stackalloc char[bufferLength];
1214             char* p = buffer + bufferLength;
1215             while (High32(value) != 0)
1216             {
1217                 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1218                 digits -= 9;
1219             }
1220             p = UInt32ToDecChars(p, Low32(value), digits);
1221
1222             for (int i = sNegative.Length - 1; i >= 0; i--)
1223             {
1224                 *(--p) = sNegative[i];
1225             }
1226
1227             return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten);
1228         }
1229
1230         private static unsafe string Int64ToHexStr(long value, char hexBase, int digits)
1231         {
1232             int bufferLength = Math.Max(digits, MaxUInt32HexDigits * 2);
1233             char* buffer = stackalloc char[bufferLength];
1234             int index = bufferLength;
1235
1236             char* p;
1237             if (High32((ulong)value) != 0)
1238             {
1239                 p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, 8);
1240                 p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
1241             }
1242             else
1243             {
1244                 p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, Math.Max(digits, 1));
1245             }
1246
1247             return new string(p, 0, (int)(buffer + bufferLength - p));
1248         }
1249
1250         private static unsafe bool TryInt64ToHexStr(long value, char hexBase, int digits, Span<char> destination, out int charsWritten)
1251         {
1252             int bufferLength = Math.Max(digits, MaxUInt32HexDigits * 2);
1253             char* buffer = stackalloc char[bufferLength];
1254             int index = bufferLength;
1255
1256             char* p;
1257             if (High32((ulong)value) != 0)
1258             {
1259                 p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, 8);
1260                 p = Int32ToHexChars(p, High32((ulong)value), hexBase, digits - 8);
1261             }
1262             else
1263             {
1264                 p = Int32ToHexChars(buffer + index, Low32((ulong)value), hexBase, Math.Max(digits, 1));
1265             }
1266
1267             return TryCopyTo(p, (int)(buffer + bufferLength - p), destination, out charsWritten);
1268         }
1269
1270         private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number)
1271         {
1272             number.precision = UInt64Precision;
1273             number.sign = false;
1274
1275             char* buffer = number.digits;
1276             char* p = buffer + UInt64Precision;
1277
1278             while (High32(value) != 0)
1279                 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1280             p = UInt32ToDecChars(p, Low32(value), 0);
1281             int i = (int)(buffer + UInt64Precision - p);
1282
1283             number.scale = i;
1284
1285             char* dst = number.digits;
1286             while (--i >= 0)
1287                 *dst++ = *p++;
1288             *dst = '\0';
1289         }
1290
1291         private static unsafe string UInt64ToDecStr(ulong value, int digits)
1292         {
1293             if (digits < 1)
1294                 digits = 1;
1295
1296             int bufferSize = Math.Max(digits, MaxUInt64DecDigits);
1297             char* buffer = stackalloc char[bufferSize];
1298             char* p = buffer + bufferSize;
1299             while (High32(value) != 0)
1300             {
1301                 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1302                 digits -= 9;
1303             }
1304             p = UInt32ToDecChars(p, Low32(value), digits);
1305
1306             return new string(p, 0, (int)(buffer + bufferSize - p));
1307         }
1308
1309         private static unsafe bool TryUInt64ToDecStr(ulong value, int digits, Span<char> destination, out int charsWritten)
1310         {
1311             if (digits < 1)
1312                 digits = 1;
1313
1314             int bufferSize = Math.Max(digits, MaxUInt64DecDigits);
1315             char* buffer = stackalloc char[bufferSize];
1316             char* p = buffer + bufferSize;
1317             while (High32(value) != 0)
1318             {
1319                 p = UInt32ToDecChars(p, Int64DivMod1E9(ref value), 9);
1320                 digits -= 9;
1321             }
1322             p = UInt32ToDecChars(p, Low32(value), digits);
1323
1324             return TryCopyTo(p, (int)(buffer + bufferSize - p), destination, out charsWritten);
1325         }
1326
1327         internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits)
1328         {
1329             char c = default;
1330             if (format.Length > 0)
1331             {
1332                 // If the format begins with a symbol, see if it's a standard format
1333                 // with or without a specified number of digits.
1334                 c = format[0];
1335                 if ((uint)(c - 'A') <= 'Z' - 'A' ||
1336                     (uint)(c - 'a') <= 'z' - 'a')
1337                 {
1338                     // Fast path for sole symbol, e.g. "D"
1339                     if (format.Length == 1)
1340                     {
1341                         digits = -1;
1342                         return c;
1343                     }
1344
1345                     if (format.Length == 2)
1346                     {
1347                         // Fast path for symbol and single digit, e.g. "X4"
1348                         int d = format[1] - '0';
1349                         if ((uint)d < 10)
1350                         {
1351                             digits = d;
1352                             return c;
1353                         }
1354                     }
1355                     else if (format.Length == 3)
1356                     {
1357                         // Fast path for symbol and double digit, e.g. "F12"
1358                         int d1 = format[1] - '0', d2 = format[2] - '0';
1359                         if ((uint)d1 < 10 && (uint)d2 < 10)
1360                         {
1361                             digits = d1 * 10 + d2;
1362                             return c;
1363                         }
1364                     }
1365
1366                     // Fallback for symbol and any length digits.  The digits value must be >= 0 && <= 99,
1367                     // but it can begin with any number of 0s, and thus we may need to check more than two
1368                     // digits.  Further, for compat, we need to stop when we hit a null char.
1369                     int n = 0;
1370                     int i = 1;
1371                     while (i < format.Length && (((uint)format[i] - '0') < 10) && n < 10)
1372                     {
1373                         n = (n * 10) + format[i++] - '0';
1374                     }
1375
1376                     // If we're at the end of the digits rather than having stopped because we hit something
1377                     // other than a digit or overflowed, return the standard format info.
1378                     if (i == format.Length || format[i] == '\0')
1379                     {
1380                         digits = n;
1381                         return c;
1382                     }
1383                 }
1384             }
1385
1386             // Default empty format to be "G"; custom format is signified with '\0'.
1387             digits = -1;
1388             return format.Length == 0 || c == '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it.
1389                 'G' : 
1390                 '\0';
1391         }
1392
1393         internal static unsafe void NumberToString(ref ValueStringBuilder sb, ref NumberBuffer number, char format, int nMaxDigits, NumberFormatInfo info, bool isDecimal)
1394         {
1395             int nMinDigits = -1;
1396
1397             switch (format)
1398             {
1399                 case 'C':
1400                 case 'c':
1401                     {
1402                         nMinDigits = nMaxDigits >= 0 ? nMaxDigits : info.CurrencyDecimalDigits;
1403                         if (nMaxDigits < 0)
1404                             nMaxDigits = info.CurrencyDecimalDigits;
1405
1406                         RoundNumber(ref number, number.scale + nMaxDigits); // Don't change this line to use digPos since digCount could have its sign changed.
1407
1408                         FormatCurrency(ref sb, ref number, nMinDigits, nMaxDigits, info);
1409
1410                         break;
1411                     }
1412
1413                 case 'F':
1414                 case 'f':
1415                     {
1416                         if (nMaxDigits < 0)
1417                             nMaxDigits = nMinDigits = info.NumberDecimalDigits;
1418                         else
1419                             nMinDigits = nMaxDigits;
1420
1421                         RoundNumber(ref number, number.scale + nMaxDigits);
1422
1423                         if (number.sign)
1424                             sb.Append(info.NegativeSign);
1425
1426                         FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, null, info.NumberDecimalSeparator, null);
1427
1428                         break;
1429                     }
1430
1431                 case 'N':
1432                 case 'n':
1433                     {
1434                         if (nMaxDigits < 0)
1435                             nMaxDigits = nMinDigits = info.NumberDecimalDigits; // Since we are using digits in our calculation
1436                         else
1437                             nMinDigits = nMaxDigits;
1438
1439                         RoundNumber(ref number, number.scale + nMaxDigits);
1440
1441                         FormatNumber(ref sb, ref number, nMinDigits, nMaxDigits, info);
1442
1443                         break;
1444                     }
1445
1446                 case 'E':
1447                 case 'e':
1448                     {
1449                         if (nMaxDigits < 0)
1450                             nMaxDigits = nMinDigits = 6;
1451                         else
1452                             nMinDigits = nMaxDigits;
1453                         nMaxDigits++;
1454
1455                         RoundNumber(ref number, nMaxDigits);
1456
1457                         if (number.sign)
1458                             sb.Append(info.NegativeSign);
1459
1460                         FormatScientific(ref sb, ref number, nMinDigits, nMaxDigits, info, format);
1461
1462                         break;
1463                     }
1464
1465                 case 'G':
1466                 case 'g':
1467                     {
1468                         bool enableRounding = true;
1469                         if (nMaxDigits < 1)
1470                         {
1471                             if (isDecimal && (nMaxDigits == -1))
1472                             {
1473                                 // Default to 29 digits precision only for G formatting without a precision specifier
1474                                 // This ensures that the PAL code pads out to the correct place even when we use the default precision
1475                                 nMaxDigits = nMinDigits = DecimalPrecision;
1476                                 enableRounding = false;  // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant
1477                             }
1478                             else
1479                             {
1480                                 // This ensures that the PAL code pads out to the correct place even when we use the default precision
1481                                 nMaxDigits = nMinDigits = number.precision;
1482                             }
1483                         }
1484                         else
1485                             nMinDigits = nMaxDigits;
1486
1487                         if (enableRounding) // Don't round for G formatting without precision
1488                             RoundNumber(ref number, nMaxDigits); // This also fixes up the minus zero case
1489                         else
1490                         {
1491                             if (isDecimal && (number.digits[0] == 0))
1492                             {
1493                                 // Minus zero should be formatted as 0
1494                                 number.sign = false;
1495                             }
1496                         }
1497
1498                         if (number.sign)
1499                             sb.Append(info.NegativeSign);
1500
1501                         FormatGeneral(ref sb, ref number, nMinDigits, nMaxDigits, info, (char)(format - ('G' - 'E')), !enableRounding);
1502
1503                         break;
1504                     }
1505
1506                 case 'P':
1507                 case 'p':
1508                     {
1509                         if (nMaxDigits < 0)
1510                             nMaxDigits = nMinDigits = info.PercentDecimalDigits;
1511                         else
1512                             nMinDigits = nMaxDigits;
1513                         number.scale += 2;
1514
1515                         RoundNumber(ref number, number.scale + nMaxDigits);
1516
1517                         FormatPercent(ref sb, ref number, nMinDigits, nMaxDigits, info);
1518
1519                         break;
1520                     }
1521
1522                 default:
1523                     throw new FormatException(SR.Argument_BadFormatSpecifier);
1524             }
1525         }
1526
1527         internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb, ref NumberBuffer number, ReadOnlySpan<char> format, NumberFormatInfo info)
1528         {
1529             int digitCount;
1530             int decimalPos;
1531             int firstDigit;
1532             int lastDigit;
1533             int digPos;
1534             bool scientific;
1535             int thousandPos;
1536             int thousandCount = 0;
1537             bool thousandSeps;
1538             int scaleAdjust;
1539             int adjust;
1540
1541             int section;
1542             int src;
1543             char* dig = number.digits;
1544             char ch;
1545
1546             section = FindSection(format, dig[0] == 0 ? 2 : number.sign ? 1 : 0);
1547
1548             while (true)
1549             {
1550                 digitCount = 0;
1551                 decimalPos = -1;
1552                 firstDigit = 0x7FFFFFFF;
1553                 lastDigit = 0;
1554                 scientific = false;
1555                 thousandPos = -1;
1556                 thousandSeps = false;
1557                 scaleAdjust = 0;
1558                 src = section;
1559
1560                 fixed (char* pFormat = &MemoryMarshal.GetReference(format))
1561                 {
1562                     while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
1563                     {
1564                         switch (ch)
1565                         {
1566                             case '#':
1567                                 digitCount++;
1568                                 break;
1569                             case '0':
1570                                 if (firstDigit == 0x7FFFFFFF)
1571                                     firstDigit = digitCount;
1572                                 digitCount++;
1573                                 lastDigit = digitCount;
1574                                 break;
1575                             case '.':
1576                                 if (decimalPos < 0)
1577                                     decimalPos = digitCount;
1578                                 break;
1579                             case ',':
1580                                 if (digitCount > 0 && decimalPos < 0)
1581                                 {
1582                                     if (thousandPos >= 0)
1583                                     {
1584                                         if (thousandPos == digitCount)
1585                                         {
1586                                             thousandCount++;
1587                                             break;
1588                                         }
1589                                         thousandSeps = true;
1590                                     }
1591                                     thousandPos = digitCount;
1592                                     thousandCount = 1;
1593                                 }
1594                                 break;
1595                             case '%':
1596                                 scaleAdjust += 2;
1597                                 break;
1598                             case '\x2030':
1599                                 scaleAdjust += 3;
1600                                 break;
1601                             case '\'':
1602                             case '"':
1603                                 while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
1604                                     ;
1605                                 break;
1606                             case '\\':
1607                                 if (src < format.Length && pFormat[src] != 0)
1608                                     src++;
1609                                 break;
1610                             case 'E':
1611                             case 'e':
1612                                 if ((src < format.Length && pFormat[src] == '0') ||
1613                                     (src + 1 < format.Length && (pFormat[src] == '+' || pFormat[src] == '-') && pFormat[src + 1] == '0'))
1614                                 {
1615                                     while (++src < format.Length && pFormat[src] == '0');
1616                                     scientific = true;
1617                                 }
1618                                 break;
1619                         }
1620                     }
1621                 }
1622
1623                 if (decimalPos < 0)
1624                     decimalPos = digitCount;
1625
1626                 if (thousandPos >= 0)
1627                 {
1628                     if (thousandPos == decimalPos)
1629                         scaleAdjust -= thousandCount * 3;
1630                     else
1631                         thousandSeps = true;
1632                 }
1633
1634                 if (dig[0] != 0)
1635                 {
1636                     number.scale += scaleAdjust;
1637                     int pos = scientific ? digitCount : number.scale + digitCount - decimalPos;
1638                     RoundNumber(ref number, pos);
1639                     if (dig[0] == 0)
1640                     {
1641                         src = FindSection(format, 2);
1642                         if (src != section)
1643                         {
1644                             section = src;
1645                             continue;
1646                         }
1647                     }
1648                 }
1649                 else
1650                 {
1651                     number.sign = false;   // We need to format -0 without the sign set.
1652                     number.scale = 0;      // Decimals with scale ('0.00') should be rounded.
1653                 }
1654
1655                 break;
1656             }
1657
1658             firstDigit = firstDigit < decimalPos ? decimalPos - firstDigit : 0;
1659             lastDigit = lastDigit > decimalPos ? decimalPos - lastDigit : 0;
1660             if (scientific)
1661             {
1662                 digPos = decimalPos;
1663                 adjust = 0;
1664             }
1665             else
1666             {
1667                 digPos = number.scale > decimalPos ? number.scale : decimalPos;
1668                 adjust = number.scale - decimalPos;
1669             }
1670             src = section;
1671
1672             // Adjust can be negative, so we make this an int instead of an unsigned int.
1673             // Adjust represents the number of characters over the formatting e.g. format string is "0000" and you are trying to
1674             // format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
1675             // -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
1676             Span<int> thousandsSepPos = stackalloc int[4];
1677             int thousandsSepCtr = -1;
1678
1679             if (thousandSeps)
1680             {
1681                 // We need to precompute this outside the number formatting loop
1682                 if (info.NumberGroupSeparator.Length > 0)
1683                 {
1684                     // We need this array to figure out where to insert the thousands separator. We would have to traverse the string
1685                     // backwards. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
1686                     // the thousands separator so we can get away with traversing forwards. Note we only have to compute up to digPos.
1687                     // The max is not bound since you can have formatting strings of the form "000,000..", and this
1688                     // should handle that case too.
1689
1690                     int[] groupDigits = info.numberGroupSizes;
1691
1692                     int groupSizeIndex = 0;     // Index into the groupDigits array.
1693                     int groupTotalSizeCount = 0;
1694                     int groupSizeLen = groupDigits.Length;    // The length of groupDigits array.
1695                     if (groupSizeLen != 0)
1696                         groupTotalSizeCount = groupDigits[groupSizeIndex];   // The current running total of group size.
1697                     int groupSize = groupTotalSizeCount;
1698
1699                     int totalDigits = digPos + ((adjust < 0) ? adjust : 0); // Actual number of digits in o/p
1700                     int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits;
1701                     while (numDigits > groupTotalSizeCount)
1702                     {
1703                         if (groupSize == 0)
1704                             break;
1705                         ++thousandsSepCtr;
1706                         if (thousandsSepCtr >= thousandsSepPos.Length)
1707                         {
1708                             var newThousandsSepPos = new int[thousandsSepPos.Length * 2];
1709                             thousandsSepPos.CopyTo(newThousandsSepPos);
1710                             thousandsSepPos = newThousandsSepPos;
1711                         }
1712
1713                         thousandsSepPos[thousandsSepCtr] = groupTotalSizeCount;
1714                         if (groupSizeIndex < groupSizeLen - 1)
1715                         {
1716                             groupSizeIndex++;
1717                             groupSize = groupDigits[groupSizeIndex];
1718                         }
1719                         groupTotalSizeCount += groupSize;
1720                     }
1721                 }
1722             }
1723             
1724             if (number.sign && section == 0)
1725                 sb.Append(info.NegativeSign);
1726
1727             bool decimalWritten = false;
1728
1729             fixed (char* pFormat = &MemoryMarshal.GetReference(format))
1730             {
1731                 char* cur = dig;
1732
1733                 while (src < format.Length && (ch = pFormat[src++]) != 0 && ch != ';')
1734                 {
1735                     if (adjust > 0)
1736                     {
1737                         switch (ch)
1738                         {
1739                             case '#':
1740                             case '0':
1741                             case '.':
1742                                 while (adjust > 0)
1743                                 {
1744                                     // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
1745                                     // the character after which the groupSeparator needs to be appended.
1746                                     sb.Append(*cur != 0 ? *cur++ : '0');
1747                                     if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
1748                                     {
1749                                         if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
1750                                         {
1751                                             sb.Append(info.NumberGroupSeparator);
1752                                             thousandsSepCtr--;
1753                                         }
1754                                     }
1755                                     digPos--;
1756                                     adjust--;
1757                                 }
1758                                 break;
1759                         }
1760                     }
1761
1762                     switch (ch)
1763                     {
1764                         case '#':
1765                         case '0':
1766                             {
1767                                 if (adjust < 0)
1768                                 {
1769                                     adjust++;
1770                                     ch = digPos <= firstDigit ? '0' : '\0';
1771                                 }
1772                                 else
1773                                 {
1774                                     ch = *cur != 0 ? *cur++ : digPos > lastDigit ? '0' : '\0';
1775                                 }
1776                                 if (ch != 0)
1777                                 {
1778                                     sb.Append(ch);
1779                                     if (thousandSeps && digPos > 1 && thousandsSepCtr >= 0)
1780                                     {
1781                                         if (digPos == thousandsSepPos[thousandsSepCtr] + 1)
1782                                         {
1783                                             sb.Append(info.NumberGroupSeparator);
1784                                             thousandsSepCtr--;
1785                                         }
1786                                     }
1787                                 }
1788
1789                                 digPos--;
1790                                 break;
1791                             }
1792                         case '.':
1793                             {
1794                                 if (digPos != 0 || decimalWritten)
1795                                 {
1796                                     // For compatibility, don't echo repeated decimals
1797                                     break;
1798                                 }
1799                                 // If the format has trailing zeros or the format has a decimal and digits remain
1800                                 if (lastDigit < 0 || (decimalPos < digitCount && *cur != 0))
1801                                 {
1802                                     sb.Append(info.NumberDecimalSeparator);
1803                                     decimalWritten = true;
1804                                 }
1805                                 break;
1806                             }
1807                         case '\x2030':
1808                             sb.Append(info.PerMilleSymbol);
1809                             break;
1810                         case '%':
1811                             sb.Append(info.PercentSymbol);
1812                             break;
1813                         case ',':
1814                             break;
1815                         case '\'':
1816                         case '"':
1817                             while (src < format.Length && pFormat[src] != 0 && pFormat[src] != ch)
1818                                 sb.Append(pFormat[src++]);
1819                             if (src < format.Length && pFormat[src] != 0)
1820                                 src++;
1821                             break;
1822                         case '\\':
1823                             if (src < format.Length && pFormat[src] != 0)
1824                                 sb.Append(pFormat[src++]);
1825                             break;
1826                         case 'E':
1827                         case 'e':
1828                             {
1829                                 bool positiveSign = false;
1830                                 int i = 0;
1831                                 if (scientific)
1832                                 {
1833                                     if (src < format.Length && pFormat[src] == '0')
1834                                     {
1835                                         // Handles E0, which should format the same as E-0
1836                                         i++;
1837                                     }
1838                                     else if (src+1 < format.Length && pFormat[src] == '+' && pFormat[src + 1] == '0')
1839                                     {
1840                                         // Handles E+0
1841                                         positiveSign = true;
1842                                     }
1843                                     else if (src+1 < format.Length && pFormat[src] == '-' && pFormat[src + 1] == '0')
1844                                     {
1845                                         // Handles E-0
1846                                         // Do nothing, this is just a place holder s.t. we don't break out of the loop.
1847                                     }
1848                                     else
1849                                     {
1850                                         sb.Append(ch);
1851                                         break;
1852                                     }
1853
1854                                     while (++src < format.Length && pFormat[src] == '0')
1855                                         i++;
1856                                     if (i > 10)
1857                                         i = 10;
1858
1859                                     int exp = dig[0] == 0 ? 0 : number.scale - decimalPos;
1860                                     FormatExponent(ref sb, info, exp, ch, i, positiveSign);
1861                                     scientific = false;
1862                                 }
1863                                 else
1864                                 {
1865                                     sb.Append(ch); // Copy E or e to output
1866                                     if (src < format.Length)
1867                                     {
1868                                         if (pFormat[src] == '+' || pFormat[src] == '-')
1869                                             sb.Append(pFormat[src++]);
1870                                         while (src < format.Length && pFormat[src] == '0')
1871                                             sb.Append(pFormat[src++]);
1872                                     }
1873                                 }
1874                                 break;
1875                             }
1876                         default:
1877                             sb.Append(ch);
1878                             break;
1879                     }
1880                 }
1881             }
1882         }
1883
1884         private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
1885         {
1886             string fmt = number.sign ?
1887                 s_negCurrencyFormats[info.CurrencyNegativePattern] :
1888                 s_posCurrencyFormats[info.CurrencyPositivePattern];
1889
1890             foreach (char ch in fmt)
1891             {
1892                 switch (ch)
1893                 {
1894                     case '#':
1895                         FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.currencyGroupSizes, info.CurrencyDecimalSeparator, info.CurrencyGroupSeparator);
1896                         break;
1897                     case '-':
1898                         sb.Append(info.NegativeSign);
1899                         break;
1900                     case '$':
1901                         sb.Append(info.CurrencySymbol);
1902                         break;
1903                     default:
1904                         sb.Append(ch);
1905                         break;
1906                 }
1907             }
1908         }
1909
1910         private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, int[] groupDigits, string sDecimal, string sGroup)
1911         {
1912             int digPos = number.scale;
1913             char* dig = number.digits;
1914
1915             if (digPos > 0)
1916             {
1917                 if (groupDigits != null)
1918                 {
1919                     int groupSizeIndex = 0;                             // Index into the groupDigits array.
1920                     int groupSizeCount = groupDigits[groupSizeIndex];   // The current total of group size.
1921                     int bufferSize = digPos;                            // The length of the result buffer string.
1922                     int groupSize = 0;                                  // The current group size.
1923
1924                     // Find out the size of the string buffer for the result.
1925                     if (groupDigits.Length != 0) // You can pass in 0 length arrays
1926                     {
1927                         while (digPos > groupSizeCount)
1928                         {
1929                             groupSize = groupDigits[groupSizeIndex];
1930                             if (groupSize == 0)
1931                                 break;
1932
1933                             bufferSize += sGroup.Length;
1934                             if (groupSizeIndex < groupDigits.Length - 1)
1935                                 groupSizeIndex++;
1936
1937                             groupSizeCount += groupDigits[groupSizeIndex];
1938                             if (groupSizeCount < 0 || bufferSize < 0)
1939                                 throw new ArgumentOutOfRangeException(); // If we overflow
1940                         }
1941
1942                         groupSize = groupSizeCount == 0 ? 0 : groupDigits[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0
1943                     }
1944
1945                     groupSizeIndex = 0;
1946                     int digitCount = 0;
1947                     int digLength = string.wcslen(dig);
1948                     int digStart = (digPos < digLength) ? digPos : digLength;
1949                     fixed (char* spanPtr = &MemoryMarshal.GetReference(sb.AppendSpan(bufferSize)))
1950                     {
1951                         char* p = spanPtr + bufferSize - 1;
1952                         for (int i = digPos - 1; i >= 0; i--)
1953                         {
1954                             *(p--) = (i < digStart) ? dig[i] : '0';
1955
1956                             if (groupSize > 0)
1957                             {
1958                                 digitCount++;
1959                                 if ((digitCount == groupSize) && (i != 0))
1960                                 {
1961                                     for (int j = sGroup.Length - 1; j >= 0; j--)
1962                                         *(p--) = sGroup[j];
1963
1964                                     if (groupSizeIndex < groupDigits.Length - 1)
1965                                     {
1966                                         groupSizeIndex++;
1967                                         groupSize = groupDigits[groupSizeIndex];
1968                                     }
1969                                     digitCount = 0;
1970                                 }
1971                             }
1972                         }
1973
1974                         Debug.Assert(p >= spanPtr - 1, "Underflow");
1975                         dig += digStart;
1976                     }
1977                 }
1978                 else
1979                 {
1980                     do
1981                     {
1982                         sb.Append(*dig != 0 ? *dig++ : '0');
1983                     }
1984                     while (--digPos > 0);
1985                 }
1986             }
1987             else
1988             {
1989                 sb.Append('0');
1990             }
1991
1992             if (nMaxDigits > 0)
1993             {
1994                 sb.Append(sDecimal);
1995                 if ((digPos < 0) && (nMaxDigits > 0))
1996                 {
1997                     int zeroes = Math.Min(-digPos, nMaxDigits);
1998                     sb.Append('0', zeroes);
1999                     digPos += zeroes;
2000                     nMaxDigits -= zeroes;
2001                 }
2002
2003                 while (nMaxDigits > 0)
2004                 {
2005                     sb.Append((*dig != 0) ? *dig++ : '0');
2006                     nMaxDigits--;
2007                 }
2008             }
2009         }
2010
2011         private static void FormatNumber(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
2012         {
2013             string fmt = number.sign ?
2014                 s_negNumberFormats[info.NumberNegativePattern] :
2015                 PosNumberFormat;
2016
2017             foreach (char ch in fmt)
2018             {
2019                 switch (ch)
2020                 {
2021                     case '#':
2022                         FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.numberGroupSizes, info.NumberDecimalSeparator, info.NumberGroupSeparator);
2023                         break;
2024                     case '-':
2025                         sb.Append(info.NegativeSign);
2026                         break;
2027                     default:
2028                         sb.Append(ch);
2029                         break;
2030                 }
2031             }
2032         }
2033
2034         private static unsafe void FormatScientific(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, char expChar)
2035         {
2036             char* dig = number.digits;
2037
2038             sb.Append((*dig != 0) ? *dig++ : '0');
2039
2040             if (nMaxDigits != 1) // For E0 we would like to suppress the decimal point
2041                 sb.Append(info.NumberDecimalSeparator);
2042
2043             while (--nMaxDigits > 0)
2044                 sb.Append((*dig != 0) ? *dig++ : '0');
2045
2046             int e = number.digits[0] == 0 ? 0 : number.scale - 1;
2047             FormatExponent(ref sb, info, e, expChar, 3, true);
2048         }
2049
2050         private static unsafe void FormatExponent(ref ValueStringBuilder sb, NumberFormatInfo info, int value, char expChar, int minDigits, bool positiveSign)
2051         {
2052             sb.Append(expChar);
2053
2054             if (value < 0)
2055             {
2056                 sb.Append(info.NegativeSign);
2057                 value = -value;
2058             }
2059             else
2060             {
2061                 if (positiveSign)
2062                     sb.Append(info.PositiveSign);
2063             }
2064
2065             char* digits = stackalloc char[MaxUInt32DecDigits];
2066             char* p = UInt32ToDecChars(digits + MaxUInt32DecDigits, (uint)value, minDigits);
2067             int i = (int)(digits + MaxUInt32DecDigits - p);
2068             sb.Append(p, (int)(digits + MaxUInt32DecDigits - p));
2069         }
2070
2071         private static unsafe void FormatGeneral(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, char expChar, bool bSuppressScientific)
2072         {
2073             int digPos = number.scale;
2074             bool scientific = false;
2075
2076             if (!bSuppressScientific)
2077             {
2078                 // Don't switch to scientific notation
2079                 if (digPos > nMaxDigits || digPos < -3)
2080                 {
2081                     digPos = 1;
2082                     scientific = true;
2083                 }
2084             }
2085
2086             char* dig = number.digits;
2087
2088             if (digPos > 0)
2089             {
2090                 do
2091                 {
2092                     sb.Append((*dig != 0) ? *dig++ : '0');
2093                 } while (--digPos > 0);
2094             }
2095             else
2096             {
2097                 sb.Append('0');
2098             }
2099
2100             if (*dig != 0 || digPos < 0)
2101             {
2102                 sb.Append(info.NumberDecimalSeparator);
2103
2104                 while (digPos < 0)
2105                 {
2106                     sb.Append('0');
2107                     digPos++;
2108                 }
2109
2110                 while (*dig != 0)
2111                     sb.Append(*dig++);
2112             }
2113
2114             if (scientific)
2115                 FormatExponent(ref sb, info, number.scale - 1, expChar, 2, true);
2116         }
2117
2118         private static void FormatPercent(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info)
2119         {
2120             string fmt = number.sign ?
2121                 s_negPercentFormats[info.PercentNegativePattern] :
2122                 s_posPercentFormats[info.PercentPositivePattern];
2123
2124             foreach (char ch in fmt)
2125             {
2126                 switch (ch)
2127                 {
2128                     case '#':
2129                         FormatFixed(ref sb, ref number, nMinDigits, nMaxDigits, info, info.percentGroupSizes, info.PercentDecimalSeparator, info.PercentGroupSeparator);
2130                         break;
2131                     case '-':
2132                         sb.Append(info.NegativeSign);
2133                         break;
2134                     case '%':
2135                         sb.Append(info.PercentSymbol);
2136                         break;
2137                     default:
2138                         sb.Append(ch);
2139                         break;
2140                 }
2141             }
2142         }
2143
2144         private static unsafe void RoundNumber(ref NumberBuffer number, int pos)
2145         {
2146             char* dig = number.digits;
2147
2148             int i = 0;
2149             while (i < pos && dig[i] != 0)
2150                 i++;
2151
2152             if (i == pos && dig[i] >= '5')
2153             {
2154                 while (i > 0 && dig[i - 1] == '9')
2155                     i--;
2156
2157                 if (i > 0)
2158                 {
2159                     dig[i - 1]++;
2160                 }
2161                 else
2162                 {
2163                     number.scale++;
2164                     dig[0] = '1';
2165                     i = 1;
2166                 }
2167             }
2168             else
2169             {
2170                 while (i > 0 && dig[i - 1] == '0')
2171                     i--;
2172             }
2173             if (i == 0)
2174             {
2175                 number.scale = 0;
2176                 number.sign = false;
2177             }
2178             dig[i] = '\0';
2179         }
2180
2181         private static unsafe int FindSection(ReadOnlySpan<char> format, int section)
2182         {
2183             int src;
2184             char ch;
2185
2186             if (section == 0)
2187                 return 0;
2188
2189             fixed (char* pFormat = &MemoryMarshal.GetReference(format))
2190             {
2191                 src = 0;
2192                 for (;;)
2193                 {
2194                     if (src >= format.Length)
2195                     {
2196                         return 0;
2197                     }
2198
2199                     switch (ch = pFormat[src++])
2200                     {
2201                         case '\'':
2202                         case '"':
2203                             while (src < format.Length && pFormat[src] != 0 && pFormat[src++] != ch)
2204                                 ;
2205                             break;
2206                         case '\\':
2207                             if (src < format.Length && pFormat[src] != 0)
2208                                 src++;
2209                             break;
2210                         case ';':
2211                             if (--section != 0)
2212                                 break;
2213                             if (src < format.Length && pFormat[src] != 0 && pFormat[src] != ';')
2214                                 return src;
2215                             goto case '\0';
2216                         case '\0':
2217                             return 0;
2218                     }
2219                 }
2220             }
2221         }
2222
2223         private static uint Low32(ulong value) => (uint)value;
2224
2225         private static uint High32(ulong value) => (uint)((value & 0xFFFFFFFF00000000) >> 32);
2226
2227         private static uint Int64DivMod1E9(ref ulong value)
2228         {
2229             uint rem = (uint)(value % 1000000000);
2230             value /= 1000000000;
2231             return rem;
2232         }
2233     }
2234 }