Search code examples
c++floating-pointtrigonometrycmath

sin and cos give unexpected results for well-known angles


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
  • cos of 0.99

But, I get:

  • sin of 3.5897934739308216e-009
  • cos of -1.00000

My code:

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

DegreesToRadians():

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

Solution

  • 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
    ...