1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System
.Buffers
.Text
;
6 using System
.Diagnostics
;
7 using System
.Globalization
;
8 using System
.Runtime
.CompilerServices
;
9 using System
.Runtime
.InteropServices
;
14 // The Format methods provided by the numeric classes convert
15 // the numeric value to a string using the format string given by the
16 // format parameter. If the format parameter is null or
17 // an empty string, the number is formatted as if the string "G" (general
18 // format) was specified. The info parameter specifies the
19 // NumberFormatInfo instance to use when formatting the number. If the
20 // info parameter is null or omitted, the numeric formatting information
21 // is obtained from the current culture. The NumberFormatInfo supplies
22 // such information as the characters to use for decimal and thousand
23 // separators, and the spelling and placement of currency symbols in monetary
26 // Format strings fall into two categories: Standard format strings and
27 // user-defined format strings. A format string consisting of a single
28 // alphabetic character (A-Z or a-z), optionally followed by a sequence of
29 // digits (0-9), is a standard format string. All other format strings are
30 // used-defined format strings.
32 // A standard format string takes the form Axx, where A is an
33 // alphabetic character called the format specifier and xx is a
34 // sequence of digits called the precision specifier. The format
35 // specifier controls the type of formatting applied to the number and the
36 // precision specifier controls the number of significant digits or decimal
37 // places of the formatting operation. The following table describes the
38 // supported standard formats.
40 // C c - Currency format. The number is
41 // converted to a string that represents a currency amount. The conversion is
42 // controlled by the currency format information of the NumberFormatInfo
43 // used to format the number. The precision specifier indicates the desired
44 // number of decimal places. If the precision specifier is omitted, the default
45 // currency precision given by the NumberFormatInfo is used.
47 // D d - Decimal format. This format is
48 // supported for integral types only. The number is converted to a string of
49 // decimal digits, prefixed by a minus sign if the number is negative. The
50 // precision specifier indicates the minimum number of digits desired in the
51 // resulting string. If required, the number will be left-padded with zeros to
52 // produce the number of digits given by the precision specifier.
54 // E e Engineering (scientific) format.
55 // The number is converted to a string of the form
56 // "-d.ddd...E+ddd" or "-d.ddd...e+ddd", where each
57 // 'd' indicates a digit (0-9). The string starts with a minus sign if the
58 // number is negative, and one digit always precedes the decimal point. The
59 // precision specifier indicates the desired number of digits after the decimal
60 // point. If the precision specifier is omitted, a default of 6 digits after
61 // the decimal point is used. The format specifier indicates whether to prefix
62 // the exponent with an 'E' or an 'e'. The exponent is always consists of a
63 // plus or minus sign and three digits.
65 // F f Fixed point format. The number is
66 // converted to a string of the form "-ddd.ddd....", where each
67 // 'd' indicates a digit (0-9). The string starts with a minus sign if the
68 // number is negative. The precision specifier indicates the desired number of
69 // decimal places. If the precision specifier is omitted, the default numeric
70 // precision given by the NumberFormatInfo is used.
72 // G g - General format. The number is
73 // converted to the shortest possible decimal representation using fixed point
74 // or scientific format. The precision specifier determines the number of
75 // significant digits in the resulting string. If the precision specifier is
76 // omitted, the number of significant digits is determined by the type of the
77 // number being converted (10 for int, 19 for long, 7 for
78 // float, 15 for double, 19 for Currency, and 29 for
79 // Decimal). Trailing zeros after the decimal point are removed, and the
80 // resulting string contains a decimal point only if required. The resulting
81 // string uses fixed point format if the exponent of the number is less than
82 // the number of significant digits and greater than or equal to -4. Otherwise,
83 // the resulting string uses scientific format, and the case of the format
84 // specifier controls whether the exponent is prefixed with an 'E' or an 'e'.
86 // N n Number format. The number is
87 // converted to a string of the form "-d,ddd,ddd.ddd....", where
88 // each 'd' indicates a digit (0-9). The string starts with a minus sign if the
89 // number is negative. Thousand separators are inserted between each group of
90 // three digits to the left of the decimal point. The precision specifier
91 // indicates the desired number of decimal places. If the precision specifier
92 // is omitted, the default numeric precision given by the
93 // NumberFormatInfo is used.
95 // X x - Hexadecimal format. This format is
96 // supported for integral types only. The number is converted to a string of
97 // hexadecimal digits. The format specifier indicates whether to use upper or
98 // lower case characters for the hexadecimal digits above 9 ('X' for 'ABCDEF',
99 // and 'x' for 'abcdef'). The precision specifier indicates the minimum number
100 // of digits desired in the resulting string. If required, the number will be
101 // left-padded with zeros to produce the number of digits given by the
102 // precision specifier.
104 // Some examples of standard format strings and their results are shown in the
105 // table below. (The examples all assume a default NumberFormatInfo.)
107 // Value Format Result
108 // 12345.6789 C $12,345.68
109 // -12345.6789 C ($12,345.68)
112 // 12345.6789 E 1.234568E+004
113 // 12345.6789 E10 1.2345678900E+004
114 // 12345.6789 e4 1.2346e+004
115 // 12345.6789 F 12345.68
116 // 12345.6789 F0 12346
117 // 12345.6789 F6 12345.678900
118 // 12345.6789 G 12345.6789
119 // 12345.6789 G7 12345.68
120 // 123456789 G7 1.234568E8
121 // 12345.6789 N 12,345.68
122 // 123456789 N4 123,456,789.0000
125 // 0x2c45e X8 0002C45E
127 // Format strings that do not start with an alphabetic character, or that start
128 // with an alphabetic character followed by a non-digit, are called
129 // user-defined format strings. The following table describes the formatting
130 // characters that are supported in user defined format strings.
133 // 0 - Digit placeholder. If the value being
134 // formatted has a digit in the position where the '0' appears in the format
135 // string, then that digit is copied to the output string. Otherwise, a '0' is
136 // stored in that position in the output string. The position of the leftmost
137 // '0' before the decimal point and the rightmost '0' after the decimal point
138 // determines the range of digits that are always present in the output
141 // # - Digit placeholder. If the value being
142 // formatted has a digit in the position where the '#' appears in the format
143 // string, then that digit is copied to the output string. Otherwise, nothing
144 // is stored in that position in the output string.
146 // . - Decimal point. The first '.' character
147 // in the format string determines the location of the decimal separator in the
148 // formatted value; any additional '.' characters are ignored. The actual
149 // character used as a the decimal separator in the output string is given by
150 // the NumberFormatInfo used to format the number.
152 // , - Thousand separator and number scaling.
153 // The ',' character serves two purposes. First, if the format string contains
154 // a ',' character between two digit placeholders (0 or #) and to the left of
155 // the decimal point if one is present, then the output will have thousand
156 // separators inserted between each group of three digits to the left of the
157 // decimal separator. The actual character used as a the decimal separator in
158 // the output string is given by the NumberFormatInfo used to format the
159 // number. Second, if the format string contains one or more ',' characters
160 // immediately to the left of the decimal point, or after the last digit
161 // placeholder if there is no decimal point, then the number will be divided by
162 // 1000 times the number of ',' characters before it is formatted. For example,
163 // the format string '0,,' will represent 100 million as just 100. Use of the
164 // ',' character to indicate scaling does not also cause the formatted number
165 // to have thousand separators. Thus, to scale a number by 1 million and insert
166 // thousand separators you would use the format string '#,##0,,'.
168 // % - Percentage placeholder. The presence of
169 // a '%' character in the format string causes the number to be multiplied by
170 // 100 before it is formatted. The '%' character itself is inserted in the
171 // output string where it appears in the format string.
173 // E+ E- e+ e- - Scientific notation.
174 // If any of the strings 'E+', 'E-', 'e+', or 'e-' are present in the format
175 // string and are immediately followed by at least one '0' character, then the
176 // number is formatted using scientific notation with an 'E' or 'e' inserted
177 // between the number and the exponent. The number of '0' characters following
178 // the scientific notation indicator determines the minimum number of digits to
179 // output for the exponent. The 'E+' and 'e+' formats indicate that a sign
180 // character (plus or minus) should always precede the exponent. The 'E-' and
181 // 'e-' formats indicate that a sign character should only precede negative
184 // \ - Literal character. A backslash character
185 // causes the next character in the format string to be copied to the output
186 // string as-is. The backslash itself isn't copied, so to place a backslash
187 // character in the output string, use two backslashes (\\) in the format
190 // 'ABC' "ABC" - Literal string. Characters
191 // enclosed in single or double quotation marks are copied to the output string
192 // as-is and do not affect formatting.
194 // ; - Section separator. The ';' character is
195 // used to separate sections for positive, negative, and zero numbers in the
198 // Other - All other characters are copied to
199 // the output string in the position they appear.
201 // For fixed point formats (formats not containing an 'E+', 'E-', 'e+', or
202 // 'e-'), the number is rounded to as many decimal places as there are digit
203 // placeholders to the right of the decimal point. If the format string does
204 // not contain a decimal point, the number is rounded to the nearest
205 // integer. If the number has more digits than there are digit placeholders to
206 // the left of the decimal point, the extra digits are copied to the output
207 // string immediately before the first digit placeholder.
209 // For scientific formats, the number is rounded to as many significant digits
210 // as there are digit placeholders in the format string.
212 // To allow for different formatting of positive, negative, and zero values, a
213 // user-defined format string may contain up to three sections separated by
214 // semicolons. The results of having one, two, or three sections in the format
215 // string are described in the table below.
219 // One - The format string applies to all values.
221 // Two - The first section applies to positive values
222 // and zeros, and the second section applies to negative values. If the number
223 // to be formatted is negative, but becomes zero after rounding according to
224 // the format in the second section, then the resulting zero is formatted
225 // according to the first section.
227 // Three - The first section applies to positive
228 // values, the second section applies to negative values, and the third section
229 // applies to zeros. The second section may be left empty (by having no
230 // characters between the semicolons), in which case the first section applies
231 // to all non-zero values. If the number to be formatted is non-zero, but
232 // becomes zero after rounding according to the format in the first or second
233 // section, then the resulting zero is formatted according to the third
236 // For both standard and user-defined formatting operations on values of type
237 // float and double, if the value being formatted is a NaN (Not
238 // a Number) or a positive or negative infinity, then regardless of the format
239 // string, the resulting string is given by the NaNSymbol,
240 // PositiveInfinitySymbol, or NegativeInfinitySymbol property of
241 // the NumberFormatInfo used to format the number.
243 internal static partial class Number
245 internal const int DecimalPrecision
= 29; // Decimal.DecCalc also uses this value
247 // SinglePrecision and DoublePrecision represent the maximum number of digits required
248 // to guarantee that any given Single or Double can roundtrip. Some numbers may require
249 // less, but none will require more.
250 private const int SinglePrecision
= 9;
251 private const int DoublePrecision
= 17;
253 // SinglePrecisionCustomFormat and DoublePrecisionCustomFormat are used to ensure that
254 // custom format strings return the same string as in previous releases when the format
255 // would return x digits or less (where x is the value of the corresponding constant).
256 // In order to support more digits, we would need to update ParseFormatSpecifier to pre-parse
257 // the format and determine exactly how many digits are being requested and whether they
258 // represent "significant digits" or "digits after the decimal point".
259 private const int SinglePrecisionCustomFormat
= 6;
260 private const int DoublePrecisionCustomFormat
= 15;
262 private const int DefaultPrecisionExponentialFormat
= 6;
264 private const int ScaleNAN
= unchecked((int)0x80000000);
265 private const int ScaleINF
= 0x7FFFFFFF;
266 private const int MaxUInt32DecDigits
= 10;
267 private const int CharStackBufferSize
= 32;
268 private const string PosNumberFormat
= "#";
270 private static readonly string[] s_singleDigitStringCache
= { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }
;
272 private static readonly string[] s_posCurrencyFormats
=
274 "$#", "#$", "$ #", "# $"
277 private static readonly string[] s_negCurrencyFormats
=
279 "($#)", "-$#", "$-#", "$#-",
280 "(#$)", "-#$", "#-$", "#$-",
281 "-# $", "-$ #", "# $-", "$ #-",
282 "$ -#", "#- $", "($ #)", "(# $)"
285 private static readonly string[] s_posPercentFormats
=
287 "# %", "#%", "%#", "% #"
290 private static readonly string[] s_negPercentFormats
=
292 "-# %", "-#%", "-%#",
295 "-% #", "# %-", "% #-",
299 private static readonly string[] s_negNumberFormats
=
301 "(#)", "-#", "- #", "#-", "# -",
304 public static unsafe string FormatDecimal(decimal value, ReadOnlySpan
<char> format
, NumberFormatInfo info
)
306 char fmt
= ParseFormatSpecifier(format
, out int digits
);
308 byte* pDigits
= stackalloc byte[DecimalNumberBufferLength
];
309 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Decimal
, pDigits
, DecimalNumberBufferLength
);
311 DecimalToNumber(ref value, ref number
);
313 char* stackPtr
= stackalloc char[CharStackBufferSize
];
314 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
318 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
322 NumberToStringFormat(ref sb
, ref number
, format
, info
);
325 return sb
.ToString();
328 public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan
<char> format
, NumberFormatInfo info
, Span
<char> destination
, out int charsWritten
)
330 char fmt
= ParseFormatSpecifier(format
, out int digits
);
332 byte* pDigits
= stackalloc byte[DecimalNumberBufferLength
];
333 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Decimal
, pDigits
, DecimalNumberBufferLength
);
335 DecimalToNumber(ref value, ref number
);
337 char* stackPtr
= stackalloc char[CharStackBufferSize
];
338 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
342 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
346 NumberToStringFormat(ref sb
, ref number
, format
, info
);
349 return sb
.TryCopyTo(destination
, out charsWritten
);
352 internal static unsafe void DecimalToNumber(ref decimal d
, ref NumberBuffer number
)
354 byte* buffer
= number
.GetDigitsPointer();
355 number
.DigitsCount
= DecimalPrecision
;
356 number
.IsNegative
= d
.IsNegative
;
358 byte* p
= buffer
+ DecimalPrecision
;
359 while ((d
.Mid
| d
.High
) != 0)
361 p
= UInt32ToDecChars(p
, decimal.DecDivMod1E9(ref d
), 9);
363 p
= UInt32ToDecChars(p
, d
.Low
, 0);
365 int i
= (int)((buffer
+ DecimalPrecision
) - p
);
367 number
.DigitsCount
= i
;
368 number
.Scale
= i
- d
.Scale
;
370 byte* dst
= number
.GetDigitsPointer();
377 number
.CheckConsistency();
380 public static string FormatDouble(double value, string? format
, NumberFormatInfo info
)
382 Span
<char> stackBuffer
= stackalloc char[CharStackBufferSize
];
383 var sb
= new ValueStringBuilder(stackBuffer
);
384 return FormatDouble(ref sb
, value, format
, info
) ?? sb
.ToString();
387 public static bool TryFormatDouble(double value, ReadOnlySpan
<char> format
, NumberFormatInfo info
, Span
<char> destination
, out int charsWritten
)
389 Span
<char> stackBuffer
= stackalloc char[CharStackBufferSize
];
390 var sb
= new ValueStringBuilder(stackBuffer
);
391 string? s
= FormatDouble(ref sb
, value, format
, info
);
393 TryCopyTo(s
, destination
, out charsWritten
) :
394 sb
.TryCopyTo(destination
, out charsWritten
);
397 private static int GetFloatingPointMaxDigitsAndPrecision(char fmt
, ref int precision
, NumberFormatInfo info
, out bool isSignificantDigits
)
401 isSignificantDigits
= true;
405 int maxDigits
= precision
;
412 // The currency format uses the precision specifier to indicate the number of
413 // decimal digits to format. This defaults to NumberFormatInfo.CurrencyDecimalDigits.
417 precision
= info
.CurrencyDecimalDigits
;
419 isSignificantDigits
= false;
427 // The exponential format uses the precision specifier to indicate the number of
428 // decimal digits to format. This defaults to 6. However, the exponential format
429 // also always formats a single integral digit, so we need to increase the precision
430 // specifier and treat it as the number of significant digits to account for this.
434 precision
= DefaultPrecisionExponentialFormat
;
438 isSignificantDigits
= true;
448 // The fixed-point and number formats use the precision specifier to indicate the number
449 // of decimal digits to format. This defaults to NumberFormatInfo.NumberDecimalDigits.
453 precision
= info
.NumberDecimalDigits
;
455 isSignificantDigits
= false;
463 // The general format uses the precision specifier to indicate the number of significant
464 // digits to format. This defaults to the shortest roundtrippable string. Additionally,
465 // given that we can't return zero significant digits, we treat 0 as returning the shortest
466 // roundtrippable string as well.
472 isSignificantDigits
= true;
480 // The percent format uses the precision specifier to indicate the number of
481 // decimal digits to format. This defaults to NumberFormatInfo.PercentDecimalDigits.
482 // However, the percent format also always multiplies the number by 100, so we need
483 // to increase the precision specifier to ensure we get the appropriate number of digits.
487 precision
= info
.PercentDecimalDigits
;
491 isSignificantDigits
= false;
499 // The roundtrip format ignores the precision specifier and always returns the shortest
500 // roundtrippable string.
503 isSignificantDigits
= true;
510 throw new FormatException(SR
.Argument_BadFormatSpecifier
);
517 /// <summary>Formats the specified value according to the specified format and info.</summary>
519 /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
520 /// Null if no existing string was returned, in which case the formatted output is in the builder.
522 private static unsafe string? FormatDouble(ref ValueStringBuilder sb
, double value, ReadOnlySpan
<char> format
, NumberFormatInfo info
)
524 if (!double.IsFinite(value))
526 if (double.IsNaN(value))
528 return info
.NaNSymbol
;
531 return double.IsNegative(value) ? info
.NegativeInfinitySymbol
: info
.PositiveInfinitySymbol
;
534 char fmt
= ParseFormatSpecifier(format
, out int precision
);
535 byte* pDigits
= stackalloc byte[DoubleNumberBufferLength
];
539 // For back-compat we currently specially treat the precision for custom
540 // format specifiers. The constant has more details as to why.
541 precision
= DoublePrecisionCustomFormat
;
544 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.FloatingPoint
, pDigits
, DoubleNumberBufferLength
);
545 number
.IsNegative
= double.IsNegative(value);
547 // We need to track the original precision requested since some formats
548 // accept values like 0 and others may require additional fixups.
549 int nMaxDigits
= GetFloatingPointMaxDigitsAndPrecision(fmt
, ref precision
, info
, out bool isSignificantDigits
);
551 if ((value != 0.0) && (!isSignificantDigits
|| !Grisu3
.TryRunDouble(value, precision
, ref number
)))
553 Dragon4Double(value, precision
, isSignificantDigits
, ref number
);
556 number
.CheckConsistency();
558 // When the number is known to be roundtrippable (either because we requested it be, or
559 // because we know we have enough digits to satisfy roundtrippability), we should validate
560 // that the number actually roundtrips back to the original result.
562 Debug
.Assert(((precision
!= -1) && (precision
< DoublePrecision
)) || (BitConverter
.DoubleToInt64Bits(value) == BitConverter
.DoubleToInt64Bits(NumberToDouble(ref number
))));
568 Debug
.Assert((fmt
== 'G') || (fmt
== 'g') || (fmt
== 'R') || (fmt
== 'r'));
570 // For the roundtrip and general format specifiers, when returning the shortest roundtrippable
571 // string, we need to update the maximum number of digits to be the greater of number.DigitsCount
572 // or DoublePrecision. This ensures that we continue returning "pretty" strings for values with
573 // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01"
574 // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation.
576 nMaxDigits
= Math
.Max(number
.DigitsCount
, DoublePrecision
);
578 NumberToString(ref sb
, ref number
, fmt
, nMaxDigits
, info
);
582 Debug
.Assert(precision
== DoublePrecisionCustomFormat
);
583 NumberToStringFormat(ref sb
, ref number
, format
, info
);
588 public static string FormatSingle(float value, string? format
, NumberFormatInfo info
)
590 Span
<char> stackBuffer
= stackalloc char[CharStackBufferSize
];
591 var sb
= new ValueStringBuilder(stackBuffer
);
592 return FormatSingle(ref sb
, value, format
, info
) ?? sb
.ToString();
595 public static bool TryFormatSingle(float value, ReadOnlySpan
<char> format
, NumberFormatInfo info
, Span
<char> destination
, out int charsWritten
)
597 Span
<char> stackBuffer
= stackalloc char[CharStackBufferSize
];
598 var sb
= new ValueStringBuilder(stackBuffer
);
599 string? s
= FormatSingle(ref sb
, value, format
, info
);
601 TryCopyTo(s
, destination
, out charsWritten
) :
602 sb
.TryCopyTo(destination
, out charsWritten
);
605 /// <summary>Formats the specified value according to the specified format and info.</summary>
607 /// Non-null if an existing string can be returned, in which case the builder will be unmodified.
608 /// Null if no existing string was returned, in which case the formatted output is in the builder.
610 private static unsafe string? FormatSingle(ref ValueStringBuilder sb
, float value, ReadOnlySpan
<char> format
, NumberFormatInfo info
)
612 if (!float.IsFinite(value))
614 if (float.IsNaN(value))
616 return info
.NaNSymbol
;
619 return float.IsNegative(value) ? info
.NegativeInfinitySymbol
: info
.PositiveInfinitySymbol
;
622 char fmt
= ParseFormatSpecifier(format
, out int precision
);
623 byte* pDigits
= stackalloc byte[SingleNumberBufferLength
];
627 // For back-compat we currently specially treat the precision for custom
628 // format specifiers. The constant has more details as to why.
629 precision
= SinglePrecisionCustomFormat
;
632 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.FloatingPoint
, pDigits
, SingleNumberBufferLength
);
633 number
.IsNegative
= float.IsNegative(value);
635 // We need to track the original precision requested since some formats
636 // accept values like 0 and others may require additional fixups.
637 int nMaxDigits
= GetFloatingPointMaxDigitsAndPrecision(fmt
, ref precision
, info
, out bool isSignificantDigits
);
639 if ((value != 0.0f
) && (!isSignificantDigits
|| !Grisu3
.TryRunSingle(value, precision
, ref number
)))
641 Dragon4Single(value, precision
, isSignificantDigits
, ref number
);
644 number
.CheckConsistency();
646 // When the number is known to be roundtrippable (either because we requested it be, or
647 // because we know we have enough digits to satisfy roundtrippability), we should validate
648 // that the number actually roundtrips back to the original result.
650 Debug
.Assert(((precision
!= -1) && (precision
< SinglePrecision
)) || (BitConverter
.SingleToInt32Bits(value) == BitConverter
.SingleToInt32Bits(NumberToSingle(ref number
))));
656 Debug
.Assert((fmt
== 'G') || (fmt
== 'g') || (fmt
== 'R') || (fmt
== 'r'));
658 // For the roundtrip and general format specifiers, when returning the shortest roundtrippable
659 // string, we need to update the maximum number of digits to be the greater of number.DigitsCount
660 // or SinglePrecision. This ensures that we continue returning "pretty" strings for values with
661 // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01"
662 // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation.
664 nMaxDigits
= Math
.Max(number
.DigitsCount
, SinglePrecision
);
666 NumberToString(ref sb
, ref number
, fmt
, nMaxDigits
, info
);
670 Debug
.Assert(precision
== SinglePrecisionCustomFormat
);
671 NumberToStringFormat(ref sb
, ref number
, format
, info
);
676 private static bool TryCopyTo(string source
, Span
<char> destination
, out int charsWritten
)
678 Debug
.Assert(source
!= null);
680 if (source
.AsSpan().TryCopyTo(destination
))
682 charsWritten
= source
.Length
;
690 public static unsafe string FormatInt32(int value, ReadOnlySpan
<char> format
, IFormatProvider
? provider
)
692 // Fast path for default format with a non-negative value
693 if (value >= 0 && format
.Length
== 0)
695 return UInt32ToDecStr((uint)value, digits
: -1);
698 char fmt
= ParseFormatSpecifier(format
, out int digits
);
699 char fmtUpper
= (char)(fmt
& 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
700 if ((fmtUpper
== 'G' && digits
< 1) || fmtUpper
== 'D')
703 UInt32ToDecStr((uint)value, digits
) :
704 NegativeInt32ToDecStr(value, digits
, NumberFormatInfo
.GetInstance(provider
).NegativeSign
);
706 else if (fmtUpper
== 'X')
708 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
709 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
710 return Int32ToHexStr(value, (char)(fmt
- ('X' - 'A' + 10)), digits
);
714 NumberFormatInfo info
= NumberFormatInfo
.GetInstance(provider
);
716 byte* pDigits
= stackalloc byte[Int32NumberBufferLength
];
717 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Integer
, pDigits
, Int32NumberBufferLength
);
719 Int32ToNumber(value, ref number
);
721 char* stackPtr
= stackalloc char[CharStackBufferSize
];
722 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
726 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
730 NumberToStringFormat(ref sb
, ref number
, format
, info
);
732 return sb
.ToString();
736 public static unsafe bool TryFormatInt32(int value, ReadOnlySpan
<char> format
, IFormatProvider
? provider
, Span
<char> destination
, out int charsWritten
)
738 // Fast path for default format with a non-negative value
739 if (value >= 0 && format
.Length
== 0)
741 return TryUInt32ToDecStr((uint)value, digits
: -1, destination
, out charsWritten
);
744 char fmt
= ParseFormatSpecifier(format
, out int digits
);
745 char fmtUpper
= (char)(fmt
& 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
746 if ((fmtUpper
== 'G' && digits
< 1) || fmtUpper
== 'D')
749 TryUInt32ToDecStr((uint)value, digits
, destination
, out charsWritten
) :
750 TryNegativeInt32ToDecStr(value, digits
, NumberFormatInfo
.GetInstance(provider
).NegativeSign
, destination
, out charsWritten
);
752 else if (fmtUpper
== 'X')
754 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
755 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
756 return TryInt32ToHexStr(value, (char)(fmt
- ('X' - 'A' + 10)), digits
, destination
, out charsWritten
);
760 NumberFormatInfo info
= NumberFormatInfo
.GetInstance(provider
);
762 byte* pDigits
= stackalloc byte[Int32NumberBufferLength
];
763 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Integer
, pDigits
, Int32NumberBufferLength
);
765 Int32ToNumber(value, ref number
);
767 char* stackPtr
= stackalloc char[CharStackBufferSize
];
768 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
772 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
776 NumberToStringFormat(ref sb
, ref number
, format
, info
);
778 return sb
.TryCopyTo(destination
, out charsWritten
);
782 public static unsafe string FormatUInt32(uint value, ReadOnlySpan
<char> format
, IFormatProvider
? provider
)
784 // Fast path for default format
785 if (format
.Length
== 0)
787 return UInt32ToDecStr(value, digits
: -1);
790 char fmt
= ParseFormatSpecifier(format
, out int digits
);
791 char fmtUpper
= (char)(fmt
& 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
792 if ((fmtUpper
== 'G' && digits
< 1) || fmtUpper
== 'D')
794 return UInt32ToDecStr(value, digits
);
796 else if (fmtUpper
== 'X')
798 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
799 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
800 return Int32ToHexStr((int)value, (char)(fmt
- ('X' - 'A' + 10)), digits
);
804 NumberFormatInfo info
= NumberFormatInfo
.GetInstance(provider
);
806 byte* pDigits
= stackalloc byte[UInt32NumberBufferLength
];
807 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Integer
, pDigits
, UInt32NumberBufferLength
);
809 UInt32ToNumber(value, ref number
);
811 char* stackPtr
= stackalloc char[CharStackBufferSize
];
812 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
816 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
820 NumberToStringFormat(ref sb
, ref number
, format
, info
);
822 return sb
.ToString();
826 public static unsafe bool TryFormatUInt32(uint value, ReadOnlySpan
<char> format
, IFormatProvider
? provider
, Span
<char> destination
, out int charsWritten
)
828 // Fast path for default format
829 if (format
.Length
== 0)
831 return TryUInt32ToDecStr(value, digits
: -1, destination
, out charsWritten
);
834 char fmt
= ParseFormatSpecifier(format
, out int digits
);
835 char fmtUpper
= (char)(fmt
& 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
836 if ((fmtUpper
== 'G' && digits
< 1) || fmtUpper
== 'D')
838 return TryUInt32ToDecStr(value, digits
, destination
, out charsWritten
);
840 else if (fmtUpper
== 'X')
842 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
843 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code produces lowercase.
844 return TryInt32ToHexStr((int)value, (char)(fmt
- ('X' - 'A' + 10)), digits
, destination
, out charsWritten
);
848 NumberFormatInfo info
= NumberFormatInfo
.GetInstance(provider
);
850 byte* pDigits
= stackalloc byte[UInt32NumberBufferLength
];
851 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Integer
, pDigits
, UInt32NumberBufferLength
);
853 UInt32ToNumber(value, ref number
);
855 char* stackPtr
= stackalloc char[CharStackBufferSize
];
856 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
860 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
864 NumberToStringFormat(ref sb
, ref number
, format
, info
);
866 return sb
.TryCopyTo(destination
, out charsWritten
);
870 public static unsafe string FormatInt64(long value, ReadOnlySpan
<char> format
, IFormatProvider
? provider
)
872 // Fast path for default format with a non-negative value
873 if (value >= 0 && format
.Length
== 0)
875 return UInt64ToDecStr((ulong)value, digits
: -1);
878 char fmt
= ParseFormatSpecifier(format
, out int digits
);
879 char fmtUpper
= (char)(fmt
& 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
880 if ((fmtUpper
== 'G' && digits
< 1) || fmtUpper
== 'D')
883 UInt64ToDecStr((ulong)value, digits
) :
884 NegativeInt64ToDecStr(value, digits
, NumberFormatInfo
.GetInstance(provider
).NegativeSign
);
886 else if (fmtUpper
== 'X')
888 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
889 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
890 // produces lowercase.
891 return Int64ToHexStr(value, (char)(fmt
- ('X' - 'A' + 10)), digits
);
895 NumberFormatInfo info
= NumberFormatInfo
.GetInstance(provider
);
897 byte* pDigits
= stackalloc byte[Int64NumberBufferLength
];
898 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Integer
, pDigits
, Int64NumberBufferLength
);
900 Int64ToNumber(value, ref number
);
902 char* stackPtr
= stackalloc char[CharStackBufferSize
];
903 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
907 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
911 NumberToStringFormat(ref sb
, ref number
, format
, info
);
913 return sb
.ToString();
917 public static unsafe bool TryFormatInt64(long value, ReadOnlySpan
<char> format
, IFormatProvider
? provider
, Span
<char> destination
, out int charsWritten
)
919 // Fast path for default format with a non-negative value
920 if (value >= 0 && format
.Length
== 0)
922 return TryUInt64ToDecStr((ulong)value, digits
: -1, destination
, out charsWritten
);
925 char fmt
= ParseFormatSpecifier(format
, out int digits
);
926 char fmtUpper
= (char)(fmt
& 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
927 if ((fmtUpper
== 'G' && digits
< 1) || fmtUpper
== 'D')
930 TryUInt64ToDecStr((ulong)value, digits
, destination
, out charsWritten
) :
931 TryNegativeInt64ToDecStr(value, digits
, NumberFormatInfo
.GetInstance(provider
).NegativeSign
, destination
, out charsWritten
);
933 else if (fmtUpper
== 'X')
935 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
936 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
937 // produces lowercase.
938 return TryInt64ToHexStr(value, (char)(fmt
- ('X' - 'A' + 10)), digits
, destination
, out charsWritten
);
942 NumberFormatInfo info
= NumberFormatInfo
.GetInstance(provider
);
944 byte* pDigits
= stackalloc byte[Int64NumberBufferLength
];
945 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Integer
, pDigits
, Int64NumberBufferLength
);
947 Int64ToNumber(value, ref number
);
949 char* stackPtr
= stackalloc char[CharStackBufferSize
];
950 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
954 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
958 NumberToStringFormat(ref sb
, ref number
, format
, info
);
960 return sb
.TryCopyTo(destination
, out charsWritten
);
964 public static unsafe string FormatUInt64(ulong value, ReadOnlySpan
<char> format
, IFormatProvider
? provider
)
966 // Fast path for default format
967 if (format
.Length
== 0)
969 return UInt64ToDecStr(value, digits
: -1);
972 char fmt
= ParseFormatSpecifier(format
, out int digits
);
973 char fmtUpper
= (char)(fmt
& 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
974 if ((fmtUpper
== 'G' && digits
< 1) || fmtUpper
== 'D')
976 return UInt64ToDecStr(value, digits
);
978 else if (fmtUpper
== 'X')
980 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
981 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
982 // produces lowercase.
983 return Int64ToHexStr((long)value, (char)(fmt
- ('X' - 'A' + 10)), digits
);
987 NumberFormatInfo info
= NumberFormatInfo
.GetInstance(provider
);
989 byte* pDigits
= stackalloc byte[UInt64NumberBufferLength
];
990 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Integer
, pDigits
, UInt64NumberBufferLength
);
992 UInt64ToNumber(value, ref number
);
994 char* stackPtr
= stackalloc char[CharStackBufferSize
];
995 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
999 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
1003 NumberToStringFormat(ref sb
, ref number
, format
, info
);
1005 return sb
.ToString();
1009 public static unsafe bool TryFormatUInt64(ulong value, ReadOnlySpan
<char> format
, IFormatProvider
? provider
, Span
<char> destination
, out int charsWritten
)
1011 // Fast path for default format
1012 if (format
.Length
== 0)
1014 return TryUInt64ToDecStr(value, digits
: -1, destination
, out charsWritten
);
1017 char fmt
= ParseFormatSpecifier(format
, out int digits
);
1018 char fmtUpper
= (char)(fmt
& 0xFFDF); // ensure fmt is upper-cased for purposes of comparison
1019 if ((fmtUpper
== 'G' && digits
< 1) || fmtUpper
== 'D')
1021 return TryUInt64ToDecStr(value, digits
, destination
, out charsWritten
);
1023 else if (fmtUpper
== 'X')
1025 // The fmt-(X-A+10) hack has the effect of dictating whether we produce uppercase or lowercase
1026 // hex numbers for a-f. 'X' as the fmt code produces uppercase. 'x' as the format code
1027 // produces lowercase.
1028 return TryInt64ToHexStr((long)value, (char)(fmt
- ('X' - 'A' + 10)), digits
, destination
, out charsWritten
);
1032 NumberFormatInfo info
= NumberFormatInfo
.GetInstance(provider
);
1034 byte* pDigits
= stackalloc byte[UInt64NumberBufferLength
];
1035 NumberBuffer number
= new NumberBuffer(NumberBufferKind
.Integer
, pDigits
, UInt64NumberBufferLength
);
1037 UInt64ToNumber(value, ref number
);
1039 char* stackPtr
= stackalloc char[CharStackBufferSize
];
1040 ValueStringBuilder sb
= new ValueStringBuilder(new Span
<char>(stackPtr
, CharStackBufferSize
));
1044 NumberToString(ref sb
, ref number
, fmt
, digits
, info
);
1048 NumberToStringFormat(ref sb
, ref number
, format
, info
);
1050 return sb
.TryCopyTo(destination
, out charsWritten
);
1054 [MethodImpl(MethodImplOptions
.AggressiveInlining
)] // called from only one location
1055 private static unsafe void Int32ToNumber(int value, ref NumberBuffer number
)
1057 number
.DigitsCount
= Int32Precision
;
1061 number
.IsNegative
= false;
1065 number
.IsNegative
= true;
1069 byte* buffer
= number
.GetDigitsPointer();
1070 byte* p
= UInt32ToDecChars(buffer
+ Int32Precision
, (uint)value, 0);
1072 int i
= (int)(buffer
+ Int32Precision
- p
);
1074 number
.DigitsCount
= i
;
1077 byte* dst
= number
.GetDigitsPointer();
1080 *dst
= (byte)('\0');
1082 number
.CheckConsistency();
1085 private static unsafe string NegativeInt32ToDecStr(int value, int digits
, string sNegative
)
1087 Debug
.Assert(value < 0);
1092 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountDigits((uint)(-value))) + sNegative
.Length
;
1093 string result
= string.FastAllocateString(bufferLength
);
1094 fixed (char* buffer
= result
)
1096 char* p
= UInt32ToDecChars(buffer
+ bufferLength
, (uint)(-value), digits
);
1097 Debug
.Assert(p
== buffer
+ sNegative
.Length
);
1099 for (int i
= sNegative
.Length
- 1; i
>= 0; i
--)
1101 *(--p
) = sNegative
[i
];
1103 Debug
.Assert(p
== buffer
);
1108 private static unsafe bool TryNegativeInt32ToDecStr(int value, int digits
, string sNegative
, Span
<char> destination
, out int charsWritten
)
1110 Debug
.Assert(value < 0);
1115 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountDigits((uint)(-value))) + sNegative
.Length
;
1116 if (bufferLength
> destination
.Length
)
1122 charsWritten
= bufferLength
;
1123 fixed (char* buffer
= &MemoryMarshal
.GetReference(destination
))
1125 char* p
= UInt32ToDecChars(buffer
+ bufferLength
, (uint)(-value), digits
);
1126 Debug
.Assert(p
== buffer
+ sNegative
.Length
);
1128 for (int i
= sNegative
.Length
- 1; i
>= 0; i
--)
1130 *(--p
) = sNegative
[i
];
1132 Debug
.Assert(p
== buffer
);
1137 private static unsafe string Int32ToHexStr(int value, char hexBase
, int digits
)
1142 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountHexDigits((uint)value));
1143 string result
= string.FastAllocateString(bufferLength
);
1144 fixed (char* buffer
= result
)
1146 char* p
= Int32ToHexChars(buffer
+ bufferLength
, (uint)value, hexBase
, digits
);
1147 Debug
.Assert(p
== buffer
);
1152 private static unsafe bool TryInt32ToHexStr(int value, char hexBase
, int digits
, Span
<char> destination
, out int charsWritten
)
1157 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountHexDigits((uint)value));
1158 if (bufferLength
> destination
.Length
)
1164 charsWritten
= bufferLength
;
1165 fixed (char* buffer
= &MemoryMarshal
.GetReference(destination
))
1167 char* p
= Int32ToHexChars(buffer
+ bufferLength
, (uint)value, hexBase
, digits
);
1168 Debug
.Assert(p
== buffer
);
1173 private static unsafe char* Int32ToHexChars(char* buffer
, uint value, int hexBase
, int digits
)
1175 while (--digits
>= 0 || value != 0)
1177 byte digit
= (byte)(value & 0xF);
1178 *(--buffer
) = (char)(digit
+ (digit
< 10 ? (byte)'0' : hexBase
));
1184 [MethodImpl(MethodImplOptions
.AggressiveInlining
)] // called from only one location
1185 private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number
)
1187 number
.DigitsCount
= UInt32Precision
;
1188 number
.IsNegative
= false;
1190 byte* buffer
= number
.GetDigitsPointer();
1191 byte* p
= UInt32ToDecChars(buffer
+ UInt32Precision
, value, 0);
1193 int i
= (int)(buffer
+ UInt32Precision
- p
);
1195 number
.DigitsCount
= i
;
1198 byte* dst
= number
.GetDigitsPointer();
1201 *dst
= (byte)('\0');
1203 number
.CheckConsistency();
1206 internal static unsafe byte* UInt32ToDecChars(byte* bufferEnd
, uint value, int digits
)
1208 while (--digits
>= 0 || value != 0)
1210 // TODO https://github.com/dotnet/coreclr/issues/3439
1211 uint newValue
= value / 10;
1212 *(--bufferEnd
) = (byte)(value - (newValue
* 10) + '0');
1218 internal static unsafe char* UInt32ToDecChars(char* bufferEnd
, uint value, int digits
)
1220 while (--digits
>= 0 || value != 0)
1222 // TODO https://github.com/dotnet/coreclr/issues/3439
1223 uint newValue
= value / 10;
1224 *(--bufferEnd
) = (char)(value - (newValue
* 10) + '0');
1230 private static unsafe string UInt32ToDecStr(uint value, int digits
)
1232 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountDigits(value));
1234 // For single-digit values that are very common, especially 0 and 1, just return cached strings.
1235 if (bufferLength
== 1)
1237 return s_singleDigitStringCache
[value];
1240 string result
= string.FastAllocateString(bufferLength
);
1241 fixed (char* buffer
= result
)
1243 char* p
= buffer
+ bufferLength
;
1248 // TODO https://github.com/dotnet/coreclr/issues/3439
1249 uint div
= value / 10;
1250 *(--p
) = (char)('0' + value - (div
* 10));
1257 p
= UInt32ToDecChars(p
, value, digits
);
1259 Debug
.Assert(p
== buffer
);
1264 private static unsafe bool TryUInt32ToDecStr(uint value, int digits
, Span
<char> destination
, out int charsWritten
)
1266 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountDigits(value));
1267 if (bufferLength
> destination
.Length
)
1273 charsWritten
= bufferLength
;
1274 fixed (char* buffer
= &MemoryMarshal
.GetReference(destination
))
1276 char* p
= buffer
+ bufferLength
;
1281 // TODO https://github.com/dotnet/coreclr/issues/3439
1282 uint div
= value / 10;
1283 *(--p
) = (char)('0' + value - (div
* 10));
1290 p
= UInt32ToDecChars(p
, value, digits
);
1292 Debug
.Assert(p
== buffer
);
1297 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
1298 private static unsafe bool TryCopyTo(char* src
, int length
, Span
<char> destination
, out int charsWritten
)
1300 if (new ReadOnlySpan
<char>(src
, length
).TryCopyTo(destination
))
1302 charsWritten
= length
;
1312 private static unsafe void Int64ToNumber(long input
, ref NumberBuffer number
)
1314 ulong value = (ulong)input
;
1315 number
.IsNegative
= input
< 0;
1316 number
.DigitsCount
= Int64Precision
;
1317 if (number
.IsNegative
)
1319 value = (ulong)(-input
);
1322 byte* buffer
= number
.GetDigitsPointer();
1323 byte* p
= buffer
+ Int64Precision
;
1324 while (High32(value) != 0)
1325 p
= UInt32ToDecChars(p
, Int64DivMod1E9(ref value), 9);
1326 p
= UInt32ToDecChars(p
, Low32(value), 0);
1328 int i
= (int)(buffer
+ Int64Precision
- p
);
1330 number
.DigitsCount
= i
;
1333 byte* dst
= number
.GetDigitsPointer();
1336 *dst
= (byte)('\0');
1338 number
.CheckConsistency();
1341 private static unsafe string NegativeInt64ToDecStr(long input
, int digits
, string sNegative
)
1343 Debug
.Assert(input
< 0);
1350 ulong value = (ulong)(-input
);
1352 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountDigits(value)) + sNegative
.Length
;
1353 string result
= string.FastAllocateString(bufferLength
);
1354 fixed (char* buffer
= result
)
1356 char* p
= buffer
+ bufferLength
;
1357 while (High32(value) != 0)
1359 p
= UInt32ToDecChars(p
, Int64DivMod1E9(ref value), 9);
1362 p
= UInt32ToDecChars(p
, Low32(value), digits
);
1363 Debug
.Assert(p
== buffer
+ sNegative
.Length
);
1365 for (int i
= sNegative
.Length
- 1; i
>= 0; i
--)
1367 *(--p
) = sNegative
[i
];
1369 Debug
.Assert(p
== buffer
);
1374 private static unsafe bool TryNegativeInt64ToDecStr(long input
, int digits
, string sNegative
, Span
<char> destination
, out int charsWritten
)
1376 Debug
.Assert(input
< 0);
1383 ulong value = (ulong)(-input
);
1385 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountDigits((ulong)(-input
))) + sNegative
.Length
;
1386 if (bufferLength
> destination
.Length
)
1392 charsWritten
= bufferLength
;
1393 fixed (char* buffer
= &MemoryMarshal
.GetReference(destination
))
1395 char* p
= buffer
+ bufferLength
;
1396 while (High32(value) != 0)
1398 p
= UInt32ToDecChars(p
, Int64DivMod1E9(ref value), 9);
1401 p
= UInt32ToDecChars(p
, Low32(value), digits
);
1402 Debug
.Assert(p
== buffer
+ sNegative
.Length
);
1404 for (int i
= sNegative
.Length
- 1; i
>= 0; i
--)
1406 *(--p
) = sNegative
[i
];
1408 Debug
.Assert(p
== buffer
);
1413 private static unsafe string Int64ToHexStr(long value, char hexBase
, int digits
)
1415 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountHexDigits((ulong)value));
1416 string result
= string.FastAllocateString(bufferLength
);
1417 fixed (char* buffer
= result
)
1419 char* p
= buffer
+ bufferLength
;
1420 if (High32((ulong)value) != 0)
1422 p
= Int32ToHexChars(p
, Low32((ulong)value), hexBase
, 8);
1423 p
= Int32ToHexChars(p
, High32((ulong)value), hexBase
, digits
- 8);
1427 p
= Int32ToHexChars(p
, Low32((ulong)value), hexBase
, Math
.Max(digits
, 1));
1429 Debug
.Assert(p
== buffer
);
1434 private static unsafe bool TryInt64ToHexStr(long value, char hexBase
, int digits
, Span
<char> destination
, out int charsWritten
)
1436 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountHexDigits((ulong)value));
1437 if (bufferLength
> destination
.Length
)
1443 charsWritten
= bufferLength
;
1444 fixed (char* buffer
= &MemoryMarshal
.GetReference(destination
))
1446 char* p
= buffer
+ bufferLength
;
1447 if (High32((ulong)value) != 0)
1449 p
= Int32ToHexChars(p
, Low32((ulong)value), hexBase
, 8);
1450 p
= Int32ToHexChars(p
, High32((ulong)value), hexBase
, digits
- 8);
1454 p
= Int32ToHexChars(p
, Low32((ulong)value), hexBase
, Math
.Max(digits
, 1));
1456 Debug
.Assert(p
== buffer
);
1461 private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number
)
1463 number
.DigitsCount
= UInt64Precision
;
1464 number
.IsNegative
= false;
1466 byte* buffer
= number
.GetDigitsPointer();
1467 byte* p
= buffer
+ UInt64Precision
;
1469 while (High32(value) != 0)
1470 p
= UInt32ToDecChars(p
, Int64DivMod1E9(ref value), 9);
1471 p
= UInt32ToDecChars(p
, Low32(value), 0);
1473 int i
= (int)(buffer
+ UInt64Precision
- p
);
1475 number
.DigitsCount
= i
;
1478 byte* dst
= number
.GetDigitsPointer();
1481 *dst
= (byte)('\0');
1483 number
.CheckConsistency();
1486 private static unsafe string UInt64ToDecStr(ulong value, int digits
)
1491 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountDigits(value));
1493 // For single-digit values that are very common, especially 0 and 1, just return cached strings.
1494 if (bufferLength
== 1)
1496 return s_singleDigitStringCache
[value];
1499 string result
= string.FastAllocateString(bufferLength
);
1500 fixed (char* buffer
= result
)
1502 char* p
= buffer
+ bufferLength
;
1503 while (High32(value) != 0)
1505 p
= UInt32ToDecChars(p
, Int64DivMod1E9(ref value), 9);
1508 p
= UInt32ToDecChars(p
, Low32(value), digits
);
1509 Debug
.Assert(p
== buffer
);
1514 private static unsafe bool TryUInt64ToDecStr(ulong value, int digits
, Span
<char> destination
, out int charsWritten
)
1519 int bufferLength
= Math
.Max(digits
, FormattingHelpers
.CountDigits(value));
1520 if (bufferLength
> destination
.Length
)
1526 charsWritten
= bufferLength
;
1527 fixed (char* buffer
= &MemoryMarshal
.GetReference(destination
))
1529 char* p
= buffer
+ bufferLength
;
1530 while (High32(value) != 0)
1532 p
= UInt32ToDecChars(p
, Int64DivMod1E9(ref value), 9);
1535 p
= UInt32ToDecChars(p
, Low32(value), digits
);
1536 Debug
.Assert(p
== buffer
);
1541 internal static unsafe char ParseFormatSpecifier(ReadOnlySpan
<char> format
, out int digits
)
1544 if (format
.Length
> 0)
1546 // If the format begins with a symbol, see if it's a standard format
1547 // with or without a specified number of digits.
1549 if ((uint)(c
- 'A') <= 'Z' - 'A' ||
1550 (uint)(c
- 'a') <= 'z' - 'a')
1552 // Fast path for sole symbol, e.g. "D"
1553 if (format
.Length
== 1)
1559 if (format
.Length
== 2)
1561 // Fast path for symbol and single digit, e.g. "X4"
1562 int d
= format
[1] - '0';
1569 else if (format
.Length
== 3)
1571 // Fast path for symbol and double digit, e.g. "F12"
1572 int d1
= format
[1] - '0', d2
= format
[2] - '0';
1573 if ((uint)d1
< 10 && (uint)d2
< 10)
1575 digits
= d1
* 10 + d2
;
1580 // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
1581 // but it can begin with any number of 0s, and thus we may need to check more than two
1582 // digits. Further, for compat, we need to stop when we hit a null char.
1585 while (i
< format
.Length
&& (((uint)format
[i
] - '0') < 10) && n
< 10)
1587 n
= (n
* 10) + format
[i
++] - '0';
1590 // If we're at the end of the digits rather than having stopped because we hit something
1591 // other than a digit or overflowed, return the standard format info.
1592 if (i
== format
.Length
|| format
[i
] == '\0')
1600 // Default empty format to be "G"; custom format is signified with '\0'.
1602 return format
.Length
== 0 || c
== '\0' ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it.
1607 internal static unsafe void NumberToString(ref ValueStringBuilder sb
, ref NumberBuffer number
, char format
, int nMaxDigits
, NumberFormatInfo info
)
1609 number
.CheckConsistency();
1617 nMaxDigits
= info
.CurrencyDecimalDigits
;
1619 RoundNumber(ref number
, number
.Scale
+ nMaxDigits
); // Don't change this line to use digPos since digCount could have its sign changed.
1621 FormatCurrency(ref sb
, ref number
, nMaxDigits
, info
);
1630 nMaxDigits
= info
.NumberDecimalDigits
;
1632 RoundNumber(ref number
, number
.Scale
+ nMaxDigits
);
1634 if (number
.IsNegative
)
1635 sb
.Append(info
.NegativeSign
);
1637 FormatFixed(ref sb
, ref number
, nMaxDigits
, info
, null, info
.NumberDecimalSeparator
, null);
1646 nMaxDigits
= info
.NumberDecimalDigits
; // Since we are using digits in our calculation
1648 RoundNumber(ref number
, number
.Scale
+ nMaxDigits
);
1650 FormatNumber(ref sb
, ref number
, nMaxDigits
, info
);
1659 nMaxDigits
= DefaultPrecisionExponentialFormat
;
1662 RoundNumber(ref number
, nMaxDigits
);
1664 if (number
.IsNegative
)
1665 sb
.Append(info
.NegativeSign
);
1667 FormatScientific(ref sb
, ref number
, nMaxDigits
, info
, format
);
1675 bool noRounding
= false;
1678 if ((number
.Kind
== NumberBufferKind
.Decimal
) && (nMaxDigits
== -1))
1680 noRounding
= true; // Turn off rounding for ECMA compliance to output trailing 0's after decimal as significant
1682 if (number
.Digits
[0] == 0)
1684 // -0 should be formatted as 0 for decimal. This is normally handled by RoundNumber (which we are skipping)
1692 // This ensures that the PAL code pads out to the correct place even when we use the default precision
1693 nMaxDigits
= number
.DigitsCount
;
1697 RoundNumber(ref number
, nMaxDigits
);
1700 if (number
.IsNegative
)
1701 sb
.Append(info
.NegativeSign
);
1704 FormatGeneral(ref sb
, ref number
, nMaxDigits
, info
, (char)(format
- ('G' - 'E')), noRounding
);
1713 nMaxDigits
= info
.PercentDecimalDigits
;
1716 RoundNumber(ref number
, number
.Scale
+ nMaxDigits
);
1718 FormatPercent(ref sb
, ref number
, nMaxDigits
, info
);
1726 if (number
.Kind
!= NumberBufferKind
.FloatingPoint
)
1731 format
= (char)(format
- ('R' - 'G'));
1732 Debug
.Assert((format
== 'G') || (format
== 'g'));
1737 throw new FormatException(SR
.Argument_BadFormatSpecifier
);
1741 internal static unsafe void NumberToStringFormat(ref ValueStringBuilder sb
, ref NumberBuffer number
, ReadOnlySpan
<char> format
, NumberFormatInfo info
)
1743 number
.CheckConsistency();
1752 int thousandCount
= 0;
1759 byte* dig
= number
.GetDigitsPointer();
1762 section
= FindSection(format
, dig
[0] == 0 ? 2 : number
.IsNegative
? 1 : 0);
1768 firstDigit
= 0x7FFFFFFF;
1772 thousandSeps
= false;
1776 fixed (char* pFormat
= &MemoryMarshal
.GetReference(format
))
1778 while (src
< format
.Length
&& (ch
= pFormat
[src
++]) != 0 && ch
!= ';')
1786 if (firstDigit
== 0x7FFFFFFF)
1787 firstDigit
= digitCount
;
1789 lastDigit
= digitCount
;
1793 decimalPos
= digitCount
;
1796 if (digitCount
> 0 && decimalPos
< 0)
1798 if (thousandPos
>= 0)
1800 if (thousandPos
== digitCount
)
1805 thousandSeps
= true;
1807 thousandPos
= digitCount
;
1819 while (src
< format
.Length
&& pFormat
[src
] != 0 && pFormat
[src
++] != ch
)
1823 if (src
< format
.Length
&& pFormat
[src
] != 0)
1828 if ((src
< format
.Length
&& pFormat
[src
] == '0') ||
1829 (src
+ 1 < format
.Length
&& (pFormat
[src
] == '+' || pFormat
[src
] == '-') && pFormat
[src
+ 1] == '0'))
1831 while (++src
< format
.Length
&& pFormat
[src
] == '0')
1841 decimalPos
= digitCount
;
1843 if (thousandPos
>= 0)
1845 if (thousandPos
== decimalPos
)
1846 scaleAdjust
-= thousandCount
* 3;
1848 thousandSeps
= true;
1853 number
.Scale
+= scaleAdjust
;
1854 int pos
= scientific
? digitCount
: number
.Scale
+ digitCount
- decimalPos
;
1855 RoundNumber(ref number
, pos
);
1858 src
= FindSection(format
, 2);
1868 if (number
.Kind
!= NumberBufferKind
.FloatingPoint
)
1870 // The integer types don't have a concept of -0 and decimal always format -0 as 0
1871 number
.IsNegative
= false;
1873 number
.Scale
= 0; // Decimals with scale ('0.00') should be rounded.
1879 firstDigit
= firstDigit
< decimalPos
? decimalPos
- firstDigit
: 0;
1880 lastDigit
= lastDigit
> decimalPos
? decimalPos
- lastDigit
: 0;
1883 digPos
= decimalPos
;
1888 digPos
= number
.Scale
> decimalPos
? number
.Scale
: decimalPos
;
1889 adjust
= number
.Scale
- decimalPos
;
1893 // Adjust can be negative, so we make this an int instead of an unsigned int.
1894 // Adjust represents the number of characters over the formatting e.g. format string is "0000" and you are trying to
1895 // format 100000 (6 digits). Means adjust will be 2. On the other hand if you are trying to format 10 adjust will be
1896 // -2 and we'll need to fixup these digits with 0 padding if we have 0 formatting as in this example.
1897 Span
<int> thousandsSepPos
= stackalloc int[4];
1898 int thousandsSepCtr
= -1;
1902 // We need to precompute this outside the number formatting loop
1903 if (info
.NumberGroupSeparator
.Length
> 0)
1905 // We need this array to figure out where to insert the thousands separator. We would have to traverse the string
1906 // backwards. PIC formatting always traverses forwards. These indices are precomputed to tell us where to insert
1907 // the thousands separator so we can get away with traversing forwards. Note we only have to compute up to digPos.
1908 // The max is not bound since you can have formatting strings of the form "000,000..", and this
1909 // should handle that case too.
1911 int[] groupDigits
= info
._numberGroupSizes
;
1913 int groupSizeIndex
= 0; // Index into the groupDigits array.
1914 int groupTotalSizeCount
= 0;
1915 int groupSizeLen
= groupDigits
.Length
; // The length of groupDigits array.
1916 if (groupSizeLen
!= 0)
1917 groupTotalSizeCount
= groupDigits
[groupSizeIndex
]; // The current running total of group size.
1918 int groupSize
= groupTotalSizeCount
;
1920 int totalDigits
= digPos
+ ((adjust
< 0) ? adjust
: 0); // Actual number of digits in o/p
1921 int numDigits
= (firstDigit
> totalDigits
) ? firstDigit
: totalDigits
;
1922 while (numDigits
> groupTotalSizeCount
)
1927 if (thousandsSepCtr
>= thousandsSepPos
.Length
)
1929 var newThousandsSepPos
= new int[thousandsSepPos
.Length
* 2];
1930 thousandsSepPos
.CopyTo(newThousandsSepPos
);
1931 thousandsSepPos
= newThousandsSepPos
;
1934 thousandsSepPos
[thousandsSepCtr
] = groupTotalSizeCount
;
1935 if (groupSizeIndex
< groupSizeLen
- 1)
1938 groupSize
= groupDigits
[groupSizeIndex
];
1940 groupTotalSizeCount
+= groupSize
;
1945 if (number
.IsNegative
&& (section
== 0) && (number
.Scale
!= 0))
1946 sb
.Append(info
.NegativeSign
);
1948 bool decimalWritten
= false;
1950 fixed (char* pFormat
= &MemoryMarshal
.GetReference(format
))
1954 while (src
< format
.Length
&& (ch
= pFormat
[src
++]) != 0 && ch
!= ';')
1965 // digPos will be one greater than thousandsSepPos[thousandsSepCtr] since we are at
1966 // the character after which the groupSeparator needs to be appended.
1967 sb
.Append(*cur
!= 0 ? (char)(*cur
++) : '0');
1968 if (thousandSeps
&& digPos
> 1 && thousandsSepCtr
>= 0)
1970 if (digPos
== thousandsSepPos
[thousandsSepCtr
] + 1)
1972 sb
.Append(info
.NumberGroupSeparator
);
1991 ch
= digPos
<= firstDigit
? '0' : '\0';
1995 ch
= *cur
!= 0 ? (char)(*cur
++) : digPos
> lastDigit
? '0' : '\0';
2000 if (thousandSeps
&& digPos
> 1 && thousandsSepCtr
>= 0)
2002 if (digPos
== thousandsSepPos
[thousandsSepCtr
] + 1)
2004 sb
.Append(info
.NumberGroupSeparator
);
2015 if (digPos
!= 0 || decimalWritten
)
2017 // For compatibility, don't echo repeated decimals
2020 // If the format has trailing zeros or the format has a decimal and digits remain
2021 if (lastDigit
< 0 || (decimalPos
< digitCount
&& *cur
!= 0))
2023 sb
.Append(info
.NumberDecimalSeparator
);
2024 decimalWritten
= true;
2029 sb
.Append(info
.PerMilleSymbol
);
2032 sb
.Append(info
.PercentSymbol
);
2038 while (src
< format
.Length
&& pFormat
[src
] != 0 && pFormat
[src
] != ch
)
2039 sb
.Append(pFormat
[src
++]);
2040 if (src
< format
.Length
&& pFormat
[src
] != 0)
2044 if (src
< format
.Length
&& pFormat
[src
] != 0)
2045 sb
.Append(pFormat
[src
++]);
2050 bool positiveSign
= false;
2054 if (src
< format
.Length
&& pFormat
[src
] == '0')
2056 // Handles E0, which should format the same as E-0
2059 else if (src
+ 1 < format
.Length
&& pFormat
[src
] == '+' && pFormat
[src
+ 1] == '0')
2062 positiveSign
= true;
2064 else if (src
+ 1 < format
.Length
&& pFormat
[src
] == '-' && pFormat
[src
+ 1] == '0')
2067 // Do nothing, this is just a place holder s.t. we don't break out of the loop.
2075 while (++src
< format
.Length
&& pFormat
[src
] == '0')
2080 int exp
= dig
[0] == 0 ? 0 : number
.Scale
- decimalPos
;
2081 FormatExponent(ref sb
, info
, exp
, ch
, i
, positiveSign
);
2086 sb
.Append(ch
); // Copy E or e to output
2087 if (src
< format
.Length
)
2089 if (pFormat
[src
] == '+' || pFormat
[src
] == '-')
2090 sb
.Append(pFormat
[src
++]);
2091 while (src
< format
.Length
&& pFormat
[src
] == '0')
2092 sb
.Append(pFormat
[src
++]);
2104 if (number
.IsNegative
&& (section
== 0) && (number
.Scale
== 0) && (sb
.Length
> 0))
2105 sb
.Insert(0, info
.NegativeSign
);
2108 private static void FormatCurrency(ref ValueStringBuilder sb
, ref NumberBuffer number
, int nMaxDigits
, NumberFormatInfo info
)
2110 string fmt
= number
.IsNegative
?
2111 s_negCurrencyFormats
[info
.CurrencyNegativePattern
] :
2112 s_posCurrencyFormats
[info
.CurrencyPositivePattern
];
2114 foreach (char ch
in fmt
)
2119 FormatFixed(ref sb
, ref number
, nMaxDigits
, info
, info
._currencyGroupSizes
, info
.CurrencyDecimalSeparator
, info
.CurrencyGroupSeparator
);
2122 sb
.Append(info
.NegativeSign
);
2125 sb
.Append(info
.CurrencySymbol
);
2134 private static unsafe void FormatFixed(ref ValueStringBuilder sb
, ref NumberBuffer number
, int nMaxDigits
, NumberFormatInfo
? info
, int[]? groupDigits
, string? sDecimal
, string? sGroup
)
2136 int digPos
= number
.Scale
;
2137 byte* dig
= number
.GetDigitsPointer();
2141 if (groupDigits
!= null)
2143 Debug
.Assert(sGroup
!= null, "Must be nulll when groupDigits != null");
2144 int groupSizeIndex
= 0; // Index into the groupDigits array.
2145 int bufferSize
= digPos
; // The length of the result buffer string.
2146 int groupSize
= 0; // The current group size.
2148 // Find out the size of the string buffer for the result.
2149 if (groupDigits
.Length
!= 0) // You can pass in 0 length arrays
2151 int groupSizeCount
= groupDigits
[groupSizeIndex
]; // The current total of group size.
2153 while (digPos
> groupSizeCount
)
2155 groupSize
= groupDigits
[groupSizeIndex
];
2159 bufferSize
+= sGroup
.Length
;
2160 if (groupSizeIndex
< groupDigits
.Length
- 1)
2163 groupSizeCount
+= groupDigits
[groupSizeIndex
];
2164 if (groupSizeCount
< 0 || bufferSize
< 0)
2165 throw new ArgumentOutOfRangeException(); // If we overflow
2168 groupSize
= groupSizeCount
== 0 ? 0 : groupDigits
[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0
2173 int digLength
= number
.DigitsCount
;
2174 int digStart
= (digPos
< digLength
) ? digPos
: digLength
;
2175 fixed (char* spanPtr
= &MemoryMarshal
.GetReference(sb
.AppendSpan(bufferSize
)))
2177 char* p
= spanPtr
+ bufferSize
- 1;
2178 for (int i
= digPos
- 1; i
>= 0; i
--)
2180 *(p
--) = (i
< digStart
) ? (char)(dig
[i
]) : '0';
2185 if ((digitCount
== groupSize
) && (i
!= 0))
2187 for (int j
= sGroup
.Length
- 1; j
>= 0; j
--)
2190 if (groupSizeIndex
< groupDigits
.Length
- 1)
2193 groupSize
= groupDigits
[groupSizeIndex
];
2200 Debug
.Assert(p
>= spanPtr
- 1, "Underflow");
2208 sb
.Append(*dig
!= 0 ? (char)(*dig
++) : '0');
2210 while (--digPos
> 0);
2220 Debug
.Assert(sDecimal
!= null);
2221 sb
.Append(sDecimal
);
2222 if ((digPos
< 0) && (nMaxDigits
> 0))
2224 int zeroes
= Math
.Min(-digPos
, nMaxDigits
);
2225 sb
.Append('0', zeroes
);
2227 nMaxDigits
-= zeroes
;
2230 while (nMaxDigits
> 0)
2232 sb
.Append((*dig
!= 0) ? (char)(*dig
++) : '0');
2238 private static void FormatNumber(ref ValueStringBuilder sb
, ref NumberBuffer number
, int nMaxDigits
, NumberFormatInfo info
)
2240 string fmt
= number
.IsNegative
?
2241 s_negNumberFormats
[info
.NumberNegativePattern
] :
2244 foreach (char ch
in fmt
)
2249 FormatFixed(ref sb
, ref number
, nMaxDigits
, info
, info
._numberGroupSizes
, info
.NumberDecimalSeparator
, info
.NumberGroupSeparator
);
2252 sb
.Append(info
.NegativeSign
);
2261 private static unsafe void FormatScientific(ref ValueStringBuilder sb
, ref NumberBuffer number
, int nMaxDigits
, NumberFormatInfo info
, char expChar
)
2263 byte* dig
= number
.GetDigitsPointer();
2265 sb
.Append((*dig
!= 0) ? (char)(*dig
++) : '0');
2267 if (nMaxDigits
!= 1) // For E0 we would like to suppress the decimal point
2268 sb
.Append(info
.NumberDecimalSeparator
);
2270 while (--nMaxDigits
> 0)
2271 sb
.Append((*dig
!= 0) ? (char)(*dig
++) : '0');
2273 int e
= number
.Digits
[0] == 0 ? 0 : number
.Scale
- 1;
2274 FormatExponent(ref sb
, info
, e
, expChar
, 3, true);
2277 private static unsafe void FormatExponent(ref ValueStringBuilder sb
, NumberFormatInfo info
, int value, char expChar
, int minDigits
, bool positiveSign
)
2283 sb
.Append(info
.NegativeSign
);
2289 sb
.Append(info
.PositiveSign
);
2292 char* digits
= stackalloc char[MaxUInt32DecDigits
];
2293 char* p
= UInt32ToDecChars(digits
+ MaxUInt32DecDigits
, (uint)value, minDigits
);
2294 int i
= (int)(digits
+ MaxUInt32DecDigits
- p
);
2295 sb
.Append(p
, (int)(digits
+ MaxUInt32DecDigits
- p
));
2298 private static unsafe void FormatGeneral(ref ValueStringBuilder sb
, ref NumberBuffer number
, int nMaxDigits
, NumberFormatInfo info
, char expChar
, bool bSuppressScientific
)
2300 int digPos
= number
.Scale
;
2301 bool scientific
= false;
2303 if (!bSuppressScientific
)
2305 // Don't switch to scientific notation
2306 if (digPos
> nMaxDigits
|| digPos
< -3)
2313 byte* dig
= number
.GetDigitsPointer();
2319 sb
.Append((*dig
!= 0) ? (char)(*dig
++) : '0');
2320 } while (--digPos
> 0);
2327 if (*dig
!= 0 || digPos
< 0)
2329 sb
.Append(info
.NumberDecimalSeparator
);
2338 sb
.Append((char)(*dig
++));
2342 FormatExponent(ref sb
, info
, number
.Scale
- 1, expChar
, 2, true);
2345 private static void FormatPercent(ref ValueStringBuilder sb
, ref NumberBuffer number
, int nMaxDigits
, NumberFormatInfo info
)
2347 string fmt
= number
.IsNegative
?
2348 s_negPercentFormats
[info
.PercentNegativePattern
] :
2349 s_posPercentFormats
[info
.PercentPositivePattern
];
2351 foreach (char ch
in fmt
)
2356 FormatFixed(ref sb
, ref number
, nMaxDigits
, info
, info
._percentGroupSizes
, info
.PercentDecimalSeparator
, info
.PercentGroupSeparator
);
2359 sb
.Append(info
.NegativeSign
);
2362 sb
.Append(info
.PercentSymbol
);
2371 internal static unsafe void RoundNumber(ref NumberBuffer number
, int pos
)
2373 byte* dig
= number
.GetDigitsPointer();
2376 while (i
< pos
&& dig
[i
] != 0)
2379 if (i
== pos
&& dig
[i
] >= '5')
2381 while (i
> 0 && dig
[i
- 1] == '9')
2391 dig
[0] = (byte)('1');
2397 while (i
> 0 && dig
[i
- 1] == '0')
2402 if (number
.Kind
!= NumberBufferKind
.FloatingPoint
)
2404 // The integer types don't have a concept of -0 and decimal always format -0 as 0
2405 number
.IsNegative
= false;
2407 number
.Scale
= 0; // Decimals with scale ('0.00') should be rounded.
2410 dig
[i
] = (byte)('\0');
2411 number
.DigitsCount
= i
;
2412 number
.CheckConsistency();
2415 private static unsafe int FindSection(ReadOnlySpan
<char> format
, int section
)
2423 fixed (char* pFormat
= &MemoryMarshal
.GetReference(format
))
2428 if (src
>= format
.Length
)
2433 switch (ch
= pFormat
[src
++])
2437 while (src
< format
.Length
&& pFormat
[src
] != 0 && pFormat
[src
++] != ch
)
2441 if (src
< format
.Length
&& pFormat
[src
] != 0)
2447 if (src
< format
.Length
&& pFormat
[src
] != 0 && pFormat
[src
] != ';')
2457 private static uint Low32(ulong value) => (uint)value;
2459 private static uint High32(ulong value) => (uint)((value & 0xFFFFFFFF00000000) >> 32);
2461 private static uint Int64DivMod1E9(ref ulong value)
2463 uint rem
= (uint)(value % 1000000000);
2464 value /= 1000000000;
2468 private static ulong ExtractFractionAndBiasedExponent(double value, out int exponent
)
2470 ulong bits
= (ulong)(BitConverter
.DoubleToInt64Bits(value));
2471 ulong fraction
= (bits
& 0xFFFFFFFFFFFFF);
2472 exponent
= ((int)(bits
>> 52) & 0x7FF);
2476 // For normalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
2477 // value = 1.fraction * 2^(exp - 1023)
2478 // = (1 + mantissa / 2^52) * 2^(exp - 1023)
2479 // = (2^52 + mantissa) * 2^(exp - 1023 - 52)
2481 // So f = (2^52 + mantissa), e = exp - 1075;
2483 fraction
|= (1UL << 52);
2488 // For denormalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
2489 // value = 0.fraction * 2^(1 - 1023)
2490 // = (mantissa / 2^52) * 2^(-1022)
2491 // = mantissa * 2^(-1022 - 52)
2492 // = mantissa * 2^(-1074)
2493 // So f = mantissa, e = -1074
2500 private static uint ExtractFractionAndBiasedExponent(float value, out int exponent
)
2502 uint bits
= (uint)(BitConverter
.SingleToInt32Bits(value));
2503 uint fraction
= (bits
& 0x7FFFFF);
2504 exponent
= ((int)(bits
>> 23) & 0xFF);
2508 // For normalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format
2509 // value = 1.fraction * 2^(exp - 127)
2510 // = (1 + mantissa / 2^23) * 2^(exp - 127)
2511 // = (2^23 + mantissa) * 2^(exp - 127 - 23)
2513 // So f = (2^23 + mantissa), e = exp - 150;
2515 fraction
|= (1U << 23);
2520 // For denormalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format
2521 // value = 0.fraction * 2^(1 - 127)
2522 // = (mantissa / 2^23) * 2^(-126)
2523 // = mantissa * 2^(-126 - 23)
2524 // = mantissa * 2^(-149)
2525 // So f = mantissa, e = -149