tags:

views:

717

answers:

4
+1  Q: 

Long Double in C

I've been reading the C Primer Plus book and got to this example

#include <stdio.h>
int main(void)
{
    float aboat = 32000.0;
    double abet = 2.14e9;
    long double dip = 5.32e-5;

    printf("%f can be written %e\n", aboat, aboat);
    printf("%f can be written %e\n", abet, abet);
    printf("%f can be written %e\n", dip, dip);

    return 0;
}

After I ran this on my macbook I was quite shocked at the output:

32000.000000 can be written 3.200000e+04
2140000000.000000 can be written 2.140000e+09
2140000000.000000 can be written 2.140000e+09

So I looked round and found out that the correct format to display long double is to use %Lf. However I still can't understand why I got the double abet value instead of what I got when I ran it on Cygwin, Ubuntu and iDeneb which is roughly

-1950228512509697486020297654959439872418023994430148306244153100897726713609
013030397828640261329800797420159101801613476402327600937901161313172717568.0
00000 can be written 2.725000e+02

Any ideas?

+4  A: 

Try looking at the varargs calling convention on OSX, that might explain it.

I'm guessing the compiler passes the first long double parameter on the stack (or in an FPU register), and the first double parameter in CPU registers (or on the stack). Either way, they're passed in different places. So when the third call is made, the value from the second call is still lying around (and the callee picks it up). But that is just a guess.

Steve Jessop
+1 Psychic debugging :)
Andomar
+3  A: 

Maybe the 64-bit ABI is different in such a way that printf looks for %f arguments in a completely different place than %LF arguments.

Try looking at the assembly output (gcc -S) to see if this is true.

Jason Orendorff
I will have a look at the assembly and see if it's the case though it might take some time because I'm not really confident in assembly especially with the x86-64 one
reubensammut
+2  A: 

The C Standard Library's printf() function is an example of a variadic function, that is one which can take different numbers of arguments. The way in which the C language implements this, the called function must know what type of arguments were passed in which order so that it can interpret them correctly. This is why you pass a format string, so that printf() can make correctly sense of the data it has to print.

If a variadic function incorrectly interprets the arguments passed to it, the C standard specifies that the behaviour is undefined, that is anything can happen (C89 standard para 4.8.1.2). In your case, where you are passing in non-matching formats and values to printf(), that is what is happening. However, if you have a decent compiler and your warning levels turned up to something sensible, you should be warned about this at compile-time. For example, on Cygwin, I get:

$ make go
cc -g -W -Wall -Wwrite-strings -ansi -pedantic    go.c   -o go
go.c: In function `main':
go.c:10: warning: double format, long double arg (arg 2)
go.c:10: warning: double format, long double arg (arg 3)
go.c:10: warning: double format, long double arg (arg 2)
go.c:10: warning: double format, long double arg (arg 3)
$

As to why you get specifically what you are seeing, this will depend on the particular implementation. In practice, what is likely to be happening is that your particular implementation of printf() is interpreting the first half of your long double as a double and printing the value which corresponds to that particular bit pattern. However, as the standard states, it could do whatever it likes.

Tim
Also note that with variadic functions, `float` parameters are always promoted and passed as `double`. Integral parameters `char` and `short` are promoted to `int` (or `unsigned int`).
tomlogic
A: 

I'm reading C Primer Plus, like you I noticed the same thing. See how I changed the format specifiers for the third printf statement.

#include <stdio.h>
#include <inttypes.h>

int main(void){

    float aboat = 320000.0;
    double abet = 2.214e9;
    long double dip = 5.32e-5;

    printf("%f can be written %e\n", aboat, aboat);
    printf("%f can be written %e\n", abet, abet);
    printf("%Lf can be written %Le\n", dip, dip);

    return 0;
}

Results after changing the format specifiers

320000.000000 can be written 3.200000e+05
2214000000.000000 can be written 2.214000e+09
0.000053 can be written 5.320000e-05
RPitre