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