很抱歉,由於sf不支持行內公式,因此只能使用行間公式,致使格式有點難看。框架
目前流行都是上層的語言和框架,一般狀況下其實咱們並不須要去了解底層實現。但有時候咱們會遇到一些奇怪的錯誤,不瞭解底層實現的話就沒法想通。
好比下面一個C的例子ui
#include <stdio.h> int main(int argc, char** argv) { int num=8; float* pfnum = # 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
-
因爲指數可能爲正數,也可能爲負數,爲了方便表示,引入了一個誤差值
$$ 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)$$
產生無窮的通常情形有:
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多是正負零而變得不肯定。因此兩害取其輕,零仍是無序問題少一點。
以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 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(錯誤)。