8

Having s as sine of some (unknown) angle "a", what is the fastest way to get the "cosine of a"?

I know two obivious ways:

c = cos(asin(s));

and

c = sqrt(1 - s*s);

But I don't know how the implementation of the functions cos(), asin() and sqrt() compares to one another in regarding speed. How faster is one over another? Are there significant difference between their implementations in modern processors, say, between x86-64 and ARM with VFP? In the end, what is the better solution?

Edit: Since there are already 3 unrelated answers now, let me clarify: I don't initally have the angle, all I have is the sine. So there is no need to tell me to rotate the angle 90 degrees so I'll have the same value from the other function...

lvella
  • 12,754
  • 11
  • 54
  • 106

5 Answers5

10

Here's one way:

sin(a)^2 + cos(a)^2 = 1 (pythagoras)

cos(a) = sqrt(1 - sin(a)^2))

You need to figure out the different quadrants (i.e. the sign of the cos() seperately). This is not possible if all you have is the sin() value (different angles can have the same sin() but cos() differs by sign).

As other people noted a lookup table may in practice be the fastest. Depends on what precision you require. This is almost certainly going to be faster than your cos(asin()) version and the square root can also be optimized in practice.

  • SSE and ARM-NEON have a square root instruction but no trig functions.
  • x87 has square root and sin/cos but no inverse sin/cos functions. Square root is faster than sin/cos.

Using Visual studio 2010 the performance of this method is about 6 times faster than the trig based version (with fast floating point option) on my Core i3 laptop (about 20ns per call). Let's look at the generated code:

Fast floating point option, using square root:

; 15   :     return sqrt(1.0 - s*s);

    movsd   xmm1, QWORD PTR __real@3ff0000000000000
    mulsd   xmm0, xmm0
    subsd   xmm1, xmm0
    sqrtsd  xmm0, xmm1

Using trig functions:

; 22   :     return cos(asin(s));
call    ___libm_sse2_asin
jmp ___libm_sse2_cos

When switching to precise floating point mode the generated trig code uses different functions (presumably the SSE optimized versions sacrifices accuracy):

fld QWORD PTR _angle_sin$[esp+esi+65600]
call    __CIasin
call    __CIcos
fstp    QWORD PTR _angle_cos$[esp+esi+65600]
Guy Sirton
  • 8,331
  • 2
  • 26
  • 36
3

The best way is to use eps * sqrt(1 - s * s), where eps is plus or minus one. It is the best way

  • In term of accuracy, since 1 - s * s stays close to one and therefore the error in computing the square root is minimal. Computing the cosine of the arc sine may lose you precision if you are in the 'near one' case (exercise: 1/ why ? hint: remember that sin is maximal at pi/2 and its derivative vanishes 2/ Try it.) EDIT: I spoke too fast, since sqrt(1 - s * s) has the same problems when s is close to one.
  • In term of speed: square rooting is easy (a few arithmetical operations for some newton-like method), but it is not clear what asin does (probably quite costly), cos is likely to be one order of magnitude slower than sqrt, and thus one square root is likely to be quickier than those two transcendental function calls. In doubt, profile (but I'm ready to bet some money).

Forget about look up tables until you have proven that sqrt(1 - s * s) is not fast enough for you (and even there, you can find ways to trade off some sqrt accuracy for speed).

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • What do you mean stays close to 1? 1-sin^2 will be between 1 and 0 amd so will the square root... – Guy Sirton Nov 23 '11 at 20:58
  • @Guy: Actually I spoke too fast: if s is close to 1, then 1 - s * s is close to zero and the square root gets imprecise (the derivative diverges at zero). So it seems that it suffers the same problem as the cos(asin) method in this respect. However, it is still likely faster. – Alexandre C. Nov 23 '11 at 21:02
  • I get this but I was talking about the first part of that paragraph: "since 1 - s * s stays close to one and therefore the error in computing the square root is minimal.". (1 - s*s) goes from 0 to 1 would be a better way of saying this IMO and I'm not sure about the accuracy when s goes close to 1 though I haven't thought about it in depth. – Guy Sirton Nov 23 '11 at 21:11
  • Yes (to the later edit)... I agree it will be faster hence my answer :-) – Guy Sirton Nov 23 '11 at 21:12
  • 1
    OK, maybe I should have tested it before asking, but since I have asked, and everybody told me that I should profile, well, I have profiled. Using sqrt is near 30% *slower* than using asin+cos (at least on my Linux x86-32 machine, maybe it is different in other architectures). I was in doubt because a long time ago I was using sqrt inside one graphic application, and it was suffering from severe performance issues. When I replaced it with trigonometry, the problems were gone. Today I had to choose between them again, and I was curious to know how things changed since then. – lvella Nov 23 '11 at 23:14
  • @lvella what you're saying does not make sense. Check your build options. What CPU is in your machine? Are you using double? sqrt() should be faster than cos when using the x86 floating point instructions and I don't think there's an asin instruction at all. I will try to test later and add to my answer. – Guy Sirton Nov 24 '11 at 02:37
-1

visualize the unit circle with polar coordinates. r=1, theta = (the angle ). then any point on the unit circle in X,Y ( cartesian ) coordinates is ( cos( theta ), sin ( theta )).

Timmah
  • 177
  • 1
  • 2
-2

Sin and Cos are simply the same curve offset by one-half radian (or 90 degrees), so:

sin(a) = cos(a + pi/2)
cos(a) = sin(pi/2 - a)

pi being 3.14159...

Marc B
  • 356,200
  • 43
  • 426
  • 500
  • 1
    But I don't have `a`, all I have is `s` that is the sine of some angle. – lvella Nov 23 '11 at 18:58
  • 1
    Ah, well, then your cos(asin(s)) is probably fastest. But do benchmarks anyways. Major math libraries usually have special conditions hard-coded so if all you're doing is 45/90/135 type angles, those could be MUCH faster than if you were doing arbitrary 1.37, 87.3, etc... type angles. – Marc B Nov 23 '11 at 19:01
-3

If your sin/cos functions take radians:

cos(x) === sin(x+pi/2)

If your sin/cos functions take degrees:

cos(x) === sin(x+90)
Edward Loper
  • 15,374
  • 7
  • 43
  • 52