5

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 :)

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
Swatcat
  • 73
  • 6
  • 21
  • 57
  • 3
    `I know that it should be: sin of 0.0547 and cos of 0.99` More like "0 and -1". – deviantfan Jul 19 '15 at 14:18
  • 3
    The sine of PI is 0, and the cosine is -1. That sounds like about what you got. – Mike Dunlavey Jul 19 '15 at 14:18
  • 2
    " sin of 0.0547 and cos of 0.99" Huh? It should be exactly 0 and -1. Your code correctly derived that (up to rounding errors). – Baum mit Augen Jul 19 '15 at 14:19
  • When you pass the angle of 180 degress the conversion should give pi. Well, `sin (pi) = 0` and `cos (pi) = -1`. Your results of `3.6E-9` and `-1.0` are pretty good given that there will be a small round-off error. – rpsml Jul 19 '15 at 14:20
  • Where are you getting your "know what they should be" values from? Draw a circle, write a '0' to the rightmost point, then draw a line from the centre to the point, next rotate a second line from the centre to the edge slowly round the circle. The angle between this line and the horizontal line you first drew is the angle you're taking the sin and cos of, the distance of the point where it meets the circle above the horizontal is the sin and the distance measured horizontally from the centre to this point the cos. It's easy to see this should be 0 and -1. – Jack Aidley Jul 19 '15 at 14:28
  • 1
    sin(pi degrees) and cos(pi degrees) are 0.0548 and 0.998 respectively. sin(pi radians) and cos(pi radians) are 0 and -1. – user12205 Jul 19 '15 at 14:34
  • 2
    This is a great question. Why would anybody downvote this? There is a bug in the standard library, and it was 'fixed' by the addition of the new __sinpi() and __cospi() functions. – Keith Knauber Nov 24 '15 at 20:21
  • Same here, I observed that after every 180 degrees, cos(double)/cosl() and cos(float)/cosf() result in substantially different results. So alleviation would be to make sure that double argument versions are used by overload or directly - that is using cos(double) or cosl(double), note that when using cosl(float) results would be wrong too. Verified on Ubuntu 21.04 GCC 8 – arapEST Dec 17 '21 at 09:36

3 Answers3

16

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
...
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • @chux I assume "gives results that differ than expected" was intended to read "gives results that differ *more* than expected"? – njuffa Mar 03 '17 at 04:04
  • @njuffa Good idea, could have said it that way It was a bit unclear of how close OP wanted. This answer shows how by using range reduction first on degrees, then conversion to radians, we can even do better and return the expected exact value with 180 degrees. – chux - Reinstate Monica Mar 03 '17 at 04:31
  • @chux-ReinstateMonica , thank you very much for this code. Is the following one can be used for cos? double cosd(double x) { if (!isfinite(x)) { return cos(x); } //if (x < 0.0) { // return cosd(x); //} int quo; double x90 = remquo(fabs(x), 90.0, &quo); cout< – Fady Samann Mar 17 '22 at 00:20
  • Looks mostly OK.1) try negative values and `x` values very near `cosd(x) nearly 0.0` 2) Once fully tested, post your implementation on [Code Review](https://codereview.stackexchange.com) for a good review and let me know. Its been 7 years since this post - perhaps some news ideas. – chux - Reinstate Monica Mar 17 '22 at 00:34
  • Very interested in the "Note: `sind(360.0*N +/- 30.0)` may not exactly equal `+/-0.5`; some additional considerations needed." loose end; it would be great to have an implementation that's exact where possible, e.g. `sind(30) == 0.5`. – Don Hatch May 09 '22 at 04:06
  • @DonHatch I suspect `remquo(fabs(x), 30.0, &quo);` would be a good first step to 'sind(360.0*N +/- 30.0) to exactly equal +/-0.5". Then use more trig identities. – chux - Reinstate Monica May 09 '22 at 04:23
  • On a different topic, is the `fabs` necessary? Seems like we know x is nonnegative at this point. – Don Hatch May 09 '22 at 05:14
  • @DonHatch Perhaps. Only -0.0 slips though. It depends on what `sind(-0.0)` should return [0.0 or -0.0](https://softwareengineering.stackexchange.com/q/261613/94903). I see compelling reasons for one versus the other. IIRC, IEEE wants `sind(neg_arg) = -sind(|neg_arg|)` and then `if (x < 0.0)` --> `if (signbit(x))` and the `fabs(), * 1.0` not needed. – chux - Reinstate Monica May 09 '22 at 11:42
  • Thanks for explaining your motivation for the `fabs`. It looks to me like the `fabs(), * 1.0` parts could use some more love, perhaps starting with a code comment saying clearly what the contract is that you're intending to satisfy (I searched for, but didn't find, a reference to an IEEE document). I'd be happy to contribute thoughts on this if you are interested, probably in chat since it would likely need some back-and-forth. If not, no worries. – Don Hatch May 10 '22 at 13:37
  • 1
    This function is far cleverer and higher quality than it looked to me at first. The first non-obvious thing, to me, is that remquo doesn't return the same as fmod(x,90), in the range [0,90), which would lead to accuracy problems; rather, it returns a number in [-45,45]. The resulting accuracy of your function meets the standards of a standard math library; that is, error within 1 or 2 ulps, on all arguments, I believe. Shockingly, it even works in the case that quo overflows an int, thanks to remquo's obscure contract in that case. It's spooky how well remquo() meets your needs here. – Don Hatch May 10 '22 at 13:37
  • Incidentally, thank you for expressing `d2r(d)` as `d/180*pi`. There are many superficially-equivalent ways of arranging that expression, and my impression is this is the best one, particularly for the case when d/180 is exactly expressible. – Don Hatch May 10 '22 at 13:41
  • Am interest in improvements, but later. – chux - Reinstate Monica May 10 '22 at 13:42
  • @FadySamann I would recommend implementing `cosd(x)` as `sind(90-fabs(x))`, unless you see a solid reason for wanting to implement separate logic for it. – Don Hatch May 10 '22 at 13:43
  • @DonHatch Note that `90-fabs(x)` can impart an inexact result when `x` is large. – chux - Reinstate Monica May 10 '22 at 13:44
  • @DonHatch thanks for the reply. I just solved the issue by returning 0 for the sin when the angle is 180 or 360 and 0 for cos when the angle is 90 or 270, else i convert the angle into radian and feed it into the standard c++ sin() or cos() – Fady Samann May 10 '22 at 16:58
8

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.

ForceBru
  • 43,482
  • 10
  • 63
  • 98
5

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.                 */
Keith Knauber
  • 752
  • 6
  • 13
  • Although using `__sinpi(), __cospi()` is a good idea to reduce error with a _radian_ argument, `ang / 180.0` itself injects rounding error for many values. So good idea for reducing error when the quotient of `ang / 180.0` is exact, not so otherwise. – chux - Reinstate Monica Nov 27 '20 at 19:18