「錯了多少」的兩種說法
從前兩篇你已經知道,每個數值結果都是近似,而誤差會從建模、資料、截斷與捨入四處滲入。接下來自然要做的,就是真的把那道落差量出來。量法誠實地說有兩種,而報告結果的全部功夫,就在於選對哪一種。設 x 是你希望知道的真值,x~ 是你的計算實際產生的那個數。
第一種最樸素:絕對誤差就是距離 |x~ - x|,用量本身的單位來算。一段橋身真值是 5.00 公尺、卻算成 5.03 公尺,絕對誤差就是 0.03 公尺。它具體、摸得著——但帶著一個盲點。0.03 的誤差,在真值一百萬旁邊小得可笑,在真值 0.001 旁邊卻是場災難。絕對誤差根本不知道它量的東西是大是小。
第二種正好補上那個盲點。相對誤差把落差按答案的大小縮放:|x~ - x| / |x|(x 非零)。如今「百萬分之 0.03」是 3e-8(極佳),而「0.001 分之 0.03」是 30(一場災難),數字本身就告訴你是哪一種。由於是個純比值,相對誤差無因次、與尺度無關——不論你用公尺還是公釐量,讀數都一樣——因此每當量值橫跨許多數量級時,它就是談誤差的自然語言。乘以 100,就得到我們熟悉的百分誤差。
在實務上、以及在高維中如何量
兩個定義都藏著一個尷尬的問題:它們都需要 x,那個真值——也就是你手上偏偏沒有的東西。如果你已經知道精確答案,就根本不必去算近似了。所以現實中你幾乎從不直接量出真誤差,而是去估計它。有個又便宜又無所不在的招數就住在迭代法裡:盯著相鄰兩次迭代之間的變化 |x_{n+1} - x_n|,把它當作那看不見的誤差的實用替身。大多數停止準則檢驗的,其實正是這個量。
當答案不是單一數字,而是一個向量或一個函數——A x = b 的解、整條取樣曲線——「距離」就需要一個範數。絕對誤差變成某個選定範數下的 ||x~ - x||:2-範數 ||x~ - x||_2 就是我們熟悉的誤差向量歐氏長度,而最大範數則回報最糟的那個單一分量。相對誤差接著除以真值的範數,||x~ - x|| / ||x||。範數的選擇是有講究的:一個結果在 2-範數下可能看來極佳(平均很小),卻有一根難看的尖峰,只有最大範數才照得出來。
有效位數:可以直接讀出的誤差
相對誤差有一個美妙又貼近人感受的翻譯:有效位數。它們是一個數中真正攜帶可信資訊的那些位數,從第一個非零數字往後數。在 0.004520 中,有效位數是 4、5、2 與末尾的 0——共四位;前導的零只標示小數點位置,不算在內。有效位數是日常用來說「某樣東西被知道得有多精確」的方式。
這裡就是那座橋,也是本篇最有用的一條法則:一致到 d 位有效數字,本質上等同於相對誤差約為 10^(-d)。更仔細地說,當 |x~ - x| / |x| 大約是 5 * 10^(-(d+1)) 時,x~ 與 x 一致到約 d 位有效數字——但乾淨好用的版本「相對誤差約 10^(-d) 就代表約 d 位可信數字」,才是你每天會用到的。所以三位正確數字對應相對誤差約 1e-3;六位,對應約 1e-6。
relative error significant digits 1e-1 ~1 1e-3 ~3 1e-6 ~6 1e-16 ~16 (double-precision ceiling) d digits <--> relative error ~ 10^(-d)
最後那一列並非巧合。標準雙精度算術以約 1.1e-16 的相對精度儲存數字,因此任何雙精度結果都不可能被信任超過約 15 至 16 位有效十進位數。這是硬體設下的硬性上限,不是你的演算法設的。舉個算例:用 22/7 = 3.142857... 近似 pi = 3.141592...;絕對誤差是 0.001264,相對誤差是 0.001264 / 3.141593 = 4.0e-4(約 0.04%),它告訴你 22/7 準到大約三位有效數字——正如你從輸出讀到的那樣。
當位數說謊:有效位數的兩個葬身之處
現在來談誠實的部分,也是這篇不只是記帳的原因。印出 16 位,不代表有 16 位是對的。一支程式可以得意地顯示 3.14159265358979,而其實只有前六位是真的,其餘純屬雜訊。有兩個不同的效應會悄悄吃掉你的有效位數,懂行的人會學著把兩者都嗅出來。
第一個是災難性抵消。把兩個幾乎相等的數相減,前導位數——它們一致的那幾位——會抵消為零,只留下低位數,而捨入雜訊早已潛伏在那裡。雜訊就被提拔到答案最重要的位置上。對極小的 x 計算 1 - cos(x) 是經典陷阱:cos(x) 捨入為 0.9999999...,相減抹去了一致的位數,真值約 5e-9,卻只剩寥寥幾位是誠實的。把它改寫為 2*sin^2(x/2) 就繞過了相減、救回了位數——這是重新表述,不是更高級的電腦。你會在浮點數那一級深入認識它。
第二個效應更深,而且完全不是你程式的錯:病態。有些問題天生就敏感——輸入的微小晃動,會逼真答案大幅擺盪——而條件數量的正是這個幅度。一條粗略卻難忘的法則:相對條件數約 10^8 的問題會把輸入誤差放大約 10^8 倍,因此在任何演算法開始之前,就先耗掉你約 16 位中的約 8 位。你還沒起跑,就已先輸掉一半精度,而且再聰明的程式也買不回來,因為那敏感性住在問題裡,不在方法裡。
像專業人士那樣報告結果
把這些湊在一起:一個沒有附上誤差陳述的結果,幾乎稱不上是結果。成熟的習慣是:報告一個數字時,同時附上一個誠實的說法,說你願意為它的幾位數背書——並且明確指出那個說法是絕對的還是相對的。下面這套小小的紀律,能把一份輸出變成值得信賴的東西。
- 先決定你的幣值。自然容忍度是固定在物理單位上的(用絕對誤差),還是答案橫跨許多量級(用相對誤差)?如果答案可能穿過零,就規劃一個混合容忍度 |誤差| / (1 + |x|),讓任何東西都不至於爆炸。
- 在沒有真值的情況下估計誤差。用迭代間落差 |x_{n+1} - x_n|、殘差檢查、或更細網格的比較——一個由你真的看得見的量得出的事後估計。
- 換算成位數。相對誤差約 10^(-d),意味大約 d 位可信的有效數字——這才是你有資格印出的位數。
- 為風險打折扣。若計算中相減了幾乎相等的數,就懷疑有抵消;若問題敏感,就先扣掉約 log10(條件數) 位。永遠別印過你那約 16 位的雙精度上限。
照這麼做,你就從一個「螢幕顯示什麼就照抄什麼」的人,升級成一個「知道那代表什麼」的人。下一篇我們轉向故事的另一半——同一個大 O 記號,被以兩種完全不同的方式重複使用:O(h^p) 講誤差縮小得多快,O(n^3) 講成本增長得多快。誤差告訴你答案好不好;成本則會告訴你你負不負擔得起。