圈比弧度更优
Turns are Better than Radians (2022)

原始链接: https://www.computerenhance.com/p/turns-are-better-than-radians

虽然关于使用“π”还是“τ”的争论旨在简化圆相关的公式,但两者其实都是多余的。大多数软件经常进行一种冗余计算:用户将某个值乘以 π 或 τ 以将其转换为弧度,底层数学库随后又立即将其除以 π 以进行运算。 这种“弧度税”不仅计算效率低下,还会降低数值精度。作者主张用“转”(turn,以一整圆为 0 到 1 的刻度)来代替弧度。由于“转”基于简单的分数,它们在二进制中比无理数的弧度值更容易被精确表示。 过渡到“转”,或利用诸如 `sincospi` 这样接受半转的现有平台内置函数,完全消除了对 π 或 τ 常量的需求。通过转向基于“转”的系统,开发者可以消除冗余操作、提高精度并简化代码库。与其争论该使用哪个常量,最有效的解决方案是彻底摒弃它们,转而使用一种更直观、更高效的单位。

这篇 Hacker News 讨论探讨了一个具有启发性的观点,即以“圈”(turns,完整旋转一周为 1)作为角度单位优于弧度制。 支持者认为,使用“圈”可以简化计算,尤其是四分之一圈的计算,因为它消除了涉及 $\pi$ 的浮点运算。一些评论者表示,为了保持精度,他们已经在自己的代码库中倾向于将角度存储为“圈”。 然而,共识也指出了其中的重大权衡: * **数学上的不一致性:** 更改单位并不能消除 $\pi$,它只是将这个因子转移到了微积分运算(例如导数)和小角度近似中。 * **兼容性:** 由于三角函数是信号处理和物理学的基础,替换弧度制将需要对现有的科学标准进行大规模且具有破坏性的彻底改革。 * **概念争议:** 讨论还涉及到一个哲学层面的争论,即数字是否应该带有单位。虽然一些人认为在工程学中将角度标记为“度”或“弧度”对于清晰度是必要的,但另一些人(引用迈克尔·斯皮瓦克的话)则认为,单位是函数的属性,而非数字本身的属性。 归根结底,虽然“圈”被认为是在特定编程任务中一种便捷的工具,但它不太可能取代根深蒂固的弧度制。
相关文章

原文

Some time ago, much effort was expended to convince people to replace approximations of “pi” (3.14159…) with approximations of “tau” (6. 28318…). The idea, according to numerous blog posts and YouTube videos, was that common formulas become simpler, and it’s easier to work with a constant describing an entire circle instead of half a circle.

Generally, I agree. While it’s a minor point, it’s worth making. Most code does get slightly better if you replace pi with tau.

However, in all the fanfare, a far more impactful opportunity was overlooked. Instead of replacing pi with tau, most of the time pi can be removed entirely.

First, consider the common case for pi and tau in code: converting things to and from radians for calls to trigonometric functions. If you’ve ever used these constants, the vast majority of what you wrote probably did something like this:

y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - 
    (cursor->get_height() / 2);

That’s not me constructing an example, that’s me randomly opening the source code for the Godot Engine on github and searching for “tau”. The piece of code above, and dozens of similar uses, is what comes up.

There is nothing special here about Godot. If you opened any random game engine codebase, you could do the exact same search and see the exact same kind of usage.

Notice what is going on here: the programmer has a value h which is already periodic on the range 0 to 1, but they multiply by tau because they need to call sin.

This may seem very sensible if that’s as far as you look. But what about the implementation of sin?

There are many implementations of sin, but no matter which one you look at, near the entry point of the function you’ll see something like this:

_PS256_CONST(cephes_FOPI, 1.27323954473516);
...
y = _mm256_mul_ps(x, *(v8sf*)_ps256_cephes_FOPI);

Again, not me making up an example - that’s from this commonly referenced AVX2 implementation of sin. It’s not unusual or weird - pretty much every fast trig library is going to do something very similar.

What does this line do? It multiplies the input by the constant 1.27323954473516.

Which just so happens to be 4/pi.

So the calling code is doing this:

sin(h * 2 * pi)

but the library code immediately does this:

y = (4 / pi) * x

which means the calling code is multiplying by a factor of pi just so the library code can immediately divide it back out again. It’s literally a conversion to radians and back for no reason. If both programmers had just agreed not to use radians, and instead used the original [0, 1] domain that h was already on, both their jobs get simpler: the caller saves a multiply, while the library gets a simpler-to-understand, exact constant.

And the “exact” part is actually quite interesting. Not only do you pay for an extra multiply when you spuriously convert to radians, but it’s also worth noting that all common radian angles besides 0 are difficult to represent. Want to store 90 degrees in radians? No matter how many bits you use, it will never be exact.

90 degrees on [0, 1], however, is just 0.25 - a bit pattern that doesn’t even require any bits of mantissa at all! 0.5? Same! 0.75? Just one bit of mantissa to represent exactly.

So the [0, 1] range is not only more computationally efficient than radians, it is also more compact and precise when representing typical values that frequently occur in practical use.

I can understand why some people would be worried about making this switch. Even if you believe me that all user-side code multiplies by pi or tau, and all library-side code divides it back out, you still may have that sinking “math class feeling” that you’d be doing something wrong if you stopped using radians.

But math never decreed that sine and cosine have to take radian arguments!

The idea of parameterizing a circle from zero to one instead of from zero to tau is not a random idea I made up for this blog post. It’s actually a legitimate, existing mathematical construct, and it even has a name: it’s called a turn.

In turns, 0 is 0 degrees, 0.5 is 180 degrees, 1 is 360 degrees, 2 is 720 degrees, and so on. It’s exactly what we wanted.

So if you are worried that your math teacher will get mad at you, there is no cause for concern. Just tell them that you considered the matter carefully, and decided that parameterizing your angles in turns instead of in radians was the most efficient method for the problem at hand!

If you wrote your own math library, or you copied someone else’s into your project, hopefully it is quite clear how you can switch away from radians and eliminate pi and tau from your codebase. All you have to do is take your sin and cos functions and make them take turns instead of radians, which usually involves nothing but a quick adjustment to a single constant.

If you want to support legacy code, pick a different name for the new turn-based trig functions. Then, for legacy code, you can still support the old radian-based sin and cos by making those routines thunk through to the new routines, doing the divide-by-tau along the way.

It’s very simple - just a few lines of code to make the switch.

However, although I find turns to be the most convenient reparameterization, it’s not the only alternative. Especially if you don’t roll your own math routines (and perhaps even if you do), you may instead want to consider using half turns, where a full circle is [0, 2]. It’s a bit more confusing, but…

It turns out (pun intended!) that if you go looking for it, in some math libraries you will already find sin and cos functions parameterized on half-turns instead of radians. For example, the CUDA sincospi intrinsic computes the sine and cosine of the input multiplied by pi, which is a half-turn.

This is great. If you’re targeting a platform with sincospi already available, you can stop using pi and tau constants in your code right now without touching your libraries at all. Just start calling sincospi with half-turns instead of sin and cos with radians, and you’re good to go.

Having now managed entire codebases where I stopped using radians, I can safely say I never miss them. All the superfluous tau’s and pi’s disappear, and everything reads more clearly.

The same logic for modifying sin and cos applies to the rest of the standard trig functions as well, so you can eliminate radians everywhere if you choose. Libraries almost always convert away from radians internally anyway, and then convert back to radians on the way out, so switching to turns or half-turns everywhere is usually just a matter of deleting code and not much else.

联系我们 contact @ memedata.com