`<format>`: The Standard should clarify that `"{:#.6}"` keeps trailing zeros
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 11.1k
- Forks
- 1.7k
- Avg merge
- 4d 15h
- Merged PRs (30d)
- 22
Description
Test case and output
Thanks to @statementreply! 😻
#include <cstdio>
#include <format>
using namespace std;
int main() {
puts(format("format(\"{{:#}}\", 1.0) = {:#}", 1.0).c_str());
puts(format("format(\"{{:#.6}}\", 1.0) = {:#.6}", 1.0).c_str());
puts(format("format(\"{{:#g}}\", 1.0) = {:#g}", 1.0).c_str());
puts(format("format(\"{{:#.6g}}\", 1.0) = {:#.6g}", 1.0).c_str());
return 0;
}
After PR #2157
format("{:#}", 1.0) = 1.
format("{:#.6}", 1.0) = 1.00000
format("{:#g}", 1.0) = 1.00000
format("{:#.6g}", 1.0) = 1.00000
libfmt behavior - https://godbolt.org/z/qns4carK7
format("{:#}", 1.0) = 1.0
format("{:#.6}", 1.0) = 1.00000
format("{:#g}", 1.0) = 1.00000
format("{:#.6g}", 1.0) = 1.00000
My analysis
- I believe that "After PR" is correct.
- I think that there's an LWG Issue here.
"{:#g}" and "{:#.6g}" - unambiguous
For "{:#g}" and "{:#.6g}", where After PR and libfmt agree that the output is "1.00000", I believe that the Standardese is clear. N4892 [format.string.std]/22 passes "6 if precision is not specified" so they are certainly equivalent. to_chars chars_format::general, 6 wants to trim the zeros and decimal point, but [format.string.std]/6 suppresses both trimmings from happening.
"{:#.6}" - wording issue
For "{:#.6}", where After PR and libfmt agree that the output is "1.00000", I believe that the Standardese is technically unclear, and should be patched with an LWG Issue. According to [format.string.std]/22, this has a type of "none", but because the precision is specified, we call to_chars with chars_format::general, 6. As before, to_chars wants to trim the zeros and decimal point. [format.string.std]/6 sentence 4 definitely keeps the decimal point. However, [format.string.std]/6 sentence 6 keeps trailing zeros "for g and G conversions".
❔ The question is, because type-none-with-precision performs a chars_format::general, 6 conversion, should it be considered a g conversion?
❕ I believe the answer is "yes" - the user's mental model should be "if I pass a precision but not a letter, the letter defaults to g and I get all the behavior that I would have gotten if I explicitly said g". This is true at the to_chars layer (as the call is exactly chars_format::general, 6) and the same should be true for any format post-processing/modified behavior. The fact that libfmt displays this behavior is some additional evidence in favor of this being a reasonable interpretation.
"{:#}" - implementation divergence
For "{:#}", where After PR outputs "1.", these sentences are even more important. [format.string.std]/22 clearly begins by calling to_chars(first, last, value), i.e. this is plain shortest, not general precision. Thus, unlike "{:#.6}" which (in my opinion above) should be considered an "honorary" g conversion, "{:#}" should not be considered a g conversion. (Plain shortest switches between fixed and scientific with the fewest-characters, tiebreak-prefers-fixed criterion; it does not trim zeros because it doesn't emit them in the first place.) Again, [format.string.std]/6 sentence 4 definitely keeps the decimal point.
❓ The question here is, does [format.string.std]/6 sentence 6 apply?
❗ I believe the answer is no: this is not a g conversion at the format specifier level, this is not a chars_format::general conversion at the to_chars level, no precision was passed to to_chars, and no trailing zeros were removed by to_chars. (There's definitely no "default of 6 precision" involved here, as plain shortest is information-preserving - it may need to emit up to 17 significant digits to round-trip a double.)
Proposed resolution: 😸
I believe that this could be clarified in the Standard by changing [format.string.std]/6 sentence 6 from:
In addition, for
gandGconversions, trailing zeros are not removed from the result.
to:
In addition, for conversions that call
to_charswithchars_format::general, trailing zeros are not removed from the result.
Notes
This captures the discussion in https://github.com/microsoft/STL/pull/2157#discussion_r702247363 and https://github.com/microsoft/STL/pull/2157#discussion_r703936220 .
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading [format.string.std]/6 and /22 in the referenced C++ draft, then review the discussion in microsoft/STL#2157. Compare the specified behavior for "{:#.6}" with the existing wording and determine whether the proposed resolution accurately clarifies trailing-zero handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100