深夜,你剛修完一個棘手的 bug,闔上筆電,癱在床上滑手機。螢幕上出現一則奇怪的推文:「徵求:熟悉高並發、數據處理的工程師,派駐北京,需熟悉古代文書,待遇從優。」你嗤笑一聲,點了「+1」回覆,隨即沉沉睡去。
再醒來時,你發現自己身穿石青色官服,跪在紫禁城養心殿的磚地上,龍涎香的氣味鑽入鼻腔。龍椅上的男人——那個在歷史課本上看過無數次的臉——乾隆皇帝,正居高臨下地看著你:「愛卿,欽天監監正告老還鄉,朕聞你通曉天文、精於算數,今日起,由你掌欽天監事。七日後,朕要在太和殿舉行秋分祭天大典,須由欽天監呈報下一季的朔望月與節氣時刻。若有一絲偏差,你這項上人頭……」
你的腦中一片空白,但工程師的直覺迅速接管了思緒。眼前沒有 VS Code,也沒有 GitHub,只有滿桌的《崇禎曆書》、《曆象考成》和散落的算籌。你知道,清朝欽天監使用的《時憲曆》,源自明代徐光啟與耶穌會士湯若望等人引進的歐洲天文學知識,其核心是「第谷宇宙體系」與幾何學模型。但你的任務不是重溫天文史的考據,而是利用一位現代軟體工程師的邏輯,在沒有電力、沒有編譯器的極端環境下,建立一套「可運作」的曆法計算「引擎」。
任何專案啟動的第一步都是確認環境。你很明白,與其抱怨沒有 numpy 和 pandas,不如先審視手上的「硬體資源」。欽天監的官員們驚恐地看著新任監正將他們分成小組,並要求列出所有的「可運算資源」。
你將目光投向那架高大的青銅天文儀器——渾天儀。它精美的龍柱與刻度圈在夕陽下閃著寒光。你撫摸著冰冷的鑄鐵,腦中浮現的是計算機組織結構中的「類比運算單元」。這架儀器由人力驅動,透過齒輪比模擬日、月、五星的運動週期,這不正是一台沒有電晶體的「機械式計算器」嗎?你立即下令:「今日起,渾天儀專責校驗太陽黃經,由經驗老道的天文生負責觀察記錄,數據每日申時呈報。」
接下來是「資料儲存層」。你翻開《曆象考成》後編,裡面詳細記錄了日躔、月離的運動表,也就是預先計算好的「數據庫」。你要做的,是將這些數據「正規化」,讓它們成為可查詢的資料表。然而,你發現這些表格夾雜著大量的中文星名與古法單位(如「度」、「分」、「秒」),換算十分複雜。於是,你下達了第二道命令:「準備空白宣紙一千張,我來設計新的表格格式,以 1 度 = 60 分 = 3600 秒的量度,將所有數據重新抄錄。」
同時,你察覺到一個嚴重的「核心運行時」問題:你完全不通滿文與拉丁文。現存的曆法文獻中,許多關鍵的計算公式仍保留了原始的歐洲幾何模型描述,翻譯與理解都需要時間。這裡沒有 Stack Overflow 可以搜尋,唯一的「API」是那些戴著水晶眼鏡、學識淵博的老天文官。你必須「逆向工程」他們腦中的知識內核,將那些口耳相傳的「演算法」轉化為自己的邏輯。
最後,你制定了團隊的「軟體開發流程」。你要求所有人必須用毛筆在「任務卡」上記錄計算過程,並在每日卯時召開「站立會議」,每個人都要用簡短的語言報告昨日計算結果與遇到的例外狀況。你扮演產品經理與技術總監,利用「看板方法」將「預測七政經緯度」這項大需求拆解成數百個小任務,貼在牆上。
現在的欽天監,像極了一家陷入「技術債」的新創公司。而你就是那位救火隊長。你深吸一口氣,開始規劃整體「系統架構」。你意識到,這個專案的核心,其實是重寫整個曆法運算邏輯。
現代程式設計師的直覺告訴你,預測天象的關鍵在於建立一個「時間與星體位置」的函數。你必須讓這個函數在給定的時間(t)下,輸出正確的位置值(p)。《時憲曆》的核心公式皆以幾何模型呈現,例如,太陽的運動軌跡是以地球為中心(第谷體系)。你手上沒有現代天文曆表,只有前人留下的「太陽均數表」。
這裡的「均數」就是因地球繞太陽(或太陽繞地球)的橢圓軌道而產生的時間差。你發現,為了精準預測,你必須求解克卜勒方程式的近似解。在現代電腦中,你可以用牛頓法迭代幾次就收斂;但在清朝,你必須使用算盤。
「給我一把算盤!」你對著小太監喊道。接著,你召集了四位最聰明的天文生,傳授他們「牛頓-拉弗森迭代法」的記憶體形式。你用毛筆在宣紙上寫下方程式:E = M + e * sin(E)。你解釋道:「我們現在要找 E 的值,它藏在等式兩邊。我們先用天文表上的年應來猜一個初始值 E₀,然後計算 E₁ = M + e * sin(E₀)。把新的 E₁ 再代回右邊,得到 E₂。如此反覆,直到兩次的差值小於」你比了比手指,「小於百分之一秒,也就是小於 0.000278 度。」
天文生們面面相覷,但當你帶著他們用算盤跑了兩次迭代後,他們的眼睛亮了起來,因為收斂的速度快得驚人。他們稱這神奇的方法為「循環逼仄法」,而你默默在心裡吐槽:這不就是最基本的 fixed-point iteration 嗎?
然而,單純的迭代還不夠。你必須處理「內插法」。古代曆表通常記錄每隔一日的太陽位置,但你需要的是任意時刻的精確位置。你教導他們使用「差分法」(有限差分),而不用複雜的牛頓內插公式。你畫了一張表,將每日黃經、一階差分、二階差分依序排列,發現二階差分幾乎恆定。你驚喜地發現,這意味着你可以透過簡單的加減法,就算出任意時刻的太陽位置,完全不需要解多項式!
「天啊,這不就是一個二階牛頓前向差分公式嗎?」你小聲嘀咕,身旁的官員疑惑地問:「監正大人,您說『牛頓』?」你趕忙掩飾:「不不,是『扭噸』,我是說扭緊發條的噸位力道控制。」
如果你以為太陽曆法搞定了就萬事大吉,那你就太天真了。真正讓欽天監頭痛的是「月離」,因為月球的運動遠比太陽複雜,受到太陽攝動的影響,有多達數十項的週期性「改正項」。
《曆象考成》後編給出了一套基於橢圓軌道與攝動理論的「均數表」,密密麻麻地列出了不同「引數」(也就是月球平均角距)對應的改正值。你的任務是將這個表視為「查表函數」,用內插法算出任意角距的精確改正值。
你設計了一個陣列(以宣紙製作),行是「初宮」的度數,列是「本輪」的均數。當天文生要計算某個時刻的月離時,他們先算出「引數」,然後從表中查出對應的均數,再透過「一次差」與「二次差」內插出更精確的數值。這項工作極度繁瑣,但只要每一格計算正確,整體的準確度就能令人滿意。
你設立了一道「品質檢驗關卡」。你讓兩組人獨立計算同一天的月離數據,如果他們兩邊的結果相差超過半度,就必須回頭檢查。這就好比是程式碼審查(Code Review),只是手段換成了毛筆與臘葉。這確保了計算過程的「無 Bug」。
「平氣」是將一回歸年平均分成 24 等份,每份約 15.2 天,這簡單易懂。但「定氣」是根據太陽的實際黃經位置來劃定,由於地球繞太陽的軌道是橢圓,太陽在近日點移動較快,在遠日點移動較慢,所以每個節氣之間的間隔是不同的。
現代的農民曆都使用定氣。然而,在清朝初年的曆法改革中,欽天監內部為了這個議題曾激烈爭論。你必須決定你的「曆法引擎」採用哪種邏輯。
你的程式設計師思維告訴你:「當然是定氣,因為那反映了真實的物理現象,而且更符合天文觀測。」然而,一位老官員顫巍巍地提醒你:「祖宗之法,不可變也。採用定氣會導致某些年份的節氣日期與舊曆書不同,恐會引起民間困惑。」
你走到白板(實際上是一面牆)前,畫出一個座標圖。「各位,」你指著曲線解釋道:「太陽的運動就像是一輛馬車,有時快,有時慢。平均速度是里程除以時間。但是,沒有一個馬車夫會以完全恆定的速度駕車。『平氣』是假設這輛車一直用平均速度前進;『定氣』是記錄真實的路上里程碑。你們說,哪一種更符合我們要『精準預測天象』的宗旨?」
最終,你拍板定案:採用定氣法,並利用你設計的「太陽中心差方程」來推算每個節氣點的精確時刻。這是你真實擁有的現代天文知識優勢,讓你能夠自信地做出正確的決策。
乾隆皇帝要求七天內呈報節氣,這還只是基本盤。真正壓力山大的是,你的任期內,遲早會碰到「日食」或「月食」的預測。在古代,日食被視為「天狗食日」,是上天對天子的警示。如果你的預測時間差了半個時辰,你的官帽可能就不保了。
你知道,預測日月食需要非常高的精度,需要同時計算太陽與月亮的精確位置,以及它們相對於黃道和白道的交點。在現代,這需要複雜的 VSOP87 理論和 ELP 理論。但現在你只有算盤與《曆象考成》的數據。
第一步:計算「朔日」或「望日」的精確時刻,也就是月相為新月或滿月的時間點。你設計了一個「朔望月迭代迴圈」,從上一個已知的朔日開始,加上 29.5306 天,得到初一個預估,再根據太陽與月亮的不均勻運動,透過迭代修正,直到將朔日控制在某個時辰內。
第二步:判定食限。只有在「朔日」或「望日」時,太陽與月亮黃經相同或相差 180 度,且它們位於黃白交點附近時,才會發生日月食。你建立了一個查表,如果當天日月距離交點超過一定的度數,就直接回報「無食」。這好比程式的「條件判斷」。
第三步:計算食分。透過計算月亮與黃白交點的距離,推算出太陽或月亮被遮蓋的面積比例。你將這項任務分配給對幾何計算較有心得的天文生,要求他們畫出精確的示意圖。
這天晚上,你徹夜未眠,將計算日月食的流程寫成了一份詳細的「虛擬碼」,用毛筆寫在奏摺上,每一步都用「若…則…否則」的語法,讓底下的人能按表操課。
你看著寫滿「if」與「else」的「程式碼」,苦笑了一下。這大概是全世界最昂貴的 Python interpreter 了——它需要上百個人力來跑一個迴圈。但為了活下去,你只能讓這個「人肉計算集群」高速運作。
除了日月星辰,你還要處理一個古典曆法的經典難題:「置閏」。為了讓陰陽合曆(農曆)與季節保持一致,必須每隔幾年增加一個閏月。清朝的規則為「無中氣置閏法」。在一個月份中,如果沒有包含任何一個「中氣」(即冬至、大寒、雨水等,從黃經 0 度開始每隔 30 度劃分),那麼這個月就是閏月。
你用此邏輯建立了流程圖。然而,系統的效能瓶頸出現了:要找出「哪個月份沒有中氣」,你需要精確計算每個中氣的時刻,這仰賴於前面提到的「定氣」計算。你為了簡化,想到了一個繞過牛頓法的「快速查表法」:提前算好一整年的所有中氣時刻,並將其轉換為儒略日數,接著比對該月朔日是否落在兩個中氣之間。
這就像是在處理兩個時間序列的比對,「朔日」序列與「中氣」序列。你的歷史背景知識在此派上用場,你知道,平均而言,大約每 19 年會有 7 個閏月,這就是「十九年七閏」的週期性。你利用這個規律,設定了一個「例外處理」條件:如果一次驗算中,連續兩年出現沒有中氣的月份,系統便要發出警告,這就是異常處理。
「我們的曆法引擎,果然需要健全的資料結構與演算法。」你喃喃自語。你開始覺得,這或許可以寫成一篇論文〈清代曆法中的資料結構設計〉。
乾隆皇帝高坐龍椅,眾臣分列兩側。他隨手翻了翻,目光停留在「秋分」的那一行。「庚申日,卯時一刻,太陽黃經一百八十度。」他讀出聲,然後看向你:「卯時一刻?朕記得前一任監正呈報的秋分是在辰時。為何與你所算相差將近一個時辰?」
你深吸一口氣,走向前,行禮後開口:「啟稟皇上,舊法所用之均數表乃百年之前所測,與天行略有出入。微臣上任後,連夜校訂了太陽近點與最高行度,並以新法推算。」你頓了頓,接著說:「再者,古法對時間的測量,以京師為準,但日月運行實有微差。微臣使用更嚴謹的三角函數疊加,精確至度分秒。」
這是考驗的時刻。你沒有望遠鏡,但你帶來了你的「殺手鐧」——一種精密的驗證方法。你早就囑咐天文生在殿外架設了「日晷」與「漏刻」。你奏請皇上移駕殿外,親自見證午時的到來。
你指著當空的太陽,對皇上說:「請皇上觀看此儀器。」你讓工匠在一個木盤中垂直豎立一根針,並畫好刻度。當太陽到達正南方向(午時)時,針影將縮至最短,指向正北方。
「今日是秋分前三日,太陽將在午時三刻達到最高點。微臣預測,當漏刻走到午時三刻整,這根針的影子會正好落在這個位置。若有偏差,微臣甘願受罰。」
人群中爆發出低低的驚呼。乾隆皇帝的嘴角微微上揚:「好,朕今日總算見識到什麼叫真才實學。傳朕旨意,欽天監監正辦事得力,賞雙眼花翎,賜黃馬褂!」
你內心的大石頭落了地,然而後背已經被冷汗浸透。這不是一場科學勝利,而是一場「風險極高的產品發表會」。你靠著對「太陽中天」這個原理的自信,成功演示了你的「曆法引擎」的可靠性。
你向皇帝建議設立「天文算法科」,挑選八名聰慧的八旗子弟,作為你的「核心開發團隊」。你教導他們十進制與六十進制的快速轉換技巧,並傳授阿拉伯數字的寫法(你假裝這是「西學東漸」的密傳)。
更重要的是,你教導他們「模組化」的概念。你將曆法運算分成「日躔」、「月離」、「交食」三大模組,每個模組有明確的輸出與輸入介面。你設計了一套標準化的「填表」格式,不同的人負責計算不同的行星,最終數據再由核心演算法整合。
為了避免「人為計算錯誤」,你發明了一種「交叉驗證法」。每個月,你都要求下屬獨立計算一個已知天象的舊時刻,例如去年的秋分,並與舊曆書比對。如果差距在一刻鐘以內,就代表該計算員「狀態良好」;若誤差過大,就必須重新培訓。這就像是在跑單元測試。
夜深人靜,你獨自一人登上觀星台,寒風陣陣。你仰望星空,那璀璨的銀河如同今日在鍵盤上敲擊的程式碼,繁複卻有邏輯。你想起你的筆電,想起現代世界的網際網路、區塊鏈、大型語言模型。那時的工程師拼命追求更高性能的顯卡,追求更快的資料傳輸速率。
而你現在,卻用著最原始的「運算硬體」——人腦與算盤,去重現一個塵封兩百年的古代星表。你意識到,技術的本質是共通的。任何工具,只要它能精準地描述自然規律,並為人類提供決策支持,它就是一種「科技」。你的 Python 知識與資料結構概念,在這個時空並沒有失效,反而化作了一種「思維框架」,幫助你在混亂的古代數據中,理出清晰的條理。
你開始理解為何古代的欽天監總顯得神秘莫測。因為他們掌握的,是當時世界上最先進的「數據處理」與「事件預測」技術。而你所做的,正是將這項古老的技術,進行了一次「代碼重構」。
如果這趟荒謬的穿越真的降臨在你頭上,除了滿腹的現代知識,你最好的武器是什麼?或許不是默寫出 Python 直譯器,也不是硬背 VSOP87 天文理論。真正的武器,是你那顆受過嚴格 software engineering 邏輯訓練的腦袋。
你懂得如何拆解問題,如何抽象化,如何用「資料結構」去理解世界,如何用「演算法」去解決難題。即使沒有電力,你依然可以找到「等效」的實現方式。你會發現「內插法」可以替代數值分析函式庫,而「有限差分」甚至可以讓你不會微積分也能預測軌道。
如果你是那位被丟到清朝的工程師,你會怎麼做?是驚慌失措,還是像我一樣,在厚重的《曆象考成》中,看見了屬於我們這個時代的「解決方案」?
或許,在那個沒有 debugger 的時代,你唯一能做的,是用你的理性與耐心,將一行行「人類程式碼」寫在歷史的奏摺上。這既是一場穿越劇,也是一場對「工程師精神」的極致致敬。
歡迎在下方留言,分享您的想法、心得或疑問。所有留言都會透過 GitHub 帳號 進行驗證。