11

I was implementing an algorithm to calculate natural logs in C.

double taylor_ln(int z) {
    double sum = 0.0;
    double tmp = 1.0;

    int i = 1;
    while(tmp != 0.0) {
        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;
    }

    return sum * 2;
}

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...
Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
mbreedlove
  • 336
  • 2
  • 5
  • 17
  • Wow, four people with the same answer at the same time. – mgiuca Feb 01 '11 at 03:24
  • Take a look at: http://stackoverflow.com/questions/4664662/understanding-floating-point-problems/4664784 . Floating point numbers can be very tricky if you don't know them well. – Nylon Smile Feb 01 '11 at 03:25
  • @mgiuca: then it _must_ be right :-) – paxdiablo Feb 01 '11 at 03: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 01 '11 at 03:30
  • possible duplicate of [When comparing for equality is it okay to use `==`?](http://stackoverflow.com/questions/2242593/when-comparing-for-equality-is-it-okay-to-use) – Ben Voigt Feb 01 '11 at 03:40

5 Answers5

12

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)
Mark Elliot
  • 75,278
  • 22
  • 140
  • 160
  • 7
    And here's the obligatory link: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=86013D0FEFFA6CD1A626176C5D4EF9E2?doi=10.1.1.102.244&rep=rep1&type=pdf) – chrisaycock Feb 01 '11 at 03:23
  • Two things: abs is an integer operation; use `fabs`. And you want while *greater than* some threshold, not less than. – mgiuca Feb 01 '11 at 03: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 01 '11 at 03: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 01 '11 at 04: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 01 '11 at 04:15
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 :-)

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • You should never use floating point arithmetic for monetary functions or operations, that's just bad thinking. you should use integers with enough bitwidth and use the least significant bits for decimal places of the integer as cents or parts of cents (or whatever your currency uses for cents). – Erik Oct 09 '22 at 12:11
  • Erik, the same lack of understanding floating point (or significant digits in math) is responsible for problems such as listed in the question, *and* blanket statements like yours. If you understand the limitations and know how they'll affect your results, you'll be fine. For example, adding a hundred floats with an error on each add of 10^-8 will never give you an erroneous value at the 10^-2 level. Fixed point or scaled integers have the same issues so do not absolve you of the responsibility of understanding/mitigating the limitations. – paxdiablo Oct 09 '22 at 21:49
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.

mgiuca
  • 20,958
  • 7
  • 54
  • 70
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.)

sth
  • 222,467
  • 53
  • 283
  • 367
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.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252