Printf width specifier to maintain precision of floating-point value
Solution 1
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.
Solution 2
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
.
Solution 3
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.
Solution 4
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.
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.
Solution 5
Simply use the macros from <float.h>
and the variable-width conversion specifier (".*"
):
float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
Comments
-
Vilhelm Gray almost 2 years
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 of2
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 original0.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 toprintf
.I could use some of the macros in
float.h
to derive the maximum width to pass toprintf
, but is there already a specifier to automatically print to the necessary number of significant digits -- or at least to the maximum width?-
bobobobo over 10 yearsIt seems that 6 is already the maximum precision the
float
type offers. (Decimal values after the 6th will likely be further and further off). Look up the actual#define
d value ofFLT_DIG
on your platform, all bets say it will just be 6. -
Admin over 10 years@bobobobo So you are just recommending that one uses an assumption out of air instead of taking the portable approach?
-
bobobobo over 10 years@H2CO3 No, I would not recommend using "an assumption out of the air", I would suggest using
printf( "%f", val );
which is already portable, efficient, and the default. -
Vilhelm Gray over 10 years@bobobobo So that I may add it to the answers, would you be able to cite the clause in C99 standard which states that the printf statement will output the float type at maximum precision by default if no precision is specified?
-
bobobobo over 10 years@VilhelmGray Well as @chux gets into, there is some pretty complicated math as to the actual precision for your particular
double
. As yourdouble
gets extremely large (very far from 1.0), it actually gets less accurate in the decimal portion (value portion less than 1.0). So you can't really have a satisfactory answer here, because your question has a false assumption in it (namely that allfloat
s/double
s are created equal) -
Vilhelm Gray over 10 years@bobobobo I apologize, I think you may have misunderstood the question. I'm interested in the precision of the output (i.e. the number of characters printed), not the precision of the data types (i.e. how accurately float/double represent the true value).
-
bobobobo over 10 years
%.12f
will just print 12 decimals, regardless if that accuracy is available from your variable type or not. You need to consider that the "printed" accuracy available (# characters printed) is completely arbitrary. The accuracy available in afloat
ordouble
is dependent on the number of mantissa bits used in the data type, as well as on the size of the number.printf
doesn't consider the size of the number however. So the answer to your question is no, you cannot print a number withprintf
to the "maximum precision possible" -
chux - Reinstate Monica over 10 years@Vilhelm Gray See you've made a small update emphasizing
float
. Forfloat
, I would recommend usingprintf("%.*e", OP_FLT_Digs-1, x)
where OP_FLT_Digs is derived correspondingly as OP_DBL_Digs below. IMHO, your focus isfloat-text-float
round tripping and that is exactly, by C spec, what xxx_DECIMAL_DIG provide. Of course%a
is great, but I assume you prefer decimal text. -
chux - Reinstate Monica over 10 years@Vilhelm Gray C11dr 5.2.4.2.2 "... 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, p log10 b b is a power of 10 ⎡1 + p log10 b⎤ otherwise FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ..." The 6,10,10 are the minimum values.
-
Pryftan over 4 yearsI'm surprised that nobody (or so it seems) has referenced the paper 'What Every Programmer Should Know About Floating-Point Arithmetic' - a reprint of which can be found at docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1251. There's also it seems a website that tries to simplify this which can be found at floating-point-gui.de (no it's not in German but English).
-
-
Vilhelm Gray almost 11 years@OliCharlesworth Do you mean like so:
printf("%." FLT_DIG "f\n", f);
-
Pascal Cuoq almost 11 years+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
. -
Admin almost 11 years@OliCharlesworth Yes, or that. But I don't think that would help very much if there is already a call to
printf()
- that itself is quite expensive. -
Vilhelm Gray almost 11 years@H2CO3 Are you referring to the preprocessor, or
printf
itself interpreting the format string passed? -
Vilhelm Gray almost 11 years@PascalCuoq What would be the problem if used with
%f
? -
Vilhelm Gray almost 11 yearsAre you referring to the rounding errors inherent in the binary representation of a floating point number? Why wouldn't this also be true for
%e
? -
chux - Reinstate Monica almost 11 years
%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
. -
Vilhelm Gray almost 11 yearsThanks @chux, I see the difference now.
-
Pascal Cuoq almost 11 years@VilhelmGray Was the comment “Are you referring to … ” intended for me? Users are not notified of comments if you do not mention their names. I was not referring to the rounding errors in representing
0.0000005
as adouble
, of which there are, but not to the point that it should lose all precision and be displayed as0
. -
Admin almost 11 years@VilhelmGray Printf, naturally.
-
bobobobo over 10 years
FLT_DIG
is actually#define
d as 6 on Mac OS X. Even using%.7f
yields more accurate results than this. -
Admin over 10 years@bobobobo What the friggin' hell are you doing? Seriously, you just edited this then you downvoted?
-
Admin over 10 years@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. Think before you downvote. -
bobobobo over 10 yearsI know. But the answer isn't upvoteable.
%.6f
is entirely equivalent, and less expensive. -
Admin over 10 years@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. -
Vilhelm Gray over 10 yearsThis 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.
-
bobobobo over 10 yearsIt still depends on the value of the number you are printing.
-
Admin over 10 yearsthis 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. -
bobobobo over 10 years@H2CO3 Maybe you should edit my post and downvote (j/k). This answer asserts
FLT_DIG
doesn't mean anything. This answer asserts the number of decimal places available depends on the value inside the float. -
Vilhelm Gray over 10 yearsWould you be able to explain the
%g
specifier? -
user1484901 over 10 years%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).
-
Albert Wiersch about 10 yearsIn my compiler (C++Builder XE), it's DBL_DIG instead of DBL_DECIMAL_DIG and the value is 15 instead of 17.
-
truthseeker over 9 yearsMantissa 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.
-
user1024 almost 9 yearswhy is 1.428571428571428492127e-01 and not 1.428571428571428492127e-001, the number of digits after 'e' should be 3?
-
Jingguo Yao over 8 years12.12.5 Floating-Point Conversions says the default precision for
%f
is 6. -
chux - Reinstate Monica over 8 years@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 - Reinstate Monica over 8 years@Jingguo Yao Or simply, perhaps I should have said "That displays 6 digits after the decimal point; 6 is the display default, not the number of significant digits of the number."
-
Slipp D. Thompson over 8 yearsMaybe I'm missing something, but I don't understand why you've listed two numbers for each macro (e.g.
FLT_DECIMAL_DIG 6, 9
). Is that 32-bit arch vs. 64-bit? -
chux - Reinstate Monica over 8 years@Slipp D. Thompson "There are shown with the C spec's minimum value and a sample C11 compiler."
-
Slipp D. Thompson over 8 years@chux Ah, I missed that correlation in the write-up. Cheers.
-
fiorentinoing about 8 years@chux it might be useful to highlight that only the first 15 digits of the number 1.428571428571428492127e-01 are correct.
-
Greg A. Woods about 8 yearsN.B.: When printing a number with a magnitude greater than 1.0 the correct way to calculate the number of digits to ask
printf("%.*f")
to print after the decimal place requires subtracting the number of digits that will be printed before the decimal place from the value given byDBL_DIG
. E.g.:printf("%.*f", DBL_DIG - (int) lrint(ceil(log10(ceil(fabs(dval))))), dval);
-
chux - Reinstate Monica about 8 years@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. -
Greg A. Woods about 8 yearsIndeed 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). -
Don Hatch almost 8 years@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 over 7 years@bobobobo and user529758: thinking and then downvoting because this answer is as wrong as can be. Even in the range in which %e isn't needed, FLT_DIG isn't adequate on my platform (FLT_DIG=6, which is common); e.g. the round trip 0.124999985f -> "0.125000" -> 0.125f is lossy.
-
Don Hatch over 7 yearsAre 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 over 7 yearsBTW 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.pdf
-
Jarrod Smith almost 5 yearsThis isn't what the question was asking.
-
Pryftan over 4 yearsI 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!
-
chux - Reinstate Monica about 4 years"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. -
Diomidis Spinellis about 4 yearsIndeed, its RAND_MAX is 32767, so your proposal correct.
-
Diomidis Spinellis about 4 yearsI updated the post to handle RAND_MAX as suggested by @chux-ReinstateMonica. The results are similar to the ones obtained before.
-
Yuki about 3 yearsIMHO, this the true correct answer. This is should be the top voted answer at the top.
-
Mark VY over 2 yearsDavid Gay, not Daniel Gay. (David M. Gay to be specific. Not sure what M stands for.)
-
pmor over 2 years@chux-ReinstateMonica Should it be
printf("%.*f\n", FLT_DECIMAL_DIG-1, f)
instead ofprintf("%.*f\n", FLT_DIG, f)
? -
chux - Reinstate Monica over 2 years@pmor Neither. I'd recommend
printf("%.*e\n", FLT_DECIMAL_DIG-1, f)
(note the"e"
),printf("%.*g\n", FLT_DECIMAL_DIG, f)
orprintf("%a\n", f)
forfloat f;
. -
chux - Reinstate Monica about 2 years@GregA.Woods Of course you are right. Sorry to have bought up a non-issue. Comment removed. (I finally found some time to look your answer in depth.)
-
chux - Reinstate Monica about 2 years@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 -. -
Greg A. Woods about 2 yearsAh, 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.