Go:浮點數精度丟失問題詳解
請看以下 Go 代碼,會返回 0.7
嗎?
var num float32
for i := 0; i < 7; i++{
num = num + 0.1
}
fmt.Println(num)
0.70000005
還有,除了語言之外,你還可以在 MySQL 等數據庫中試試 float 類型數據的字段疊加,得到的數據是否精確。
要了解產生這個現象的原因,就要先了解計算機是如何定義和表示 float 類型的。不同於正整數類型的表示方法,float 類型在計算機中的表示略顯複雜,遵循的是 IEEE754標準
。
下面,我們就講一下 IEEE754標準
。
我們首先回顧一下整數類型在計算機中的表示。我們知道: 計算機只認識 0 和 1;那麼,對於像 6 一樣的這種正整數,我們要做十進制到二進制的轉換。即:
所以,十進制 6
最終轉化爲二進制爲 110
。
這很好理解,但是,如何表示 6.1
等這類小數呢?有人說了,可以找個特殊的符號,用來表示小數點 .
,把 6.1
中 6
和 1
隔開;聽起來是個不錯的辦法。其實 IEEE754
還真就是這麼做的,只不過思路略有些複雜,總體思路就是:仿照用 "科學計數法"!
我們再回顧一下什麼是 科學計數法
。把一個數表示成a與10的n次冪相乘的形式(1≤|a|<10,a不爲分數形式,n爲整數),這種記數法叫做科學記數法。
也就是:1.360X10^4
這種計數方式。
我們可以仿照科學計數法,來表示浮點數,把二進制數統一表示成 1.0110101X2^n
這種形式。數據層面怎麼表示出這種形式呢?根據 IEEE754
的標準,將數據分爲三部分:
從左到右分別表示:符號位 (正負數)、指數位和小數位
以單精度浮點數爲例,單精度浮點數一共 32 位 (雙精度 64 位,即平時所說的 double
類型),具體內部表示爲:
這裏有個地方要特別注意:因爲數據最終要表示成 1.0110101X2^n
這種形式,整數位在二進制下,永遠都是 1
,所以在表示 float 類型的時候,直接把 1
給去掉了,假如有就佔據一個 bit 的空間,既然那個 bit 位上永遠都是 1,所以乾脆去掉了。
那麼,具體該如何展示呢?例如小數點後的數字怎麼表示?6.1
能否寫成 110.1
呢?如果能的話小數點後這個 1 代表什麼呢?個數一?那添加幾個零的話,能否認爲是十、一百、一千?似乎是不可以,因爲這樣只能滿足 "視覺效果", 邏輯層面直接說不通。
要明白在小數點後的數字代表除以 2 後的數字,例如二進制下小數點後的第一位 1 代表 1 / 2 等於 0.5
,第二位 1 代表 1/2/2 等於 0.25
,依次類推第三位 1 則代表 0.125
... 具體請看下圖:
所以,給定一個小數,譬如 0.1
,要想得到對應的二進制數,應該是和小數點左邊的計算方式相反:乘以2,記錄整數位
0.1 X 2 = 0.2 0
0.2 X 2 = 0.4 0
0.4 X 2 = 0.8 0
0.8 X 2 = 1.6 1
(1.6 - 1 = 0.6)
0.6 X 2 = 1.2 1
(1.2 - 1 = 0.2)
0.2 X 2 = 0.4 0
0.4 X 2 = 0.8 0
0.8 X 2 = 1.6 1
(1.6 - 1 = 0.6)
0.6 X 2 = 1.2 1
(1.2 - 1 = 0.2)
0.2 X 2 = 0.4 0
0.4 X 2 = 0.8 0
0.8 X 2 = 1.6 1
...
// 無限循環下去
所以, 0.1
用二進制表示爲:0.000110011001100110011...
因此 6.1
用二進制應該表示爲:110.000110011001100110011...
用” 科學計數法 “表示爲:1.10000110011001100110011...X2^2
OK,看來小數位的數可以確定了是 10000110011001100110011
,即去掉整數位 1 後,向後截取的 23 位數 (浮點數不精確的本質原因)。
符號位 0 表示正數,1 表示負數,所以可以確定是 6.1
的符號位是 0;現在符號位有了,小數位有了,只剩下指數 2 如的表示了,該如何表示呢?直接在 8 位的空間內轉化爲 000000010
?
顯然不可以,首先,如果指數位用 原碼
表示,那麼,針對指數位爲負的情況,就得加一個符號位去表示,而且還會出現兩個零的情況:00000000
和 1000000
,操作起來過程複雜~
有人要問那如果使用補碼呢?如果使用補碼,會出現以下情況,請看例子:
可見使用補碼,也不是很方便,於是,引用了另外一種編碼方式——- 移碼。先說說移碼的定義:將每一個數值加上一個偏置常數(Excess/bias),通常,當編碼位數爲n的時候,bias取"2^n-1"或者"2^n-1 - 1"
承接以上 1.01 X 2^-1 和 1.11 X 2^3 比較大小的例子:
1.01 X 2^-1 和 1.11 X 2^3大小?
指數:
-1 + 4 = 3,二進制表示爲:"011"
3 + 4 = 7 二進制表示爲:"111"
7 > 3,即 "111" > "011" 比較完畢
就這樣,浮點數”科學計數法 “的指數位比較變得簡單了,而且,消除了” 正零 “ 和 ” 負零“ 不相同的問題。
因爲 :
假設偏移量是:4
則移碼錶示的0只有:0 + 4 = 4,即“100”
在 IEEE754
中,指數位移碼的偏移量爲指數位數的 2^n-1-1
,爲 127。
所以,回到 6.1
表示的問題上,指數位爲:2+127=129
,二進制表示爲:10000001
因此, 6.1
在 IEEE754
單精度浮點數標準的下,表示爲:
好了,現在瞭解了浮點數 IEEE754
標準的表示方法,知道爲何浮點數相加總是不精確了吧?
因爲浮點數很多小數在二進制環境下很多都無法完整的表示,只能截取部分數據來近似的表示,兩個數相加的話,就是兩個近似的數相加的和,如果相加次數足夠多,精確度自然也就會越來越低
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/xw37DwMwkWay8RWfJ3OCLg