printf(3) is a variadic C function and its (mis)use is not dictated by language syntax or compiler beyond function call syntax. As such, you can pass
printf(3) as many arguments as you want of any type and won't typically be punished for it by the compiler. Although your program will crash or print out garbage, or worse, it could print out reasonable output that you won't know is bogus without lots of debugging!
EOF is a #define for integer -1. You're telling
printf(3) that you passed a float! As a consequence,
printf(3) interprets the 4 bytes that you passed as though it were a float. And through IEEE754, the raw bits that define -1 as integer is somehow equivalent to 0 when the raw bits are interpreted as IEEE754 single precision.
Sometimes compilers will specially inspect
printf(3) calls and generate warnings or cast your arguments for you. Otherwise, you promise that the substitution literals are the same as the types you passed to
printf(3)!
Moral of the story? Use those substitution literals carefully and precisely. If you're not sure of the argument's type or which substitution literal to use, cast to a known type. Didn't know EOF wasn't a float?
Code:
printf("%.4f", (float)EOF);
This is the right way to do it.
Or better yet, use C++ and std::cout avoids this issue entirely!
References:
stdarg(3) -- How
printf(3) really works.
/usr/include/inttypes.h -- #defines for substitution literals for
stdint(7) integer typedefs.
https://en.wikipedia.org/wiki/IEEE_floating_point
https://en.wikipedia.org/wiki/Single-precision_floating-point_format