Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why printf takes the last number printed?

Tags:

c

printf

EDIT: I already knew that printf is not typesafe I am just looking for an explination about what exactly ocurred (I mean describe the undefined behavior).

Why if I print "7" in the second printf, the program prints 9.334354. I know that if I don't write 7.0, this won't be printed but Why does it take the first number writted instead?.

#include <stdio.h>  

int main()  
{  
    printf("%.2f\n", 9.334354);    
    printf("%.5f\n", 7);  
    printf("%03d\n", 9);  
    getchar();  
}

This is the output

    9.33
    9.33435
    009
like image 981
Julio Vga Avatar asked Nov 29 '22 03:11

Julio Vga


1 Answers

Repeat this to yourself for two weeks once a night before going to bed:

printf is not typesafe. printf is not typesafe. printf is not typesafe.

The function will only work if you pass it an argument of the type that you promise. Everything else is undefined behaviour. You promise a double (via %f) but provide an int (the type of the literal 7), so it's undefined behaviour. Shame on you.

(I did once go into details to explain the actual output, in case you're interested.)


Update: Since you're interested in the explanation for this particular behaviour, here's the (relevant) assembly for that code on my x86/GCC4.6.2/-O3:

First the data sections:

.LC0:
        .long   1921946325
        .long   1076013872   // 0x 4022AB30 728E92D5 is the binary rep of 9.334354
.LC1:
        .string "%.2f\n"
.LC2:
        .string "%.5f\n"
.LC3:
        .string "%03d\n"

Now the code:

        fldl    .LC0              // load number into fp register
        fstpl   4(%esp)           // put 64-bit double on the stack
        movl    $.LC1, (%esp)     // first argument (format string)
        call    printf            // call printf

        movl    $7, 4(%esp)       // put integer VA (7) onto stack
        movl    $.LC2, (%esp)     // first argument (format string)
        call    printf            // call printf

        movl    $9, 4(%esp)       // put integer VA (9) onto stack
        movl    $.LC3, (%esp)     // first argument (format string)
        call    printf            // call printf

The reason you see what you see is simple now. Let's for a moment switch to full 17-digit output:

  printf("%.17f\n", 9.334354);
  printf("%.17f\n", 7);

We get:

9.33435399999999937
9.33435058593751243

Now let's replace the integer by the "correct" binary component:

 printf("%.17f\n", 9.334354);
 printf("%.17f\n", 1921946325);

And voila:

9.33435399999999937
9.33435399999999937

What happens is that the double occupies 8 bytes on the stack, of value 0x4022AB30728E92D5. The integer only occupies 4 bytes, and as it happens, the least significant four bytes are overwritten, so the floating point value is still nearly the same. If you overwrite the four bytes with the same bytes that occur in the original float, then you get the exact same result.

I might add that it's pure luck that the most significant four bytes remain intact. In different circumstances, they might have been overwritten with something else. In short, "undefined behaviour".

like image 149
Kerrek SB Avatar answered Dec 06 '22 21:12

Kerrek SB