Double equals 0 problem in C
Asked 8 years, 11 months ago
Active 8 years, 11 months ago
Viewed 17k times
9
1
I was implementing an algorithm to calculate natural logs in C.
code snippet widget
As shown by the print statement, tmp does equal 0.0 eventually, however, the loop continues. What could be causing this?
I am on Fedora 14 amd64 and compiling with:
clang -lm -o taylor_ln taylor_ln.c
Example:
$ ./taylor_ln 2
(1.0 / 1) * (pow(((2 - 1.0) / (2 + 1.0)), 1)) = 0.333333
(1.0 / 3) * (pow(((2 - 1.0) / (2 + 1.0)), 3)) = 0.012346
(1.0 / 5) * (pow(((2 - 1.0) / (2 + 1.0)), 5)) = 0.000823
(1.0 / 7) * (pow(((2 - 1.0) / (2 + 1.0)), 7)) = 0.000065
(1.0 / 9) * (pow(((2 - 1.0) / (2 + 1.0)), 9)) = 0.000006
(1.0 / 11) * (pow(((2 - 1.0) / (2 + 1.0)), 11)) = 0.000001
(1.0 / 13) * (pow(((2 - 1.0) / (2 + 1.0)), 13)) = 0.000000
(1.0 / 15) * (pow(((2 - 1.0) / (2 + 1.0)), 15)) = 0.000000
(1.0 / 17) * (pow(((2 - 1.0) / (2 + 1.0)), 17)) = 0.000000
(1.0 / 19) * (pow(((2 - 1.0) / (2 + 1.0)), 19)) = 0.000000
(1.0 / 21) * (pow(((2 - 1.0) / (2 + 1.0)), 21)) = 0.000000
and so on...
59.4k77 gold badges9393 silver badges157157 bronze badges
asked Feb 1 '11 at 3:19
30622 gold badges55 silver badges1616 bronze badges
-
Wow, four people with the same answer at the same time. – mgiuca Feb 1 '11 at 3:24
-
Take a look at: stackoverflow.com/questions/4664662/… . Floating point numbers can be very tricky if you don't know them well. – Nylon Smile Feb 1 '11 at 3:25
-
@mgiuca: then it must be right :-) – paxdiablo Feb 1 '11 at 3:27
-
If you use
%g
to print the floating point number instead of%f
, you'll see that it's not actually zero. – caf Feb 1 '11 at 3:30 -
possible duplicate of When comparing for equality is it okay to use
==
? – Ben Voigt Feb 1 '11 at 3:40
add a comment
5 Answers
10
The floating point comparison is exact, so 10^-10
is not the same as 0.0
.
Basically, you should be comparing against some tolerable difference, say 10^-7
based on the number of decimals you're writing out, that can be accomplished as:
while(fabs(tmp) > 10e-7)
answered Feb 1 '11 at 3:21
62.3k1515 gold badges128128 silver badges153153 bronze badges
-
6
And here's the obligatory link: What Every Computer Scientist Should Know About Floating-Point Arithmetic – chrisaycock Feb 1 '11 at 3:23
-
Two things: abs is an integer operation; use
fabs
. And you want while greater than some threshold, not less than. – mgiuca Feb 1 '11 at 3:23 -
@chrisaycock: You should have made that an answer... it's (or the simplified version) is the right answer to most of the floating-point related questions around here. – Ben Voigt Feb 1 '11 at 3:39
-
@Ben Questions of this variety are asked on SO everyday. I think anyone above a certain reputation level has posted that link at least once in his SO career. – chrisaycock Feb 1 '11 at 4:11
-
@chrisaycock: I certainly have. Really the only thing that surprises me about this question is that it hasn't already been closed as a duplicate, with five different questions proposed as the "original". – Ben Voigt Feb 1 '11 at 4:15
add a comment
2
Don't use exact equality operations when dealing with floating point numbers. Although your number may look like 0
, it likely to be something like 0.00000000000000000000001
.
You'll see this if you use %.50f
instead of %f
in your format strings. The latter uses a sensible default for decimal places (6 in your case) but the former explicitly states you want a lot.
For safety, use a delta to check if it's close enough, such as:
if (fabs (val) < 0.0001) {
// close enough.
}
Obviously, the delta depends entirely on your needs. If you're talking money, 10-5 may be plenty. If you're a physicist, you should probably choose a smaller value.
Of course, if you're a mathematician, no inaccuracy is small enough :-)
answered Feb 1 '11 at 3:21
689k192192 gold badges13571357 silver badges17421742 bronze badges
add a comment
0
Just because a number displays as "0.000000" does not mean it is equal to 0.0. The decimal display of numbers has less precision than a double can store.
It's possible that your algorithm is getting to a point where it is very close to 0, but the next step moves so little that it rounds to the same thing it was at before, and hence it never gets any closer to 0 (just goes into an infinite loop).
In general, you should not compare floating-point numbers with ==
and !=
. You should always check if they are within a certain small range (usually called epsilon). For example:
while(fabs(tmp) >= 0.0001)
Then it will stop when it gets reasonably close to 0.
answered Feb 1 '11 at 3:22
18.4k66 gold badges4646 silver badges6666 bronze badges
add a comment
0
The print statement is displaying a rounded value, it is not printing the highest possible precision. So your loop has not really reached zero yet.
(And, as others have mentioned, due to rounding issues it might actually never reach it. Comparing the value against a small limit is therefore more robust than comparing for equality with 0.0.)
answered Feb 1 '11 at 3:30
179k4444 gold badges254254 silver badges341341 bronze badges
add a comment
0
Plenty of discussion of the cause, but here's an alternative solution:
double taylor_ln(int z)
{
double sum = 0.0;
double tmp, old_sum;
int i = 1;
do
{
old_sum = sum;
tmp = (1.0 / i) * (pow(((z - 1.0) / (z + 1.0)), i));
printf("(1.0 / %d) * (pow(((%d - 1.0) / (%d + 1.0)), %d)) = %f\n",
i, z, z, i, tmp);
sum += tmp;
i += 2;
} while (sum != old_sum);
return sum * 2;
}
This approach focuses on whether each decreasing value of tmp makes a tangible difference to sum. It's easier than working out some threshold from 0 at which tmp becomes insignificant, and probably terminates earlier without changing the result.
Note that when you sum a relatively big number with a relatively small one, the significant digits in the result limit the precision. By way of contrast, if you sum several small ones then add that to the big one, you may then have enough to bump the big one up a little. In your algorithm small tmp values weren't being summed with each other anyway, so there's no accumulation unless each actually affects sum - hence the approach above works without further compromising precision.