Is there a printf
width specifier which can be applied to a floating point specifier that would automatically format the output to the necessary number of significant digits such that when scanning the string back in, the original floating point value is acquired?
For example, suppose I print a float
to a precision of 2
decimal places:
float foobar = 0.9375;
printf("%.2f", foobar); // prints out 0.94
When I scan the output 0.94
, I have no standards-compliant guarantee that I'll get the original 0.9375
floating-point value back (in this example, I probably won't).
I would like a way tell printf
to automatically print the floating-point value to the necessary number of significant digits to ensure that it can be scanned back to the original value passed to printf
.
I could use some of the macros in float.h
to derive the maximum width to pass to printf
, but is there already a specifier to automatically print to the necessary number of significant digits -- or at least to the maximum width?
9 Answers 9
I recommend @Jens Gustedt hexadecimal solution: use %a.
OP wants "print with maximum precision (or at least to the most significant decimal)".
A simple example would be to print one seventh as in:
#include <float.h>
int Digs = DECIMAL_DIG;
double OneSeventh = 1.0/7.0;
printf("%.*e\n", Digs, OneSeventh);
// 1.428571428571428492127e-01
But let's dig deeper ...
Mathematically, the answer is "0.142857 142857 142857 ...", but we are using finite precision floating point numbers.
Let's assume IEEE 754 double-precision binary.
So the OneSeventh = 1.0/7.0
results in the value below. Also shown are the preceding and following representable double
floating point numbers.
OneSeventh before = 0.1428571428571428 214571170656199683435261249542236328125
OneSeventh = 0.1428571428571428 49212692681248881854116916656494140625
OneSeventh after = 0.1428571428571428 769682682968777953647077083587646484375
Printing the exact decimal representation of a double
has limited uses.
C has 2 families of macros in <float.h>
to help us.
The first set is the number of significant digits to print in a string in decimal so when scanning the string back,
we get the original floating point. There are shown with the C spec's minimum value and a sample C11 compiler.
FLT_DECIMAL_DIG 6, 9 (float) (C11)
DBL_DECIMAL_DIG 10, 17 (double) (C11)
LDBL_DECIMAL_DIG 10, 21 (long double) (C11)
DECIMAL_DIG 10, 21 (widest supported floating type) (C99)
The second set is the number of significant digits a string may be scanned into a floating point and then the FP printed, still retaining the same string presentation. There are shown with the C spec's minimum value and a sample C11 compiler. I believe available pre-C99.
FLT_DIG 6, 6 (float)
DBL_DIG 10, 15 (double)
LDBL_DIG 10, 18 (long double)
The first set of macros seems to meet OP's goal of significant digits. But that macro is not always available.
#ifdef DBL_DECIMAL_DIG
#define OP_DBL_Digs (DBL_DECIMAL_DIG)
#else
#ifdef DECIMAL_DIG
#define OP_DBL_Digs (DECIMAL_DIG)
#else
#define OP_DBL_Digs (DBL_DIG + 3)
#endif
#endif
The "+ 3" was the crux of my previous answer. Its centered on if knowing the round-trip conversion string-FP-string (set #2 macros available C89), how would one determine the digits for FP-string-FP (set #1 macros available post C89)? In general, add 3 was the result.
Now how many significant digits to print is known and driven via <float.h>
.
To print N significant decimal digits one may use various formats.
With "%e"
, the precision field is the number of digits after the lead digit and decimal point.
So - 1
is in order. Note: This -1
is not in the initial int Digs = DECIMAL_DIG;
printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh);
// 1.4285714285714285e-01
With "%f"
, the precision field is the number of digits after the decimal point.
For a number like OneSeventh/1000000.0
, one would need OP_DBL_Digs + 6
to see all the significant digits.
printf("%.*f\n", OP_DBL_Digs , OneSeventh);
// 0.14285714285714285
printf("%.*f\n", OP_DBL_Digs + 6, OneSeventh/1000000.0);
// 0.00000014285714285714285
Note: Many are use to "%f"
. That displays 6 digits after the decimal point; 6 is the display default, not the precision of the number.
-
1@Jingguo Yao Agree that reference says "The precision specifies how many digits follow the decimal-point character for the ‘%f’". The word "precision" there is not used in a mathematically sense, but simply to define the number of digits after the decimal point. 1234567890.123, mathematically has 13 digits of precision or significant digits. 0.000000000123 has 3 digits of mathematical precision, not 13. Floating point numbers are logarithmically distributed.This answer uses significant digits and the mathematical sense of precision.chux– chux2015年08月31日 14:23:10 +00:00Commented Aug 31, 2015 at 14:23
-
1@Slipp D. Thompson "There are shown with the C spec's minimum value and a sample C11 compiler."chux– chux2015年09月21日 22:11:36 +00:00Commented Sep 21, 2015 at 22:11
-
1@chux Ah, I missed that correlation in the write-up. Cheers.Slipp D. Thompson– Slipp D. Thompson2015年09月22日 05:09:40 +00:00Commented Sep 22, 2015 at 5:09
-
1@Greg A. Woods Interesting idea - worthy of posting an answer. Yet I do not agree when code needs to print "to maintain precision" as OP posted. Your solution could print 100s of digits. Once
DBL_DECIMAL_DIG
significant digits are printed, additional digits serve no purpose "to maintain precision" and recover the originaldouble
. Hence this answer's"%.*e\n", DBL_DECIMAL_DIG - 1, x
approach.chux– chux2016年03月26日 15:29:10 +00:00Commented Mar 26, 2016 at 15:29 -
1Indeed you are correct -- my trick is only valid for values with a magnitude between 1.0 and 1.0eDBL_DIG, which is arguably the only range really suitable for printing with
"%f"
in the first place. Using"%e"
as you showed is of course a better approach all round and effectively a decent answer (though perhaps it is not as good as using"%a"
might be if it is available, and of course"%a"
should be available if `DBL_DECIMAL_DIG is). I have always wished for a format specifier which would always round to exactly the maximum precision (instead of the hard-coded 6 decimal places).Greg A. Woods– Greg A. Woods2016年03月26日 20:20:19 +00:00Commented Mar 26, 2016 at 20:20
The short answer to print floating point numbers losslessly (such that they can be read back in to exactly the same number, except NaN and Infinity):
- If your type is float: use
printf("%.9g", number)
. - If your type is double: use
printf("%.17g", number)
.
Do NOT use %f
, since that only specifies how many significant digits after the decimal and will truncate small numbers. For reference, the magic numbers 9 and 17 can be found in float.h
which defines FLT_DECIMAL_DIG
and DBL_DECIMAL_DIG
.
-
12Would you be able to explain the
%g
specifier?William Breathitt Gray– William Breathitt Gray2014年01月16日 14:56:48 +00:00Commented Jan 16, 2014 at 14:56 -
21%g prints the number with as many digits as needed for precision, preferring exponential syntax when the numbers are small or huge (1e-5 rather than .00005) and skipping any trailing zeroes (1 rather than 1.00000).ccxvii– ccxvii2014年01月16日 23:49:33 +00:00Commented Jan 16, 2014 at 23:49
-
1Mantissa length of double value is 53 bits (1 bit is implicit). Precision of double value is therefore 53 / log2(10) = 15.95 decimal places. So if you want represent IEEE 754 number in decimal format unambigously, you need at least ceil(53 / log2(10)) = 16 decimal places. In my programs I am using 17 decimal places just to be sure. Dont know exactly which value is correct 16 or 17. But 15 places are surely insufficient.truthseeker– truthseeker2014年11月21日 11:37:09 +00:00Commented Nov 21, 2014 at 11:37
-
5@chux - You're mistaken about the behavior of %.16g; it's not adequate for your example of distinguishing 1.000_0000_0000_0000_2e-01 from 1.000_0000_0000_0000_3e-01. %.17g is needed.Don Hatch– Don Hatch2016年06月14日 01:03:42 +00:00Commented Jun 14, 2016 at 1:03
-
2BTW here are a couple of nice references that explain and derive the 9 and 17: en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2005.pdfDon Hatch– Don Hatch2016年09月23日 19:50:25 +00:00Commented Sep 23, 2016 at 19:50
If you are only interested in the bit (resp hex pattern) you could use the %a
format. This guarantees you:
The default precision suffices for an exact representation of the value if an exact representation in base 2 exists and otherwise is sufficiently large to distinguish values of type double.
I'd have to add that this is only available since C99.
No, there is no such printf width specifier to print floating-point with maximum precision. Let me explain why.
The maximum precision of float
and double
is variable, and dependent on the actual value of the float
or double
.
Recall float
and double
are stored in sign.exponent.mantissa format. This means that there are many more bits used for the fractional component for small numbers than for big numbers.
enter image description here
For example, float
can easily distinguish between 0.0 and 0.1.
float r = 0;
printf( "%.6f\n", r ) ; // 0.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // 0.100000
But float
has no idea of the difference between 1e27
and 1e27 + 0.1
.
r = 1e27;
printf( "%.6f\n", r ) ; // 999999988484154753734934528.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // still 999999988484154753734934528.000000
This is because all the precision (which is limited by the number of mantissa bits) is used up for the large part of the number, left of the decimal.
The %.f
modifier just says how many decimal values you want to print from the float number as far as formatting goes. The fact that the accuracy available depends on the size of the number is up to you as the programmer to handle. printf
can't/doesn't handle that for you.
-
2This is an excellent explanation of the limitations of accurately printing out floating point values to specific decimal places. However, I believe I was too ambiguous with my original choice of words, so I have updated my question to avoid the term "maximum precision" in the hopes that it may clear up the confusion.William Breathitt Gray– William Breathitt Gray2013年11月18日 19:24:47 +00:00Commented Nov 18, 2013 at 19:24
-
It still depends on the value of the number you are printing.bobobobo– bobobobo2013年11月18日 19:43:35 +00:00Commented Nov 18, 2013 at 19:43
-
3this is partly true, but it doesn't answer the question and you are confused as to what OP is asking. He is asking if one can query the number of significant [decimal] digits a
float
provides, and you assert that there's no such thing (i. e. that there's noFLT_DIG
), which is wrong.user529758– user5297582013年12月23日 09:20:53 +00:00Commented Dec 23, 2013 at 9:20 -
1Are you assuming the format letter has to be "f"? I don't think that's required. My reading of the question is that the OP is looking for some printf format specifier that produces a non-lossy round trip, so @ccxvii 's answer ("%.9g" for float, "%.17g" for double) is a good one. Probably the question would be better worded by removing the word "width" from it.Don Hatch– Don Hatch2016年09月23日 19:46:08 +00:00Commented Sep 23, 2016 at 19:46
-
1This isn't what the question was asking.sleep– sleep2019年06月10日 05:56:38 +00:00Commented Jun 10, 2019 at 5:56
Simply use the macros from <float.h>
and the variable-width conversion specifier (".*"
):
float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
-
3@OliCharlesworth Do you mean like so:
printf("%." FLT_DIG "f\n", f);
William Breathitt Gray– William Breathitt Gray2013年05月30日 15:31:45 +00:00Commented May 30, 2013 at 15:31 -
4+1 but this works best for
%e
, not so well for%f
: only if it is knows that the value to print is close to1.0
.Pascal Cuoq– Pascal Cuoq2013年05月30日 15:33:23 +00:00Commented May 30, 2013 at 15:33 -
4
%e
prints significant digits for very small numbers and%f
does not. e.g.x = 1e-100
.%.5f
prints0.00000
(a total loss of precession).%.5e
prints1.00000e-100
.chux– chux2013年05月30日 17:11:14 +00:00Commented May 30, 2013 at 17:11 -
1@bobobobo Also, you're wrong in that it "yields more accurate reasons".
FLT_DIG
is defined to the value it is defined to for a reason. If it's 6, that's becausefloat
isn't able to hold more than 6 digits of precision. If you print it using%.7f
, the last digit will have no meaning.user529758– user5297582013年11月10日 20:50:46 +00:00Commented Nov 10, 2013 at 20:50 -
7@bobobobo No,
%.6f
isn't equivalent, becauseFLT_DIG
is not always 6. And who cares about efficiency? I/O is already expensive as hell, one digit more or less precision won't make a bottleneck.user529758– user5297582013年11月10日 20:51:57 +00:00Commented Nov 10, 2013 at 20:51
I run a small experiment to verify that printing with DBL_DECIMAL_DIG
does indeed exactly preserve the number's binary representation. It turned out that for the compilers and C libraries I tried, DBL_DECIMAL_DIG
is indeed the number of digits required, and printing with even one digit less creates a significant problem.
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
union {
short s[4];
double d;
} u;
void
test(int digits)
{
int i, j;
char buff[40];
double d2;
int n, num_equal, bin_equal;
srand(17);
n = num_equal = bin_equal = 0;
for (i = 0; i < 1000000; i++) {
for (j = 0; j < 4; j++)
u.s[j] = (rand() << 8) ^ rand();
if (isnan(u.d))
continue;
n++;
sprintf(buff, "%.*g", digits, u.d);
sscanf(buff, "%lg", &d2);
if (u.d == d2)
num_equal++;
if (memcmp(&u.d, &d2, sizeof(double)) == 0)
bin_equal++;
}
printf("Tested %d values with %d digits: %d found numericaly equal, %d found binary equal\n", n, digits, num_equal, bin_equal);
}
int
main()
{
test(DBL_DECIMAL_DIG);
test(DBL_DECIMAL_DIG - 1);
return 0;
}
I run this with Microsoft's C compiler 19.00.24215.1 and gcc version 7.4.0 20170516 (Debian 6.3.0-18+deb9u1). Using one less decimal digit halves the number of numbers that compare exactly equal. (I also verified that rand()
as used indeed produces about one million different numbers.) Here are the detailed results.
Microsoft C
Tested 999507 values with 17 digits: 999507 found numericaly equal, 999507 found binary equal Tested 999507 values with 16 digits: 545389 found numericaly equal, 545389 found binary equal
GCC
Tested 999485 values with 17 digits: 999485 found numericaly equal, 999485 found binary equal Tested 999485 values with 16 digits: 545402 found numericaly equal, 545402 found binary equal
-
1"run this with Microsoft's C compiler" --> That compiler may have
RAND_MAX == 32767
. Consideru.s[j] = (rand() << 8) ^ rand();
or the like to make certain all bits get a chance at being 0 or 1.chux– chux2020年03月04日 00:30:14 +00:00Commented Mar 4, 2020 at 0:30 -
Indeed, its RAND_MAX is 32767, so your proposal correct.Diomidis Spinellis– Diomidis Spinellis2020年03月05日 08:02:08 +00:00Commented Mar 5, 2020 at 8:02
-
1I updated the post to handle RAND_MAX as suggested by @chux-ReinstateMonica. The results are similar to the ones obtained before.Diomidis Spinellis– Diomidis Spinellis2020年03月05日 08:15:32 +00:00Commented Mar 5, 2020 at 8:15
-
@DiomidisSpinellis It seems both your equalities are equivalent.Wolf– Wolf2024年05月24日 18:23:58 +00:00Commented May 24, 2024 at 18:23
To my knowledge, there is a well diffused algorithm allowing to output to the necessary number of significant digits such that when scanning the string back in, the original floating point value is acquired in dtoa.c
written by David Gay, which is available here on Netlib (see also the associated paper). This code is used e.g. in Python, MySQL, Scilab, and many others.
In one of my comments to an answer I lamented that I've long wanted some way to print all the significant digits in a floating point value in decimal form, in much the same way the as the question asks. Well I finally sat down and wrote it. It's not quite perfect, and this is demo code that prints additional information, but it mostly works for my tests. Please let me know if you (i.e. anyone) would like a copy of the whole wrapper program which drives it for testing.
static unsigned int
ilog10(uintmax_t v);
/*
* Note: As presented this demo code prints a whole line including information
* about how the form was arrived with, as well as in certain cases a couple of
* interesting details about the number, such as the number of decimal places,
* and possibley the magnitude of the value and the number of significant
* digits.
*/
void
print_decimal(double d)
{
size_t sigdig;
int dplaces;
double flintmax;
/*
* If we really want to see a plain decimal presentation with all of
* the possible significant digits of precision for a floating point
* number, then we must calculate the correct number of decimal places
* to show with "%.*f" as follows.
*
* This is in lieu of always using either full on scientific notation
* with "%e" (where the presentation is always in decimal format so we
* can directly print the maximum number of significant digits
* supported by the representation, taking into acount the one digit
* represented by by the leading digit)
*
* printf("%1.*e", DBL_DECIMAL_DIG - 1, d)
*
* or using the built-in human-friendly formatting with "%g" (where a
* '*' parameter is used as the number of significant digits to print
* and so we can just print exactly the maximum number supported by the
* representation)
*
* printf("%.*g", DBL_DECIMAL_DIG, d)
*
*
* N.B.: If we want the printed result to again survive a round-trip
* conversion to binary and back, and to be rounded to a human-friendly
* number, then we can only print DBL_DIG significant digits (instead
* of the larger DBL_DECIMAL_DIG digits).
*
* Note: "flintmax" here refers to the largest consecutive integer
* that can be safely stored in a floating point variable without
* losing precision.
*/
#ifdef PRINT_ROUND_TRIP_SAFE
# ifdef DBL_DIG
sigdig = DBL_DIG;
# else
sigdig = ilog10(uipow(FLT_RADIX, DBL_MANT_DIG - 1));
# endif
#else
# ifdef DBL_DECIMAL_DIG
sigdig = DBL_DECIMAL_DIG;
# else
sigdig = (size_t) lrint(ceil(DBL_MANT_DIG * log10((double) FLT_RADIX))) + 1;
# endif
#endif
flintmax = pow((double) FLT_RADIX, (double) DBL_MANT_DIG); /* xxx use uipow() */
if (d == 0.0) {
printf("z = %.*s\n", (int) sigdig + 1, "0.000000000000000000000"); /* 21 */
} else if (fabs(d) >= 0.1 &&
fabs(d) <= flintmax) {
dplaces = (int) (sigdig - (size_t) lrint(ceil(log10(ceil(fabs(d))))));
if (dplaces < 0) {
/* XXX this is likely never less than -1 */
/*
* XXX the last digit is not significant!!! XXX
*
* This should also be printed with sprintf() and edited...
*/
printf("R = %.0f [%d too many significant digits!!!, zero decimal places]\n", d, abs(dplaces));
} else if (dplaces == 0) {
/*
* The decimal fraction here is not significant and
* should always be zero (XXX I've never seen this)
*/
printf("R = %.0f [zero decimal places]\n", d);
} else {
if (fabs(d) == 1.0) {
/*
* This is a special case where the calculation
* is off by one because log10(1.0) is 0, but
* we still have the leading '1' whole digit to
* count as a significant digit.
*/
#if 0
printf("ceil(1.0) = %f, log10(ceil(1.0)) = %f, ceil(log10(ceil(1.0))) = %f\n",
ceil(fabs(d)), log10(ceil(fabs(d))), ceil(log10(ceil(fabs(d)))));
#endif
dplaces--;
}
/* this is really the "useful" range of %f */
printf("r = %.*f [%d decimal places]\n", dplaces, d, dplaces);
}
} else {
if (fabs(d) < 1.0) {
int lz;
lz = abs((int) lrint(floor(log10(fabs(d)))));
/* i.e. add # of leading zeros to the precision */
dplaces = (int) sigdig - 1 + lz;
printf("f = %.*f [%d decimal places]\n", dplaces, d, dplaces);
} else { /* d > flintmax */
size_t n;
size_t i;
char *df;
/*
* hmmmm... the easy way to suppress the "invalid",
* i.e. non-significant digits is to do a string
* replacement of all dgits after the first
* DBL_DECIMAL_DIG to convert them to zeros, and to
* round the least significant digit.
*/
df = malloc((size_t) 1);
n = (size_t) snprintf(df, (size_t) 1, "%.1f", d);
n++; /* for the NUL */
df = realloc(df, n);
(void) snprintf(df, n, "%.1f", d);
if ((n - 2) > sigdig) {
/*
* XXX rounding the integer part here is "hard"
* -- we would have to convert the digits up to
* this point back into a binary format and
* round that value appropriately in order to
* do it correctly.
*/
if (df[sigdig] >= '5' && df[sigdig] <= '9') {
if (df[sigdig - 1] == '9') {
/*
* xxx fixing this is left as
* an exercise to the reader!
*/
printf("F = *** failed to round integer part at the least significant digit!!! ***\n");
free(df);
return;
} else {
df[sigdig - 1]++;
}
}
for (i = sigdig; df[i] != '.'; i++) {
df[i] = '0';
}
} else {
i = n - 1; /* less the NUL */
if (isnan(d) || isinf(d)) {
sigdig = 0; /* "nan" or "inf" */
}
}
printf("F = %.*s. [0 decimal places, %lu digits, %lu digits significant]\n",
(int) i, df, (unsigned long int) i, (unsigned long int) sigdig);
free(df);
}
}
return;
}
static unsigned int
msb(uintmax_t v)
{
unsigned int mb = 0;
while (v >>= 1) { /* unroll for more speed... (see ilog2()) */
mb++;
}
return mb;
}
static unsigned int
ilog10(uintmax_t v)
{
unsigned int r;
static unsigned long long int const PowersOf10[] =
{ 1LLU, 10LLU, 100LLU, 1000LLU, 10000LLU, 100000LLU, 1000000LLU,
10000000LLU, 100000000LLU, 1000000000LLU, 10000000000LLU,
100000000000LLU, 1000000000000LLU, 10000000000000LLU,
100000000000000LLU, 1000000000000000LLU, 10000000000000000LLU,
100000000000000000LLU, 1000000000000000000LLU,
10000000000000000000LLU };
if (!v) {
return ~0U;
}
/*
* By the relationship "log10(v) = log2(v) / log2(10)", we need to
* multiply "log2(v)" by "1 / log2(10)", which is approximately
* 1233/4096, or (1233, followed by a right shift of 12).
*
* Finally, since the result is only an approximation that may be off
* by one, the exact value is found by subtracting "v < PowersOf10[r]"
* from the result.
*/
r = ((msb(v) * 1233) >> 12) + 1;
return r - (v < PowersOf10[r]);
}
-
I don't care whether it answers the question or not - this is really impressive to do. It took some thought and should be acknowledged and commended. Maybe it would be good if you were to include somehow (whether here or else) the full code for testing but even without it this is really a good job. Have a +1 for that!Pryftan– Pryftan2019年10月15日 14:58:20 +00:00Commented Oct 15, 2019 at 14:58
-
@GregA.Woods Code does have issues with negative numbers though as the last digit place is off by one. Perhaps use a
snprintf(df, n, "% .1f", d);
(space added) to fix the buffer length, be it + or -.chux– chux2022年04月05日 03:13:13 +00:00Commented Apr 5, 2022 at 3:13 -
Ah, yes, negative numbers. Thanks for your comment! I will make a note in the original source and try improving it when I get some spare time.Greg A. Woods– Greg A. Woods2022年04月05日 17:55:02 +00:00Commented Apr 5, 2022 at 17:55
To add to Stéphane's answer, the state-of-the-art algorithms are called "Dragon4" and "Grisu".
While researching this problem for myself I found this article with background information, links to the relevant papers and an implementation:
https://www.ryanjuckett.com/printing-floating-point-numbers/
Explore related questions
See similar questions with these tags.
printf( "%f", val );
which is already portable, efficient, and the default.