IEEE754浮點數

前言

  Go語言之父Rob Pike大神曾吐槽:不能掌握正則表達式或浮點數就不配當碼農!正則表達式

  You should not be permitted to write production code if you do not have an journeyman license in regular expressions or floating point math.express

  此前使用Java寫Spark SQL業務時,也有遇到浮點數比較問題即x>70的記錄行竟然出現了70的記錄,儘管SQL作了類型轉換再比較也無濟於事....spa

  所以瞭解浮點數是頗有必要的喲~~code

什麼是浮點數

  電氣和電子工程師協會IEEE對於計算機浮點數的存儲、運算、表示等推出了IEEE754標準!blog

  標準中規定:ip

    float32位單精度浮點數在機器中表示用 1 位表示數字的符號,用 8 位表示指數,用 23 位表示尾數。it

    double64位雙精度浮點數,用 1 位表示符號,用 11 位表示指數,52 位表示尾數。io

    其中指數域也稱爲階碼。浮點數存儲字節定義如圖:console

         

浮點數正規化

  尾數不爲0時,尾數域的最高有效位爲1,這稱爲規格化。不然,以修改階碼同時左右移動小數點位置的辦法,使其成爲規格化數的形式。class

  浮點數x真值表示:

  x=(1)S×(1.M)×2e

  float:    e=E127

  double:     e=E1023 

  • S  符號位    0表示正 1表示負
  • e  指數位    階碼E減去移碼
  • M 尾數位    二進制形式移碼

移碼

  移碼是真值補碼的符號位取反,通常用做浮點數的階碼,目的是便於浮點數運算時的對階操做。

  對於定點整數,計算機通常採用補碼的來存儲。

  正整數的符號位爲0,反碼和補碼等同於原碼。

  負整數符號位都爲1,原碼,反碼和補碼的表示都不相同,由負數原碼錶示法變成反碼和補碼有以下規則:
  (1)原碼符號位爲1不變,整數的每一位二進制數位求反得反碼;
  (2)反碼符號位爲1不變,反碼數值位最低位加1得補碼。

   好比,以一個字節來表示-3,那麼[−3]原=10000011 [−3]反=11111100 [−3] 補=11111101  [−3]移=01111101

  

舉個栗子

3.14的單精度浮點數表示

首先將3.14轉成二進制:

整數部分3的二進制是11

小數部分0.14的二進制是:0.0010001111010111000010[10001111.....](方括號中表示小數點後第23位及以後)

這樣,3.14的二進制代碼就是:11.0010001111010111000010[10001111....]×20

那麼用正規化表示就是:1.10010001111010111000010[10001111....]×21

方括號表示的超出23位以後的二進制部分,因爲單精度浮點數尾數只有23位,因此須要舍入(舍入方法見後)

因爲第24位爲1,且以後 不全爲 0,因此須要向第23位進1完成上舍入:1.10010001111010111000011×21

而其指數1,須要加上移碼127,即128,也就是1000 0000

它又是正數,因此符號爲0

綜上所述,3.14的單精度浮點數表示爲:
0 1000-0000 1001-0001-1110-1011-1000-011

S符號位    

e指數位 1000-0000

M尾數位  1001-0001-1110-1011-1000-011

十六進制代碼爲:0x4048F5C3

偏差

  經過栗子可知,3.14的單精度浮點數表示是0 1000-0000 1001-0001-1110-1011-1000-011。如今咱們來還原,看看它的偏差:

  指數是128,那麼還原回去(減去移碼),實際指數就是1

  尾數還原也就是:10010001111010111000011,因此正規化形式是:1.10010001111010111000011×21

  也就是11.0010001111010111000011

  利用二進制轉十進制,可得它對應的十進制數是:3.1400001049041748046875  不等於3.14

  這就是爲何浮點數運算結果在業務代碼中老是不可確切預期的緣由!!!!

機器ε

  機器ε表示1與大於1的最小浮點數之差。例如雙精度表示1和表示大於1的最小浮點數

  

  

  雙精度浮點數的機器ε = 2-52 ≈ 2.220446049250313e-16

  同理,單精度的機器ε = 2-23 ≈ 1.1920928955078125e-7

  在舍入規則中,相對舍入偏差不能大於機器ε的一半。

非正規化

    單精度浮點數爲例

  (1)0的表示

    對於階碼爲0或255的狀況,IEEE754標準有特別的規定:

    若是 階碼E=0而且尾數M是0,則這個數的真值爲±0(正負號和數符位有關)。

    +0的機器碼爲:0 00000000 000 0000 0000 0000 0000 0000

    -0的機器碼爲:1 00000000 000 0000 0000 0000 0000 0000

    須要注意一點,浮點數不能精確表示0,而是以很小的數來近似表示0。由於浮點數的真值等於

    x=(−1)S×(1.M)×2e 

    e=E−127

    那麼

    +0的機器碼真值爲  1.0×2−127 

     -0機器碼真值爲  −1.0×2−127

  (2)無窮的表示
    若是階碼E=255 而且尾數M全是0,則這個數的真值爲±∞(一樣和符號位有關)。

    所以

    +∞的機器碼爲:0 11111111 000 0000 0000 0000 0000 0000

    -∞的機器嗎爲:1 11111111 000 0000 0000 0000 0000 0000

  (3)NaN(Not a Number)
    若是 E = 255 而且 M 不全是0,則這不是一個數(NaN)。

舍入規則

  以23位尾數位的單精度浮點數爲例,舍入時須要重點參考第24位

  若第24位爲1,且第24位以後所有爲0。此時就要使第23位爲0:若第23位原本就是0則無論,若第23位爲1,則第24位就要向第23位進一位,這樣第23位就能夠爲0

  若第24位爲1,且第24位以後不全爲0,則第24位就要向第23位進一完成上舍入。

  若第24位爲0,此時直接捨去不進位,稱爲下舍入。

再來個栗子

JavaScript console 雙精度浮點數

>>9.4 - 9 - 0.4 === 0
<<false
>>(9.4-9-0.4).toFixed(20)
<<"0.00000000000000033307"

9.4-9-0.4不嚴格等於0,其運算結果偏差。

由於按照上面的浮點數知識可知

9.4在機器內被表示爲:9.4+0.2×2-49

0.4被表示爲:0.4+0.1×2-52

當9.4-9時(由於9是整數是能夠精確存儲的)得0.4+0.2×2-49,再減去0.4+0.1×2-52得3×2-53,約等於"0.00000000000000033307"。

 

詳細解釋:

9的二進制是1001,而0.4的二進制是0.0110-0110-0110-……無限循環的。從而9.4的二進制是1001.0110-0110……,正規化之後就變成 1.001-0110-0110-……×2^3

由於雙精度浮點數是52位尾數,因此小數部分保留0.001-0110-0110-……-0110-0 [110-0110-0110-……]。即001後跟12個0110循環節,而後第52位是0,中括號表示從

第53位起開始捨棄的部分。根據我提到的舍入規則,第53位1且後面不全爲0,要向第52位完成上舍入,因此小數部分就變成 0.001-0110-0110-……-0110-1。至此咱們

能夠看到,這個數較之9.4,因爲小數部分第52位由0變爲1,因此多加了2-52,可是由於從小數部分第53位開始捨棄了,捨棄部分是 0.1100-1100-…×2-52 = 0.8×2-52

因此咱們多加了2-52,可是少了0.8×2-52,這就意味着,但考慮尾數部分,這個數比9.4多了 2-52 - 0.8×2-52 = 0.2×2-52,別忘記以前還有一個2^3,因此整

體多了0.2×2-52×2^= 0.2×2-49

這就是爲何9.4在機器內被表示爲:9.4+0.2×2-49

同理,0.4在機器內被表示爲:0.4+0.1×2-52

相關文章
相關標籤/搜索