金融、支付行業的開發人員不得不知道的float、double計算誤差問題

來源:互聯網
上載者:User

標籤:

在大多數行業涉及到浮點數的計算的情境比較少,但是在金融、支付行業就比較多了,而且在這兩個行業一個小小的錯誤

可能將會給公司帶來極大的損失。


以前我們公司就出現了這樣的一個問題,當時使用的是double類型進行計算,導致計算出來的結果與實際的結果少了幾十

元錢。雖然數額不大,但是引起產品、技術的重視。通過查閱相關資料,終於知道了是因為float、double在對含有小數的數值進行

計算過程中的舍入產生了誤差。


在浮點運算中,浮點運算很少是精確的。雖然一些數字(譬如 0.5 )可以精確地表示為二進位(底數 2)小數(因為 0.5 

等於 2 -1),但其它一些數字(譬如 0.1 )就不能精確的表示。因此,浮點運算可能導致舍入誤差,產生的結果接近但不等於你

可能希望的結果。比如:下面的代碼運行結果的實際值為100958.34,而不是100000。大家可以運行試一下。

public static void main(String[] args) {float f = 0.1f;float sum = 0;for( int i=0; i<1000000; i++){    sum += f;}System.out.println(sum);}
類似的,.1*26相乘所產生的結果不等於.1自身加26次所得到的結果。當將浮點數強制轉換成整數時,產生的舍入誤差甚至

更嚴重,因為強制轉換成整數類型會捨棄非整數部分,甚至對於那些“看上去似乎”應該得到整數值的計算,也存在此類問題。例如

下面這段代碼:

public static void main(String[] args) {double d = 29.0 * 0.01;System.out.println(d);System.out.println((int) (d * 100));}

運行結果:

0.2928

看到結果是不是非常差異,所以建議大家不要用float、double等浮點值表示精確值一些非整數值(如幾美元和幾美分這樣的

小數)需要很精確。浮點數不是精確值,所以使用它們會導致舍入誤差。因此,使用浮點數來試圖表示象貨幣量這樣的精確數量不是

一個好的想法。使用浮點數來進行美元和美分計算會得到災難性的後果。浮點數最好用來表示像測量值這類數值,這類值從一開始就

不怎麼精確。


那我們怎樣來解決這樣的問題呢?


JDK開發人員在很早就遇到了這個問題,並在JDK1.3起給我們提供了一種新的處理精確值的類BigDecimal,BigDecimal是標

準的類,在編譯器中不需要特殊支援,它可以表示任意精度的小數,並對它們進行計算。在內部,可以用任意精度任何範圍的值和一個

換算因子來表示 BigDecimal,換算因子表示左移小數點多少位,從而得到所期望範圍內的值BigDecimal 給我們提供了加、減、乘和除

等算術運算,由於BigDecimal是一個類,而且對象是不可變,不像float、double是基本變數,所以計算後的結果將放入一個新BigDec

imal對象中,所以使用BigDecimal會佔用很大的開銷,不適合大規模的數學計算。設計它的目的是用來精確地表示小數,所以我們可以

用它來表示貨幣和金額的計算。


BigDecimal 用法:

BigDecimal構造方法:


BigDecimal     一共有4個構造方法BigDecimal(int)                 建立一個具有參數所指定整數值的對象。BigDecimal(double)        建立一個具有參數所指定雙精確度值的對象。BigDecimal(long)             建立一個具有參數所指定長整數值的對象。BigDecimal(String)          建立一個具有參數所指定以字串表示的數值的對象。

BigDecimal 的運算方式 不支援 + - * / 這類的運算 它有自己的運算方法:

BigDecimal add(BigDecimal augend) 加法運算BigDecimal subtract(BigDecimal subtrahend) 減法運算BigDecimal multiply(BigDecimal multiplicand) 乘法運算BigDecimal divide(BigDecimal divisor) 除法運算


 我們現在來看以第一個float精度錯誤的例子,用BigDecimal計算:

public static void main(String[] args) {float f = 0.1f;float sum = 0;for( int i=0; i<1000000; i++){    sum += f;}System.out.println("float sum="+sum);BigDecimal b1 = new BigDecimal(Double.toString(0.01)); BigDecimal total = new BigDecimal(Double.toString(0)); for( int i=0; i<1000000; i++){total=total.add(b1);}System.out.println("BigDecimal total="+total);}

結果:

float sum=100958.34BigDecimal total=10000.00
我們從結果中可以看出使用float計算結果是有誤差的,而使用BigDecimal計算結果是正確的。我們再看以第二個double精度

錯誤的例子,用BigDecimal計算:

public static void main(String[] args) {double d = 29.0 * 0.01;System.out.println(d);/***double計算**/System.out.println(d * 100);/***BigDecimal計算**/BigDecimal b1 = new BigDecimal(Double.toString(d));  BigDecimal b2 = new BigDecimal(Double.toString(100)); System.out.println( b1.multiply(b2).doubleValue() );}

結果:

0.2928.99999999999999629.0

我們從結果中可以看出使用double計算結果是有誤差的,而使用BigDecimal計算結果是正確的


從上面兩個例子可以看出BigDecimal能夠很好處理浮點數計算精度問題,但是效能方面比float、double低很多,但是為了提高

計算的精確度,尤其是像處理金額,建議大家還是使用BigDecimal進行計算,避免造成損失。

結束語:

結束語是引用IBM文件庫裡面的一段話:"在Java程式中使用浮點數和小數充滿著陷阱。浮點數和小數不象整數一樣“循規蹈矩”,

不能假定浮點計算一定產生整型或精確的結果,雖然它們的確“應該”那樣做。最好將浮點運算保留用作計算本來就不精確的數值,譬如

測量。如果需要表示定點數(譬如,幾美元和幾美分),則使用 BigDecimal 。"。


註:本文部分內容參考 IBM developerWorks中國的文件庫


金融、支付行業的開發人員不得不知道的float、double計算誤差問題

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.