Reputation: 95
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
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 followinga
,A
,e
,E
,f
,F
,g
, orG
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
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