Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to get cos(a) from sin(a)

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

like image 776
lvella Avatar asked Nov 23 '11 18:11

lvella


2 Answers

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]
like image 162
Guy Sirton Avatar answered Sep 29 '22 03:09

Guy Sirton


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

like image 43
Alexandre C. Avatar answered Sep 29 '22 02:09

Alexandre C.