整數轉浮點數精度溢出的原因和處理方式

       對於64位浮點數來說,其尾數有53位(包含首位的隱藏位),當一個整數轉爲浮點數時,只有當該整數的二進制位數不超過53位時,64位浮點數纔可以精確的表示該整數,不然會造成精度丟失。試想一下,當一個十進制整數的二進制有54位,那麼最後一位無法準確儲存,64位浮點數就會造成精度的丟失,自然地,當位數超過53位後,64位浮點數的可表示整數並不是連續的。

       在筆者的這篇文章《浮點數的各種最值推算以及對python sys.float_info的解釋》中,講過浮點數能在該範圍內精確表示的最大十進制整數爲9007199254740991。那麼如果一個整數大於該數,即其二進制位數超過53位,浮點數是怎麼表示的呢?即當一個整數轉成浮點數時,如果造成了精度溢出,那麼浮點數對此是怎麼處理的呢?
       首先我們來看一個簡單的精度丟失的例子,如下所示。d就是9007199254740991,然後我們逐漸增加1,發現d+2,d+4,d+6的浮點數表示都是有誤差的,即造成了精度丟失。

d=9007199254740991
float(d+1)
[out]:
9007199254740992.0

float(d+2)
[out]:
9007199254740992.0

float(d+3)
[out]:
9007199254740994.0

float(d+4)
[out]:
9007199254740996.0

float(d+5)
[out]:
9007199254740996.0

float(d+6)
[out]:
9007199254740996.0

       從上面例子可以看到,d+2,d+4,d+6是有誤差的,但是d+1,d+3,d+5是沒有誤差的。有的有誤差,有的沒有誤差,而且對於有誤差的來說,有的是偏小,有的是偏大的,比如d+4是偏大的,而d+6是偏小的,這和浮點數對其的特殊處理方式有關,下面我們來說明這是爲什麼。

       我們截取上面數字二進制的最後五位來進行說明。對於d,其最後五位爲11111,但是對於d+1,其變成了54位,並且首位是1,其他全是0,所以其最後五位是00000,同樣的d+2爲00001,d+3爲00010,d+4爲00011,d+5爲00100,d+6爲00101。除了d本身,其他的數字的最後一位都是超出了53位的,排在第54位,因此無法被64位浮點數儲存。首先對於第54位是0的數字,我們發現64浮點數對其表示是準確的,並沒有誤差,這是因爲雖然64位浮點數尾數不夠,但是其指數是足夠的,而指數對二進制的作用類似十進制一樣,最後是在尾數的有效數字後面添加0,因此對於第54位是0的數字,浮點數可以準確表示,這就是爲什麼對d+1,d+3和d+5的表示沒有誤差的原因。

       但對於d+2,d+4和d+6,我們發現d+2和d+6都是偏小1,d+4是偏大1的。這是因爲,當浮點數無法準確表示一個整數時,其約定,將該整數近似到一個最靠近該整數本身的可以表示的浮點數,因爲最靠近一個整數的數,那麼只有偏大最靠近和偏小最靠近兩種情況,如果偏大最靠近和偏小最靠近距離該整數一樣近,則選取讓最低位(即第53位)爲0的那個浮點數。比如對於d+2,其最後五位爲00001,那麼偏大最靠近的數爲00010,偏小最靠近的數爲00000,由於這種距離00001都一樣近的情形選擇的是讓第53位爲0的情形,所以浮點數最終表示的是0000+0,最後一個0通過指數的移位得到,所以最終得到的數是偏小1的;同理的,對於d+4,偏大最靠近是00100,偏小最靠近是00010,所以選擇的是00100,所以最終表示的是偏大1,;對於d+6,偏大最靠近是00110,偏小最靠近是00100,所以選擇的是00100,所以最終表示的是偏小1。

       最後需要注意的是,以上舉的例子都是54位的,如果整數更大,超過的位數更多,那麼偏大最靠近和偏小最靠近的可表示浮點數就不僅僅是相差1了,很可能會造成更大的誤差,因具體的數字而已。

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