
Sin and Cos: The Programmer's Pals - 1bytebeta
http://www.helixsoft.nl/articles/circle/sincos.htm
======
pcwalton
> For some reason I can't quite put my finger on, some experienced game
> programmers are reluctant to use atan2() and prefer the dot product.

It's because atan2, along with all the other inverse trigonometric functions,
is incredibly slow. On a GPU, sin and cos may be a couple of cycles, but
asin/acos/atan/atan2 may be upwards of 30 or 40.

Often times, angles are best avoided when possible for this reason. Normalized
vectors end up faster and simpler in many cases.

Relevant anecdote: I recently wrote some code to compute normals in 2D and
thought I would be clever by encoding the normal as an angle to save space. It
turned out that the atan2 call at the end was so slow that it outweighed the
cost of everything else I was doing several times over. Lesson learned…

~~~
60654
Yeah, there are multiple reasons. Perf is important: inverse trig functions
are generally much slower both on the CPU and on the GPU [1]

But also - most of the time gamedev happens in 3D space not in 2D space (even
if it gets rendered down to 2D). So it's more natural to do a dot product of
two 3d vectors, than try to mess around with trig functions that work on
scalar angles. Same with rotations: it's easier and better to use quaternions
in 3D space than try to rotate along each axis separately.

[1] GPU intrinsic estimated costs:
[http://www.fractalforums.com/programming/shader-function-
or-...](http://www.fractalforums.com/programming/shader-function-or-
instruction-cost-\(performance\)/msg84618/#msg84618)

~~~
vvanders
Quaternions have a bunch of properties that make them awesome:

* Compact storage compared to matrices

* No gimbal-lock

* Multiplication is cheap (mult + add, very similar to vector operations)

* Interpolation is cheap

You really only pay the sin/cos costs when translating out to a Mat3x3 at the
end.

~~~
Const-me
> You really only pay the sin/cos costs when translating out to a Mat3x3 at
> the end.

You don’t need sin/cos for that either.

Here’s a code that does that:

[https://github.com/Microsoft/DirectXMath/blob/master/Inc/Dir...](https://github.com/Microsoft/DirectXMath/blob/master/Inc/DirectXMathMatrix.inl#L1654-L1736)

~~~
vvanders
You're right, I was mixing that up with the initial conversion from axis+angle
to quaternion.

------
0xfaded
I'm half way through writing a blog post on how transcendental functions are
computed in glibc, and more importantly how to extend the methods to compute
things like

    
    
      sin(x) / x
      (1 - cos(x)) / x^2
    

directly without incurring the numerical problems arising from the division.

One thing to understand is that the stdlib math functions are accurate to
machine precision, i.e. the closest representable value to the actual answer.

For 32-bit floats, this is achievable using a 5 or 6 order polynomial
approximation. However, there was one surprise I was previously unaware of.

Obtaining the correct precision becomes difficult when the result is close to
zero, since any absolute error is divided by the magnitude of the result.
Silly example, if

    
    
      sin(0.000001) ~= 0.1
    

Then the absolute error is small (~0.1), but the result is still off by four
orders of magnitude.

Sometimes this is not a problem. When x is small, sin(x) can be computed by
simply returning x. This rule is valid until x^3/6 > precision ~= 1e-38, or x
= 4e-16. So exact precision is obtainable by simply returning x if x < 1e-16.

But now consider:

    
    
      x = pi/2; cos(x) = 0
    

The logical way to compute this is as

    
    
      -sin(x - pi/2)
    

But this creates another problem, since x - pi/2 ~= 0, a catastrophic
cancellation occurs. Now recall that glibc is required to be accurate to
machine precision, which goes down to 1e-38. But x is order 1 and only
represents 7 digits, down to 1e-6. So glibc is forced to scour lookup tables
to recover the missing digits of pi.

This makes computing cos(x > pi/4) about twice as expensive than cos(x <
pi/4), but there is a certain irony since it is very unlikely x is known down
to 7+ digits, so those CPU cycles are wasted. How many times have you seen x =
sqrt(a _a + b_ b + c*c); ?

On my 4 year old laptop:

    
    
      time ./a.out 0.5   4.12s
      time ./a.out 1.5   7.31s
    

On my raspberry pi:

    
    
      time ./a.out 0.5   1m15.829s
      time ./a.out 1.5   1m47.522s

~~~
vanderZwan
Do you have advice for which fast, good-enough approximations one can use
instead in the situations where the precision is not needed?

In many use-cases we would pre-multiply the value we throw into _sin_ and
_cos_ with two times Pi anyway, so couldn't one skip the circle constant
altogether and a write function like _sin_one(t)_ and _cos_one(t)_ , where
each call "implicitly multiplies" t by two times Pi? (perhaps, if constructed
well, the function could even be _more_ precise, since it avoids the rounding
errors, however small, of multiplying by the pi constant)

~~~
stochastic_monk
I don't have advice on the specific `sin(x) / x`/`cos(x) / x^2`, but the Sleef
vectorization library [0,1] supports operations with varying precision.
(0.506, 1.0, and 3.5 ULP)

If you're working in a case where you can afford slightly lower accuracy, the
3.5ULP method can be a very significant speedup, especially since you're
vectorizing at the same time. I got dozens of times in speedup when testing
just the 1.0 ULP bound when performing sin/cos on a large vector. (I don't
remember the precise number and should just re-run the tests.)

To generically dispatch this library, in case there's any interest, I wrapped
it in some template metaprogramming in [2], so that the widest vectorization
available is selected at compile-time when selecting a given operation.

[0]: [http://sleef.org/](http://sleef.org/)

[1]: [https://github.com/shibatch/sleef](https://github.com/shibatch/sleef)

[2]: [https://github.com/dnbaker/vec](https://github.com/dnbaker/vec)

~~~
vanderZwan
Thanks, forwarded your comment to a few colleagues who might have a use for
it!

------
pjc50
How old is this article? I ask because the examples use Allegro and DJGPP, and
reference the 1991 games Micro Machines and F-Zero. And also the giveaway
sentence "Floats are so slooooooooow! Why don't you use fixed-point numbers?"

I learned games programming as a teenager in that era, and I present my fixed-
point 3D code from 1996, which does sin and cos through the medium of
512-entry 32-bit integer lookup tables:

[https://github.com/pjc50/ancient-3d-for-
turboc](https://github.com/pjc50/ancient-3d-for-turboc)

~~~
vidarh
While I'm sure the article is old, Allegro is still around. The latest release
was February 25th:

[http://liballeg.org/](http://liballeg.org/)

~~~
skykooler
Allegro is also the library currently being used by Factorio, although the dev
team is currently porting it to a custom game engine to try and get more
performance.

------
donquichotte
Ah, the race-car example is bringing back memories!

In the early days of Macromedia Flash, I wanted to build a GTA I clone. I
didn't know about trigonometric functions yet, so I had a line of height 1,
width 0 that I rotated around one of its ends at a fixed angular velocity
while the user pressed the left and right arrow keys.

Then I read the height and width of the rotated object and added it to the x
and y coordinates of the car, respectively.

For some reason, trigonometry is treated pretty late in Swiss schools (often
only during the Matura, where students are 16-20y/o). I never understood this,
since understanding trig can be really useful in a lot of everyday situations.

~~~
_sdegutis
What everyday situations can trigonometry be useful in?

~~~
webnrrd2k
You rely om the results of trigonometry in the thousands of engineered object
you encounter every day. Almost anything having to do with physics will have a
fair amout of trig underlying it, and a lot of calculus relys on trig. So if
you want to understand a lot of what's going on, you'll need trig.

It's like asking "what everyday situations is addition useful in".

~~~
gdubs
I think that’s like saying one “uses” advanced physics every day because they
use a cell phone. The OP question was probably asking more about what
practical uses are there for normal people to actually use trig, the way you
use arithmetic to calculate a tip at a restaurant.

To that I’d say: finding the height of something only knowing the length of
the base and an angle. Or triangulating your position, surveying your property
to make a garden, etc.

------
twic
I was trying to do some calculus the other day, and ended up reading about the
history of logarithms. As you may know, logarithms were valuable in the pre-
mechanical era because they allowed people to reduce multiplication to
addition and a couple of table lookups, which is much faster to do - a huge
boon if you're doing navigational calculations on a rapidly-moving ship, or
calculating vast astronomical ephemerides.

What i hadn't realised is that before logarithms were developed, people used
to do it with sines and cosines, exploiting some identities from spherical
geometry, and the tables they already had for navigation:

[https://en.wikipedia.org/wiki/Prosthaphaeresis](https://en.wikipedia.org/wiki/Prosthaphaeresis)

~~~
makmanalp
I WISH this is how we learned math in school. Knowing how stuff was used / why
it was invented would have gone a very long way in making everything much more
grounded and interesting.

~~~
Abekkus
I once got a two week survey course in the history of mathematics. I can
confirm it was riveting the whole way through, seeing the evolution of the
toolset laid out.

------
amarillion
Hey, nice to see my venerable tutorial get some renewed attention. I got a lot
of positive feedback on it over the years and that's why I kept it around,
even though it's a bit outdated.

I'd like to do an update or write a follow up article. What would you be
interested in seeing? More examples? Different concepts? Should I port the
examples to a different framework?

~~~
VectorLock
Different concepts: yes. Different framework: no.

------
tgb
Hmm it's been my opinion that most of the time that trigonometry is used, one
should work with vectors instead. This goes doubly for inverse trig. If you
have trig then inverse trig, you can almost always turn it into a
geometrically meaningful vector operation and avoid the difficulty of handling
the ambiguous range of inverse trig functions properly. Remember your
sohcahtoa for doing this.

~~~
DanielBMarkham
_"...Remember your sohcahtoa..."_

My _what_?

Oh, you mean Oscar Had A Headache Over Algebra.

I'd love to find a math cheat sheet poster with comparative mnemonics for the
office wall. There are a ton of them. I imagine they vary depending on the
pedigree of the teacher.

~~~
KineticLensman
we were taught... The Cat Sat On An Orange And Howled Horribly

~~~
Zircom
We were taught "Some Old Hippy Caught Another Hippy Tripping on Acid"

~~~
jessaustin
This seems to be the only mnemonic in this thread that matches soh-cah-toa,
and it's quite memorable, so kudos to your teacher!

~~~
nineteen999
The one we were taught was "Silly old Hitler caused awful headaches to our
allies".

~~~
bigger_cheese
"All Stations To Central" is the mnemonic I still remember as you progress
around quadrants of circle:

Whenever I pass through central station in Sydney I hear my high school math
teacher's voice:

Less than PI/2 "All are positive PI/2 through to PI "Sin is positive" PI
through to 3/2 PI "Tan is positive" 3/2 PI through to 2PI "Cos is positive"

------
msarnoff
The example code uses Allegro ([http://liballeg.org/](http://liballeg.org/))
which was one of my _favorite_ libraries when I was learning to make games as
a teenager in the early 2000s.

I had come from a QBasic background and was trying to learn C++. QBasic was
wonderful because it put everything a budding game developer could want at
their fingertips—full screen graphics and text modes, drawing primitives,
sprites, rudimentary sound and music using the PC speaker, etc.

In 1999 I got my first taste of C++ with MSVC++ 6.0, but soon lost interest. I
had to limit myself to text-based command-line programs, or attempt to learn
the Windows APIs, which seemed way over my head at that point. What I really
wanted was C++ but with QBasic-like APIs.

Probably around 2002-2003 I discovered Dev-C++ and the Allegro library. It was
exactly what I had been looking for and more! It reinvigorated my interest in
programming and helped me learn tricky concepts like pointers and memory
management. I probably wouldn't be an engineer today if it weren't for
Allegro.

------
meuk
Sin and cos were the first mathematical concepts I truly understood through
programming rather than through high-school mathematics. The traditional
method of teaching them through triangles feels very unintuitive.

~~~
candu
Agreed re: triangle method. At the very least, the triangle should be explored
in the context of a unit circle. Otherwise, most students struggle with
negative sin / cos / tan values ("how do the quadrants work again?"), the
connection to Cartesian coordinates, etc. It's a shame that we present so much
of mathematics as arcane magic requiring incomprehensible mnemonics to
understand - especially in trigonometry, where there are much more intuitive /
visual representations of the basic ideas.

(Source: I volunteer with the Toronto Public Library to help high school
students with homework, mainly math.)

~~~
threatofrain
What's a top alternative you've found to work aside from triangles?

~~~
meuk
For me it was associating (sin(r), cos(r)) with a _direction_ (on the unit
circle), instead of with a triangle. After that, the relation with a triangle
where the longest side has unit length is obvious.

~~~
hexane360
Don't you mean (cos(r), sin(r))?

I guess your method works if your angle starts at north and increases
clockwise.

------
mgeorgoulo
I like using trigonometric functions for other things beyond rotations.

For instance, a cropped sine ( [-pi/2,3pi/2] ), remapped to [0,1] in both axes
is very handy as a smooth-step function.

Another range ( [0, pi] ) is very convincing as a screen flash.

------
Tade0
I remember my amazement watching our CS teacher in highschool expand the sine
function into its Taylor series.

I just couldn't wrap my head around the idea that you could simply calculate
this using basic arithmetic operations.

One thing I learned from this experience was that these functions are
expensive to calculate and it's actually pretty easy to write your own, faster
but less precise implementation.

~~~
kevin_thibedeau
CORDIC is cheap and fundamentally simpler than Taylor series since it works on
the essential principal of unit circle rotations that the trigonometric
functions derive from. It isn't just a close enough approximation.

------
dahart
> Floats are so slooooooooow! Why don't you use fixed-point numbers? On newer
> computers, there is not much speed difference, but on older computers the
> speed gain of using fixed numbers is significant.

This is now even more true than when this article was written, enough that
I've been surprised lately and feel like I need to unlearn things I thought I
knew about floats and transcendentals. They only cost a handful of clocks now
in the worst case, with dependent instructions before and after. Depending on
what else is going on, I think you can sometimes get sin & cos for the same
cost as 1 add instruction; as long as you have a lot of other independent math
nearby and/or memory access. Sin & cos & sqrt are crazy fast on GPUs, when
using ShaderToy, for example.

~~~
sp332
Logarithm too. I had written a fractal program in python, and one optimization
for adding up a lot of logarithms was to multiply several values together
first, then take the log of the batch. When I converted it to a shadertoy, I
found that I could strip out the crufty batching code, converting hundreds of
multiplies into logarithms with no noticeable change in performance.

------
marktangotango
A real blast from the past, I used this reference to implement ray tracing in
java. Was it fast? Not really, was it fun? Yes!

------
Symmetry
I recently wrote a routine to do fast IKs[1] for a new robot arm and let me
say, I am filled with profound love for atan2. It saved me from having to
explicitly dealing with _so_ many corner cases.

[1][https://en.wikipedia.org/wiki/Inverse_kinematics](https://en.wikipedia.org/wiki/Inverse_kinematics)

------
japaget
Follow-on article:
[http://www.helixsoft.nl/articles/sphere/sphere.html](http://www.helixsoft.nl/articles/sphere/sphere.html)

------
jonplackett
I remember the exact time and place I first understood sin, cos, tan and SOH
CAH TOA after months of being confused. The guy next to be at school Shaun
Marshall just explained it to me in a sentence and bam, I got it, and have
been using it in various forms of programming for about 20 years since then.
Thanks Shaun, wherever you are now.

~~~
ayi
can you share it with us?

~~~
d0mine
Definition via the unit circle
[https://youtu.be/IxNb1WG_Ido?t=1m40s](https://youtu.be/IxNb1WG_Ido?t=1m40s)

------
voltagex_
Slightly unrelated to this: I've always wanted to play with simple demoscene
graphics that make use of sine waves (think scrollers) - what would be the
simplest programming language to give this a shot?

~~~
jmts
I'd also recommend processing. But if you wanted to try your hand at GLSL you
could also play around with shadertoy [1], though the learning curve is likely
to be much steeper. It has genuine demoscene heritage, coming from a talented
demoscener named 'iq' [2].

[1] [https://www.shadertoy.com/](https://www.shadertoy.com/)

[2]
[http://www.iquilezles.org/apps/index.htm](http://www.iquilezles.org/apps/index.htm)

------
agjacobson
The trick with sin and cos is to never use atan (or atan2).

------
chillingeffect
Is there a more widespread name for a circle split into 256 units than
"allegro degrees"?

~~~
stephc_int13
BAM : Binary Angular Measure

[https://en.wikipedia.org/wiki/Binary_scaling](https://en.wikipedia.org/wiki/Binary_scaling)

