-5
{
    double A = 373737.0;
    double B;

    B = A*A*A + 0.37/A - A*A*A - 0.37/A;
    printf("The value of B is %f.\n", B);
}

In this C code, why is the value of B not zero, but instead -0.000001?

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • Hint: What is 0.1 + 0.2? It's not what you think. When thinking about `float`, think about it being *approximate*, with precision depending on using `float` vs. `double` as well as the magnitude of the value in question. – tadman Oct 06 '22 at 02:33
  • 2
    [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – paulsm4 Oct 06 '22 at 02:35
  • 1
    The suggested duplicate asks why 0.1+0.2 is not 0.3, and the correct answers (of which there are about 20) all discuss the problem of accurately representing decimal fractions in binary floating point. That is not the problem revealed by this question. You could replace 0.37/A with 1, and the same problem would occur. You could replace 373737.0 with 524288 (2**19), and the same problem would occur, even though all the numbers are exact The problem is a bad choice of operation order leading to extreme loss of accuracy. So it's nota dupe of that question, and I think it's worth keeping. – rici Oct 06 '22 at 06:08
  • 1
    @n.1.8e9-where's-my-sharem. Beware the knee-jerk close. I thought this was a duplicate of [that question](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) too, but rici's right, it's not. – Steve Summit Oct 06 '22 at 18:32
  • @rici I don't think there is substantial difference but whatever. – n. m. could be an AI Oct 07 '22 at 05:18
  • Follow-up question: https://stackoverflow.com/questions/73985098/clang-14-0-0-floating-point-optimizations. Compiling with clang gets interesting. – Lundin Oct 07 '22 at 09:32

3 Answers3

3

Several people have mentioned in comments that neither 0.37 nor 0.37/373737.0 can be represented accurately in binary floating point, which is true. But that's a bit of a red herring. You could replace 0.37/A with 1, which is clearly representable accurately in floating point, and you'd still get an odd result. Perhaps that odd result would be more revealing. It's certainly worth trying the experiment yourself.

Floating point arithmetic might turn out differently on different machines (although these days, that's uncommon) but under most circumstances, in a single program run on a single machine, 0.37/373737.0 will always be the same value. So you might expect that the two instances of 0.37/A will cancel out.

And they would have, had you written A*A*A - A*A*A + 0.37/A - 0.37/A. But that's not the order you did the operations, and the result is the round-off error that you experienced. The problem is that A*A*A is quite a large number, well outside the range of integers which can be represented accurately in a 64-bit double. And moreover, 0.37/A is a small number. Consequently, A*A*A + 0.37/A is exactly the same as A*A*A; the exact sum is quite a bit smaller than the next larger double-precision value.

Thus, A*A*A + 0.37/A - A*A*A is 0; the 0.37/A simply vanishes. And then A*A*A + 0.37/A - A*A*A - 0.37/A is -0.37/A, which is approximately 0.000001 (if you printed it with more precision, you'd see a more accurate output).

Standard C does not allow the compiler to regroup floating point computations, because it would make it impossible to predict the result. If you need a particular order of evaluation to avoid numeric instability, you have to do it yourself. (GCC does provide the -funsafe-math-optimizations flag, which can violate the C standard, produce unpredictable results, and sometimes accidentally produce the result you expected. Don't use it. It's unsafe.)

And part of learning how to write numerical programs is learning how to reorder your computations to avoid this type of error. One rule is, never subtract two large but similar numbers, since that will produce a wildly inaccurate result. (That's the rule which your expression breaks.)

Any good text book on numerical computation should go into much more detail about these issues. You could also start with the venerable essay, already mentioned in comments, What every computer scientist should know about floating point arithmetic.

rici
  • 234,347
  • 28
  • 237
  • 341
0

If your math gives irrational or repeating numbers after one or more opraters is done, the computer will cut it off or rounded, therefor, it's not 0. This is called round off error.

  • That's still not the reason. `1.0/3.3 + 90.7/7.1 - 1.0/3.3 - 90.7/7.1` is full of repeating numbers, but it computes to 0. Replace both instances of `1.0/3.3` with `sqrt(10.0)` (irrational); still computes 0. On the other hand `double A = 1048576; double B = A*A*A + 1.0 - A*A*A - 1.0;` assigns B as -1.0, not zero, even though A, A*A*A, and 1.0 are all powers of two, and thus have exact representations. – rici Oct 06 '22 at 04:27
-1

There are many reason may be you expression opration become out of range of double variable.You are printing double value using float format pacifier.Resolve this may it will give you desired output.

sahilatahar
  • 568
  • 2
  • 13
  • There are many reasons but this isn't one. Because of historical brain-farts that occurred when `stdio.h` was designed, `printf` has no unique conversion specifier only for `float`, so it's actually impossible to print a `float` in C. All `float` variables that are passed to variadic functions are promoted to `double`. `%f` and `%lf` are both used to print `double`. – Lundin Oct 07 '22 at 08:31