46 Comments
Sep 26, 2022Liked by Casey Muratori

Just keep in mind, though, that all those formulas of derivatives and primitives of trig functions in standard calculus books assume that arguments are expressed in radians. Say, the derivative of sin x w.r.t. x is cos x --- that is only true if x is in radians. Otherwise, you would get an extra factor, due to the chain rule. So, if the mathematical expression you are working with happens to come, somehow, from derivatives or primitives, chances are that you will miss those extra factors, by simply deleting every ocurrence of pi or tau.

Expand full comment
author

This is a good point. I have not had that kind of code in anything radian-free yet, so I haven't thought through what you want to do. Since the radian alternative is to divide out the pi on each call, it may be that there is still some way to be more efficient than that if you look at what tends to happen in that kind of code.

Expand full comment
Sep 27, 2022·edited Sep 27, 2022

Note 1: Whilst I am familiar with numerical analysis, I never rolled out my own trig functions so I am ignorant in that aspect.

Note 2: I am being verbose to make sure I understand what you are saying correctly. Consider this post more as a question not a statement of fact. My apologies if I am just reiterating the obvious.

I don't believe this would be an issue. It is true what Eduardo said that if you have a function that is in turns (or degrees or etc), the derivatives will have an extra factor that is inconvenient. So the function sin_turn( turns ) is more cumbersome to use than sin_rad ( radians ).

However the advantages that you listed are not so much in changing the function but just the way we encode the parameter. What I understood from your post is not that we should start using sin_turn( t ), but instead that most of the libraries should have as options:

sin_rad ( radians_div_pi ) or sin_rad( radians_div_2pi )

Of course that radians_div_pi and radians_div_2pi correspond to half-turns and turns (like in the CUDA example), and we could think of them that way. It is equivalent to writing:

sin_rad ( half_turns) or sin_rad( turns )

Under the assumption that : 1. Programs will use the angle values only (or mostly) as parameters to the trig functions. 2. The multiply / divide is common in trig function implementations.

then the sin_rad( radians_div_2pi ) functions :

A. Keep the mathematical niceties of sin_rad, B. We save time overall by reducing multiplies / divides , and C. we represent the values more accurately

Would this be a fair summary of what you suggest?

Expand full comment
author

Yes, that is what I suggest. But what I took Eduardo to be saying is just "please be careful, anyone who reads this, that you cannot do derivatives _of code_ that you read and expect it to work the same as if _the code_ had been in radians". Maybe they weren't saying that, but that's what I took them to be saying, and I think that's a good cautionary statement, just in case someone wasn't aware of the fact that sine/cosine in radians are "optimized" in the math sense because they don't produce a chain rule term for the angle, but any other parameterization of angle would.

Expand full comment
Sep 27, 2022·edited Sep 27, 2022

Right so what I would add to that is to keep in mind that if in the code you see :

sin ( x )

where x is in turns.

You have to be careful to know if you can derive "the usual way" but maybe (probably I would say) you can!

Because the sin function can still be the same mathematical function. And so it will still have the same derivatives regardless of how the parameter is represented. So you can get the best of both worlds. :)

I assume this is the case of sincospif for instance.

Expand full comment
author
Sep 27, 2022·edited Sep 27, 2022Author

No, I think the point of this comment thread is just to point out you can't make that assumption. If you have sin_radians(x), then its derivative is cos_radians(x) _specifically because_ radians is the unit where that is true. For any other angle measure (turns or otherwise), a constant will pop out of the derivative.

So if you have sin_turns(x), the derivative is not cos_turns(x), it's c*cos_turns(x). And specifically c is going to contain pi, so you're going to have pi come back again. Does that make sense?

A more math-oriented person can step in here and explain better than I can, but suffice to say the whole reason people like using radians as angles in math is _because_ they have simplifying properties when doing derivations. They're the "natural unit" for doing algebraic math with angles. And this derivative-doesn't-require-a-constant aspect is just one of the places where that is true.

Expand full comment

I think this warning assumes that whatever functions I'm using are so complicated that the angle variable `x` is appearing both inside the trigonometric function and outside it. I rarely ever see those kinds of functions. What I'm saying is, can't I just make sure I do the derivative with respect to the turn variable? Then I shouldn't have to worry about this kind of chain rule complication. Also, if its a function of two variables where the angle variable is independent of all other variables I should still be safe right? Is this warning just unduly scary? Although, I guess in the scientific applications where representing the values of PI to a high degree of accuracy is important, these kinds of functions are just commonplace. Even the first PDE we learn (the wave equation) is like that.

And I would shamelessly put the wave equation in functions I find complex.

Expand full comment

Ah! Yes totally agree.

I was making the mistake of only considering trivial cases so I was not understanding what you were saying by "derivatives in the code".

Makes total sense now.

Cheers

Expand full comment

If you need accurate derivatives (as you may do in engineering and physics), then this certainly matters a lot. But in graphics, remember that constant factors in derivatives may not matter as much as you think.

Think of local/Frenet frame vectors (tangent/bitangent/normal), for example. They typically get normalised before you use them, so any extra constant factor on derivatives makes no practical difference.

Expand full comment

wouldn't you just treat it like it's a normal sine/cosine? afaik it's not that it's stretching the sine over a different range, and more that it's stretching the number line to differently measure the same sine.

Expand full comment

Yea, I suspect his is why most libraries have trig functions done in radians. Back in the day, most applications, especially those were runtimes were a serious concern, were scientific and had a lot of derivatives running around, so avoiding the extra constants made sense.

Expand full comment

pico-8 does it this way: https://pico-8.fandom.com/wiki/Cos

Expand full comment
author

Awesome! Another reason to like PICO-8 :)

Expand full comment
Oct 25, 2022·edited Oct 25, 2022

Yup, after playing with PICO-8 for five or six years, I really wish I'd had turn-based sin/cos during my real gamedev career. So many things would have been simpler and, I suspect, more efficient under the hood because converting back to quadrants or octants for the computation or table lookup would be trivial. Not to mention there would be no error propagation at the lowest bits because there'd be no multiply or divide by an almost-irrational number. Radians have their place and I'd still want versions for that, but 99% of the time I'd want turns. I suspect the radians version would simply divide by n*pi and pass through to the turn-based version, too.

Expand full comment

If we don't care about legacy then we don't need to call them cos and sin either, right? Maybe call these new functions turnX and turnY?

Expand full comment
author

Yes. In fact, I woild strongly recommend NOT calling them sin and cos because that will confuse people who expect them to take radians! Picking new names is definitely the right thing to do.

Expand full comment
Sep 28, 2022·edited Sep 28, 2022

Is there any good C/C++ (preferably single header) math library out there that does this? Or do I have to roll my own?

(Took a look at avx_mathfun.h for something to start with maybe, but want non-SIMD as well.)

Expand full comment
Sep 27, 2022·edited Sep 27, 2022

Have you ever heard of Rational Trigonometry? It doesn't use angles or turns, but instead uses spread. I've always wondered how a game engine would look like if it used those primitives.

https://www.youtube.com/watch?v=0gWWDUtmf-w

Expand full comment
author

I watched some of these videos and hey were definitely interesting. My first thought, however, is that it might be difficult to apply the rational geometry approach to, for example, replacing sine and cosine as I am discussing in this article.

In the rational geometry framework, it sounds like they parameterize circles as a function of the y intercept, which would not be useful for generating rotations and such, because the output would move at a nonlinear speed around the circle relative to the input parameter.

That said, that is just my first thought having watched the videos. Probably it would take someone who really spent a lot of time with rational geometry to say for sure if there is a win to be had there.

Thanks for the pointer to the videos though, either way!

Expand full comment
author

I haven't, no. I will take a look.

Expand full comment

In the AVX exapmle, I don't understand why sin(x) is equals to 4 / PI * x? If x equals to TAU then wouldn't the function return 8?

Expand full comment
author
Sep 26, 2022·edited Sep 26, 2022Author

It's not. That is merely the first line of the implementation. You can click on the associated hyperlink to see the rest of it if you're curious.

Expand full comment

I totally thought this article was going to be something about inductors

Nice article! GLSL/HLSL should provide a turn version of trig funcs

Expand full comment

Just mentioning that we ALREADY do that for exp() and log(), having multiple variants (base-e, base-10 and base-2) in the same lib, makes me wonder why we still don't have ""base-1"" and ""base-tau"" trig alongside the regular ""base-pi"" trig in so many libs.

Expand full comment

This reminds me of the utopian 'gradians' button (technically DRG) I see on calculators and always wish we could switch to.

Expand full comment

If I understand the suggestion correctly, this wasn't overlooked, it was suggested here for instance:

http://thomascool.eu/Papers/Math/TrigRerigged.pdf "Abstract: Didactic issues in trigonometry concern the opaque names of sine and cosine and the cluttering of questions with p or 360 whereas a simple 1 suffices. (Linked from here: https://en.wikipedia.org/wiki/Turn_(angle) )

Astronomer Fred Hoyle proposed a decimal system with "milliturns". :-)

However, computing essentially re-parametrized basic functions (X(t), Y(t)) = (cos 2pi t, sin 2pi t) instead of (cos t, sin t) that was suggested there, and seems to be the suggestion here too, might be natural "conceptually" (that's the point of tau and the manifesto, to use fractions of the whole circle rather than half as reference), but computationally they are not friendly.

A great (definitive) recent book on mathematical function computation is:

https://link.springer.com/content/pdf/bfm:978-3-319-64110-2/1.pdf

"Nelson H.F. Beebe The Mathematical-Function Computation Handbook"

The beauty of the existing functions (and radian measure) is that they are arc-length or "unit speed" parametrizations of the circle, so as someone else pointed out, all of the series coefficients are 1, 0, -1, 0, and many other computational conveniences and mathematical relationships would be lost.

The circle constant 6.28... was never proposed for computational considerations, but for mathematical conceptual ones, to make the parametrization portions of 1 rather than of 2.

So that a quarter turn replaces "pi over 2" for what every child gets is a quarter of an hour.

Of the videos, Phil Moriarty's Numberphile segment.

https://www.youtube.com/watch?v=83ofi_L6eAo

is probably the best at explaining the point. He has a great fun book on quantum mechanics too: https://www.goodreads.com/en/book/show/34196224

Expand full comment

You consider turns where a full circle is [0,1] or half turns, where a full circle is [0, 2].

How about full turns where a full turn is [0,360] and half turns are [0,180]?

Expand full comment

That is doable, but normalized values (i.e., 0.0-1.0) are so common that you would end up multiplying by 360 most of the time. And then you'd just have to divide by 360 in the trig function.

Expand full comment

For speed: use a (limited term) Taylor series.

MUCH faster.

Expand full comment
author

What are you talking about? How does this pertain to the content of this article in any way whatsoever?

Expand full comment

You're talking about speed increases for trig functions. Yet you still use trig functions instead of Taylor/Maclaurin series or using the Chebyshev polynomial.

Screw radian substitution for 'turns': use a better/faster algo which can match your required accuracy.

Maybe you should have at the very least googled and/or read wikipedia about Taylor series approximation wrt trig functions before you wrote that.

Expand full comment
author

I'm really not sure how to reply to this. We'll try some questions:

1) Did you look at the actual "trig function" I referenced? It has a hotlink there, below the code snippet, that says "from this commonly referenced AVX2 implementation of sin". Did you read that code? Does it look familiar? What does that code look like it is doing to you?

2) Have you ever implemented your own sine function (using any method - Taylor, Chebyshev, Pade, etc.)? What is the first step that you did in that implementation?

3) When you say I still "use trig functions instead of ... polynomial", I'm very curious: how did you think these magical "trig functions" I'm using compute sine, exactly, without one of those polynomials you mentioned?

4) You don't by any chance post comments on Hacker News regularly, do you?

Expand full comment
Sep 28, 2022·edited Sep 28, 2022

Just to underline Casey's point #2 here, I have implemented sine and cosine. The first step is range reduction. Look up Handmade Hero episode 440 if you want an explanation of what the code linked below is and how to design it, but the important point for now is that k1+k2+k3 is a higher-precision approximation to 2pi than will fit in a 32-bit float.

I took that range reduction code (as originally written in my own style rather than HMH style) and rewrote it to use half turns instead of radians. Here, the number you would need to "approximate" is not 2pi, but 2, which is exactly representable in a single float.

You be the judge as to which is better/faster.

https://gist.github.com/Deguerre/8d589c8a7cf3bf103126e255b333dde6

Expand full comment

Of course !!, all the code is faster, my math lib using this.

https://github.com/phreda4/r3/blob/main/r3/lib/math.r3

Expand full comment

Yes, turns are better than radians in many cases. But in a lot of those cases degrees are even better. 360 is a superior highly composite number (https://en.wikipedia.org/wiki/Superior_highly_composite_number) and we can get exact representations for many more common and useful angles than with turns. Want to split a circle into 6 equal parts? 1/6 doesn't have exact representation in float. 360/6 is not only exact in float32 but also an integer.

Expand full comment
author

No. If you used degrees, you'd replace the multiply by tau with a multiply by 360, _and then you would immediately have to divide by 360_ in the math routine, thus losing bits of precision for absolutely no reason whatsoever. The only place degrees make sense is when storing things if you really need to be exact for 3rd's or 6th's, and that's something you would only see in CAD or something similar. You would not use degrees in the general case unless you really knew that behavior was important.

Expand full comment

Yes, I was thinking more about CAD applications. For general-purpose gamedev or rigid body dynamics angles represented in turns will be a more direct way.

Expand full comment

Thanks, man. I learned a few things here.

Expand full comment

ty casey

Expand full comment