Note: this question originated from this answer.
How to compute precision for conversion specification %.*f
to maintain precision of floating-point value?
Note: Here the "maintain precision" means that after the printed value is read back (for example, by strtod
or by scanf
) the resulted value is equal (except NaNs) to the original value (used for the printing with conversion specification %.*f
).
To round trip double
(binary floating-point) values to decimal text via "%.*f"
and back to the same value requires up to DBL_DECIMAL_DIG
(commonly 17) significant digits.
DBL_DECIMAL_DIGITS
number of decimal digits, n, such that any floating-point number with p radix b digits can be rounded to a floating-point number with n decimal digits and back again without change to the value,
C23dr § 5.2.4.2.2 24
Any value magnitude >= 10DBL_DECIMAL_DIG - 1 printed with "%.0f"
will print at least DBL_DECIMAL_DIG
digits. It is only values smaller than that may require some digits after the decimal point.
int prec = DBL_DECIMAL_DIG - log10(fabs(x));
if (prec < 0) {
prec = 0;
}
printf("%.*f\n", prec, x);
Care is needed with int prec = DBL_DECIMAL_DIG - log10(fabs(x))
as values very close to a power-of-10 may incur computational errors that result in an off-by-one error. Better to round and potential incur a +1 precision.
Select double
values may easily get by with fewer digits. Could try with incrementally reducing precision.
Infinites, NaN and zeros may needed special handing.
Values about -DBL_TRUE_MIN
likely needs the longest string. This is about 2 /* "-0." */ - DBL_MIN_10_EXP + DBL_DECIMAL_DIG + 1 /* \0 */
.
To find an optimal minimal format precision:
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
// Buffer size needed for all `double`
#define BUF_N (3 /* "-0." */ - DBL_MIN_10_EXP + DBL_DECIMAL_DIG + 1 /* \0 */)
// Untested code. Grandparent duty calls.
// Needs review for off-by-1 errors.
int round_trip_precision_min(double x) {
if (!isfinite(x) || x == 0.0) {
return 0;
}
char buf[BUF_N + 10]; // 10 extra for margin
int prec = (int) (DBL_DECIMAL_DIG - lround(log10(fabs(x))));
if (prec < 0) {
prec = 0;
}
// Try with less precision
while (prec > 0) {
sprintf(buf, "%.*f", prec - 1, x);
if (atof(buf) != x) {
break;
}
prec--;
}
return prec;
}