rsarson
rsarson

Reputation: 95

nan returned using long doubles with math.h functions

I'm trying to learn about floating point precision in C. I came across this problem while experimenting with promotion and demotion in math.h functions.

In the following program, I get a -nan for the first equation using long doubles and a result for the second. I do not understand why. If pow(x,y) with long doubles returns a -nan, why does (pow(x,y) - z) with long doubles return the correct result?

#include <stdio.h>
#include <math.h>

/* equation from xkcd: e to the pi minus pi is 19.999099979 */
int main(void)
{
    printf("f  %#.9f\n",
           pow(2.71828182846F, 3.14159265359F));
    printf("f  %#.9f\n",
           pow(2.71828182846F, 3.14159265359F) - 3.14159265359F);
    printf("d  %#.9lf\n",
           pow(2.71828182846, 3.14159265359));
    printf("d  %#.9lf\n",
           pow(2.71828182846, 3.14159265359) - 3.14159265359);
    printf("ld %#.9Lf\n",
           pow(2.71828182846L, 3.14159265359L));
    printf("ld %#.9Lf\n",
           pow(2.71828182846L, 3.14159265359L) - 3.14159265359L);
    return 0;
}

output:
f  23.140692448
f  19.999099707
d  23.140692633
d  19.999099979
ld -nan
ld 19.999099979

Upvotes: 0

Views: 363

Answers (2)

paxdiablo
paxdiablo

Reputation: 881463

It is undefined behaviour if your format specifiers do not match the data types of the arguments you pass to printf (a).

If you were using a decent compiler, it would warn you of this:

myprog.c:15:12: warning: format '%Lf' expects argument of type
    'long double', but argument 2 has type 'double' [-Wformat=]
     printf("ld %#.9Lf\n",
            ^

The solution is to ensure the argument is of the correct type to match the format either by casting:

printf("ld %#.9Lf\n", (long double)pow(2.71828182846L, 3.14159265359L));

or, better yet:

printf("ld %#.9Lf\n", powl(2.71828182846L, 3.14159265359L));

The latter would be preferred since the former may (b) involve loss of precision.


(a) See C11 7.21.6 Formatted input/output functions /9 for the "undefined behaviour" specification:

If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

and /7 of that same section for the type requirement of %Lf:

L Specifies that a following a, A, e, E, f, F, g, or G conversion specifier applies to a long double argument.


(b) May, because the standard states that each "lesser" floating point type is a subset of the next "greater" type. Since subset (as opposed to proper subset) means "lesser or equal to the superset", it's possible for double and long double to be exactly the same underlying type.

However, it's safer to assume that it is a proper subset since you don't really lose anything in that assumption.

Upvotes: 3

Louis Langholtz
Louis Langholtz

Reputation: 3123

It's undefined behavior as paxdiablo pointed out. Here's what my compiler shows as an example of the warning he mentions:

gcc main.c -o main
.main.c:10:24: warning: format specifies type 'long double' but the argument has type 'double' [-Wformat]
    printf("ld %#.9Lf\n", pow(2.71828182846L, 3.14159265359L));
               ~~~~~~     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
               %#.9f
1 warning generated.

And here's an example where NaN doesn't show up:

$ ./main 
f  23.140692448
f  19.999099707
d  23.140692633
d  19.999099979
ld -0.000000000
ld 19.999099979

Upvotes: 0

Related Questions