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位的存儲空間是夠用的,就是說定點數存在空間浪費的缺點。code

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

2. 浮點數

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

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

image.png

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

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)。table

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的數。gcc

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

參考資料

相關文章
相關標籤/搜索