Sin and Cos give unexpected results for well-known angles

35,311

Solution 1

C/C++ provides sin(a), cos(a), tan(a), etc. functions that require a parameter with radian units rather than degrees. double DegreesToRadians(d) performs a conversion that is close but an approximate as the conversion results are rounded. Also machine M_PI is close, but not the same value as the the mathematical irrational π.

OP's code with 180 passed to DegreesToRadians(d) and then to sin()/cos() gives results that differ than expected due to rounding, finite precision of double() and possible a weak value for PI.

An improvement is to perform argument reduction in degrees before calling the trig function. The below reduces the angle first to a -45° to 45° range and then calls sin(). This will insure that large values of N in sind(90.0*N) --> -1.0, 0.0, 1.0. . Note: sind(360.0*N +/- 30.0) may not exactly equal +/-0.5. Some additional considerations needed.

#include <math.h>
#include <stdio.h>

static double d2r(double d) {
  return (d / 180.0) * ((double) M_PI);
}

double sind(double x) {
  if (!isfinite(x)) {
    return sin(x);
  }
  if (x < 0.0) {
    return -sind(-x);
  }
  int quo;
  double x90 = remquo(fabs(x), 90.0, &quo);
  switch (quo % 4) {
    case 0:
      // Use * 1.0 to avoid -0.0
      return sin(d2r(x90)* 1.0);
    case 1:
      return cos(d2r(x90));
    case 2:
      return sin(d2r(-x90) * 1.0);
    case 3:
      return -cos(d2r(x90));
  }
  return 0.0;
}

int main(void) {
  int i;
  for (i = -360; i <= 360; i += 15) {
    printf("sin()  of %.1f degrees is  % .*e\n", 1.0 * i, DBL_DECIMAL_DIG - 1,
        sin(d2r(i)));
    printf("sind() of %.1f degrees is  % .*e\n", 1.0 * i, DBL_DECIMAL_DIG - 1,
        sind(i));
  }
  return 0;
}

Output

sin()  of -360.0 degrees is   2.4492935982947064e-16
sind() of -360.0 degrees is  -0.0000000000000000e+00  // Exact

sin()  of -345.0 degrees is   2.5881904510252068e-01  // 76-68 = 8 away
//                            2.5881904510252076e-01
sind() of -345.0 degrees is   2.5881904510252074e-01  // 76-74 = 2 away

sin()  of -330.0 degrees is   5.0000000000000044e-01  // 44 away
//  0.5                       5.0000000000000000e-01
sind() of -330.0 degrees is   4.9999999999999994e-01  //  6 away

sin()  of -315.0 degrees is   7.0710678118654768e-01  // 68-52 = 16 away
// square root 0.5 -->        7.0710678118654752e-01
sind() of -315.0 degrees is   7.0710678118654746e-01  // 52-46 = 6 away

sin()  of -300.0 degrees is   8.6602540378443860e-01
sind() of -300.0 degrees is   8.6602540378443871e-01
sin()  of -285.0 degrees is   9.6592582628906842e-01
sind() of -285.0 degrees is   9.6592582628906831e-01
sin()  of -270.0 degrees is   1.0000000000000000e+00  // Exact
sind() of -270.0 degrees is   1.0000000000000000e+00  // Exact
...

Solution 2

First of all, a cosine of 180 degrees should be equal to -1, so the result you got is right.

Secondly, you sometimes can't get exact values when using sin/cos/tan etc functions as you always get results that are the closest to the correct ones. In your case, the value you got from sin is the closest to zero.

The value of sin(PI) that you got differs from zero only in the 9th (!) digit after the floating point. 3.5897934739308216e-009 is almost equal to 0.000000004 and that's almost equal to zero.

Solution 3

I have the same problem as the OP when converting app to 64-bit.
My solution is to use the new math.h functions __cospi() and __sinpi().
Performance is similar (even 1% faster) than cos() and sin().

//    cos(M_PI * -90.0 / 180.0)   returns 0.00000000000000006123233995736766
//__cospi(       -90.0 / 180.0)   returns 0.0, as it should
// #define degree2rad 3.14159265359/180
// #define degree2rad M_PI/ 180.0
// double rot = -degree2rad * ang;
// double sn = sin(rot);
// double cs = cos(rot);

double rot = -ang / 180.0;
double sn = __sinpi(rot);
double cs = __cospi(rot);

From math.h:

/*  __sinpi(x) returns the sine of pi times x; __cospi(x) and __tanpi(x) return
the cosine and tangent, respectively.  These functions can produce a more
accurate answer than expressions of the form sin(M_PI * x) because they
avoid any loss of precision that results from rounding the result of the
multiplication M_PI * x.  They may also be significantly more efficient in
some cases because the argument reduction for these functions is easier
to compute.  Consult the man pages for edge case details.                 */
Share:
35,311
Gemma Morriss
Author by

Gemma Morriss

I was a C/C++ developer for over 12 years, having seen the errors of my way, I moved into a QA role for nearly 10 years at a very large American computer and printer company. These days I a senior Automated QA Engineer/SDET in the financial sector. I have an interest in the following languages: Python, C#, C++, HTML, and PHP. When not coding, I play the saxophone and try not to cut my fingers off woodworking :)

Updated on July 05, 2022

Comments

  • Gemma Morriss
    Gemma Morriss almost 2 years

    I am sure this is a really stupid question, but when I pass an angle of 180 degrees into c/c++'s cos() and sin() functions I appear to receive an incorrect value. I know that it should be: sin of 0.0547 and cos of 0.99 but I get sin of 3.5897934739308216e-009 and cos of -1.00000

    My code is:

    double radians = DegreesToRadians( angle );
    double cosValue = cos( radians );
    double sinValue = sin( radians );
    

    DegreesToRadians() is:

    double DegreesToRadians( double degrees )
    { 
        return degrees * PI / 180; 
    } 
    

    Thank you :)