microsoft / microsoft/STL

`<format>`: The Standard should clarify that `"{:#.6}"` keeps trailing zeros

Open
#2,192 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

format LWG issue needed
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 g and G conversions, trailing zeros are not removed from the result.

to:

In addition, for conversions that call to_chars with chars_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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.