返回

文章详情

转弯比弧度更好

Hacker News2026年8月20日 01:29

不久前,花费了很多努力来说服人们用“tau”(6.28318…)的近似值替代“pi”(3.14159…)的近似值。根据许多博客文章和YouTube视频的说法,这一理念是常见的公式变得更简单,使用描述整个圆的常数而非半个圆的常数更容易。一般来说,我同意。虽然这是一个小问题,但值得一提。如果用tau替代pi,大多数代码确实会稍微变得更好。然而,在所有的喧闹中,一个更具影响力的机会却被忽视了。与其替代pi,不如大多数情况下完全去掉pi。首先,考虑代码中pi和tau的常见用例:在调用三角函数时进行弧度之间的转换。如果你曾经使用过这些常数,你写的大部分代码可能类似于这样:y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (cursor->get_height() / 2); 这不是我构造的例子,而是我随机打开Godot引擎的源代码在github上搜索“tau”的结果。上面的代码片段和几十个类似的用法就是搜索结果。这里没有关于Godot的特别之处。如果你打开任何随机的游戏引擎代码库,你可以做同样的搜索,看到完全相同的使用方式。注意到这里发生了什么:程序员有一个值h,已经在0到1的范围内是周期性的,但是他们乘以tau是因为需要调用sin。如果这是你所看到的全部,可能看起来很合理。但sin的实现呢?有许多sin的实现,但不论你查看哪一个,在函数的入口点你总会看到类似这样的东西:_PS256_CONST(cephes_FOPI, 1.27323954473516); ... y = _mm256_mul_ps(x, *(v8sf*)_ps256_cephes_FOPI); 同样,这不是我编造的例子——那是来自这个广泛引用的AVX2 sin实现。这并不奇怪或奇异——几乎所有快速的三角函数库都会做类似的事情。那一行代码做了什么?它将输入乘以常数1.27323954473516。恰好是4/pi。因此调用代码在执行:sin(h * 2 * pi),而库代码立即执行:y = (4 / pi) * x,这意味着调用代码正在乘以一个pi的因子,只是为了让库代码能立即将其除以。同时进行的弧度转换实在没有理由。如果这两个程序员达成共识不使用弧度,而是使用h原本就处于的[0, 1]范围,他们的工作都会变得简单:调用者省去了乘法,而库获得更易于理解、精确的常数。“精确”这一点实际上相当有趣。不仅在无谓地转换为弧度时付出了额外的乘法代价,而且值得注意的是,除了0以外,所有常见的弧度角度都难以表示。想用弧度存储90度?无论你使用多少比特,都无法精确表示。然而,90度在[0, 1]上,恰好是0.25——一个甚至不需要任何尾数位的比特模式!0.5?同样!0.75?只需一位尾数就能精确表示。因此,[0, 1]的范围不仅在计算上比弧度更高效,它在表示在实际使用中经常发生的典型值时也是更加紧凑和精确的。我可以理解为什么有人会担心做出这一转换。即使你相信我说的所有用户侧代码都在乘以pi或tau,而所有库侧代码则将其除回,你仍可能会有那种消沉的“数学课感觉”,觉得如果停止使用弧度就会做错事。然而,数学从未规定正弦和余弦必须接受弧度参数!将圆从零到一进行参数化,而不是从零到tau的想法并不是我为这篇博客文章随意编造的。它实际上是一个合法的、现存的数学构造,甚至有个名字:称之为转弯。在转弯中,0是0度,0.5是180度,1是360度,2是720度,依此类推。这正是我们所想要的。因此,如果你担心你的数学老师会对你生气,完全没有必要担心。只需告诉他们,你仔细考虑了这个问题,并决定用转弯而不是弧度参数化你的角度是解决当前问题的最高效方式!如果你编写了自己的数学库,或者将其他人的库复制到你的项目中,希望现在很清楚你如何可以摆脱弧度并从代码库中消除pi和tau。你所要做的就是调整你的正弦和余弦函数,让它们接受转弯而不是弧度,这通常只需要快速修改一个常量。如果你想支持遗留代码,可以给新的基于转弯的三角函数命名另外一个名字。然后,对于遗留代码,你可以通过创建这些例程来支持旧的基于弧度的正弦和余弦。

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡