很久以前,人們曾大力推廣用「τ」(tau,約等於 6.28318...)取代「π」(pi,約等於 3.14159...)的近似值。根據許多部落格文章和 YouTube 影片的說法,這樣做的目的是讓常見的公式變得更簡單,並且處理描述整個圓的常數比處理半個圓的常數更容易。
總的來說,我同意這個觀點。雖然這是一個小細節,但值得提出。大多數程式碼如果將 π 替換為 τ,都會變得稍微好一些。
然而,在所有這些喧囂中,一個影響更為深遠的機會卻被忽略了。與其用 τ 取代 π,大多數情況下 π 根本可以完全移除。
首先,考慮程式碼中 π 和 τ 的常見用法:在呼叫三角函數時進行角度轉換。如果你曾經使用過這些常數,你寫的大部分程式碼可能都會像這樣:
這不是我編造的例子,而是我隨機打開 GitHub 上的 Godot 引擎原始碼並搜尋「tau」的結果。上面的程式碼片段,以及數十個類似的用法,就是我找到的。
Godot 這裡並沒有什麼特別之處。如果你打開任何一個隨機的遊戲引擎程式碼庫,你都可以進行完全相同的搜尋,看到完全相同的用法。
請注意這裡發生了什麼:程式設計師有一個值 h,它在 0 到 1 的範圍內已經是週期性的,但他們需要呼叫 sin 函數,所以乘以 τ。
如果只看這一步,這似乎非常合理。但 sin 函數的內部實現又是如何呢?
sin 有許多種實現方式,但無論你查看哪一種,在函數的入口點附近,你都會看到類似這樣的程式碼:
同樣,這也不是我編造的例子——這是來自這個常用參考的 AVX2 實現的 sin 函數。這並不罕見或奇怪——幾乎所有快速的三角函數庫都會做類似的事情。
這行程式碼做了什麼?它將輸入乘以常數 1.27323954473516。
但函式庫程式碼緊接著會這樣做:
這意味著呼叫程式碼乘以 π 的因子,只是為了讓函式庫程式碼能夠立即將其除回來。這實際上是毫無意義地進行弧度轉換和再轉換。如果兩位程式設計師都同意不使用弧度,而是使用 h 原本就存在的 [0, 1] 範圍,那麼他們的工作都會變得更簡單:呼叫者節省了一個乘法,而函式庫則得到一個更容易理解、精確的常數。
而「精確」這一點其實相當有趣。不僅在你無謂地轉換為弧度時需要支付額外的乘法費用,還值得注意的是,除了 0 之外,所有常見的弧度角度都難以精確表示。想儲存 90 度為弧度嗎?無論你使用多少位元,它永遠無法精確。
然而,在 [0, 1] 範圍內,90 度只是 0.25——一個甚至不需要任何尾數位元就能表示的位元模式!0.5 呢?一樣!0.75 呢?只需要一個尾數位元就能精確表示。
因此,[0, 1] 範圍不僅比弧度在計算上更有效率,而且在表示實際使用中經常出現的典型值時,也更緊湊、更精確。
我能理解為什麼有些人會擔心做出這種改變。即使你相信我,認為所有使用者端的程式碼都乘以 π 或 τ,而所有函式庫端的程式碼都將其除回來,你仍然可能會有一種「數學課的感覺」,覺得如果你停止使用弧度,就是在做錯事。
但數學從未規定正弦和餘弦必須接受弧度參數!
將圓周從零到一參數化而不是從零到 τ 的想法,並不是我為了這篇部落格文章隨意想出來的。它實際上是一個合法、現有的數學結構,甚至還有一個名稱:它被稱為「圈」(turn)。
以圈為單位,0 是 0 度,0.5 是 180 度,1 是 360 度,2 是 720 度,依此類推。這正是我們想要的。
所以,如果你擔心你的數學老師會生你的氣,那就沒有必要。只要告訴他們,你仔細考慮了這個問題,並決定以圈為單位參數化你的角度而不是以弧度為單位,是當前問題最有效的方法!
如果你自己編寫了數學函式庫,或者將別人的程式碼複製到你的專案中,希望你已經很清楚如何擺脫弧度並從程式碼庫中移除 π 和 τ。你所要做的就是修改你的 sin 和 cos 函數,讓它們接受圈而不是弧度,這通常只需要快速調整一個常數。
如果你想支援舊程式碼,可以為新的基於圈的三角函數選擇一個不同的名稱。然後,對於舊程式碼,你可以透過讓這些例程透過新的例程進行調用,並在此過程中進行除以 τ 的操作,來繼續支援舊的基於弧度的 sin 和 cos。
這非常簡單——只需要幾行程式碼就可以完成轉換。
然而,雖然我發現圈是最方便的重新參數化方式,但它並非唯一的選擇。特別是如果你不自己編寫數學例程(甚至可能即使你編寫了),你可能更傾向於考慮使用「半圈」(half turns),其中一個完整的圓是 [0, 2]。這有點令人困惑,但是……
事實證明(雙關語!),如果你去尋找,在一些數學函式庫中,你會已經找到以半圈而不是弧度為參數的 sin 和 cos 函數。例如,CUDA 的 sincospi intrinsic 函數計算輸入乘以 π 的正弦和餘弦,而 π 正好是一個半圈。
這很棒。如果你針對的平台已經有 sincospi 可用,你現在就可以停止在程式碼中使用 π 和 τ 常數,而無需觸碰你的函式庫。只需開始使用半圈而不是弧度來呼叫 sincospi,而不是 sin 和 cos,這樣就可以了。
在我管理過不再使用弧度的整個程式碼庫之後,我可以放心地說,我從未懷念過它們。所有多餘的 τ 和 π 都消失了,一切都讀起來更清晰。
修改 sin 和 cos 的邏輯同樣適用於所有其他的標準三角函數,所以如果你願意,可以在任何地方消除弧度。函式庫在內部幾乎總是會從弧度轉換出來,然後在輸出時再轉換回弧度,所以將所有內容切換到圈或半圈通常只是刪除程式碼,沒什麼別的。
不過請記住,標準微積分書籍中所有那些關於三角函數的導數和積分公式都假設參數是以弧度表示的。例如,sin x 相對於 x 的導數是 cos x --- 這只有在 x 是弧度時才成立。否則,由於鏈式法則,你會得到一個額外的因子。所以,如果你正在處理的數學表達式碰巧來自導數或積分,那麼你很可能會因為簡單地刪除 π 或 τ 的每一個出現而錯過那些額外的因子。
pico-8 就是這樣做的:https://pico-8.fandom.com/wiki/Cos