Search code examples
cgccclang

Why does gcc converts floating point literals to double even after using an f suffix but clang seems not?


Consider the following code from C Primer Plus:

#include <stdio.h>

int main()
{
    float f = 7.0f;
    float g = 8.0f;

    printf("%d %d\n", f, g);        // wrong kind of values

    return 0;
}

using the clang compiler produces the following output which is an expected result by the way. The compiler rightfully complains about the float type arguments that are assigned to wrong format specifiers:

~$ clang badcount.c -o badcount
badcount.c:11:23: warning: format specifies type 'int' but the argument has type 'float' [-Wformat]
printf("%d %d\n", f, g);        // wrong kind of values
        ~~        ^
        %f
badcount.c:11:26: warning: format specifies type 'int' but the argument has type 'float' [-Wformat]
printf("%d %d\n", f, g);        // wrong kind of values
           ~~        ^
           %f
2 warnings generated.

however compiling the same code on gcc 8.2.0 will produce the following output:

~$ gcc-8 badcount.c -fsingle-precision-constant -o badcount -Wall
badcount.c: In function 'main':
badcount.c:11:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
 printf("%d %d\n", f, g);        // wrong kind of values
         ~^        ~
         %f
badcount.c:11:17: warning: format '%d' expects argument of type 'int', but argument 3 has type 'double' [-Wformat=]
 printf("%d %d\n", f, g);        // wrong kind of values
            ~^        ~
            %f

even though f and g are of type float and even though an f suffix is used to explicitly make literal values of type float, gcc will still promote them into double types. I even tried the following code but still gcc ignores explicit float types and promote them into double. I looked up the gcc documentation but it looks like there are no options to prevent automatic promotion of literal floats to literal doubles, and gcc implicitly does this, and ignores f suffixes on literals.

    printf("%d %d\n", f, 45.9f);

In the above code 45.9f will still be promoted to type double even though I have implicitly made it float.

Are there any options or solutions to prevent this? Or is this intended by design in gcc?

Edit: I forgot to put this code that I tried, using an explicit cast to type float doesn't prevent this either:

printf("%d %d\n", f, (float)(45.9f));

not even this:

float f = (float)7.0f;

It seems gcc doesn't care about explicit castings either, and will promote floats to double all the same.


Solution

  • Both compilers respect the standard and generate equivalent code.

    Let's check what your code gets translated to at the assembly level.

    clang -O3 -fomit-frame-pointer -S ctest.c -o ctest1.s

    LCPI0_0:
        .quad   4619567317775286272
    LCPI0_1:
        .quad   4620693217682128896
    
    _main:
        pushq   %rax
        leaq    L_.str(%rip), %rdi
        movsd   LCPI0_0(%rip), %xmm0
        movsd   LCPI0_1(%rip), %xmm1
        movb    $2, %al
        callq   _printf
        xorl    %eax, %eax
        popq    %rcx
        retq
    
    L_.str:
        .asciz  "%d %d\n"
    

    gcc -O3 -S ctest.c -o ctest2.s

    lC2:
        .ascii "%d %d\12\0"
    
    _main:
        leaq    lC2(%rip), %rdi
        subq    $8, %rsp
        movl    $2, %eax
        movsd   lC0(%rip), %xmm1
        movsd   lC1(%rip), %xmm0
        call    _printf
        xorl    %eax, %eax
        addq    $8, %rsp
        ret
    
    lC0:
        .long   0
        .long   1075838976
    lC1:
        .long   0
        .long   1075576832
    

    (Some labels and assembler directives removed for brevity)

    The conversion from float to double is performed in both cases. The doubles were actually pre-computed at compile time, and are then loaded into SSE registers with movsd.

    The difference you observe is simply a choice in the way the compiler reports the error. Clang displays the type before the implicit conversion, and gcc, the type post conversion. But in both cases, the arguments passed to printf are of type double.