大名鼎鼎的 IEEE 754 是定義 32-bit 浮點數的官方文件,於 1985 年由 發布第一個版本。
下圖[1] 顯示 Float32 各位元的功能:
位元可以劃分為三部分:
1
表示負,共 1 位計算的方法如下:
浮點數具有幾種特殊值:
0b1111_1111
&& fraction 為 0
&& sign 為 1
: -INF
0b1111_1111
&& fraction 為 0
&& sign 為 0
: INF
0b1111_1111
&& fraction 為非 0
&& sign 為 0
: NaN
要將 10 進位小數轉換成浮點數格式需要兩個步驟
10 進位數值是如何轉換成 2 進制呢?
假設有 為一 10 進制數字,可分為整數 和小數 兩部分:
使用除 2 取餘可得,餘數順序分別是 ,當被除數小於 2 時停止
使用乘 2 取整方式 (乘 2 後看整數是否為 1) 得到,當被乘數等於 0 時停止
讓我們以 9.73
為例
將 換成以浮點數表示:
組合起來就是:
透過印出 9.73
float 的 binary format 得到同樣的結果:
浮點數的表達極限在哪裡?
由於不可能透過有限位數表達無限多的實數,因此透過位元表達勢必存在極限,下圖[3]很好地說明符點數的分佈會隨著數字增加,而使最小精度單位 (Unit in Last Place, ULP) 以 2 的指數倍下降。例如:
時 ULP 是 1,而對於 則是 2。
浮點數特別的極限:
範例觀察
接下來讓我們用程式觀察幾個 ULP 計算範例:
觀察以下兩組比較:
123456792.0
和 123456784.0
, 由於 123456789.0
更靠近前者,因此會選用前者的表示方式123456800.0
和 123456808.0
, 由於 123456804.0
與兩者同樣接近,最後選擇了前者,Why?這跟浮點數的捨入方法有關:
123456800.0
的 LSB 是 0 而,12345808.0
則是 1,故捨入前者了解上述內容後,讓我們回顧問題並釐清答案吧!
上述程式的輸出居然是 50002896
,與實際答案 50005000
相差了 2104
,雖然知道浮點數運算會有誤差,但 2104
是怎麼來的,你說的出來嗎?
透過 AI 撰寫以下程式分析該過程
結合上述得知僅當浮點數超過 $2^{24} 時,其 ULP 才會 > 1.0。
因此將 float32_sum
依照 (), (33554432) 進行分界,得知 num
分別為 :
觀察這附近的輸出:
在前半部分 (),可以參考這張圖:
在藍色區域的中點(含)以左都會被捨入到左側的浮點數 (e.g., 步驟 2),在綠色區域中點(含)以右都會被捨入到右側的浮點數。
誤差可以這樣統計:
1, 0
,循環長度為 ULP。在 為 0, 1, 2, -1
,在 為 1, 2, 3, 4, -3, -2, -1, 0
。每次循環的和是 ULP / 2。 區間內的等差數值數量不一定會整除 ULP,要計算最後一組剩下的 error_diff 加上前面每一組的 error_diff_sum
目前頂多是暴力解,有數值解嗎?算式如何列?
背後的原理
todo
由於浮點數間隔在一段距離內 () 是 均勻分布 的,又我們的目標數是遞增,所以
至此,我們知道累積的誤差來源正是因為捨入方向。IEEE 754 預設的捨入模式稱為銀行家捨入法 (Banker's rounding, 四捨六入五取偶),詳見延伸閱讀: 捨入方法。
我們可以藉由 Kahan summation algorithm
其原理是…
Kahan summation algorithm 的限制?
Linux 核心在大部分情況下禁止使用 FP 暫存器,因其上下文切換時造成的 CPU 性能損耗非常大。
定點數運算