Reputation: 1555
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.
Upvotes: 3
Views: 2101
Reputation: 229334
Therefore the only thing I can surmise is that all float values passed to a variadic function would be upgraded to double, unconditionally.
Yes - that's precisely right.
From the C standard;
6.5.2.2.7 The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.
And the "default argument promotions" rules will promote float
to double
, the relevant part being:
6.5.2.2.6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double.
Upvotes: 6
Reputation: 13599
See Variadic arguments and Default argument promotions:
At the function call, each argument that is a part of the variable argument list undergoes special implicit conversions known as default argument promotions.
.
Each argument of integer type undergoes integer promotion (see below), and each argument of type float is implicitly converted to the type double.
Upvotes: 5