How is conversion of float/double to int handled in printf?
Solution 1:
The printf
function uses the format specifiers to figure out what to pop off the stack. So when it sees %d
, it pops off 4 bytes and interprets them as an int
, which is wrong (the binary representation of (float)3.0
is not the same as (int)3
).
You'll need to either use the %f
format specifiers or cast the arguments to int
. If you're using a new enough version of gcc
, then turning on stronger warnings catches this sort of error:
$ gcc -Wall -Werror test.c
cc1: warnings being treated as errors
test.c: In function ‘main’:
test.c:10: error: implicit declaration of function ‘printf’
test.c:10: error: incompatible implicit declaration of built-in function ‘printf’
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 4 has type ‘double’
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 5 has type ‘double’
Response to the edited part of the question:
C's integer promotion rules say that all types smaller than int
get promoted to int
when passed as a vararg. So in your case, the 'd'
is getting promoted to an int
, then printf is popping off an int
and casting to a char
. The best reference I could find for this behavior was this blog entry.
Solution 2:
There's no such thing as "casting to int
in printf
". printf
does not do and cannot do any casting. Inconsistent format specifier leads to undefined behavior.
In practice printf
simply receives the raw data and reinterprets it as the type implied by the format specifier. If you pass it a double
value and specify an int
format specifier (like %d
), printf
will take that double
value and blindly reinterpret it an an int
. The results will be completely unpredictable (which is why doing this formally causes undefined behavior in C).
Solution 3:
Jack's answer explains how to fix your problem. I'm going to explain why you're getting your unexpected results. Your code is equivalent to:
float f = 11.22;
double d = 44.55;
int i,j,k,l;
i = (int) f;
j = (int) d;
k = *(int *) &f; //cast float to int
l = *(int *) &d; //cast double to int
printf("i = %d, j = %d, f = %d, d = %d", i,j,k,l);
The reason is that f
and d
are passed to printf
as values, and then these values are interpreted as int
s. This doesn't change the binary value, so the number displayed is the binary representation of a float
or a double
. The actual cast from float
to int
is much more complex in the generated assembly.
Solution 4:
Because you are not using the float format specifier, try with:
printf("i = %d, j = %d, f = %f, d = %f", i,j,f,d);
Otherwise, if you want 4 ints you have to cast them before passing the argument to printf:
printf("i = %d, j = %d, f = %d, d = %d", i,j,(int)f,(int)d);
Solution 5:
The reason your follow-up code works is because the character constant is promoted to an int before it is pushed onto the stack. So printf pops off 4 bytes for %c and for %d. In fact, character constants are of type int, not type char. C is strange that way.