用一個浮點數相加的例子來演示計算機在計算時所產生的誤差。
在Python中,用0.2+0.4 會得到0.6000000000000001。
浮點數簡介
浮點數的表示方法:目前流行的浮點數標准是IEEE754。用64個bit來表示雙精度。
首位為符號位s,0代表正,1代表負。
接下來的11位代表指數,將其理解為一個無符號的數字e,例如,00000000011就代表3。定義指數(階碼)M和偏置Bias,其中偏置,定義,容易看出E的范圍為-1022到+1023。對於單精度,。
最后的52位是編碼尾數M,第一位的權重是1/2,第二位的權重是1/4… 第52位的權重是。這里有一個隱藏位,在首位之前,代表1.因此M的范圍實際上是. 當52位全為0時,M為1 當全為1時,M非常接近2,但還差了一個
浮點數. M在1到2之間,E在到,這兩者配合無論是精度還是范圍都足夠大了。
浮點數加法(以0.2+0.4為例)
浮點數加法的計算步驟:浮點數有自己的一套計算方法,以下借助例子詳細闡述,總之核心思想就是保持階碼一致,當需要移位的時候,就拋棄掉尾數的最后一位,因為這一位的權重最小,但就是拋棄尾數的最后幾位導致了誤差。
對階&移位-->有效數求和-->規格化-->舍入處理-->溢出判斷.
以0.2+0.4為例逐步分析。
首先,利用http://www.binaryconvert.com 將0.2和0.4轉換為二進制表示,之后從二進制到十進制的逆轉換同樣是由這個網站完成的。
0.2:0 01111111100 1001100110011001100110011001100110011001100110011010
0.4:0 01111111101 1001100110011001100110011001100110011001100110011010
為了下面闡述方便,直接將隱藏位也一起寫出來。
0.2:0 01111111100 11001100110011001100110011001100110011001100110011010
0.4:0 01111111101 11001100110011001100110011001100110011001100110011010
簡單check一下,可以看出0.2的階碼E=-3,也就是1/8,乘上M后可以得到0.2;同理0.4的階碼E=-2.
1. 對階&移位
對階:先求階差,明顯0.2的階碼比0.4的階碼小1.
移位:小碼向大碼看齊,即將0.2的階碼變成-2,同時將尾數右移一位。這個操作其實就是讓階碼+1,導致原數V擴大兩倍,同時尾數右移一位,導致原數V縮小兩倍。二者相互抵消。要注意在移動尾數時要連隱藏位一起移動,同時拋棄末位。這樣一來0.2變為:
0.2:0 01111111101 01100110011001100110011001100110011001100110011001101
2. 有效數求和
將0.2和0.4的尾數求和,包含隱藏位
0.2:01100110011001100110011001100110011001100110011001101
0.4:11001100110011001100110011001100110011001100110011010
同樣利用一個小網站http://www.99cankao.com 來實現這一計算,計算結果已經過手動check,為:
100110011001100110011001100110011001100110011001100111
有5位。
3. 規格化
雙精度數字只允許尾數為52位,所以上述求和的數字要進行規格化,即將原來的大階碼(-2)加一,變成-1,即01111111110
同時將尾數和右移,與前面移位中類似,階碼+1讓V乘以2,尾數右移讓V除以2。但要注意此時右移是不包括隱藏位的,簡單分析一下原因:
如果不進行規格化,相當於:
100110011001100110011001100110011001100110011001100111
中,10的權重為1,也即換位十進制為1*2+0+1=2,再加上后面的小數,我們這里假設為0.4(數字隨便取的,只為了說明方便)。那么此時尾數M=2.4. 為了使其回到原本的浮點數表示,我們將階碼加一,那么相應的尾數就要除以2,變成1.2。在浮點數表示中,隱藏位始中給出一個1,就需要尾數給出0.2,這樣才能得到1.2。那么自然地,100110011001100110011001100110011001100110011001100111中,黑色部分給出0.4,將其右移一位,拋棄末位,就可以得到0.2。
綜上所述,規格化時尾數的移位不包括隱藏位,但是第一步移位的時候要帶上隱藏位,原因是相似的,這里就不展開了。
4. 舍入處理
在對0.2規格化時,100110011001100110011001100110011001100110011001100111,末位是1,IEEE754的策略是0舍1入,因此去掉隱藏位,初位前加0,拋棄末位,並加一后變為:
0011001100110011001100110011001100110011001100110100
5. 溢出處理
整合得到最后的結果為
0 01111111110 0011001100110011001100110011001100110011001100110100
這就是我們最后得到的0.2+0.4得二進制表示,將其轉換為十進制看一下結果:
0011111111100011001100110011001100110011001100110011001100110100
可以看出最后的結果6.00000000000000088817841970013E-1,略大於0.6,在Python中將其處理為0.6000000000000001也就很自然了。
總結
通過這個例子我們可以看出,計算機表示數字具有天然的誤差,不管你采用多高的精度,多少個bit,最后至少都會產生一個的誤差。