As noted in other answers, this is due to a mismatch between the format string and the type of the argument.
I assume that you are using x86 here (based on observed results).
Arguments are passed on the stack, and x/y , although of type float , will be passed as a double to the varargs function (due to rules of the type "advance").
An int is a 32-bit value, and double is a 64-bit value.
In both cases, you go twice x/y (= 0.5). The representation of this value as a 64-bit double is 0x3fe0000000000000 . As a pair of 32-bit words, it is stored as 0x00000000 (the least significant 32 bits), followed by 0x3fe00000 (the most significant 32-bit). Thus, the arguments on the stack, as seen from printf() , look like this:
0x3fe00000 0x00000000 0x3fe00000 0x00000000 <-- stack pointer
In the first of two cases, %d calls the first 32-bit value 0x00000000 , which must be popped up and printed. %f displays the following two 32-bit values, 0x3fe00000 (least significant 32 bits 64 double bits), and then 0x00000000 (most significant). The resulting 64-bit value 0x000000003fe00000 , interpreted as double , is a very small number. (If you change %f in the format string to %g , you will see that it is almost 0, but not quite).
In the second case, %f correctly displays the first double , and %d gives 0x00000000 half the second double , so it seems to work.