Document Number: N2098
Submitter: Martin Sebor
Submission Date: November 2, 2016
Subject: Number Of Fractional Digits In printf %a Output

Summary

The number of digits after the decimal point output by the fprintf a/A conversion specifiers is described in §7.21.6.1 The fprintf function, paragraph 8, as follows:

a,A    A double argument representing a floating-point number is converted in the style [-]0xh.hhhh p±d, where there is one hexadecimal digit (which is nonzero if the argument is a normalized floating-point number and is otherwise unspecified) before the decimal-point character278) and the number of hexadecimal digits after it is equal to the precision; if the precision is missing and FLT_RADIX is a power of 2, then the precision is sufficient for an exact representation of the value; if the precision is missing and FLT_RADIX is not a power of 2, then the precision is sufficient to distinguish values of type double, except that trailing zeros may be omitted; ...

First, the sentence "when the precision is missing ... it is sufficient for..." doesn't make complete sense. A missing precision cannot very well be sufficient for something. Looking at the g/G conversion specifier for guidance, the wording there is much clearer:

...if the precision is missing, it is taken as 6...

Presumably, what is meant in the first instance is that when the precision is missing it is taken as if one had been provided that' sufficiently large for an exact representation of the value.

Second, the use of the word sufficient raises the question whether it is meant to impose a lower bound on the number of digits after the decimal point (leaving the upper bound unspecified), or whether it requires exactly as many digits after the decimal point as is necessary for an exact representation of the value. The part of the second sentence that reads "except that trailing zeros may be omitted" suggests that trailing zeros may only be omitted when FLT_RADIX is not a power of 2. But then how many trailing zeros are required when FLT_RADIX is a power of 2? And the not-a-power of 2 case, since trailing zeros may but are not required to be omitted, how are portable programs expected to determine how many trailing zeros an implementation will append?

For example, when FLT_RADIX is 2, can printf ("%a", 1.0) print something like

    0x8.00000000000000p-3
with (redundant) trailing zeros, or is it meant to print exactly
    0x8p-3
with the trailing zeros omitted?

Both styles have been observed on a variety of systems (the former on Solaris, for example, and the latter on Linux), and the divergence leads to portability problems in programs that use the sprintf or snprintf functions to format floating point numbers into fixed size buffers. Programs written with the assumption that the latter is intended may either write past the end of the buffer when using sprintf or encounter premature output truncation when using snprintf.

The problem is similar when FLT_RADIX is not a power of 2.

Suggested Technical Corrigendum

To reduce the likelihood of programs writing past the end of the buffer (or that of truncation), trailing zeros should either not be allowed at all, or be required to be bounded by some maximum, otherwise portable programs have no way of determining how big a buffer to allocate.

Although the specification seems to allow an arbitrary number of digits after the decimal point, a survey of 21 different operating systems where FLT_RADIX is a power of 2 revealed that implementations fall into two categories:

  1. those that print the same number of digits after the decimal point for all values (typically 13 for double) including trailing zeros, and
  2. those that print exactly as many digits after the decimal point as is necessary for an exact representation of the value, with no trailing zeros.
In light of this existing practice we see three options to change the standard and resolve the issue:
  1. require the first kind of output (the same number of digits for all values, including trailing zeros, regardless of precision), or
  2. require the second kind of output (no trailing zeros), or
  3. allow either kind of output but not any other.
We do not consider it an acceptable resolution to leave the number of fractional digits unbounded, nor do we think that making the number implementation-defined would be conducive to portability.

Of the three options above, we believe that the second is best for portability. Under this option, existing programs that call sprintf and run on the first set of implementations are unlikely to be adversely impacted by the absence of trailing zeros and will remain portable to the first set of implementations without change. The same programs that run on the second set of implementations (but that would have undefined or unexpected behavior when ported to the first set) will become portable to the first set without change.

With that in mind we propose changing §7.21.6.1, paragraph 8 as indicated below:

...; if the precision is missing and FLT_RADIX is a power of 2, then the precision is as if one had been provided that had been sufficient for an exact representation of the value; if the precision is missing and FLT_RADIX is not a power of 2, then the precision is as if one had been provided that is sufficient to distinguish 279) values of type double, except that trailing zeros may be omitted; if the precision is zero and the # flag is not specified, no decimal-point character appears. In no case shall trailing zeros appear in the output.

We have no formal proposed resolution to offer for the case when FLT_RADIX is no a power of 2 beyond an informal suggestion to specify a new <inttypes.h>macro, say PRIa_PRECISION(type), for implementations to define to the query and provide in the form of arguments to the printf family of functions the precision for a value of each floating point type. For example like so:

    char buf[4 + 7 + PRIa_PRECISION (double)];

    sprintf (buf, "pi=%.*a", PRIa_PRECISION (double), 3.14);

The macro could be defined as follows:

    #define PRIa_PRECISION(type) \
        _Generic ((type)0, float: 22, double 22, long double: 32)

and could be used by all programs, even those targeting implementations where FLT_RADIX is a power of 2. We offer this solution not a formal proposal but merely as an idea to consider.