How to print a single-precision float with printf
I'm trying to print a floating point number in x86_64 assembly but it just prints the value as zero.
There's a few questions about this already. One appeared to be resolved by ensuring that you set the number of vector registers you're using in %al. Another showed that you need to have a stack alignment of 16 bytes. But, I'm doing both those things and still not getting correct output.
This is my program:
# prints a floating point value
.section .rodata
.fmt: .string "num: %f\n"
.num: .float 123.4
.section .text
.global main
.type main, @function
main:
subq $8, %rsp # 16-byte alignment
# print my number
movss .num, %xmm0 # load float value
movq $.fmt, %rdi # load format string
movb $1, %al # use 1 vector register
call printf
# exit
addq $8, %rsp # undo alignment
movq $0, %rax # return 0
ret
printf(3)
's %f
format specifier wants a double
. There is no way to get printf to accept a float
, only double
or long double
.
C's default argument promotions specify that calls to variadic functions like foo(char *fmt, ...)
promote float
to double
, and perform the usual integer promotions of narrow integer types to int
, for trailing args that match the ...
part of the prototype. (The same applies to all args for calling functions with no prototype.) N1570 6.5.2.2 Function calls, subsections 6 and 7.
Thus C provides no way for a caller to pass a float
to printf
, so it has no conversion for it, and %f
means double
. (%lf
also works for double
, assuming the implementation ignores it for non-integer / wchar_t
conversions. Accepting %lf
for double
is required in C99/C11 and C++11 printf
implementations, so you can safely use the same %lf
format string with a double
for printf
and scanf
).
Note that scanf
is different, because float *
and double *
aren't affected by those promotions.
In this case, load with CVTSS2SD .num(%rip), %xmm0
.
If you look at compiler output, you'll see gcc do everything you did, and pxor
-zero the register first to break the false dependency on the old value of %xmm0
. (cvtss2sd
's poor design leaves the upper 64 bits of the destination unchanged.) gcc errs on the side of caution, and inserts xor-zeroing instructions to break false dependencies in many cases.
You're probably getting 0 because the upper bits of xmm0 happen to be zero. When printf
looks at the low 64 bits of xmm0 as a double
(IEEE binary64 on x86), it finds the bit pattern for 123.4f
in the low 32 bits of the mantissa, and the rest zero. As a 64-bit double
, this bit-pattern represents a very small (subnormal) number, so it comes out as zero with %f
.
You can try the equivalent with a float
, (e.g. on http://www.h-schmidt.net/FloatConverter/IEEE754.html), setting some bits in the low half to see what you get.
If you used %g
(scientific notation) or %a
(hex representation of the double
bit-pattern), the non-zero bits would show up. (Unless you had Denormals Are Zero mode enabled in the MXCSR.)