Repository navigation
Numeric parsing precision breaking change #23046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
be4e3d8
add skeleton
gewarren bdf23b2
flesh out new article
gewarren a56f277
respond to feedback
gewarren 1e63ba2
fix example; add heading to format specifiers
gewarren 369afc5
fix build warnings; review on staging
gewarren 93540b8
fix build warnings
gewarren File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
...atibility/core-libraries/6.0/numeric-format-parsing-handles-higher-precision.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| --- | ||
| title: "Breaking change: Standard numeric format parsing precision" | ||
| description: Learn about the .NET 6.0 breaking change in core .NET libraries where standard numeric format parsing now handles higher precisions. | ||
| ms.date: 02/26/2021 | ||
| --- | ||
| # Standard numeric format parsing precision | ||
|
|
||
| .NET now supports greater precision values when formatting numbers as strings using `ToString` and `TryFormat`. | ||
|
|
||
| ## Change description | ||
|
|
||
| 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. | ||
|
|
||
| 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. | ||
|
|
||
| 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: | ||
|
|
||
| - The format specifier character is not a [standard format specifier](../../../../standard/base-types/standard-numeric-format-strings.md#standard-format-specifiers). | ||
| - The precision is greater than <xref:System.Int32.MaxValue?displayProperty=nameWithType>. | ||
|
|
||
| This change was implemented in the parsing logic that affects all numeric types. | ||
|
|
||
| The following table shows the behavior changes for various format strings. | ||
|
|
||
| | Format string | Previous behavior | .NET 6+ behavior | | ||
| | - | - | - | | ||
| | `C2` | Denotes currency with two decimal digits | Denotes currency with two decimal digits (*no change*) | | ||
| | `C100` | Denotes custom numeric format string that prints "C100" | Denotes currency with 100 decimal digits | | ||
| | `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*) | | ||
| | `H100` | Denotes custom numeric format string | Throws <xref:System.FormatException> due to invalid standard format specifier "H" | | ||
|
|
||
| ## Version introduced | ||
|
|
||
| 6.0 Preview 2 | ||
|
|
||
| ## Reason for change | ||
|
|
||
| This change corrects unexpected behavior when using higher precision for numeric format parsing. | ||
|
|
||
| ## Recommended action | ||
|
|
||
| In most cases, no action is necessary and the correct precision will be shown in the resulting strings. | ||
|
|
||
| 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+. | ||
|
|
||
| The following format strings will continue to be interpreted as custom numeric format strings: | ||
|
|
||
| - Start with any character that is not an ASCII alphabetical character, for example, `$` or `è`. | ||
| - Start with an ASCII alphabetical character that's not followed by an ASCII digit, for example, `A$`. | ||
| - 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`. | ||
|
|
||
| ## Affected APIs | ||
|
|
||
| This change was implemented in the parsing logic that affects all numeric types. | ||
|
|
||
| - <xref:System.Numerics.BigInteger.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Numerics.BigInteger.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Numerics.BigInteger.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Int32.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Int32.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Int32.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.UInt32.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.UInt32.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.UInt32.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Byte.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Byte.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Byte.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.SByte.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.SByte.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.SByte.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Int16.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Int16.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Int16.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.UInt16.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.UInt16.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.UInt16.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Int64.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Int64.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Int64.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.UInt64.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.UInt64.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.UInt64.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Half.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Half.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Half.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Single.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Single.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Single.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Double.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Double.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Double.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Decimal.ToString(System.String)?displayProperty=fullName> | ||
| - <xref:System.Decimal.ToString(System.String,System.IFormatProvider)?displayProperty=fullName> | ||
| - <xref:System.Decimal.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)?displayProperty=fullName> | ||
|
|
||
| ## See also | ||
|
|
||
| - [Standard numeric format strings](../../../../standard/base-types/standard-numeric-format-strings.md) | ||
| - [Character literals in custom format strings](../../../../standard/base-types/custom-numeric-format-strings.md#character-literals) | ||
|
|
||
| <!-- | ||
|
|
||
| ### Category | ||
|
|
||
| Core .NET libraries | ||
|
|
||
| ### Affected APIs | ||
|
|
||
| - `M:System.Numerics.BigInteger.ToString(System.String)` | ||
| - `M:System.Numerics.BigInteger.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Numerics.BigInteger.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.Int32.ToString(System.String)` | ||
| - `M:System.Int32.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Int32.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.UInt32.ToString(System.String)` | ||
| - `M:System.UInt32.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.UInt32.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.Byte.ToString(System.String)` | ||
| - `M:System.Byte.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Byte.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.SByte.ToString(System.String)` | ||
| - `M:System.SByte.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.SByte.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.Int16.ToString(System.String)` | ||
| - `M:System.Int16.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Int16.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.UInt16.ToString(System.String)` | ||
| - `M:System.UInt16.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.UInt16.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.Int64.ToString(System.String)` | ||
| - `M:System.Int64.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Int64.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.UInt64.ToString(System.String)` | ||
| - `M:System.UInt64.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.UInt64.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.Half.ToString(System.String)` | ||
| - `M:System.Half.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Half.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.Single.ToString(System.String)` | ||
| - `M:System.Single.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Single.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.Double.ToString(System.String)` | ||
| - `M:System.Double.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Double.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
| - `M:System.Decimal.ToString(System.String)` | ||
| - `M:System.Decimal.ToString(System.String,System.IFormatProvider)` | ||
| - `M:System.Decimal.TryFormat(System.Span{System.Char},System.Int32@,System.ReadOnlySpan{System.Char},System.IFormatProvider)` | ||
|
|
||
| --> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.