關於浮點數誤差與IEEE-754

在程式語言中,浮點數基本都是用 float 與 double來表示,但都會存在誤差

正常6.9 * 10 應該要等於6.9,但是答案卻是不相等?

這個原因跟浮點數的儲存原理有關,讓我們開始吧!

 

何謂IEEE-754

自電腦發明以來,曾出現過各種不同的浮點數表示法,但目前最通用的是IEEE二進制運算標準(IEEE Standard for Binary Floating-Point Arithmetic , 簡稱IEEE-754)

在IEEE-754標準中定義了四種浮點數格式,但我只講基本的兩種,分別為單精準度float(32bit)和雙精準度double(64bit)。其中單精準度有24位有效儲存數字,而雙精準則有53位有效數字,相對於十進位來說,分別是7位(224 = 107)和16位(253 = 1016)。

 

為了方便說明,所以先解釋什麼是正規化

正規化就像是數學中的科學記號,如123456通常會表示成1.23456 x 105,而指數部分也有可能是負的,如 0.1234 就會變成1.234 x 1.23456 x 10-1

二進位的正規化 :

這邊以13.125為例,先13轉換為2進制,可得1101,再將0.125轉為2進制

  • 0.125 x 2 = 0.25 … 整數為0 -> 0
  • 0.25 x 2 =0.5 … 整數為0 -> 0
  • 0.5 x 2 = 1 … 整數為1 -> 1

所以13.125 = 1101.001,經過正規化後可得 1.101001 x 23

 

在IEEE-754中,浮點數通常由三個部分組成 :

  • 符號(S) : 用來表示正/負(0/1)。
  • 指數(E) : 正規化後的次方數,採用超127格式,即將原本的次方數加上127,因為次方數有可能是負的,加上在電腦中要表示負號時,必須拿一個位元來表示,所以就將-128~+127改為0~255,所以基準點就從0變成127。
  • 尾數(M) : 正規化後的小數點。

以下範例皆為單精準度 :

 

浮點數與10進制的轉換

以剛剛的 13.125 轉浮點數為例 :

  1. 由於13.125為正,所以符號(S) = 0
  2. 先將數值轉成二進位並正規化 13.125 = 1101.001 = 1.101001 x 23
  3. 計算指數(E) = 127 + 3 = 01111111 + 11 = 10000010
  4. 計算尾數(M) = 101001,因為正規化後一定是1.xxxx,所以不需要儲存個位數
  5. 將各個數值填入浮點數規格中
    S——–E———————M———————–
    0   10000010   101001 0000 0000 0000 0000 0

這樣就就完成了10進制轉IEEE-754浮點數

 

而浮點數轉10進制也是一樣

將剛剛的0 10000010 10100100000000000000000轉10進制 :

  1. 由於S = 0,所以此數為正
  2. 中間8位元的超127指數(E)為 100000102,將其還原130 – 127 = 100000102 – 01111112 可得 3 = 112
    所以要將尾數乘上23
  3. 最右邊23個為位元值為101001……,將隱藏的個位數還原,可得1.101001……
  4. 最後將還原後的尾數乘上指數 1.1010012 x 23,並轉為10進位,即可得到 13.125

 

所以我們可以知道,以32bit的單精度浮點數來說,可以儲存的最大位數為 尾數 23+隱藏個位數 1 = 24位。

 

關於浮點數的精度

因為有些10進制小數無法完美的用2進制表示,只能用無限的位數來趨近於10進制小數,當我們以24位數為上限時,在儲存時就會省略一些位數,導致還原時的小數不夠精準。

 

以0.01為例,將它轉為單純的二進制可得

0.000000 101000111101011100001010 0011110101 ….

正規化後得 1.01000111101011100001010  *  2-7,在將它存入32bit的IEEE754規格中。

但是單精度浮點數最多只能存24位數,代表後面一串位數都必須省略,所以當我們再還原時,就不再是0.01了

而是 0.0099999997764826

 

在浮點數中,最能逼近1的數為 2-1  x  2-2  x  2-3  x  2-4 + ……

根據等比級數計算,所以單精度浮點數能表達的最大小數就是 :

= 1 – 0.000000059604…. (浮點數誤差)

= 0.99999994039

 

從上面的計算可以知道,小數點後7位的數在儲存時不會被誤差影響,這就是為什麼程式中的float的準度為小數後7位,double為小數後16位的原因。

 

如果想要有更精確的科學運算的話,只能透過陣列來搭建一個小數運算,這時你要多精確都沒有問題,之後有機會再寫一篇關於大數運算的文章。

 

如有錯誤請指證

 

參考資源

WIKI IEEE-754 : here

IEEE 浮點運算標準 : here

C 語言取出/設定浮點數正規化欄位  : here

C/C++ 浮點數特殊值 : here

[C&C++] 浮點數精準度 (Floating-Point Precision) : here

IEEE-754 浮點數的表示法 : here

MSDN IEEE 浮點表示 : here

Binary floating point and .NET : here

What Every Computer Scientist Should Know About Floating-Point Arithmetic : here