6

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?

reubensammut
  • 182
  • 1
  • 1
  • 12
  • Are you sure you had that code exactly? I just ran it on my MacBook - MacOS X 10.5.8 (GCC 4.0.1) and got your Cygwin result. – Jonathan Leffler Nov 22 '09 at 15:47
  • Yes I just rechecked it. Btw I'm running MacOS X 10.6.2 (GCC 4.2.1) – reubensammut Nov 22 '09 at 15:50
  • Actually I do get the expected nonsensical answer on Mac. Puzzling. Running `otool -L` on my executable tells me that it's running against /usr/lib/libSystem.B.dylib version 111.1.4; that's the library that provides `printf`. If you have the same version of that library, you should get the same answer. – Jason Orendorff Nov 22 '09 at 15:53
  • Jason Orendorrf suggests 64-bit ABI; compiling the code with 'gcc -m64' I can reproduce the result. My immediate reaction is 'bug in compiler/library combination', but that is probably wrong. It is interpreting the correct format string - I converted the text to upper case and the erroneous ('duplicated') answer appeared in upper-case. It is likely due to the calling conventions. – Jonathan Leffler Nov 22 '09 at 17:25
  • The output from 'gcc -S' has only 10 lines in common between the 32-bit and 64-bit assembler. You get different results if you print the long double first. Basically, by giving '`printf()`' the incorrect format specifier for the arguments (or the wrong arguments for the format specifier), you invoke 'undefined' behaviour; what you are seeing is the result of that. – Jonathan Leffler Nov 22 '09 at 17:38

5 Answers5

8

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
  • 273,490
  • 39
  • 460
  • 699
4

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
  • 9,171
  • 33
  • 51
  • 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 Jun 07 '10 at 01:30
  • @tomlogic: K&R had a good reason for coercing all floating-point values to the same type in variadic functions; ANSI C should have followed suit by allowing prototypes to designate a floating-point type to which all floating-point values would be converted. Many compilers for machines which performed computations using 80-bit floating-point worked around the lack of such a feature by making `long double` 64 bits and not providing an 80-bit type, thus leading to a degradation of floating-point maths which continues to this day. – supercat Jun 05 '15 at 17:44
4

Use specifier as %LF for the long double instead of %lf or %f. %LF always has a different meaning than %lf.

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

    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;
}

Output:

The output; transcript below

32000.000000 can be written 3.200000e+04
2140000000.000000 can be written 2.140000e+09
0.000053 can be written 5.320000E-05
wizzwizz4
  • 6,140
  • 2
  • 26
  • 62
  • This is correct, but "f" vs "F" only affects case. The important difference between "%lf" and "%LF" is between "l" (ell) and "L". "L" specifies "long double", "l" (ell) specifies "long integer". The "l" (ell) in "%lf" is ignored. "F" and "f" are identical except that "F" prints "INF" and "NAN" in uppercase, and "f" prints "inf" and "nan" in lowercase.. – Charles Nicholson Jul 05 '19 at 21:50
3

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
  • 42,793
  • 6
  • 62
  • 96
  • 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 Nov 22 '09 at 18:28
1

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
  • 2,071
  • 1
  • 13
  • 5