1990年代,遊戲開發者面臨嚴峻挑戰。由於運算能力極度有限,他們必須盡可能高效地編寫程式碼。以第一人稱射擊遊戲《雷神之鎚3》(Quake III Arena,簡稱Quake 3)為例:玩家在三維空間中移動,因此程式設計師必須尋找處理三維圖形及相關計算的最巧妙方法。

Quake 3 於1999年發行,被認為是當時最頂尖的電腦遊戲之一,對產業產生了深遠影響。其影響力並非來自劇情,而是因為Quake 3 是最早的多人連線第一人稱射擊遊戲之一。玩家可以透過網路線或網際網路連線,即時對戰。

遊戲的程式碼也留下了印記。它包含一個至今仍讓專家驚嘆、激發科學家好奇心的極其高效的演算法。

要在數學上確定物體、角色或其他玩家在三維空間中的方向,需要建立一個向量,本質上是一個指示方向的箭頭。為了比較向量,它們需要被正規化到相同的長度,因此必須進行相應的縮放。這就引出了一個棘手的計算:反平方根,即一個數字的平方根的倒數。

如果我請你在沒有計算機的情況下計算26的反平方根,你可能會卡住一段時間——老實說,我也是。在1990年代,電腦也面臨同樣的挑戰。雖然它們能夠進行數字運算,但這個過程需要大量的處理能力——這意味著計算可能耗費大量時間。一個問題是平方根本身;另一個是除法。這就是為什麼Quake 3 的程式設計師們尋找一種更好的方法來找到這個反平方根。確實,他們的原始程式碼揭示了一個巧妙的解決方案。

引人入勝的是,開發者從未宣傳過他們的技巧。如果Quake 3 的原始程式碼沒有開源,他們的方法可能永遠不為人知。但一旦發布,好奇的愛好者們就注意到了。當他們發現計算反平方根的程式碼片段時,他們感到困惑——很難理解,開發者附帶的註解也不是特別有幫助。但漸漸地,人們弄清楚了程式碼是如何運作的。

如今有許多教學指南,一步一步引導你了解程式碼。這些指南利用了C程式語言的特殊功能。例如,數字儲存在稱為記憶體位址的電腦位置,然後對其進行操作。這是一種避免計算密集型操作(如除法)的巧妙方法。多倫多大學的電腦科學家Daniel Harrington在一次演講中解釋說:「想像一下,就像在商店裡給某樣東西貼錯標籤,然後騙過店員,但這裡是在C語言中欺騙。」

從數學角度來看,程式碼很容易解釋。要確定反平方根,你首先對解進行一個猜測(通常是錯誤的),然後透過一個特定的程序來精煉這個估計。這樣,它會逐漸達到更好的解決方案。

這一切都不是開創性的或新的。然而,令人印象深刻的是,通常需要四到五次迭代才能使結果接近實際解。這個過程需要大量的計算能力。在Quake 3 中,起始值——即過程中第一步使用的估計數字——被巧妙地選擇,以至於只需要一次優化步驟。

優化步驟對應於所謂的牛頓-拉夫森方法,該方法透過多次迭代來近似函數輸出為0的點,即函數的根。這乍聽之下可能有些違反直覺,因為人們想計算的是反平方根,而不僅僅是任何零點。但程式設計師們採用了一個技巧:他們將要近似的函數定義為初始估計值與實際結果之間的差值。透過牛頓-拉夫森方法,誤差因此逐漸減小,讓人們越來越接近精確解。

為了理解這一點,想像一下你想計算2.5的反平方根。演算法從一個特定的猜測開始:假設是3.1。要確定與實際解的差異,你需要將初始值平方,然後將1除以結果。如果3.1真的是2.5的反平方根,那麼1除以3.1的平方就是2.5。實際結果是0.1。因此,差異是2.4。

牛頓-拉夫森方法在每次迭代中都會減小這個差異,讓你逐漸接近精確值。通常需要四到五個這樣的步驟才能得到可靠的結果。然而,Quake 3 大大減少了迭代次數。

關鍵在於如何計算牛頓步驟的起始值。該方法的演算法基本上分為三個步驟:

1. 取得要計算反平方根的給定數字,並將其轉換為對應的記憶體位址(電腦儲存資料中的一個位置)。

2. 將此值除以二,然後從十六進位值0x5f3759df中減去。這就是牛頓方法的起始值。

特別神秘的是這個晦澀的字串0x5f3759df,它後來成為電腦科學史上的「魔術數字」。正是因為它,才只需要一次迭代就能獲得一個近似解,其誤差最多為0.175%。

一旦程式碼以開源形式提供,專家們就開始研究這個魔術數字的來源。在2003年發表的一篇技術論文中,電腦科學家Chris Lomont寫道:「這個值從何而來,程式碼為何有效?」

十六進位數字0x5f3759df相當於十進位表示的1,597,463,007。透過分解程式碼的個別步驟,Lomont意識到他可以透過某些計算得到1,597,463,007。為了簡化數學,這裡有一種表示相關計算的方式:

3/2、2^23和127這些值來自於將數字表示轉換為C語言。但0.0450465的來源不太明顯。

Lomont從數學上研究了哪個值能為不同的輸入產生最佳結果。換句話說:哪個起始值最能近似反平方根,因此應該導致最小的誤差?他得到的值是1,597,465,647,大約是:

這對應於在Quake 3 原始程式碼中找到的值。結果與那裡找到的值非常接近。

當Lomont將他的結果與原始結果進行比較時,他遇到了一個驚喜。在牛頓-拉夫森方法的兩個步驟中,他計算出的常數實際上效果更好:最大可能誤差比原始程式碼中的值要小。「然而令人驚訝的是,在一次牛頓迭代之後,它具有更高的最大相對誤差,」Lomont寫道。「這再次引發了問題:原始程式碼中的常數是如何得出的?」

在他的計算中,這位電腦科學家只考慮了哪個數字理論上能產生最佳值,而忽略了牛頓步驟的數量。為了尋找一個更好的常數,Lomont重複了他的計算,並針對單次牛頓步驟的最佳可能解進行了優化。他得到的值是1,597,463,174,大約是:

當他將這個結果付諸實踐時,它實際上產生了比Quake 3 程式碼中的魔術數字略好的結果。

Lomont在他的論文中指出,由於兩個常數都是近似值,在實踐中兩者都是不錯的選擇。他補充說,他希望遇到這個常數的原始作者,以了解他們是如何得出這個魔術數字的。

線上社群開始不懈地尋找這位神秘人物。電腦科學家Rys Sommefeldt尤其致力於此,他首先聯繫了Quake 3 的首席開發者John Carmack。然而,Carmack不確定是誰編寫了這段程式碼,只能提供猜測。

Sommefeldt聯繫了1990年代一些最傑出的開發者,他們各自提出了可能的作者,但沒有人聲稱是作者本人。現在看來,Greg Walsh,他在1980年代後期曾在Ardent Computer公司工作,將這個魔術數字引入了反平方根演算法。然後,透過其他幾個人,它進入了Quake 3 的演算法。但魔術數字究竟是如何確定的,至今仍不清楚。

這不是一個令人滿意的結論。然而,Quake 3 程式碼的故事——或者至少是圍繞反平方根的部分——極其引人入勝。令人驚訝的是,當時在高效軟體程式設計上投入了多少努力和腦力——由於當前的運算能力,這一趨勢如今常常被忽視。