|
| 1 | +--- |
| 2 | +title: "Breaking change: Standard numeric format parsing precision" |
| 3 | +description: Learn about the .NET 6.0 breaking change in core .NET libraries where standard numeric format parsing now handles higher precisions. |
| 4 | +ms.date: 02/26/2021 |
| 5 | +--- |
| 6 | +# Standard numeric format parsing precision |
| 7 | + |
| 8 | +.NET now supports greater precision values when formatting numbers as strings using `ToString` and `TryFormat`. |
| 9 | + |
| 10 | +## Change description |
| 11 | + |
| 12 | +When formatting numbers as strings, the *precision specifier* in the [format string](../../../../standard/base-types/standard-numeric-format-strings.md) represents the number of digits in the resulting string. Depending on the *format specifier*, which is the [character at the beginning of the string](../../../../standard/base-types/standard-numeric-format-strings.md#standard-format-specifiers), the precision can represent the total number of digits, the number of significant digits, or the number of decimal digits. |
| 13 | + |
| 14 | +In previous .NET versions, the standard numeric format parsing logic is limited to a precision of 99 or less. Some numeric types have more precision, but `ToString(string format)` does not expose it correctly. If you specify a precision greater than 99, for example, `32.ToString("C100")`, the format string is interpreted as a [custom numeric format string](../../../../standard/base-types/custom-numeric-format-strings.md) instead of "currency with precision 100". In custom numeric format strings, characters are interpreted as [character literals](../../../../standard/base-types/custom-numeric-format-strings.md#character-literals). In addition, a format string that contains an invalid format specifier is interpreted differently depending on the precision value. `H99` throws a <xref:System.FormatException> for the invalid format specifier, while `H100` is interpreted as a custom numeric format string. |
| 15 | + |
| 16 | +Starting in .NET 6, .NET supports precision up to <xref:System.Int32.MaxValue?displayProperty=nameWithType>. A format string that consists of a format specifier with any number of digits is interpreted as a standard numeric format string with precision. A <xref:System.FormatException> is thrown for either or both of the following conditions: |
| 17 | + |
| 18 | +- The format specifier character is not a [standard format specifier](../../../../standard/base-types/standard-numeric-format-strings.md#standard-format-specifiers). |
| 19 | +- The precision is greater than <xref:System.Int32.MaxValue?displayProperty=nameWithType>. |
| 20 | + |
| 21 | +This change was implemented in the parsing logic that affects all numeric types. |
| 22 | + |
| 23 | +The following table shows the behavior changes for various format strings. |
| 24 | + |
| 25 | +| Format string | Previous behavior | .NET 6+ behavior | |
| 26 | +| - | - | - | |
| 27 | +| `C2` | Denotes currency with two decimal digits | Denotes currency with two decimal digits (*no change*) | |
| 28 | +| `C100` | Denotes custom numeric format string that prints "C100" | Denotes currency with 100 decimal digits | |
| 29 | +| `H99` | Throws <xref:System.FormatException> due to invalid standard format specifier "H" | Throws <xref:System.FormatException> due to invalid standard format specifier "H" (*no change*) | |
| 30 | +| `H100` | Denotes custom numeric format string | Throws <xref:System.FormatException> due to invalid standard format specifier "H" | |
| 31 | + |
| 32 | +## Version introduced |
| 33 | + |
| 34 | +6.0 Preview 2 |
| 35 | + |
| 36 | +## Reason for change |
| 37 | + |
| 38 | +This change corrects unexpected behavior when using higher precision for numeric format parsing. |
| 39 | + |
| 40 | +## Recommended action |
| 41 | + |
| 42 | +In most cases, no action is necessary and the correct precision will be shown in the resulting strings. |
| 43 | + |
| 44 | +However, if you want to revert to the previous behavior where the format specifier is interpreted as a literal character when the precision is greater than 99, you can wrap that character in single quotes or escape it with a backslash. For example, in previous .NET versions, `42.ToString("G999")` returns `G999`. To maintain that behavior, change the format string to `"'G'999"` or `"\\G999"`. This will work on .NET Framework, .NET Core, and .NET 5+. |
| 45 | + |
| 46 | +The following format strings will continue to be interpreted as custom numeric format strings: |
| 47 | + |
| 48 | +- Start with any character that is not an ASCII alphabetical character, for example, `$` or `è`. |
| 49 | +- Start with an ASCII alphabetical character that's not followed by an ASCII digit, for example, `A$`. |
| 50 | +- Start with an ASCII alphabetical character, followed by an ASCII digit sequence, and then any character that is not an ASCII digit character, for example, `A12A`. |
| 51 | + |
| 52 | +## Affected APIs |
| 53 | + |
| 54 | +This change was implemented in the parsing logic that affects all numeric types. |
| 55 | + |
| 56 | +- <xref:System.Numerics.BigInteger.ToString(System.String)?displayProperty=fullName> |
| 57 | +- <xref:System.Numerics.BigInteger.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 58 | +- <xref:System.Numerics.BigInteger.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 59 | +- <xref:System.Int32.ToString(System.String)?displayProperty=fullName> |
| 60 | +- <xref:System.Int32.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 61 | +- <xref:System.Int32.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 62 | +- <xref:System.UInt32.ToString(System.String)?displayProperty=fullName> |
| 63 | +- <xref:System.UInt32.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 64 | +- <xref:System.UInt32.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 65 | +- <xref:System.Byte.ToString(System.String)?displayProperty=fullName> |
| 66 | +- <xref:System.Byte.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 67 | +- <xref:System.Byte.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 68 | +- <xref:System.SByte.ToString(System.String)?displayProperty=fullName> |
| 69 | +- <xref:System.SByte.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 70 | +- <xref:System.SByte.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 71 | +- <xref:System.Int16.ToString(System.String)?displayProperty=fullName> |
| 72 | +- <xref:System.Int16.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 73 | +- <xref:System.Int16.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 74 | +- <xref:System.UInt16.ToString(System.String)?displayProperty=fullName> |
| 75 | +- <xref:System.UInt16.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 76 | +- <xref:System.UInt16.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 77 | +- <xref:System.Int64.ToString(System.String)?displayProperty=fullName> |
| 78 | +- <xref:System.Int64.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 79 | +- <xref:System.Int64.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 80 | +- <xref:System.UInt64.ToString(System.String)?displayProperty=fullName> |
| 81 | +- <xref:System.UInt64.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 82 | +- <xref:System.UInt64.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 83 | +- <xref:System.Half.ToString(System.String)?displayProperty=fullName> |
| 84 | +- <xref:System.Half.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 85 | +- <xref:System.Half.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 86 | +- <xref:System.Single.ToString(System.String)?displayProperty=fullName> |
| 87 | +- <xref:System.Single.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 88 | +- <xref:System.Single.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 89 | +- <xref:System.Double.ToString(System.String)?displayProperty=fullName> |
| 90 | +- <xref:System.Double.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 91 | +- <xref:System.Double.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 92 | +- <xref:System.Decimal.ToString(System.String)?displayProperty=fullName> |
| 93 | +- <xref:System.Decimal.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> |
| 94 | +- <xref:System.Decimal.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> |
| 95 | + |
| 96 | +## See also |
| 97 | + |
| 98 | +- [Standard numeric format strings](../../../../standard/base-types/standard-numeric-format-strings.md) |
| 99 | +- [Character literals in custom format strings](../../../../standard/base-types/custom-numeric-format-strings.md#character-literals) |
| 100 | + |
| 101 | +<!-- |
| 102 | +
|
| 103 | +### Category |
| 104 | +
|
| 105 | +Core .NET libraries |
| 106 | +
|
| 107 | +### Affected APIs |
| 108 | +
|
| 109 | +- `M:System.Numerics.BigInteger.ToString(System.String)` |
| 110 | +- `M:System.Numerics.BigInteger.ToString(System.String,System.IFormatProvider)` |
| 111 | +- `M:System.Numerics.BigInteger.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 112 | +- `M:System.Int32.ToString(System.String)` |
| 113 | +- `M:System.Int32.ToString(System.String,System.IFormatProvider)` |
| 114 | +- `M:System.Int32.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 115 | +- `M:System.UInt32.ToString(System.String)` |
| 116 | +- `M:System.UInt32.ToString(System.String,System.IFormatProvider)` |
| 117 | +- `M:System.UInt32.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 118 | +- `M:System.Byte.ToString(System.String)` |
| 119 | +- `M:System.Byte.ToString(System.String,System.IFormatProvider)` |
| 120 | +- `M:System.Byte.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 121 | +- `M:System.SByte.ToString(System.String)` |
| 122 | +- `M:System.SByte.ToString(System.String,System.IFormatProvider)` |
| 123 | +- `M:System.SByte.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 124 | +- `M:System.Int16.ToString(System.String)` |
| 125 | +- `M:System.Int16.ToString(System.String,System.IFormatProvider)` |
| 126 | +- `M:System.Int16.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 127 | +- `M:System.UInt16.ToString(System.String)` |
| 128 | +- `M:System.UInt16.ToString(System.String,System.IFormatProvider)` |
| 129 | +- `M:System.UInt16.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 130 | +- `M:System.Int64.ToString(System.String)` |
| 131 | +- `M:System.Int64.ToString(System.String,System.IFormatProvider)` |
| 132 | +- `M:System.Int64.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 133 | +- `M:System.UInt64.ToString(System.String)` |
| 134 | +- `M:System.UInt64.ToString(System.String,System.IFormatProvider)` |
| 135 | +- `M:System.UInt64.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 136 | +- `M:System.Half.ToString(System.String)` |
| 137 | +- `M:System.Half.ToString(System.String,System.IFormatProvider)` |
| 138 | +- `M:System.Half.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 139 | +- `M:System.Single.ToString(System.String)` |
| 140 | +- `M:System.Single.ToString(System.String,System.IFormatProvider)` |
| 141 | +- `M:System.Single.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 142 | +- `M:System.Double.ToString(System.String)` |
| 143 | +- `M:System.Double.ToString(System.String,System.IFormatProvider)` |
| 144 | +- `M:System.Double.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 145 | +- `M:System.Decimal.ToString(System.String)` |
| 146 | +- `M:System.Decimal.ToString(System.String,System.IFormatProvider)` |
| 147 | +- `M:System.Decimal.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` |
| 148 | +
|
| 149 | +--> |
0 commit comments