Skip to content

Commit 914740e

Browse files
authored
Numeric parsing precision breaking change (#23046)
1 parent 903912d commit 914740e

File tree

6 files changed

+195
-59
lines changed

6 files changed

+195
-59
lines changed

docs/core/compatibility/6.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ If you're migrating an app to .NET 6.0, the breaking changes listed here might a
2222
## Core .NET libraries
2323

2424
- [Changes to nullable reference type annotations](core-libraries/6.0/nullable-ref-type-annotation-changes.md)
25+
- [Standard numeric format parsing precision](core-libraries/6.0/numeric-format-parsing-handles-higher-precision.md)
2526

2627
## Windows Forms
2728

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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+
-->

docs/core/compatibility/toc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
items:
4141
- name: Nullability annotation changes
4242
href: core-libraries/6.0/nullable-ref-type-annotation-changes.md
43+
- name: Standard numeric format parsing precision
44+
href: core-libraries/6.0/numeric-format-parsing-handles-higher-precision.md
4345
- name: Windows Forms
4446
items:
4547
- name: APIs throw ArgumentNullException
@@ -434,6 +436,8 @@
434436
items:
435437
- name: Nullability annotation changes
436438
href: core-libraries/6.0/nullable-ref-type-annotation-changes.md
439+
- name: Standard numeric format parsing precision
440+
href: core-libraries/6.0/numeric-format-parsing-handles-higher-precision.md
437441
- name: .NET 5.0
438442
items:
439443
- name: Assembly-related API changes for single-file publishing

docs/csharp/tutorials/exploration/interpolated-strings-local.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ In the previous section, two poorly formatted strings were inserted into the res
9797
Console.WriteLine($"On {date:d}, the price of {item} was {price:C2} per {unit}.");
9898
```
9999

100-
You specify a format string by following the interpolation expression with a colon (":") and the format string. "d" is a [standard date and time format string](../../../standard/base-types/standard-date-and-time-format-strings.md#the-short-date-d-format-specifier) that represents the short date format. "C2" is a [standard numeric format string](../../../standard/base-types/standard-numeric-format-strings.md#the-currency-c-format-specifier) that represents a number as a currency value with two digits after the decimal point.
100+
You specify a format string by following the interpolation expression with a colon (":") and the format string. "d" is a [standard date and time format string](../../../standard/base-types/standard-date-and-time-format-strings.md#the-short-date-d-format-specifier) that represents the short date format. "C2" is a [standard numeric format string](../../../standard/base-types/standard-numeric-format-strings.md#currency-format-specifier-c) that represents a number as a currency value with two digits after the decimal point.
101101

102102
A number of types in the .NET libraries support a predefined set of format strings. These include all the numeric types and the date and time types. For a complete list of types that support format strings, see [Format Strings and .NET Class Library Types](../../../standard/base-types/formatting-types.md#format-strings-and-net-types) in the [Formatting Types in .NET](../../../standard/base-types/formatting-types.md) article.
103103

docs/csharp/tutorials/exploration/interpolated-strings.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ items:
6464
Console.WriteLine($"On {date:d}, the price of {item.Name} was {item.Price:C2} per {item.perPackage} items");
6565
```
6666
67-
You specify a format string by following the interpolation expression with a colon (":") and the format string. "d" is a [standard date and time format string](../../../standard/base-types/standard-date-and-time-format-strings.md#the-short-date-d-format-specifier) that represents the short date format. "C2" is a [standard numeric format string](../../../standard/base-types/standard-numeric-format-strings.md#the-currency-c-format-specifier) that represents a number as a currency value with two digits after the decimal point.
67+
You specify a format string by following the interpolation expression with a colon (":") and the format string. "d" is a [standard date and time format string](../../../standard/base-types/standard-date-and-time-format-strings.md#the-short-date-d-format-specifier) that represents the short date format. "C2" is a [standard numeric format string](../../../standard/base-types/standard-numeric-format-strings.md#currency-format-specifier-c) that represents a number as a currency value with two digits after the decimal point.
6868
6969
A number of types in the .NET libraries support a predefined set of format strings. These include all the numeric types and the date and time types. For a complete list of types that support format strings, see [Format Strings and .NET Class Library Types](../../../standard/base-types/formatting-types.md#format-strings-and-net-types) in the [Formatting Types in .NET](../../../standard/base-types/formatting-types.md) article.
7070

0 commit comments

Comments
 (0)