IEEE754浮點表示法詳解

很抱歉,由於sf不支持行內公式,因此只能使用行間公式,致使格式有點難看。框架

引言

目前流行都是上層的語言和框架,一般狀況下其實咱們並不須要去了解底層實現。但有時候咱們會遇到一些奇怪的錯誤,不瞭解底層實現的話就沒法想通。
好比下面一個C的例子ui

#include <stdio.h>
int main(int argc, char** argv)
{
    int num=8;
    float* pfnum = &num;
    printf("num = %d\n", num);
    printf("*pfnum = %f\n", *pfnum);
    *pfnum = 8.0;
    printf("num = %d\n", num);
    printf("*pfnum = %f\n", *pfnum);
    return 0;
}

輸出結果爲spa

num = 8
*pfnum = 0.000000
num = 1090519040
*pfnum = 8.000000

另一個有趣的應用是計算2的74次方,很明顯64位系統上只能表示到2的64次方
但下面的例子能夠獲得3d

#include <stdio.h>
#include <math.h>

int main(int argc, char** argv)
{
    printf(" num = %f\n", pow(2, 74));
    return 0;
}

輸出是code

num = 18889465931478580854784.000000

要理解以上問題,那咱們就須要對浮點在底層的實現有必定了解orm

總述

IEEE754是IEEE二進制浮點算術標準。這個標準定義了表示浮點數的常規值與非規格化值(denormal number),一些特殊值(infinity)和非數值(NaN), 以及這些數值的浮點運算。另外它還規定了運算結果的近似原則和例外情況(包括例外發生的時機和處理方式).
雖然IEEE754只定義了單精度(32位),雙精度(64位),擴展單精度(43位以上),與擴展單精度(79位以上)。但實現上它的定義法能夠擴展到任意精度。因此下面的公式儘可能針對任意精度。blog

格式

做爲對比,咱們先列出實數表示法ip

msb ... lsb
n-1 ........................ 0

下面是浮點表示法it

Sign Exponent Fraction
(e+f) (e+f-1)......f (f-1)................0

從上面表格能夠看出浮點由三部分組成:io

  • (e+f)位 : 表示浮點的符號位
  • (e+f-1)......f 位 : 表示浮點的指數域
  • (f-1)......0 位 : 表示浮點的尾數域

-

分類

clipboard.png

常規最大值和最小值

因爲指數可能爲正數,也可能爲負數,爲了方便表示,引入了一個誤差值

$$ Ebias = 2^{e-1}-1 $$

假設把咱們要表示的浮點值改寫成這樣的二進制格式

$$ (-1)^{S}\times1.M_{0}M_{1}M_{2}...M_{22}\times2^{N} $$

那麼表示成浮點數就是

Sign Exponent Fraction
S N+Ebias $$M_{0}M_{1}M_{2}...M_{22}$$

N+Ebias一共有e位,那麼它們的取值範圍是0 - (2^{e}-1))
其中Ebias = 2^{e-1}-1
N的取值範圍是(-2^{e-1}+1) - (2^{e-1})
-2^{e-1}+1 和 2^{e-1} 被保留,用來表示一些特殊值和非數值。

因此作爲常規值, N的取值範圍是(-2^{e-1}+2) - (2^{e-1}-1)

MinNorm MaxNorm
$$(-1)^{S} \times 2^{-2^{e-1}+1} \times 1.0$$ $$(-1)^{S} \times (2-2^{-f}) \times 2^{2^{e-1}-1} $$

上面公式可能有點繞,不過若是代入具體數值就比較好理解了。 咱們以單精度爲例

Sign 8-Bit Based Exponent 23-Bit Normalized Fraction
[31] [30:23] [22:0]

取w=8; t=23;
因此Ebais = 2^{8-1}-1 = 127
N的取值範圍是 (-2^{8-1}+1) - (2^{8-1}), 即-127 -- 128
作爲常規值, N的取值範圍是-126 -- 127
最大值和最小值爲

MinNorm MaxNorm
$$(-1)^{S} \times 2^{-126} \times 1.0$$ $$(-1)^{S} \times 2^{127} \times (2-2^{-23}) $$
S 00000001 00000000000000000000000 S 11111110 11111111111111111111111

非規格化數

對於單精度值,有兩個數:
$$1.001 \times 2^{-125}$$
$$1.01 \times 2^{-125}$$
它們的差值是
$$0.001 \times 2^{-125} = 1.0 \times 2^{-128}$$
-128超過了咱們常規值容許的最小值-126.
若是近似爲0, 那麼下面的公式就會出問題

if(x != y) { z = 1/(x-y)}

爲了解決上面的問題,引入了非規格化浮點數。
IEEE754規定當指數N是-2^{e-1}+1(此時E=0)時,尾數沒必要是規範化的, 也就是尾數域再也不隱藏1.
把上面的差表示成$$0.01 \times 2^{-126}$$, 它的二進制浮點數爲0_00000000_01000000000000000000000.
注意 非規格化數沒有隱藏位,或者是能夠看隱藏位是0
它能夠表示成
$$\pm(f) \times 2^{0-Ebias}$$
f的取值範圍是(0,2)
IEEE754標準中對它的值定義爲:
$$v=(-1)^s \times 2^{emin} \times (0+2^{-t} \times M)$$

無窮大

產生無窮的通常情形有:

  • 自身運算, 如負無窮+2.0獲得負無窮
  • 被0除, 例如1/0獲得正無窮
  • 上溢, 即計算結果超出類型範圍,經過舍入獲得無窮

NaN

QNaN(Quiet NaN): 參與運算不觸發異常
SNaN(signal NaN): 參與運算觸發異常
IEEE引入NaN的目的是給compiler等系統一個約定的值未初始化的數據,或者在計算出問題時能夠返回一個值來提示計算出問題了。

正零和負零

零有正負之分,很是容易讓人困惑。這主要是基於數值分析後的權衡結果。
IEEE規定+0 == -0.
好比,若是零無符號, 則等式1/(1/x) == x 在x = 正負無窮 時再也不成立。
緣由是 1除負無窮都等於0, 1除以0等於正無窮,與x不相等。要解決這個問題的一個方法是無窮也無符號,但正無窮和負無窮顯然分佈在軸的兩側,能夠表示上溢和下溢發生在哪一側,因此不能不要。
固然零有符號也形成了一些問題,好比當x=y時, 1/x=1/y在x和y分別爲+0和-0時,再也不成立。解決這個問題的方法是規定零是有序的,即+0不等於-0, 但若是這樣的話,即便if(x==0)這樣簡單的判斷也會因爲x多是正負零而變得不肯定。因此兩害取其輕,零仍是無序問題少一點。


計算異常

clipboard.png


和十進制間的轉換

十進制浮點數到二進制浮點數的轉換

  1. 十進制到二進制: 整數部分用2來除,小數部分用2來乘
  2. 規格化二進制數: 改變小數點,使小數點前只有第一位有效數字
  3. 計算指數的移碼:原來的指數加上2^{e-1}-1
  4. 把符號位,指數的移碼,尾數合在一塊兒 (尾數不夠補0)

以100.25變例

  • 第一步
100/2 = 50 ... 0
50/2  = 25 ... 0
25/2  = 12 ... 1
12/2  = 6  ... 0
6/2   = 3  ... 0
3/2   = 1  ... 1
1/2   = 0  ... 1      // 獲得0,  中止

0.25 * 2 = 0.5 ... 0
0.5  * 2 = 1.0 ... 1     // 獲得1.0, 中止

$$(100.25)_{10} = (1100100.01)_{2}$$

  • 第二步

$$1100100.01 = (-1)^0 \times 1.10010001 \times 2^{6}$$

  • 第三步

$$(6+127)_{10} = (133)_{10} = (85)_{16}$$

  • 第四步

拼接結果以下

符號 指數 尾數
0 1000_0101 1001_0001_0000_0000_0000_000

二進制浮點數到十進制浮點數的轉換

過程同十進制到二進制相逆

  1. 分割符號位, 指數移碼, 尾數
  2. 將移碼送去偏移2^{e-1}-1, 獲得真正的指數
  3. 寫成規格化的二進制浮點數
  4. 寫成非規格化的二進制浮點數形式
  5. 把二進制數轉換成十進制數

以1 10001000 10011111100000000000000爲例

符號 指數 尾數
1 1000_1000 1001_1111_1000_0000_0000_000

$$(10001000)_{2} - (127)_{10} = (136)_{10} - (127)_{10} = (9)_{10}$$

$$(-1 \times 2^{9} \times 1.100111111)_{2} = (-1 \times 1100111111.1)_{2} = (-831.5)_{10}$$

精度取捨規則

簡明口訣:「4舍6入5看右,5後有數進上去,尾數爲0向左看,左數奇進偶捨棄」。爲了不四捨五入規則形成的結果偏高,偏差偏大的現象出現,通常採用四捨六入五留雙規則。  當尾數小於或等於4時,直接將尾數捨去例如將下列數字所有修約到兩位小數,結果爲:10.2731——10.2718.5049——18.5016.4005——16.4027.1829——27.18當尾數大於或等於6時將尾數捨去向前一位進位例如將下列數字所有修約到兩位小數,結果爲:16.7777——16.7810.29701——10.3021.0191——21.02(三)當尾數爲5,而尾數後面的數字均爲0時,應看尾數「5」的前一位:若前一位數字此時爲奇數,就應向前進一位;若前一位數字此時爲偶數,則應將尾數捨去。數字「0」在此時應被視爲偶數。例如將下列數字所有修約到兩位小數,結果爲:12.6450——12.6418.2750——18.2812.7350——12.7421.845000——21.84(四)當尾數爲5,而尾數「5」的後面還有任何不是0的數字時,不管前一位在此時爲奇數仍是偶數,也不管「5」後面不爲0的數字在哪一位上,都應向前進一位。例如將下列數字所有修約到兩位小數,結果爲:12.73507——12.7421.84502——21.8512.64501——12.6518.27509——18.2838.305000001——38.31按照四捨六入五留雙規則進行數字修約時,也應像四捨五入規則那樣,一次性修約到指定的位數,不能夠進行數次修約,不然獲得的結果也有多是錯誤的。例如將數字10.2749945001修約到兩位小數時,應一步到位:10.2749945001——10.27(正確)。若是按照四捨六入五留雙規則分步修約將獲得錯誤結果:10.2749945001——10.274995——10.275——10.28(錯誤)。

相關文章
相關標籤/搜索