Why do sin(45) and cos(45) give different results? [duplicate]
Asked Answered
P

2

2

This is something I wasn't expecting. I know these numbers are not 100% exact, but I wasn't expecting complementary angles giving different results of sin and cos:

This following function returns 0.70710678118654746000000...

sin(45 * PI / 180.0);

while this following function returns 0.70710678118654757000000...

cos(45 * PI / 180.0);

so, it's:

0.707106781186547**46**000000... vs
0.707106781186547**57**000000...

and not only those... sin(1 * PI / 180.0) also returns a slightly different number than cos(89 * PI / 180.0) although they should be the same.

Moreover it's not only a sin vs cos problem, it's also a sin vs sin problem: sin(1 * PI / 180.0) returns a different value than sin(179 * PI / 180.0), again they should be the same.

I tried to use radians and not degrees, and there is exactly the same difference, I tried to use a small PI value, a huge PI value (around 100 decimals and more) and they're still different, I tried to use cmath instead of math.h, I tried to use M_PI instead of a PI defined by myself.

The difference is always the same, around the 16th decimal. Don't get me wrong, I know I will never get a 100% precise value of these numbers, but AT LEAST I was expecting to get the same "imprecise" value of sin and cos of complementary angles. What the hell is wrong with all this?


I need them to be the same because the program I'm working on (a gravity simulator I was asked to do) uses objects that have double (I also tried float) variables which are basically angles (degrees or radians, I've tried both). Those are the directions the objects use to move, also I need the angles to calculate the interactions between the objects.

The angles change in every iteration of the program, and in every iteration, the angles change basing on the calculations on the previous iteration's angles, so if there is any minimally wrong angle value at any point, that error gets amplified more and more in every iteration.

The program runs thousands and even millions of iterations, so the value's error gets absurdly huge! To put it clear, planets eventually get out of their balance and everything becomes a disaster, I'm really mad :(

P.s. I'm on Windows 7, 32 bits.

Purebred answered 20/7, 2015 at 4:49 Comment(3)
There's only about 16 decimal digits of precision in a double.Forcefeed
Well, I know I won't ever get a perfect value for sin(45), such value doesn't exist, the perfect and round PI value doesn't exist either, but I was at least expecting that complementary angles go wrong in the same way :/Purebred
You might want to look for (or implement) a symbolic computation library. You will, of course, be sacrificing performance for precision.Handlebar
G
3

I know I will never get a 100% precise value of these numbers, but AT LEAST I was expecting to get the same "unprecise" value of sin and cos of complementary angles.

Why? The are calculated differently, so different floating point errors will occur (and accumulate). What you see is not an error; FP arithmetic isn't predictable by math laws.

Btw., providing eg. 30 or 100 digits of PI won't be anything different
if your type can't hold 30 digits in the first place.

Gusti answered 20/7, 2015 at 4:52 Comment(9)
I know the thing about PI, it was a desperate attempt to make it work properly. Well, it's clearly a huge mistake then, regardless of how they are calculated, they are supposed to return the same value :/ Thanks for answering!!Purebred
@Purebred If you call it a mistake, how about proposing a solution? Right, it´s impossible to fulfill such expectations, because there are infinite special cases like that. Eg sin(60)==sqrt(3)/2 is another one. ... Without having an inifnite amount of memory, there will be errors, and nothing can prevent that.Gusti
Providing even 30 digits is total overkill for most purposes, 355/113 is, from memory, good enough to locate a car on the surface of the planet. 3.141592653589 (what I can remember off the top of my head) can probably (though I haven't checked) locate an pea in the solar system :-)Kohinoor
@Gusti Well I don't know a solution, I'm clueless about this problem and how to fix it :/ The only thing I can think about is to make a function that equals the values of all these numbers that should be the same, like: when the program needs to calculate "cos(270°)" or "cos(90°)" or "sin (180°)" , instead of that, make this other calculation: "sin(0°)", and they will all return the same value (because they will all make the same actual calculation (sin 0°)... maybe that's how it should work the sin and cos functions but well I'm not sure, I'm clearly far from being an expert.Purebred
@Purebred What do you want to achieve in the first place? To compare floating point numbers, a "safety margin" is often a good idea, ie. you´re treating them as equal if the difference between them is less than 0.00001 or something like that.Gusti
@Kohinoor Yes, actually the margin of error is too low, but millions of iterations dragging a calculation mistake and amplifying it, make "disasters". There's a special situation when two massive bodies are orbiting one each other in a perfect round orbit, and there is one small planet in the perfect middle of the other two massive planet orbiting themselves. The small planet in the middle should stay still forever, because the gravity of the other planets are all the time canceling each other (same mass, same distance), the planet gets kicked out of its stillnessPurebred
@Gusti The perfect scenario would be have perfect and devine accurate values :P, but since such thing is not possible, I guess I would be fine if I can have the same result when I need a sin(45°) than the result that I would have when I need a cos(45°). I'll try to equal all the results, and see if that fixes the problem. Thanks again!Purebred
@Gusti Treating as equals if the difference is less than a chosen margin, is actually a great idea! Thanks! I'll also try that, and see what results I get :DPurebred
Do not compare floating-point values with ==. Always compare with an epsilon except in very specific cases https://mcmap.net/q/63760/-how-do-you-compare-float-and-double-while-accounting-for-precision-loss/995714 floating-point-gui.de/errors/comparison https://mcmap.net/q/383432/-how-to-correctly-and-standardly-compare-floats/995714Orcinol
O
3

regardless of how they are calculated, they are supposed to return the same value

Your expectations are incorrect. In IEEE-754 only basic operators (+-*/) and sqrt are required to be correctly rounded. Transcendental functions like sin, cos, exp... are not required because it's very complex

There is no standard that requires faithful rounding of transcendental functions. IEEE-754 (2008) recommends, but does not require, that these functions be correctly rounded.


Now if you look at your values

                                      ↓
0.70710678118654746 = 0x1.6a09e667f3bccp-1
0.70710678118654757 = 0x1.6a09e667f3bcdp-1
                                      ↑

So they are within 1ulp of each other and is precise enough in double precision

Don't get me wrong, I know I will never get a 100% precise value of these numbers, but AT LEAST I was expecting to get the same "unprecise" value of sin and cos of complementary angles

There's not only a single algorithm to calculate sin and cos. Each will be correct for some set of inputs but incorrect for some others. They also have different memory and time requirements so some can be very fast with higher error, some will require a lot more time and memory but they can achieve much higher accuracy.

Compiler implementations may use different algorithms for those functions so if you need consistent results, use a single carefully designed library across platforms. For example GCC uses MPFR for achieving correctly rounded results regardless of platforms

The GCC middle-end has been integrated with the MPFR library. This allows GCC to evaluate and replace at compile-time calls to built-in math functions having constant arguments with their mathematically equivalent results. In making use of MPFR, GCC can generate correct results regardless of the math library implementation or floating point precision of the host platform. This also allows GCC to generate identical results regardless of whether one compiles in native or cross-compile configurations to a particular target

https://gcc.gnu.org/gcc-4.3/changes.html#mpfropts

How does C compute sin() and other math functions?

Orcinol answered 20/7, 2015 at 5:25 Comment(2)
Thanks! It gives me a better idea of why my program is failing, when I have cos and sin values that are supposed to be the same, I'll try to force them to actually be the same, if that's not working, I'll try with a different library like you say! :)Purebred
Just a small addendum: Even if you have an 'exact' implementation of sin and cos (i.e., one which is never wrong by more than 0.5ulp) you would not get the same values for sine and cosine here, since the input argument is not exactly PI/4 (depending on how this input value is rounded, either sine or cosine will be bigger). You can actually observe a similar issue when calculating sin(M_PI) (which is not 0.0)Hyposthenia

© 2022 - 2024 — McMap. All rights reserved.