下溢與浮點陷阱
浮點能表示極大的範圍,但兩端的範圍都不是無限的。溢位是大家熟悉的問題:一個大到無法表示的結果變成無窮。下溢是它在小端的鏡像:一個太接近零的結果,落到格式允許的最小指數以下,於是這個數向零塌縮、失去剩餘的位數。想像一把刻度最細處不再更細的尺——比最小刻度還小的長度根本畫不出來,所以讀作零。
IEEE-754 用次正規(非正規)數來緩和下溢的斷崖。一般每個值都有一個隱藏的領頭 1,但對小於最小正規數的量值,格式放棄這個要求、讓領頭位元可以是零,於是它仍能表示比最小正規數逐步更小的值——漸進下溢,向零淡出而非瞬間跳到零。代價是精度:次正規數越小帶的有效位元越少,而且在某些硬體上慢得多,這就是為什麼高效能程式有時啟用「沖刷為零」模式,把次正規數當成精確的零(較快,但較不準確)。
下溢只是浮點那一冊誠實意外裡的一條,初學者該預期它們而非驚慌。加法與乘法不滿足結合律:(a + b) + c 可能不同於 a + (b + c),所以求和的順序會改變答案,平行歸約也可能與串列的不符。你以為相等的數可能並不相等(二進位裡 0.1 + 0.2 不剛好是 0.3),所以你要用容差比較、而非精確相等。相減幾乎相等的數會摧毀精度(災難性抵銷)。標準還定義了特殊值——正零與負零、正負無窮、以及給 0/0 之類用的 NaN(非數值),其中 NaN 不等於任何東西、包括它自己。這些都不是錯誤;它們是一個以精確性換取範圍與速度的系統,有文件記載、可重現的行為。
在單精度下,0.1 + 0.2 算出 0.30000001192...,並非剛好 0.3,所以 (0.1 + 0.2) == 0.3 為偽。而把一個巨大數與許多微小數以不同順序相加會得到不同總和——加法不滿足結合律。
用容差比較浮點數,而非精確相等;求和順序會改變結果。
這些不是硬體錯誤,而是有文件記載的 IEEE-754 行為。實務規則:永遠不要測試浮點數是否精確相等、留意長求和的順序、避免相減幾乎相等的量。沖刷為零以次正規數的準確性換速度——要知道你啟用了哪個。