0

Can someone explain why these two outputs are vastly different?

When I print out (float)direction*PI/180.0f and theta they both evaluate to 1.5708 as expected. Why does putting one in a float variable before putting it in the sin function make such a huge difference in output? Also note that test2 returns the correct answer (1), while test returns the wrong answer (0).

#include <iostream>
#include <cmath>

#define PI 3.14159265


int main()
{   
    int direction = 90;
    float theta = (float)direction*PI/180.0f;
    int test =  (int)sin(theta);
    int test2 = (int)sin((float)direction*PI/180.0f);
    
    std::cout << theta << " " << (float)direction*PI/180.0f << " " << test << " " << test2;
}
rico5678
  • 33
  • 3
  • Because `direction/180` in the initialization of `test2` is still performing integer division. You might want to try `180.0f` to perform division with `float` instead – alter_igel Dec 31 '20 at 03:26
  • Thanks for replying! I just edited to change to 180.0f and am still having the same issue. – rico5678 Dec 31 '20 at 03:32
  • 3
    What are you hoping to achieve casting the return value of `sin` to an int? This discards the fractional part, effectively rounding towards zero. Perhaps you want [`std::round`](https://en.cppreference.com/w/cpp/numeric/math/round) instead? – alter_igel Dec 31 '20 at 03:35
  • hoping to achieve a better understanding of c++ my friend. This problem came up when I was using the sin function in a larger equation which would eventually be rounded to an integer value. – rico5678 Dec 31 '20 at 03:38
  • 2
    Your problem is that you are not understanding that `sin()` is guaranteed to produce a result between `-1` and `1`, so converting it to `int` always produces `0` except in the case where `sin()` returns exactly `-1.0` (which is rare). – Peter Dec 31 '20 at 03:42
  • my goodness, thank you Peter I am a fool. I had it stuck in my head that (int) cast would round to the nearest integer, but it always floors it. Thank you!! – rico5678 Dec 31 '20 at 03:45
  • 5
    A lot of people are floored by that revelation. – user4581301 Dec 31 '20 at 03:47
  • `the correct answer (1)` No, the correct answer is not `1`. Mathematically it is `sin(3.14159265/2) =` [`0.999999999999999998389173...`](https://www.wolframalpha.com/input/?i=sin%2890+*+3.14159265+%2F+180%29&assumption=%22TrigRD%22+-%3E+%22R%22), because `#define PI 3.14159265` is a rather poor approximation of `π`. It then gets compounded by the limited precision of floating point calculations, but this shows that even the basic premise of the question is wrong. – dxiv Dec 31 '20 at 03:49
  • 1
    Closely related: [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – user4581301 Dec 31 '20 at 03:51
  • @dxiv The correct answer (for IEEE floating point) is `1`, since the closest `double`/`float` to that exact value is `1`. – HTNW Dec 31 '20 at 04:23
  • 2
    @HTNW That is true, but not so relevant to OP's question. It is true as a consequence of the lucky chance that `sin` is almost flat around `π/2`. But, in general, one should not feed approximations with 9 significant digits into a calculation and expect results with 17 exact significant digits. – dxiv Dec 31 '20 at 04:27

2 Answers2

3

(float)direction*PI/180.0f uses double arithmetic. PI is a double literal, since it has no f suffix. The other two operands ((float)direction and 180.0f) are floats, yes, but they just get promoted to double (and the conversion is exact in IEEE floating point). Note that the (float) is only applying to direction due to operator precedence. For test2, you pass the resulting double directly to sin, and sin returns exactly 1. For test, you cast the double down to float in the assignment to theta first, and then you cast it back up to double for the call to sin (note that you are calling C's double sin(double), not C++'s float std::sin(float)). Through the casts, theta loses a little of its value to rounding. sin then gives a value correspondingly a little less than 1, which then rounds all the way to 0 when cast to int. If you had called std::sin, then you would have gotten 1, since std::sin would round that slightly less than 1 double into a float, which would give 1 (unlike truncating to int).

Printing floating point values to std::cout like this is not useful for debugging floating point, since the values get rounded. I like using std::hexfloat, which shows the real binary values (converted to hexadecimal), not decimal lies. I've also gotten rid of PI and turned the C-style casts into functional-style casts to show what's going on more clearly. I've turned test and test2 into doubles (sin's return type) so we can take a real look at them.

int main() {
    int direction = 90;
    float theta = float(direction)*3.14159265/180.0f;
    double test1 = sin(theta);
    double test2 = sin(float(direction)*3.14159265/180.0f);
    std::cout << std::hexfloat;
    std::cout << theta << " " << float(direction)*3.14159265/180.0f << "\n";
    std::cout << test1 << " " << test2 << "\n";
}

Godbolt

It gives

0x1.921fb6p+0 0x1.921fb53c8d4fp+0
0x1.ffffffffffff7p-1 0x1p+0

Which neatly shows us that the value we're using in test2 has more digits than theta, since it's a double but float theta. You also see that test1 is almost 1, but not quite.

HTNW
  • 27,182
  • 1
  • 32
  • 60
2

Rounding errors. Your test variable is < 1.0, your test2 variable is >= 1.0. I C and C++, 0.9999999999999 gets rounded to 0.

#include <iostream>
#include <cmath>

#define PI 3.14159265


int main()
{   
    int direction = 90;
    float theta = (float)direction*PI/180.0f;
    auto test =  sin(theta);
    auto test2 = sin((float)direction*PI/180.0f);
    
    std::cout << theta << " " << (float)direction*PI/180.0f << " " << (int) test << " " << (int) test2 << std::endl;
    std::cout <<std::boolalpha << "test >= 1.0 = " << (test >= 1.0) << std::endl;
    std::cout << "test2 >= 1.0 = " << (test2 >= 1.0) << std::endl;
    std::cout << "test == test2 = " << (test == test2) << std::endl;
    std::cout << "test < test2 = " << (test < test2) << std::endl;
}

Output:

1.5708 1.5708 0 1
test >= 1.0 = false
test2 >= 1.0 = true
test == test2 = false
test < test2 = true
Mirko
  • 1,043
  • 6
  • 12