IEEE754標準浮點數表示與舍入

原文地址:https://blog.fanscore.cn/p/26/

友情提示:本文排版不太好,但內容簡單,請耐心觀看,總會搞懂的。

1. 定點數

對於一個無符號二進制小數,例如101.111,如果我們要用2個字節即16位來存儲它,我們可以約定用高8位存儲小數點前的數字,用低8位存儲小數點後的數字,這樣的話它在存儲空間中就是這樣的:00000101.11100000。這種存儲方式中小數點的位置是固定的,這稱爲定點數。這種存儲方式有個問題那就是存儲的數值是有上限的即11111111.11111111 = 27+26+25+24+23+22+21+20+2-1+2-2+2-3+2-4+2-5+2-6+2-7+2-8。如果我們要存儲1111111111111111.這個數的話,用這個存儲方式是無法存儲的,但是實際上對於這個數來說16位的存儲空間是夠用的,就是說定點數存在空間浪費的缺點。

基於這個缺點,計算機中通常用浮點數來表示一個小數。

2. 浮點數

IEEE754標準使用V = (-1)s × M × 2E表示浮點數,符號位(sign)s 決定該數是正數(s=0)還是負數(s=1),尾數(significand)M是一個二進制小數,階碼(exponent) E。

單精度浮點數中,s佔用1位,M佔用23位,E佔用8位,總共32位,雙精度浮點數s佔1位,M佔52位,E佔11位,總共64位,這兩種分別對應C中的float和double,另外還有一個擴展雙精度它佔用80位。

image.png

根據E值,浮點數有三種情況,

2.1 規格化的:E所有位既不全爲0也不全爲1。

在這種情況中,階碼被解釋爲以偏置(biased)形式表示的有符號整數,這時E的值表示爲E=e-Bias,其中e爲E所佔位所表示的無符號整數,Bias=2E所佔位數-1。舉個單精度浮點數的🌰,假設當前E爲00001010那麼E = (00001010所對應的無符號整數) - (28 - 1) = 10 - 127 = -117。

這種情況中M用來表示小數,其二進制表示爲1.f-1f-2f-3……fn。舉個單精度的例子,假設當前M爲01100000000000000000100,那麼M=1 + (2-2 + 2-3 + 2-21)。

2.2 非規格化的:E所有位都爲0

在這種情況中,階碼值E=1-Bias,而尾數M二進制表示爲0.f-1f-2f-3……fn,沒有規格化值前面的1。
非規格化值有兩個用途。首先規格化值M始終>1,所以沒法表示0,所以+0.0的浮點表示的位模式爲全0:符號位0,階碼字段全爲0(表明是一個非規格化值),尾數都是0就得到M=0.0。如果符號位爲1,我們就得到了-0.0。其次非規格值的另外一個用途是表示那些非常接近0.0的數。

2.3 特殊值:E所有位都爲1,這時又有兩種以下兩種情況

  1. 無窮大:M所有位全爲0,當符號位爲0是就是正無窮,當符號位爲1時就表示負無窮。當我們把兩個特別大的數相乘或者除0的時候無窮能表示溢出的結果。
  2. NaN(Not a Number):M不全爲0,如果一些運算的結果不能是實數或者無窮,比如對-1開平方根時就會返回NaN。

經過上面的講解後我們思考下十進制數15.3203125使用單精度浮點數來表示的話其二進制形式應該是什麼呢?我們首先將它轉爲二進制數,即:1111.0101001 = 1.1110101001 × 23,即M=1.1110101001,E=3。

3. 浮點數舍入

浮點數並不能表示所有的實數,比如十進制的2.1沒有完全對應的二進制數,浮點數只能近似的表示一些實數,爲了儘量精確的表示這個實數就只能儘量增加二進制的位數,但是數據類型的位數是有限的,比如C中float只有32位。

關於十進制小數如何轉二進制不清楚的同學可以自行搜索下相關文章,很簡單,這裏就不詳述了。

這裏舉個例子:將十進制的2.1用單精度浮點數表示。首先小數點前的2轉爲二進制是10,然後我們將小數點後的0.1轉爲2進制,它是這個樣子的:0.000110011001100110011001100110011001100110011001100110011...(後面是0011無限循環)所以2.1轉爲二進制就是:10.000110011001100110011001100110011001100110011001100110011...,轉爲IEEE標準表達方式就是
1.0000110011001100110011001100110011001100110011001100110011... × 21,即M=0.0000110011001100110011001100110011001100110011001100110011... + 1,但單精度浮點數位數只有23位,這樣就面臨一個問題00001100110011001100110(這裏是23)01100110011001100110011001100110011...這一長串23位之後的數字怎麼辦?直接捨去後面的位的話意味着計算機中所有小數都小於等於它的實際值,進1的話意味着計算機中所有小數都大於等於它的實際值,四捨五入看起來不錯,但是由於中間的5會進位,所以仍然會使計算系統中的小數整體偏大。在進行一些大量數據的統計時,這三種方式都回累計一個相當大的誤差。

IEEE浮點格式定義了四種不同的舍入方式,下面以十進制的小數舍入只保留小數點後0位爲例:

方式 1.40 1.60 1.50 2.50 -1.50
向偶數舍入 1 2 2 2 -2
向零舍入 1 1 1 2 -1
向下舍入 1 1 1 2 -2
向上舍入 2 2 2 2 -1

向偶數舍入這個方式乍看可能沒看懂,它其實是使舍入後的值的最低有效數字是偶數。1.5舍入有兩個選擇:1和2,但由於2是偶數所以就舍入到2,同樣2.5舍入有兩個選擇:2和3,但由於3是奇數,所以還是舍入到2。

向偶數舍入的方式使得在大多數情況下,5捨去還是進位的概率是差不多的,在進行一些大量數據的統計時產生的偏差相較其他方式小一些。

4. 二進制舍入的🌰與規則總結

好多中文資料一般到這裏就戛然而止了,CSAPP書中講到這也沒有給到一個二進制的例子,相信大部分讀者看完了上面也不知道二進制裏是怎麼處理的,所以下面給個二進制舍入的例子。

假設我們要求只保留小數點後三位,有以下例子:

  1. 1.001 011 舍入後: 1.001 原因: 1.001 011舍入有兩個選擇:1.0011.010|1.001 011 - 1.001| = 0.000 011|1.001 011 - 1.010| = 0.000 101,顯然0.000 011 < 0.000 101,所以1.0011.010更接近原值1.001 011,所以舍入到了1.001
  2. 1.001 101 舍入後: 1.010 原因: 1.001 101舍入有兩個選擇:1.0011.010|1.001 101 - 1.001| = 0.000 101|1.001 101 - 1.010| = 0.000 011,顯然0.000 101 > 0.000 011所以舍入到後者。
  3. 1.001 100 舍入後: 1.010 原因: 1.001 100舍入有兩個選擇:1.0011.010|1.001 100 - 1.001| = 0.000 100|1.001 100 - 1.010| = 0.000 100,兩種選擇的差值是相同的,這時使用向偶數舍入的方式,1.010是偶數(0偶1奇),所以舍入到1.010

根據上面的例子我們總結出以下規律:
我們用RR...RDD...D來表示一個二進制小數,R表示保留位,D表示捨去位,那麼有以下規則:

  1. DD...D < 10...0 直接捨去
  2. DD...D > 10...0 向上舍入
  3. DD...D = 10...0 向偶數舍入,細則:
    1. RR...R = XX...0,直接捨去
    2. RR...R = XX...1,向上舍入

5. 代碼驗證下

最後,我們寫一段C代碼,看下到底是不是按照IEEE754標準存的浮點數,代碼如下:

int main(void) {
    float a = 2.1;
    float b = a + 3;
    return 0;
}

gcc編譯下:

$ gcc -O0 -g float.c // -O0禁用優化,-g以下面使用gdb調試

gdb調試下:

$ gdb ./a.out

進入gdb後,輸入start再輸入layout asm查看反彙編結果:
image.png
可以看到a的值被存入了寄存器eax,在gdb中通過i r eax查看eax寄存器中的值:
image.png
可以看到eax寄存器中保存的值是0x400666666,轉爲二進制:01000000000001100110011001100110,套入IEEE754標準表示法:
0 10000000 00001100110011001100110,即符號位爲0,M = 1.00001100110011001100110,E = 27 - (27 - 1) = 1

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章