I'm maintaining a C json library and I need to know what's the maximum numbers of characters sprintf will output with "%1.17g" format string. Currently I'm allocating 1100 bytes (based on What is the maximum length in chars needed to represent any double value?) which seems quite wasteful. If I understand correctly it should never be longer than 22 characters (1 for integer part, 1 for dot, 16 for mantissa, 4 for "e-XX"). However problems with floating point numbers can be quite counterintuitive and I'm not sure if I'm not missing something. Is my reasoning correct?
-
4The `%1.` specifies the *minimum field width*. For `g` or `G` conversion specifiers the `17` specifies the *"the maximum number of significant digits"* Further *"Style **e** is used if the exponent from its conversion is less than -4 or **greater than or equal to the precision**."* The maximum number of digits would then be `(+/-) + 19 + 'E' + (+/-) + XXX + '\0'= 26` For good measure a buffer of 32-chars should suffice. There is nothing wrong with an `1100-char` buffer. I'd rather be 10,000 bytes too long, than 1-byte too short. – David C. Rankin Apr 15 '18 at 11:01
2 Answers
Continuing from the comment,
The %1.
(one before the '.'
) specifies the minimum field-width, it provides no limitation on the number of digits that can appear. If the number of digits exeeds the field-width, the field is expanded.
For g
or G
conversion specifiers the 17 specifies the "the maximum number of significant digits". Further "Style e is used if the exponent from its conversion is less than -4 or greater than or equal to the precision."
e, E The double argument is rounded and converted in the style
[-]d.ddde±dd
where there is one digit before the decimal-point character and the number of digits after it is equal to the precision; if the precision is missing, it is taken as 6; if the precision is zero, no decimal-point character appears. AnE
conversion uses the letter'E'
(rather than'e'
) to introduce the exponent. The exponent always contains at least two digits; if the value is zero, the exponent is 00.
The maximum number of digits would then be:
'(+/-)' + 1 + '.' + (17 - 1) + 'e' + '(+/-)' + XXX + '\0' = 25-chars
(where XXX
is a maximum of 308
, and the 17 - 1
reflects the "significant digits" defined in the man page for g/G
)
For good measure a buffer of 32-chars should suffice. There is nothing wrong with an 1100-char buffer. I'd rather be 10,000 bytes too long, than 1-byte too short.

- 81,885
- 6
- 58
- 85
-
This assumes the maximum exponent is 308. It is if IEEE-754 basic 64-bit binary floating-point is used, but this is not requiired. Nothing in the C standard imposes any bound. Also, a NaN could result in a longer sequence. – Eric Postpischil Apr 15 '18 at 13:14
-
Thanks @EricPostpischil. The answer assumes the maximum exponent is less than `1000`, but notes the maximum range for [IEEE-754 Double Precision Floating Point](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) is `308`. (e.g. `2.2250738585072014e-308 - 1.7976931348623157e308`). I'm curious, what would the longest `NaN` be? – David C. Rankin Apr 15 '18 at 13:43
-
For a NaN, “[-]nan[(*n-char-sequence*)]” may be printed, and the meaning of any *n-char-sequence* is implimentation-defined. (The brackets here mean optional, so there may or may not be a “-” and may or may not be an *n-char-sequence* in parentheses.) Inserting the contents of the significand field in hexadecimal would not be an unusual choice, but an implementation could insert them in binary, or it could insert some message, an encoding of an error condition, or whatever. – Eric Postpischil Apr 15 '18 at 13:48
-
2Anybody concerned about field length ought to allow a few extra characters, use `snprintf` instead of `sprintf`, and check the return value. – Eric Postpischil Apr 15 '18 at 13:51
-
To deal with nan safely, just use `isnan` first and bypass the `snprintf`, using a fixed string of `"NAN"` instead. – R.. GitHub STOP HELPING ICE Apr 15 '18 at 14:05
-
@EricPostpischil, that makes sense, thanks, and the `isnan` check is a way to avoid the implementation defined surprise. – David C. Rankin Apr 15 '18 at 15:19
What's the longest string that can be printed with “%1.17g” format for any double
Using "%1.17g"
prints the double
using various styles:
// Large/small values in exponential notation
printf("%1.17g\n", -1.0e200/7);
printf("%1.17g\n", -1.0e-200/7);
printf("%1.17g\n", -1.0e0/7);
-1.4285714285714286e+199
-1.4285714285714286e-201
-1.4285714285714286e-06
// middle values in fixed notation
printf("%1.17g\n", -1.0e-2/7);
printf("%1.17g\n", -1.0e-5/7);
-0.14285714285714285
-0.0014285714285714286
// non-finite values
printf("%1.17g\n", -NAN);
printf("%1.17g\n", -INFINITY);
-nan /* this may be longer */
-inf
The longest apparent string size is 25 char
:
sign digit point fraction e sign exponent null
- 1 . 4285714285714286 e + 199 \0
1 1 1 17-1 1 1 3 1
What could this be longer?
- C allows not-a-numbers to also include a payload with may include many characters. (I doubt more than the payload written in decimal. 16 with binary64)
- The exponent range may be need more than 3 characters. (perhaps a 4 or 5 digit exponent)
double
may require more the 17 digits to differentiate alldouble
. (Detectable withDBL_DECIMAL_DIG
)- The present locale may add extra characters for a
double
(not so likely)
The lead 1
in "%1.17g"
is the minimum characters to print. It serves scant purpose here.
Solution: estimate the longest buffer using generous considerations - and then double it.
#define G_SIZE (1 + 1 + 1 + DBL_DECIMAL_DIG-1 + 1 + 1 + 5 + 1)
char buf[G_SIZE * 2];
int cnt = snprintf(buf, sizeof buf, "%.*g", DBL_DECIMAL_DIG, value);
if (cnt < 0 || (unsigned) cnt >= sizeof buf) {
unexpected_conversion_hanlder();
}
or use a variable length array and 2 calls to snprintf()
int cnt = snprintf(NULL, 0, "%.*g", DBL_DECIMAL_DIG, value);
char buf[cnt + 1];
snprintf(buf, sizeof buf , "%.*g", DBL_DECIMAL_DIG, value);

- 143,097
- 13
- 135
- 256
-
*"estimate the longest buffer using generous considerations - and then double it."* - well advised. – David C. Rankin Apr 15 '18 at 15:20
-
@DavidC.Rankin Thanks. The double call to `snprintf()` is a classic _safe_ solution (barring some strange concurrency and VLAs are not always available). Yet I do not like the 2-`sprintf()` calls and prefer the reasonable compromise of a 2x working buffer. If a 2x buffer was not big enough, certainly something strange is going on. – chux - Reinstate Monica Apr 15 '18 at 16:26