[ JAVA编程 ] double类型计算精度丢失问题及解决方法
前言
如果你在測試金融相關產品,請務必覆蓋交易金額為小數的場景。特別是使用Java語言的初級開發。
Java基本實例
先來看Java中double類型數值加、減、乘、除計算式實例:
public class Test{
public static void main(String [] args){
System.out.println(0.06+0.01);
System.out.println(1.0-0.42);
System.out.println(4.015*100);
System.out.println(303.1/1000);
}
}
運行結果如下:
D:linyfeng>java Test 0.06+0.01 = 0.06999999999999999 1.0-0.42 = 0.5800000000000001 4.015*100 = 401.49999999999994 303.1/1000 = 0.30310000000000004
我們發現,計算出來的值和我們預期結果不一致。原因在于我們的計算機是二進制的。浮點數沒有辦法使用二進制進行精確表示。計算機的CPU表示浮點數由兩個部分組成:指數和尾數,這樣的表示方法一般都會失去一定的精確度,有些浮點數運算也會產生一定的誤差。如:2.4的二進制表示并非就是精確的2.4。反而最為接近的二進制表示是 2.3999999999999999。浮點數的值實際上是由一個特定的數學公式計算得到的??蓞⒖糷ttp://blog.csdn.net/abing37/article/details/5332798
那么我們如何才能夠獲取我們想要的預期結果呢?特別是在處理金額交易計算上。其實java的float只能用來進行科學計算或工程計算,在大多數的商業計算中,一般采用java.math.BigDecimal類來進行精確計算。在使用BigDecimal類來進行計算的時候,主要分為以下步驟:
(1)用float或者double變量構建BigDecimal對象。通常使用BigDecimal的構造方法或者靜態方法的valueOf()方法把基本類型的變量構建成BigDecimal對象。
(2)通過調用BigDecimal的加,減,乘,除等相應的方法進行算術運算。
(3)把BigDecimal對象轉換成float,double,int等類型。
BigDecimal類基本介紹
在修改實例之前,我們先簡單了解一下BigDecimal類的構造函數和成員方法。
BigDecimal(int var) //創建一個具有參數所指定整數值的對象。 BigDecimal(double var) //創建一個具有參數所指定雙精度值的對象。 BigDecimal(long var) //創建一個具有參數所指定長整數值的對象。 BigDecimal(String var) //創建一個具有參數所指定以字符串表示的數值的對象。
成員方法(BigDecimal 的運算方式 不支持 + - * / 這類的運算 它有自己的運算方法)
BigDecimal add(BigDecimal augend) //加法運算 BigDecimal subtract(BigDecimal subtrahend) //減法運算 BigDecimal multiply(BigDecimal multiplicand) //乘法運算 BigDecimal divide(BigDecimal divisor) //除法運算
好,既然我們知道方法了,那么我們就用新方法來解決一下上述的問題。修改一下代碼,如下:
import java.math.*;
public class Test{
public static void main(String [] args){
double d1 = 0.06;
double d2 = 0.01;
BigDecimal b1 = new BigDecimal(d1);
BigDecimal b2 = new BigDecimal(d2);
System.out.println(b1.add(b2).doubleValue());
double d3 = 1.0;
double d4 = 0.42;
BigDecimal b3 = new BigDecimal(d3);
BigDecimal b4 = new BigDecimal(d4);
System.out.println(b3.subtract(b4).doubleValue());
double d5 = 4.015;
double d6 = 100;
BigDecimal b5 = new BigDecimal(d5);
BigDecimal b6 = new BigDecimal(d6);
System.out.println(b5.multiply(b6).doubleValue());
double d7 = 303.1;
double d8 = 1000;
BigDecimal b7 = new BigDecimal(d7);
BigDecimal b8 = new BigDecimal(d8);
System.out.println(b7.divide(b8).doubleValue());
}
}
運行結果:
D:linyfeng>java Test 0.06999999999999999 0.5800000000000001 401.49999999999994 0.30310000000000004
我們發現結果還是不對。從上述實例我們知道調用的構造方法為BigDecimal(double var)。BigDecimal(double val)將 double 轉換為 BigDecimal,后者是double的二進制浮點值準確的十進制表示形式。返回的BigDecimal的標度是使 (10scale × val) 為整數的最小值。這里也幾個特別要注意的地方:
(1)此構造方法的結果有一定的不可預知性。有人可能認為在 Java 中寫入 new BigDecimal(0.1) 所創建的 BigDecimal 正好等于 0.1(非標度值 1,其標度為 1),但是它實際上等于 0.1000000000000000055511151231257827021181583404541015625。這是因為 0.1 無法準確地表示為 double(或者說對于該情況,不能表示為任何有限長度的二進制小數)。這樣,傳入 到構造方法的值不會正好等于 0.1(雖然表面上等于該值)。
(2)另一方面,String 構造方法是完全可預知的:寫入 new BigDecimal("0.1") 將創建一個 BigDecimal,它正好 等于預期的 0.1。因此,比較而言,通常建議優先使用 String 構造方法。
(3)當 double 必須用作 BigDecimal 的源時,請注意,此構造方法提供了一個準確轉換;它不提供與以下操作相同的結果:先使用 Double.toString(double) 方法,然后使用 BigDecimal(String) 構造方法,將 double 轉換為 String。要獲取該結果,請使用 static valueOf(double) 方法。
根據上述描述,我們繼續修改下例子,修改后如下:
import java.math.*;
public class Test{
public static void main(String [] args){
double d1 = 0.06;
double d2 = 0.01;
BigDecimal b1 = new BigDecimal(Double.toString(d1));
BigDecimal b2 = new BigDecimal(Double.toString(d2));
System.out.println(b1.add(b2).doubleValue());
double d3 = 1.0;
double d4 = 0.42;
BigDecimal b3 = new BigDecimal(Double.toString(d3));
BigDecimal b4 = new BigDecimal(Double.toString(d4));
System.out.println(b3.subtract(b4).doubleValue());
double d5 = 4.015;
double d6 = 100;
BigDecimal b5 = new BigDecimal(Double.toString(d5));
BigDecimal b6 = new BigDecimal(Double.toString(d6));
System.out.println(b5.multiply(b6).doubleValue());
double d7 = 303.1;
double d8 = 1000;
BigDecimal b7 = new BigDecimal(Double.toString(d7));
BigDecimal b8 = new BigDecimal(Double.toString(d8));
System.out.println(b7.divide(b8).doubleValue());
}
}
運行結果如下:
D:linyfeng>java Test 0.07 0.58 401.5 0.3031
計算精度正確。
總結
(1)需要精確的表示兩位小數時我們需要把他們轉換為BigDecimal對象,然后再進行運算。
(2)使用BigDecimal(double val)構造函數時仍會存在精度丟失問題,建議使用BigDecimal(String val)。這就需要先把double類型(調用Double.toString(double var))轉換為字符串然后在作為BigDecimal(String val)構造函數的參數。轉換為BigDecimal對象之后再進行加減乘除操作,這樣精度就不會出現問題了。這也是為什么有關金錢數據存儲都使用BigDecimal。
參考文檔
1、使用BigDecimal進行精確運算
2、java中double和float精度丟失問題及解決方法
3、百度百科BigDecimal
總結
以上是生活随笔為你收集整理的[ JAVA编程 ] double类型计算精度丢失问题及解决方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电风扇的英文(电风扇用英语怎么说?)
- 下一篇: 计算视频播放的时间(pts)