8

I was having an issue with some floating point math and I've found that if I do my math on one line, I get -0 passed to tan(), and if I do it across two lines, I get 0 passed to tan(). Have a look:

float theta = PI / 2.f;
float p = (PI / 2.f) - theta;
float result = tan(p);

The above, p = -0, result = -4.37...

float theta = PI / 2.f;
float p = PI / 2.f;
p -= theta;
float result = tan(p);

The above, p = 0, result = 0.

Can anyone explain the difference? I assume the -0 is causing that result from tan(), although I can't find anything on google that explains why. Why does the exact same calculation spread across different lines result in a different answer?

Thanks

taurous
  • 177
  • 7
  • are you asking why there is `-0` or why `tan(-0)` gives `-4.37`(which is wrong)? – apple apple Dec 28 '18 at 07:34
  • Are you asking what rule permits this to happen on any platform? Or are you asking why it happens to occur on your particular platform? If the latter, what platform? – David Schwartz Dec 28 '18 at 07:37
  • Possible duplicate of [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – apple apple Dec 28 '18 at 07:39
  • and I cannot [reproduce](https://wandbox.org/permlink/KI1lLQKlg2uZOshm) the `tan` behavior. – apple apple Dec 28 '18 at 07:41
  • It seems to have been some sort of conversion issue when converting a #define to float, as far as i can tell. Some of the confusion also came from the fact that printf was printing different values than what the locals view in MSVC was telling me. `printf("%2.f", tan(p));` was printing as 0 in the console, but MSVC showed -4.27 as the result. Either way problem seems to have been solved. – taurous Dec 28 '18 at 07:50
  • I was about to post a sample program that reproduced the problem ... and the solution. `#define PI 3.1412` is a double literal; it reproduces the problem. Performing the arithmetic in double (instead of float) resolves the problem. As, I'm sure, would casting PI to (float), among other solutions. – paulsm4 Dec 28 '18 at 07:54

3 Answers3

6

It is probably because of the type of PI.

If you use double it will change to float and then the outcome will be as you just represent.

But if PI is float both of this test scenarios are equal.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
Naor Tedgi
  • 5,204
  • 3
  • 21
  • 48
  • Thanks, that seems to be the culprit. The pi I'm using is a #define. If I using a const float for PI, the odd behavior disappears. – taurous Dec 28 '18 at 07:46
  • 1
    The lesson is thus: never use `#define` and instead use `const` variables : ) – Tas Dec 28 '18 at 07:47
  • True, I'm using Allegro 5 and saw they had a PI defined so I used it out of convenience. Guess it makes sense since its a C library. Making my own const! – taurous Dec 28 '18 at 07:52
  • “If you use `double` it will change to float`” does not make sense. – Eric Postpischil Dec 28 '18 at 13:29
  • Another issue is that C++ allows implementations to use extended precision while evaluating expressions. When an assignment or cast is performed, this extended precision must be discarded. Thus `float p = (PI / 2.f) - theta;` may be computed with excess precision, even if `PI` were `float`, but `p = PI / 2.f; p -= theta;` must produce `PI / 2.f` as a float and then subtract `theta`; it cannot use excess precision. – Eric Postpischil Dec 28 '18 at 13:31
5

What @Naor says is probably correct. but I'd like to add something.

You probably not getting -4.37xx but -4.37xxxe-xx which is a pretty small negative number.

Since you can always get errors in floating point math. I'd say there is no need to change your code. Both snips are correct.

apple apple
  • 10,292
  • 2
  • 16
  • 36
  • Ahhh, thank you that also teaches me something. You are correct in that it is a very small number. I did not realized (nor did I really look all that hard at it..) that it was showing scientific notation. Good to know. – taurous Dec 28 '18 at 07:54
0

So this is what, in my opinion, is happening:

In both examples PI is a define, probably defined like this:

#define 3.14 //and some more digits

In C++, number like this is treated as double.

After preprocessing, this expression:

PI / 2.0f

will be treated as double-typed prvalue. This means that this line hides one more operation:

float theta = PI / 2.f;

which is a double-to-float conversion, which definitely looses some precision in this case.

In first example this also happens here:

float p = (PI / 2.f) - theta;

but only after evaluating whole expression. Note that during this evaluation (PI / 2.f) will be still double, but theta will be a float-to-double converted value, which explains the slight difference in result from 0.0.

In your last example you first convert (PI / 2.f) to float:

float p = PI / 2.f;

to subtract float-typed theta from it in next line. Which must result to 0.0, which probably compiler optimized out anyway ; ).

Michał Łoś
  • 633
  • 4
  • 15