Runtime: API suggestion: Provide an overload of float/double.ToString that allow precision greater than 99

Created on 22 Aug 2019  Â·  16Comments  Â·  Source: dotnet/runtime

At the moment, there is no way to specify precisions greater than 99 when using standard format specifiers. This is because the specifiers only allow no numbers, or numbers that are less than two digits (inclusive). Numbers bigger than that will result in rather unexpected ways, as it is recognised as custom format specifiers.

For example: 10f.ToString("G100") returns "G110", format: "G999" returns "G999", double.MaxValue.ToString("G308") returns "G3179769313486232000000000"............"000000" etc. This can be confusing as many are not aware of such facts, and leaves no way of obtaining precise strings except implementing the ToString ourselves manually.

Unfortunately, allowing interpretation of such strings would be a breaking change, since it changes the behaviour of ToString.

One way to solve this could be to add an overload that accepts a char and int, the char for format and int for the desired precision.

public struct Single
{
    public string ToString(char format, int precision);
}

* Wonder if similar overload of TryFormat would be nice to have as well?

api-approved area-System.Numerics up-for-grabs

Most helpful comment

There was a lot of oddities in how custom format strings worked and it has a much greater risk of breaking some existing code, even if accidentally.

All 16 comments

Decimal points rather than precision, I should say.

The purpose of it isn't really for producing roundtrippable values, since as you said G17 / G9 (well, or R on Core) will suffice already. I just noticed this while playing around with it; thought it'd be nice to have, really. Heard it can be used to correctly validate results of complex algorithms; but not really sure myself about uses of it.

Heard it can be used to correctly validate results of complex algorithms; but not really sure myself about uses of it.

I don't think that's a good argument for changing the behavior of .Net. You should actually explain how the change would be useful, saying you heard it could be useful is not enough.

when G17 for double (and G9 for float) already gives you all the precision that's encoded in those values?

This isn't quite an accurate statement. double/float require no more than 17/9 significant digits to produce a roundtrippable string, but that is not all the precision encoded.

For example, the double with the most significant digits is 0x000F_FFFF_FFFF_FFFF (these are the raw bits, as returned by BitConverter.DoubleToInt64Bits(value)). This represents the max subnormal double and is 2^−1022 * (1 − 2^−52).

Expanded:

2.2250738585072008890245868760858598876504231122409594654935248025624400092282356951787758888037591552642309780950434312085877387158357291821993020294379224223559819827501242041788969571311791082261043971979604000454897391938079198936081525613113376149842043271751033627391549782731594143828136275113838604094249464942286316695429105080201815926642134996606517803095075913058719846423906068637102005108723282784678843631944515866135041223479014792369585208321597621066375401613736583044193603714778355306682834535634005074073040135602968046375918583163124224521599262546494300836851861719422417646455137135420132217031370496583210154654068035397417906022589503023501937519773030945763173210852507299305089761582519159720757232455434770912461317493580281734466552734375 × 10^-308

That string contains 767 significant digits and contains the full precision of the value represented by the double.

For various scenarios, knowing the full precision of the underlying value is important. One example is when trying to compute or account for rounding error.

We already support producing these full strings in the underlying algorithm. We just don't expose any API that allows you to request that many digits, so the work here is actually trivial.

I'm not sure what practical use so much precision has, but if G precision > 99 results in inconsistent behavior, I'd treat this as a bug rather than a suggestion.

I'd treat this as a bug rather than a suggestion.

This behavior has been around since at least .NET 2. I would guess .NET 1 as well, but I don't have an install right now.

There would need to be more discussion on this to determine if changing G999 to being handled as format: G, precision 999 is fine as today it is treated as a custom format string and returns "G999" instead.

It would be nice if we can change the current behaviour, but is it actually inconsistent? it seemed to follow the behaviour specified by the custom numeric string format documents.

Video

  • There are issues with supporting more thant 99 digits of precision, some are in parsing behavior as pointed out by the OP, some of them are in public data structures which are size limited, and some are likely in the implementation.
  • Normally, it would seem better to extend the existing format specifier because it works better with resource strings and localization
  • However, given that production code is very unlikely to ever use more than 99 digits, it seems acceptable to have separate overloads to override the precision for debugging purposes

C# namespace System { public struct Single { public string ToString(char format, int precision); } public struct Double { public string ToString(char format, int precision); } }

Video

  • It was suggested to add support for custom cultures. New shape:

C# namespace System { public struct Single { public string ToString(char format, int precision); public string ToString(char format, int precision, IFormatProvider? provider); } public struct Double { public string ToString(char format, int precision); public string ToString(char format, int precision, IFormatProvider? provider); } }

Was alteration of the existing format specifier also considered as well? just curious, because it would be a neater solution if it's possible - I'm just not sure if it holds more value than the cost of it being technically a breaking change. Although I don't think people would actually do something like dbl.ToString("D123") to print the thing, when one could already do that without all the ToString things, the value is fairly weak at the same time as well because scenarios requiring more than 99 digits is uncommon.

There was a lot of oddities in how custom format strings worked and it has a much greater risk of breaking some existing code, even if accidentally.

I can put up a PR for this.

Question: Do we want to enforce a limit on what kind of values are supported for the precision? I think the longest output is something like 308 digits, and I'm not sure what we should do for anything above 308. Just pad it with 0s?

Also.... how about new overloads of TryFormat? it's definitely nice to have and provides some flexibility in high performance scenarios.

Feels weird that we're not doing it even though we are adding those TryFormat APIs everywhere to give people a choice.

...And, what for invalid / unsupported format specifiers? the default behaviour with the string based overloads would have been to just treat it as a custom format specifier, but I don't think it applies here...

I think the longest output is something like 308 digits, and I'm not sure what we should do for anything above 308. Just pad it with 0s

It should work like it does today, which is that it may just cap at whatever the actual digit count is (but the exact behavior depends on the format string). The actual max is 112 digits for float and 767 digits for double.

Also.... how about new overloads of TryFormat? it's definitely nice to have and provides some flexibility in high performance scenarios.

I think we can bring that forward in a separate proposal. The scenario of producing a fully qualified string is already fairly rare and is largely useful for diagnostic purposes so I'm not quite convinced having a Span<char> API is necessary from day 1.
However, the actual implementation is trivial since it just forwards to the generic implementation that can handle any digit count, so I would be fine with taking it forward in a separate proposal.

...And, what for invalid / unsupported format specifiers? the default behaviour with the string based overloads would have been to just treat it as a custom format specifier, but I don't think it applies here...

It will throw, as is already the case today: https://source.dot.net/#System.Private.CoreLib/Number.Formatting.cs,508

Was this page helpful?
0 / 5 - 0 ratings

Related issues

GitAntoinee picture GitAntoinee  Â·  3Comments

sahithreddyk picture sahithreddyk  Â·  3Comments

matty-hall picture matty-hall  Â·  3Comments

iCodeWebApps picture iCodeWebApps  Â·  3Comments

jchannon picture jchannon  Â·  3Comments