Skip to content

Custom Numeric Format String Rounds Erroneously #114104

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

Closed
jim-noble-milliman opened this issue Apr 1, 2025 · 5 comments
Closed

Custom Numeric Format String Rounds Erroneously #114104

jim-noble-milliman opened this issue Apr 1, 2025 · 5 comments
Labels
area-System.Numerics needs-author-action An issue or pull request that requires more info or actions from the author. question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@jim-noble-milliman
Copy link

jim-noble-milliman commented Apr 1, 2025

Description

double precision floating point values appear to get erroneously rounded by custom format strings using # when they exceed a threshold of decimal precision.

It appears to occur when we exceed 13 digits of decimal precision (to the right of the decimal point). For example, given the custom format string 0.############## ('up to fourteen decimal digits'), the value 28.5999999999998 (thirteen decimal digits) will render as a string correctly, but 28.59999999999998 (fourteen decimal digits) will be rendered as 28.6.

Reproduction Steps

var x1 = 28.5999999999998d;
var x2 = 28.59999999999998d;

Console.WriteLine($"{x1:0.##############}"); // "28.5999999999998"
Console.WriteLine($"{x2:0.##############}"); // "28.6"

Expected behavior

As long as the number of # to the right of the decimal meets or exceeds the number of decimal digits in the value, the full exact decimal value should be preserved in the string output.

Actual behavior

When the decimal digits exceeds thirteen, the custom string format executes erroneous rounding against the developer's request, and without offering e.g. an error or exception regarding an invalid format string.

Regression?

Tested also with Framework 4.8.1 and the same behavior is present, so this does not appear to be a regression.

Known Workarounds

No response

Configuration

  • .NET 8, .NET Framework 4.8.1
  • Windows 11
  • x64
  • Unknown if this is architecture-specific

Other information

No response

@ghost ghost added the area-System.Numerics label Apr 1, 2025
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Apr 1, 2025
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

@tannergooding
Copy link
Member

This isn't erroneous, the values you typed for x1/x2 aren't the actual values.

That is, for all floating-point values (float, double, Half, even decimal) the literal you type is taken as given. It is parsed as if to infinite precision and unbounded range. Finally, it is rounded to the nearest representable result.


So you typed:

var x1 = 28.5999999999998d;
var x2 = 28.59999999999998d;

This is then compiled down such that the actual value is:

double x1 = 28.599999999999798916405779891647398471832275390625;
double x2 = 28.5999999999999801048033987171947956085205078125;

Because these are the representable values that are nearest to the literal you typed.


You then gave a custom format string of 0.############## where 0 indicates a digit is placed here if present and otherwise the literal 0 should appear; . is the decimal point placeholder; and # indicates a digit is placed here if present and otherwise nothing appears.

Matching that against the actual represented values we get:

 0.##############
28.59999999999979 8 916405779891647398471832275390625
28.59999999999998 0 1048033987171947956085205078125

Now, for historical reasons, custom format strings for binary floating-point values support printing no more than 15 total significant digits (this is partially covered under https://devblogs.microsoft.com/dotnet/floating-point-parsing-and-formatting-improvements-in-net-core-3-0/).

You've specified 14 # signs past the decimal point and these numbers have 2 significant integral digits and so the actual formatting ends up ignoring one #, giving us:

 0.############# #
28.5999999999997 9 8916405779891647398471832275390625
28.5999999999999 8 01048033987171947956085205078125

This means that the first number has the last digit of 7 round to 8 . While the second number has the last digit of 9 round up to 0 and that cascades through the rest of the 9 to give 28.6.

As per the blog post, there would be significantly more work required to support custom numeric format strings with more than 15 significant digits (for double, technically there needs to be support for up to 767 significant digits) and the risk of breaking existing code, particularly code that is likely used for user display, is significant; so it hasn't been done yet.

@tannergooding tannergooding added question Answer questions and provide assistance, not an issue with source code or documentation. needs-author-action An issue or pull request that requires more info or actions from the author. and removed untriaged New issue has not been triaged by the area owner labels Apr 1, 2025
Copy link
Contributor

This issue has been marked needs-author-action and may be missing some important information.

@huoyaoyuan
Copy link
Member

huoyaoyuan commented Apr 1, 2025

#113155 ?

Update: looks unrelated

Copy link
Contributor

This issue has been automatically marked no-recent-activity because it has not had any activity for 14 days. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will remove no-recent-activity.

@github-actions github-actions bot locked and limited conversation to collaborators May 29, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Numerics needs-author-action An issue or pull request that requires more info or actions from the author. question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

3 participants