The missing upper bound in hexadecimal floating point output
WG14 paper N2098 examines whether hexadecimal floating point formatting permits redundant trailing zeros, and what that means for fixed size buffers.
An exact representation of a number does not necessarily have a predictable length. That distinction causes the portability problem in Martin Sebor's N2098, entered in the WG14 document log on November 14. The paper examines hexadecimal floating point output when a format string leaves the precision unspecified. Its question concerns the storage needed for the result, even when every implementation prints the same numerical value.
The paper reports that existing implementations disagree about redundant fractional zeros. Some print enough digits to represent the value and stop. Others retain zeros to produce a consistent number of fractional digits. A program that allocates space based on the shorter output can fail when linked against a library that produces the longer form. No arithmetic error needs to occur for that difference to matter.
I like C's small vocabulary, but the library contract often requires as much attention as the language itself. This proposal concerns a particularly useful boundary, where a numerical guarantee becomes a memory allocation decision. Understanding the value being printed is insufficient if the caller also needs to know the maximum size of its textual representation.
What the precision promises
The relevant conversion specifiers are %a and %A. N2098 reproduces the standard's description of a hexadecimal significand followed by a power of two exponent. When the precision is present, it specifies the number of hexadecimal digits after the decimal point. When it is absent and FLT_RADIX is a power of two, the wording requires sufficient precision for an exact representation. The paper asks whether that word establishes only a minimum.
The ambiguity becomes visible with the value one. Sebor gives 0x8p-3 and a spelling with fractional zeros as examples of the same value. Multiplying eight by two to the power of minus three produces one in either case. The zeros increase the string length without changing the represented number. Exactness therefore does not decide which spelling an implementation should choose.
This also explains why a numerical round trip is a weak test for this particular issue. Both spellings can preserve the number while requiring different buffer capacities. An application that only compares recovered values has not checked its assumption about output length. Conversely, a test that compares strings may reject a numerically valid spelling because it has confused its own formatting policy with the library's contract.
The nonbinary case needs separate treatment. N2098 notes that the wording for a radix that is not a power of two allows trailing zeros to be omitted. That permission does not itself tell a caller how many zeros an implementation may retain. Merely repeating the requirement that the output distinguish values leaves the allocation question unanswered.
Why bounded output still needs a checked result
Sebor connects the ambiguity to sprintf and snprintf. A shorter spelling assumed by the caller can lead to an overwrite with the former or truncation with the latter. Changing the function name therefore changes the failure mechanism, but it does not establish that the complete formatted value reached the destination.
The C11 committee draft N1570, section 7.21.6.5, specifies how to distinguish complete output from truncation. snprintf returns the character count that the full output would require, excluding the terminating null character. A negative result indicates an encoding error. A nonnegative result smaller than the supplied capacity indicates that the complete string fits.
For a fixed local buffer, a caller can express that decision directly. This example reports failure to its caller instead of accepting a truncated number as a complete value.
#include <stdio.h>
int format_value(char *buffer, size_t capacity, double value)
{
int count = snprintf(buffer, capacity, "%a", value);
if (count < 0)
return -1;
if ((size_t)count >= capacity)
return -1;
return 0;
}
The check treats representation length as an output of formatting. It does not depend on a guessed count of fractional digits. Applications can choose a different policy on failure, such as allocating more storage, but the capacity decision must remain separate from whether the number has an exact hexadecimal spelling.
The proposed restriction
N2098 considers requiring a fixed number of digits, requiring the shortest form without trailing zeros, or allowing both established practices while excluding other behaviour. Sebor favours omitting redundant zeros. His compatibility argument concerns programs already depending on the shorter form, which would stop encountering longer strings when moved between the surveyed implementations.
The paper also sketches a macro for querying a suitable precision by floating type. It explicitly presents that mechanism as an informal idea, not a completed proposal for the nonbinary case. Nothing in this document supplies a macro that applications can use today. The submitted wording is a proposed correction, so existing code cannot assume that implementations have adopted it.
I favour a finite, stated bound for an interface whose result commonly enters caller-owned storage. Allowing multiple textual representations can be reasonable, but each extra freedom needs consideration when the caller has to reserve bytes before invoking the function. Here the proposal makes a concrete connection between specification wording and the information available to that caller.
The remaining design question is how tightly the library should prescribe the spelling. A shortest-form rule reduces variation, while accepting both observed forms preserves more existing implementation behaviour. Either approach needs wording that answers the upper-bound question explicitly. Otherwise a guarantee about numerical precision still leaves portable programs without the corresponding guarantee about representation size.