基礎野:細說浮點數

Brief                              

 原本只打算理解JS中0.1 + 0.2 == 0.30000000000000004的緣由,但發現本身對計算機的數字表示和運算十分陌生,因而只好惡補一下。
 本篇咱們一塊兒來探討一下基礎——浮點數的表示方式和加減乘除運算。
 在深刻前有兩點咱們要明確的:html

  1. 在同等位數的狀況下,浮點數可表示的數值範圍比整數的大;git

  2. 浮點數沒法精確表示其數值範圍內的全部數值,只能精確表示可用科學計數法m*2e表示的數值而已;(如0.5的科學計數法是2-1,則可被精確存儲;而0.2則沒法被精確存儲)github

  3. 浮點數不只可表示有限的實數,還能夠表示有限的整數。併發

Encode                            

 20世紀80年代前每一個計算機制造商都自定義本身的表示浮點數的規則,及浮點數執行運算的細節。並且不太關注運算的精確性,而是更多地關注速度和簡便性。
 1985年左右推出IEEE 754標準的浮點數表示和運算規則,才讓浮點數的表示和運算均有可移植性。(IEEE,讀做Eye-Triple-Eee,電器與電子工程師協會)
 上述的IEEE 754稱爲IEEE 754-1985 Floating point,直到2008年對其進行改進獲得咱們如今使用的IEEE 754-2008 Floating point標準。
 IEEE 754的二進制編碼由3部分組成,分別是sign-bit(符號位),biased-exponent(基於偏移的階碼域)和significant(尾數/有效數域)。
Sign-bit:0表示正,1表示負。佔1bit;
Biased-exponent:首先exponent表示該域用於表示指數,也就是數值可表示數值範圍,而biased則表示它採用偏移的編碼方式。那麼什麼是採用偏移的編碼方式呢?也就是位模式中沒有設立sign-bit,而是經過設置一箇中間值做爲0,小於該中間值則爲負數,大於改中間值則爲正數。IEEE 754中規定bias = 2^e-1 - 1,e爲Biased-exponent所佔位數;
Significant:表示有效數,也就是數值可表示的精度。(注意:Significant採用原碼編碼;假設有效數位模式爲0101,那麼其值爲02-1+12-2+02-3+12-4,即有效數域的指數爲負數)
 另外IEEE 754還提供4個精度級別的浮點數定義(單精度、雙精度、延生單精度和延生雙精度),單精度和雙精度具體定義以下:編碼

Level Width(bit) Range at full precision Width of biased-exponent(bit) Width of significant(bit)
Single Precision 32 1.1810-38 ~ 3.41038 8 23
Double Precision 64 2.2310-308 ~ 1.8010308 11 52es5

圖片描述
 爲了簡便,下面以Single Precision來做敘述。
 如今咱們瞭解到32bit的浮點數由3部分組成,那麼它們具體又有怎樣的組合規則呢?具體分爲4種:
Normalized(規格化)
 編碼規則spa

  1. biased-exponent != 0 && biased-exponent != 2e-1;.net

  2. exponent = biased-exponent - Bias,其中exponent是指實際的指數值;rest

  3. significant前默認含數值爲1的隱藏位(implied leading 1),即若significant域存儲的是0001,而實際值是10001。code

Denormalized(非規格化)
 用於表示很是接近0的數值和+0和-0。
 +0的位模式爲:0-00000000-00000000000000000000000
 -0的位模式爲: 1-00000000-00000000000000000000000

 編碼規則

  1. biased-exponent == 0 ;

  2. exponent = 1 - Bias,其中exponent是指實際的指數值;

  3. significant前默認含數值爲0的隱藏位(implied leading 1),即若significant域存儲的是0001,而實際值是00001。

Infinity(無限大)
 用於表示溢出。
 編碼規則

  1. biased-exponent == 2e-1 ;

  2. significant == 0。

運算結果爲Infinity的表達式

  1. Infinity == N/0,其中N爲任意正數;

  2. -Infinity == N/-0,其中N爲任意正數;

  3. Infinity == Infinity * N,其中N爲任意正數。

NaN(非數值)
 用於表示 結果既不是 實數 又不是 無窮。
 編碼規則

  1. biased-exponent == 2e-1 ;

  2. significant != 0。

 運算結果爲NaN的表達式

1. Math.sqrt(-1)
2. Infinity - Infinity
3. Infinity * 0
4. 0/0

 注意

  1. NaN與任何數值做運算,結果均爲NaN;

  2. NaN != NaN;

  3. 1 == Math.pow(NaN, 0)。

Rounding modes(aka Rounding scheme,舍入模式)
 因爲浮點數沒法精確表示全部數值,所以在存儲前必須對數值做舍入操做。具體分爲如下5種舍入模式

  1. Round to nearest, ties to even(IEEE 754默認的舍入模式)
      舍入到最接近且能夠表示的值,當存在兩個數同樣接近時,取偶數值。(如2.4舍入爲2,2.6舍入爲3;2.5舍入爲2,1.5舍入爲2。)

    Q:爲何會當存在兩個數同樣接近時,取偶數值呢?
    A:因爲其餘舍入方式均令結果單方向偏移,致使在運算時出現較大的統計誤差。而採用這種偏移則50%的機會偏移兩端方向,從而減小誤差。
  2. Round to nearest, ties to zero
    舍入到最接近且能夠表示的值,當存在兩個數同樣接近時,取離0較遠的數值

  3. Round to infinity
    向正無窮方向舍入

  4. Round to negative infinity
    向負無窮方向舍入

  5. Round to zero
    向0方向舍入

Overflow                          

 到這裏咱們對浮點數的表示方式已經有必定程度的瞭解了,也許你會火燒眉毛想了解它的運算規則,但在這以前我想你們應該要想理解溢出和如何判斷溢出,否則沒法理解後續對運算的講解。
Q1:何爲溢出?
A1:溢出,就是運算結果超出可表示的數值範圍。注意:進位、借位不必定會產生溢出。

發生溢出:
4位有符號整數 7+7
  0111
+ 0111
  1110 => -2
只有最高數值位發生進位,所以發生溢出

沒有發生溢出:
4位有符號整數 7-2
  0111
+ 1110
 10101 => 取模後獲得5
符號位和最高數值位均發生進位,所以沒有發生溢出

Q2:如何判斷溢出?
A2:有兩種方式判斷溢出,分別是 進位判斷法 和 雙符號判斷法。

進位判斷法

 前提:採用單符號位,即+4的二進制位模式爲0100。
 無溢出:符號位 和 數值域最高位 一塊兒進位或一塊兒不發生進位。
 溢出:符號位 或 數值域最高位 其中一個發生進位。

雙符號判斷法

 前提:採用雙符號位,即+4的二進制位模式爲00100。
 無溢出:兩符號位相同。
 上溢出:兩符號位爲01。
 下溢出:兩符號位爲10。

Q3:溢出在有符號整數和浮點數間的區別?
A3:對於有符號整數而言,溢出意味着運算結果將與期待值不一樣從而致使錯誤;

對於浮點數而言,會對上溢出和下溢出進行特殊處理,從而返回一個可被IEEE 754表示的浮點數。
所以對於有符號整數的運算咱們採用進位判斷法判斷溢出便可,而對於浮點數則須要採用雙符號判斷法了。

Q4:浮點數運算中上溢出和下溢出具體的特殊處理是什麼啊?
A4:首先浮點數運算中僅對階碼進行溢出判斷,當階碼出現下溢出時運算結果爲0(符號取決於符號位);當階碼出現上溢出時運算結果爲Infinity(符號取決於符號位)。

 PS:發生溢出時,當前程序會被掛起,併發送溢出中斷信號量,此時溢出中斷處理程序接收信號量後會作對應的處理。

Addition & Subtraction                    

 恭喜你還沒被上述的前置知識搞暈而選擇X掉網頁,下面咱們終於能夠着手處理運算問題,順便驗證一下本身對上述內容是否真的理解了。
 步驟:

  1. 對0、Infinity和NaN操做數做檢查

    如有一個操做數爲NaN則直接返回NaN;
    如有一個操做數爲0則直接返回另外一個操做數;
    如有一個操做數爲Infinity,
    若另外一個操做數也是Infinity且符號相同則返回第一個操做數;
    若另外一個操做數也是Infinity且符號不一樣則返回NaN;
    若其餘狀況則返回Infinity。
  2. 對階,若兩操做數的階碼不相等,則對階(小數點對齊)。

    因爲尾數右移所損失的精度比左移的小,所以小階向大階看齊。
  3. 符號位+尾數(包含隱藏位)執行加減法。

    按有符號整數加減法進行運算,並採用雙符號法判斷是否溢出。
  4. 規格化。

    向右規格化:若步驟3出現溢出,則尾數右移1位,階碼+1;
    向左規格化:若步驟3沒有出現溢出,且數值域最高位與符號位數值相同,則尾數左移1位且階碼-1,直到數值域最高位爲1爲止。
  5. 舍入處理

  6. 溢出判斷

      因爲規格化時可能會致使階碼發生溢出
    若無發生溢出,則運算正常結束。
    若發生上溢出,則返回Infinity。
    若發生下溢出,則返回0。

    示例1,0.75+(-0.75) = 0:

    0.75+(-0.75) = 0
    
    以8位存儲,尾數採用原碼編碼
    0.75  存儲位模式 0-0110-100
    -0.75 存儲位模式 1-0110-100
    
    1. 對階
    階碼同樣跳過。
    
    2. 符號位+尾數(含隱藏位)相加
    因爲尾數以有符號數的方式進行運算,所以要對尾數進行取補操做
      00-1100
    +11-0100
     100-0000
     對符號位截斷後獲得00-0000
    
    3. 規格化 
    根據符號位相同,則執行左規格化操做,也就是尾數不斷向左移而階碼不斷減1,直到尾數最高位爲1爲止。
    
    4. 舍入處理
    
    5.溢出判斷 
      在執行規格化時發生階碼下溢出,總體結果返回0-0000-000.

    示例2,0.75-0.25 = 0.5:

    0.75-0.25 = 0.5
    
    以8位存儲,尾數採用原碼編碼
    0.75  存儲位模式 0-0110-100
    0.25  存儲位模式 0-0101-000
    
    1. 對階
    0.25的階碼小於0.75相同,所以0.25向0.75的看齊。
    0-0110-100
    
    2. 尾數相加(採用雙位符號法),減法轉換爲加法,對0.75和-0.25取補
      00-1100
    +11-1100
     100-1000
     對符號位截斷後獲得00-1000
    
    3. 規格化 
    根據符號位相同,則執行左規格化操做,也就是尾數不斷向左移而階碼不斷減1,直到尾數最高位爲1爲止。
    
    4. 舍入處理
    
    5. 溢出判斷
    階碼沒有發生溢出,正常返回運算結果0-0110-000(注意:舍入處理後數值域的最高位是位於隱藏位的)

    示例3, 0.25-0.75 = -0.5:

Comparison                          

 比較運算(cmp指令)實際上就是對兩操做數作減法,而後經過標誌寄存器(80x86的rflags)中的CF(Carry flag)、ZF(Zero flag)、OF(Overflow flag)、SF(Sign flag)狀態標誌來判斷二者的關係。
 對於無符號數A與B而言,則是經過CF和ZF來判斷。

  1. 若CF爲1,表示A-B時A發生借位操做,那麼能夠斷定 A<B

  2. 若CF爲0且ZF不爲0,表示A-B時沒有發生借位操做,那麼能夠斷定 A>B

  3. 若ZF爲0,則可斷定A==B
    對於有符號數C和D而言,則是經過ZF、OF和SF來判斷。

  4. 若ZF爲1,則可斷定C == D

  5. 若SF爲0,OF爲0,表示結果爲正數且沒有發生溢出,則C>D

  6. 若SF爲0,OF爲1,表示結果爲正數且發生溢出,則C<D

  7. 若SF爲1,OF爲0,表示結果爲負數且沒有發生溢出,則C<D

  8. 若SF爲1,OF爲1,表示結果爲負數且發生溢出,則C>D

 而對於浮點數而言,因爲階碼域採用的是biased-exponent的編碼方式,所以在進行比較時咱們能夠將整個浮點數看做有符號數來執行減法運算便可,而沒必要執行Addition/Subtraction那樣繁瑣的運算步驟。

Multiplication                        

步驟:

  1. 對0、Infinity和NaN操做數做檢查

      如有一個操做數爲NaN則直接返回NaN;
      如有一個操做數爲0則直接返回另外一個操做數;
      如有一個操做數爲Infinity,
        若另外一個操做數也是Infinity且符號相同則返回第一個操做數;
        若另外一個操做數也是Infinity且符號不一樣則返回NaN;
      若其餘狀況則返回Infinity。
  2. 計算階碼(公式:e = e1 + e2 - Bias)
      公式推導過程:

    ∵ E1 = e1 - Bias
    ∵ E2 = e2 - Bias
    ∴ E = E1+E2 = e1 + e2 - 2*Bias
    ∵ e = E + Bias
    
    ∴ e = e1 + e2 - Bias
    對非規格化e1爲1
    注意:計算階碼時,是執行無符號數的加減法。
  3. 尾數相乘

    注意:尾數相乘時,是執行無符號數的乘法,而且不對結果進行截斷。
  4. 結果左規格化,尾數左移,且階碼減1,直到最高位爲1爲止

  5. 舍入處理

  6. 溢出判斷
      因爲規格化時可能會致使階碼發生溢出

    若無發生溢出,則運算正常結束。
        若發生上溢出,則返回Infinity。
        若發生下溢出,則返回0。
  7. 符號位執行 異或 運算

示例,0.5*(-0.25) = -0.125:

0.5*(-0.25) = -0.125

以8位存儲,尾數採用原碼編碼
0.5  存儲位模式 0-0110-000
-0.25  存儲位模式 1-0101-000

1. 計算階碼(公式e1+e2-Bias)
+0101
+1001
取模獲得 0100

2. 尾數相乘
1000*1000
1000<<(3+1) - 1000<<(3)
- 0100000
減法轉換爲加法
+1100000
取模獲得0100000

3. 左規格化
0100000左移1位獲得 100000
階碼-1獲得 0011

4. 舍入處理
尾數爲1000

5. 溢出判斷
無溢出

6. 符號位異
xor 1 = 1

結果
1-0011-000

Divsion                            

步驟:

  1. 對0、Infinity和NaN操做數做檢查
        如有一個操做數爲NaN則直接返回NaN;

    如有一個操做數爲0則直接返回另外一個操做數;
    如有一個操做數爲Infinity,
      若另外一個操做數也是Infinity且符號相同則返回第一個操做數;
      若另外一個操做數也是Infinity且符號不一樣則返回NaN;
      若其餘狀況則返回Infinity。

  1. 計算階碼(公式:e = e1 - e2 + Bias + m-1) m爲尾數的位數
         公式推導過程:

    ∵E1 = e1 - Bias
    ∴E2 = e2 - Bias
    ∴E = E1-E2 = e1 - e2
    ∵e = E + Bias
    
    ∴e = e1 - e2 + Bias
    ∵除法會消權,所以經過移位去除負指數以便後續計算
    ∴e = e1 - e2 + Bias + m-1,其中m爲尾數的位數
     對非規格化e一、e2爲1
    注意:計算階碼時,是執行無符號數的加減法。
  2. 尾數相除

    注意:尾數相除時,是執行無符號數的除法,而且不對結果進行截斷。
  3. 結果左規格化,尾數左移,且階碼減1,直到最高位爲1爲止

  4. 舍入處理

  5. 溢出判斷
        因爲規格化時可能會致使階碼發生溢出

    若無發生溢出,則運算正常結束。
              若發生上溢出,則返回Infinity。
              若發生下溢出,則返回0。
  6. 符號位執行 異或 運算

示例,0.5/(-0.25) = -2:

0.5/(-0.25) = -2

以8位存儲,尾數採用原碼編碼
0.5  存儲位模式 0-0110-000
-0.25  存儲位模式 1-0101-000

2. 計算階碼(公式e1-e2+Bias+(m-1))
+1011
+00111
+00011

取模獲得 1011

3. 尾數相除
1000/1000 = 1000 >> 3 獲得0001

4. 左規格化
0001左移3位獲得 1000
階碼-3獲得 1000

5. 舍入處理
尾數爲1000

6. 溢出判斷
無溢出

7. 符號位異
xor 1 = 1

結果
1-1000-000

Conclusion                          

 總算寫完了:)本文以單精度做爲敘述對象,爲簡化手工運算各示例均以8bit浮點數做爲講解,其實32bit和64bit的浮點數表示和運算規則與其相同,理解規則就OK了。
 看完這麼多原理性的東西,是時候總結一下咱們對浮點數應有的印象了:

  1. 浮點數可表示的值範圍比同等位數的整數表示方式的值範圍要大得多;

  2. 浮點數沒法精確表示其值範圍內的全部數值,而有符號和無符號整數則是精確表示其值範圍內的每一個數值;

  3. 浮點數只能精確表示m*2e的數值;

  4. 當biased-exponent爲2e-1-1時,浮點數能精確表示該範圍內的各整數值;

  5. 當biased-exponent不爲2e-1-1時,浮點數不能精確表示該範圍內的各整數值。

    例如:1000000000000000128與1000000000000000129以雙精度浮點數表示時,均爲
          0-10000111010-11011110000010110110101100111010011101100100000000001。
              若以64bit無符號整數表示時,1000000000000000128爲 0000110111100000101101101011001110100111011001000000000010000000;
                                         1000000000000000129爲 0000110111100000101101101011001110100111011001000000000010000001。

 例子源自:http://es5.github.io/#x15.7.4.5
 尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohnhuang/p/5109766.html 肥子John^_^

Thanks                            

https://en.wikipedia.org/wiki/IEEE_754-1985
http://geeklu.com/2011/03/ieee754-floating-point-arithmetic/
http://blog.csdn.net/hillchan31/article/details/7565782
http://blog.csdn.net/jn1158359135/article/details/7761011
http://laokaddk.blog.51cto.com/368606/284280/《深刻理解計算機系統》

相關文章
相關標籤/搜索