JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

機器 epsilon 與單位捨入

浮點數彼此之間留有空隙,而這個空隙的大小是有名字的。認識機器 epsilon 與單位捨入——這兩個數字精準地告訴你,你能信任多少位數字。

數字之間的空隙

在前一篇指南裡,你看到電腦在 IEEE 754 標準下,把一個實數存成一個符號、一段固定長度的二進位尾數,以及一個指數。關鍵的後果是:可表示的數字只有有限多個,因此這些數字落在一個離散的網格上,彼此之間有空隙。關於這個網格最重要的事實是:它不是等距的——數字愈大,空隙也愈大,因為指數每往上移一格,間距就加倍。

想像 1 到 2 之間的數字。尾數的位元數固定,所以它們均勻地鋪在這段區間上。再看 2 到 4 之間:同樣多的尾數樣式必須覆蓋一段寬兩倍的區間,因此那裡的間距正好是兩倍。在 4 到 8 之間又再加倍。網格在接近零時細密,在遠處時粗疏——就像一把尺,刻度往右讀時愈來愈疏。這正是為什麼浮點誤差最好用相對的方式衡量,而不是絕對的方式。

機器 epsilon 究竟是什麼

機器 epsilon是緊鄰數字 1 上方那個空隙的大小。更精確地說,它是從 1 到下一個更大的可表示數字之間的距離。如果尾數在領頭的 1 之後帶有 p 個有效位元,那麼下一個數字就是 1 + 2^(-(p-1)),所以機器 epsilon 等於 2^(-(p-1))。對於雙精度——它保留 52 個尾數位元——這就是 2^(-52),約為 2.22 × 10^(-16)。單精度則是 2^(-23),約為 1.19 × 10^(-7)。

一個方便的心理測試:機器 epsilon 是使電腦把 1 + eps 算出大於 1 的最小數 eps。加上任何小於半個空隙的量,它就會直接捨回到剛好是 1——這個加法消失了。這就是為什麼像 `while (1 + x > 1) x = x/2;` 這樣的迴圈,會在 x 縮到大約機器 epsilon 時停下:它其實是在摸索網格的邊緣。

單位捨入:相對誤差的天花板

機器 epsilon 描述空隙;單位捨入(記作 u)描述的是當你把一個實數捨入到網格上時所犯的最壞誤差。在四捨五入到最近——IEEE 754 預設的捨入模式——之下,任何在範圍內的實數 x,會落在不超過半個空隙之外的網格點上。半個空隙相對於該數字而言,至多是 2^(-p),也就是機器 epsilon 的一半。所以對雙精度而言,u = eps/2 = 2^(-53) ≈ 1.11 × 10^(-16)。

最乾淨的陳述方式是捨入模型。令 fl(x) 表示最接近 x 的浮點數。那麼對範圍內每一個 x,都有 fl(x) = x(1 + delta),其中 |delta| <= u。把它唸出來:捨入改變一個數字的相對量不會超過 u。這一條不等式是幾乎所有浮點數運算分析的基石——儲存一個數字的相對誤差以 u 為界,就這麼簡單。

fl(x)         = x (1 + delta),    |delta| <= u
fl(a op b)    = (a op b)(1 + d),   |d| <= u,   op in { +, -, *, / }

double:  eps = 2^-52 ~ 2.22e-16,   u = eps/2 = 2^-53 ~ 1.11e-16
single:  eps = 2^-23 ~ 1.19e-07,   u = eps/2 = 2^-24 ~ 5.96e-08
捨入模型:儲存一個數字,以及每一次正確捨入的運算,相對代價至多為 u。

捨入運算模型

IEEE 754 承諾的不只是把數字存得好:基本運算 +、-、*、/(以及開平方根)每一個都是正確捨入的。意思是硬體會算出該運算的精確實數結果,然後把它捨入一次到最近的網格點。所以對任意兩個已儲存的數字 a 與 b,算出的結果滿足 fl(a op b) = (a op b)(1 + d),其中 |d| <= u。每一次運算注入的誤差至多為相對的 u——不多,也不會有意外。

這既令人安心,也是一個陷阱。安心之處:單一運算的精度就跟格式允許的一樣高。陷阱之處:誤差會在許多運算之間累積,更糟的是,一個本來極小的捨入誤差可能被一個條件惡劣的步驟放大。經典的禍首是災難性抵消——兩個幾乎相等的數字相減。每個輸入的相對捨入誤差至多為 u,但相減把相同的領頭數字全丟掉,只留下充滿雜訊的尾巴,於是差值的相對誤差可以遠遠膨脹超過 u,即使沒有任何一個運算違背它的承諾。

為什麼在你算任何東西之前這就很重要

單位捨入不是個註腳——它是後面每一篇指南衡量誤差所用的單位。當你之後讀到某個演算法是向後穩定的,精確的陳述是:它對一個資料只被擾動了 u 的小倍數的問題,回傳了精確的答案。當你讀到一個數值穩定性的界,右手邊幾乎總是某個量乘以 u 再乘以一個條件數。把 u 記在腦中——雙精度 10^(-16)、單精度 10^(-7)——能讓你在幾秒內為一個結果做合理性檢查。

  1. 決定你的格式:雙精度(u ≈ 1.11 × 10^(-16),約 16 位)是預設;單精度(u ≈ 5.96 × 10^(-8),約 7 位)只在記憶體或速度真的有需要時才用。
  2. 把 u 當成你的起始誤差預算——在工作開始之前,任何結果所能有的最佳相對精度。
  3. 對每一步,問自己:它的條件好嗎,還是會放大誤差?條件數為 10^k 大約會從你的預算中扣掉 k 位。
  4. 拿最終答案的預期精度去對照 u 做合理性檢查:如果你期待 10 位好數字,但數學說你只能保住 4 位,那就相信數學,重新思考演算法。