The %f
printf format code is specified as operating on a value of type double
[source]. However, a simple test program demonstrates that it can also be used with values of type float
. How does this work?
The equivalent case with integer types (e.g. int
and long long int
) "works" because on little-endian machines, the low-order bytes of a 32-bit integer happen to overlap the low-order bytes of a 64-bit integer, so as long as the upper bits are 0, you'll get the "right" answer.
But this cannot possibly be the case for float
and double
, because the floating point formats are not interchangeable like this. You simply cannot print a floating point value as a double without doing the (rather complicated) conversion into the other format. An attempt to do so by type-punning will simply print garbage.
On top of that, printf
is variadic. The compiler doesn't necessarily know at compile time what format specifiers will be used, only the types of the arguments. Therefore the only thing I can surmise is that all float
values passed to a variadic function would be upgraded to double
, unconditionally. But it boggles my mind that I could have been programming in C for so long and not know this.
How is C doing the implicit coercion here?
Source:
#include <stdio.h>
#include <math.h>
int main() {
float x[2] = {M_PI, 0.0};
printf("value of x: %.16e\n", x[0]);
printf("size of x: %lu\n", sizeof(x[0]));
double *xp = (double *)&x[0];
printf("value of *xp: %.16e\n", *xp);
printf("size of *xp: %lu\n", sizeof(*xp));
double y = M_PI;
printf("value of y: %.16e\n", y);
printf("size of y: %lu\n", sizeof(y));
int i[2] = {1234, 0};
printf("value of i: %lld\n", i[0]);
printf("sizeof of i: %lu\n", sizeof(i[0]));
long long *ip = (long long *)&i[0];
printf("value of i: %lld\n", *ip);
printf("sizeof of i: %lu\n", sizeof(*ip));
return 0;
}
Output:
value of x: 3.1415927410125732e+00
size of x: 4
value of *xp: 5.3286462644388174e-315
size of *xp: 8
value of y: 3.1415926535897931e+00
size of y: 8
value of i: 1234
sizeof of i: 4
value of i: 1234
sizeof of i: 8
Compile command and version:
$ gcc test_float.c -o test_float
$ gcc --version
gcc (Ubuntu 5.5.0-12ubuntu1~16.04) 5.5.0 20171010
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.