java 浮點數值計算誤差
問題
當我們執行如下函式時
public void test() {
System.out.println(2.0-1.1); //=>0.8999999999999999
}
我們發現其結果並非是我們預想的0.9
原因
其主要原因是浮點數值採用二進位制系統表示,而在二進位制系統中無法精確表示1/10。這就好像十進位制無法精確表示1/3一樣。如果需要在數值計算中不含有任何舍入誤差,就應該使用BigDecimal類.
上述原因摘自“Java 核心技術 卷一 (第九版) p35”
解決方案
public void test() { System.out.println(new BigDecimal("2.0").subtract(new BigDecimal("1.1"))); }
詳解
首先我們要知道,計算機是如何將10進位制的浮點型別轉化成2進位制進行計算的。這裡我們需要知道一個概念,所有的浮點數值計算都遵循IEEE 745規範,所以我們來看一個該規範。
IEEE 745規範定義
IEEE 754是最廣泛使用的二進位制浮點數算術標準,被許多CPU與浮點運算器所採用。IEEE 754規定了多種表示浮點數值的方式,在本文件裡只介紹32bits的float浮點型別。它被分為3個部分,分別是符號位S(sign bit)、指數偏差E(exponent bias)和小數部分F(fraction)。
其中S位佔1bit,為bit31。S位為0代表浮點數是正數,S位為1代表浮點數是負數,比如說0x449A522C的S位為0,表示這是一個正數,0x849A522C的S位為1,表示這是一個負數。
E位佔8bits,為bit23~bit30。E位代表2的N次方,但需要減去127,比如說E位為87,那麼E位的值為2(87-127)=9.094947017729282379150390625e-13。
F位佔23bits,為bit0~bit22。F位是小數點後面的位數,其中bit22是2-1=0.5,bit21是2-2=0.25,以此類推,bit0為2-23=0.00000011920928955078125。但F位裡隱藏了一個1,也就是說F位所表示的值是1+(F位bit22~bit0所表示的數值),比如說F位是0b10100000000000000000001,只有bit22、bit20和bit0為1,那麼F位的值為1+(2-1+2-3+2-23),為1.62500011920928955078125。
綜上所述,從二進位制數換算到浮點數的公式為:(-1)S×2E-127×(1+F)。但還有幾個特殊的情形:
若E位為0並且F位也為0時表示浮點數0,此時浮點數受S位影響,表現出+0和-0兩種0,但數值是相等的。比如二進位制數0x00000000表示+0,二進位制數0x80000000表示-0。
若E位為0並且F位不為0時浮點數為(-1)S×2-126×F,注意,E位的指數是-126,而不是0-127=-127,而且F位是0.xx格式而不是1.xx格式,比如0x00000001的浮點數為2-126×2-23=1.4012984643248170709237295832899e-45,而不是20-121×(1+2-23)。一旦E為不為0,從0變為1,不是增加2倍的關係,因為公式改變了。
若E位為255並且F位不為0時表示非數值,也就是說是非法數,例如0x7F800001。
若E位為255並且F位為0時表示無窮大的數,此時浮點數受S位影響,例如0x7F800000表示正無窮大,0xFF800000表示負無窮大。當我們使用1個數除以0時,結果將被記作0x7F800000。上述定義摘自:http://bbs.chinaunix.net/thread-3746530-1-1.html
上述解釋為float型別轉化規則,與double型別規則一樣,double型別就不在進行贅述了。
示例2.0-1.1預算
下面我們來回顧一下2.0-1.1的問題,我們先將這2個數轉化為2進位制
2.0=10.0000000000000000
1.1=01.0001100110011001 //此為無限迴圈小數
10.0000000000000000-01.0001100110011001=0.1110011001100111轉為10進位制只能接近0.8999999999999999了。