Java中的float、double計算精度問題


java中的float、double計算存在精度問題,這不僅僅在java會出現,在其他語言中也會存在,其原因是出在IEEE 754標准上。

而java對此提供了一個用於浮點型計算的類——BigDecimal(java.math.BigDecimal),通過將double替換成BigDecimal進行計算可以獲得較為精確的計算結果。

BigDecimal的構造方法有許多,在此推薦使用BigDecimal(String val)的構造方法,通過String字符串進行構造。可能會有人直接使用BigDecimal(double val)去構造,但為什么推薦要使用String而不用double直接構造?原因如BigDecimal(double val)前的注釋所說:

    Translates a {@code double} into a {@code BigDecimal} which is the exact decimal representation of the {@code double}'s binary floating-point value. The scale of the returned {@code BigDecimal} is the smallest value such that (10 scale val) is an integer.
    Notes: The results of this constructor can be somewhat unpredictable. One might assume that writing {@code new BigDecimal(0.1)} in Java creates a {@code BigDecimal} which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625.This is because 0.1 cannot be represented exactly as a {@code double} (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
    The {@code String} constructor, on the other hand, is perfectly predictable: writing {@code new BigDecimal("0.1")} creates a {@code BigDecimal} which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the {@linkplain #BigDecimal(String) String constructor} be used in preference to this one.
    When a {@code double} must be used as a source for a {@code BigDecimal}, note that this constructor provides an exact conversion; it does not give the same result as converting the {@code double} to a {@code String} using the {@link Double#toString(double)} method and then using the {@link #BigDecimal(String)} constructor. To get that result, use the {@code static} {@link #valueOf(double)} method.

balabala的一大段?挑出重點看,Notes(說明):The results of this constructor can be somewhat unpredictable(這個結果可能是不可預測的)。原因在后面一句話也說了,若用 0.1 new一個BigDecimal,則它實際上是等於0.1000000000000000055511151231257827021181583404541015625,原因無需細看,跳過。

The {@code String} constructor, on the other hand, is perfectly predictable(通過字符串構造,是可以完全預測的)。到此注釋說的差不多了。

而BigDecimal中一系列的add、subtract等方法對應着加減乘除就不必多說。

到這里就結束了嗎?不,如果單純的將double替換成BigDecimal,就會大幅降低程序的運行速度,因此需要進行一定的優化:非替換,而是改進。

依舊用double定義、儲存數據,但計算時,使用BigDecimal進行計算(若需要精確的計算),最后只需.doubleValue()即可得到較為精確的double類型的計算結果了。

由此可以編寫一個用於浮點型計算的工具類,專職浮點型計算工作。

 1 import java.math.BigDecimal;
 2 
 3 /**
 4  * 浮點型較為精確計算工具類
 5  */
 6 public class CalculateUtil {
 7     /**
 8      * 私有構造
 9      */
10     private CalculateUtil(){}
11     /**
12      * 加法運算
13      * @param num1 被加數
14      * @param num2 加數
15      * @return 兩數之和
16      */
17     public static double add(double num1, double num2){
18         BigDecimal b1 = new BigDecimal(Double.toString(num1));
19         BigDecimal b2 = new BigDecimal(Double.toString(num2));
20         return b1.add(b2).doubleValue();
21     }
22     /**
23      * 減法運算
24      * @param num1 被減數
25      * @param num2 減數
26      * @return 兩數之差
27      */
28     public static double sub(double num1, double num2){
29         BigDecimal b1 = new BigDecimal(Double.toString(num1));
30         BigDecimal b2 = new BigDecimal(Double.toString(num2));
31         return b1.subtract(b2).doubleValue();
32     }
33     /**
34      * 乘法運算
35      * @param num1 被乘數
36      * @param num2 乘數
37      * @return 兩數之積
38      */
39     public static double mul(double num1, double num2){
40         BigDecimal b1 = new BigDecimal(Double.toString(num1));
41         BigDecimal b2 = new BigDecimal(Double.toString(num2));
42         return b1.multiply(b2).doubleValue();
43     }
44     /**
45      * 除法運算(小數點后10位)
46      * @param num1 被除數
47      * @param num2 除數
48      * @return 兩數之商
49      */
50     public static double div(double num1, double num2){
51         return div(num1, num2, 10);
52     }
53     
54     /**
55      * 除法運算
56      * @param num1 被除數
57      * @param num2 除數
58      * @param scale 小數點后精度位數
59      * @return 兩數之商
60      */
61     public static double div(double num1, double num2, int scale){
62         if(scale<0)
63             throw new IllegalArgumentException("The scale must be a positive integer or zero");  
64         BigDecimal b1 = new BigDecimal(Double.toString(num1));
65         BigDecimal b2 = new BigDecimal(Double.toString(num2));
66         return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
67     }
68     /**
69      * 四舍五入
70      * @param num 需要四舍五入的數
71      * @param scale 小數點后精度位數
72      * @return 四舍五入值
73      */
74     public static double round(double num, int scale){
75         if(scale<0)
76             throw new IllegalArgumentException("The scale must be a positive integer or zero");  
77         BigDecimal b1 = new BigDecimal(Double.toString(num));
78         return b1.divide(new BigDecimal("1"), scale, BigDecimal.ROUND_HALF_UP).doubleValue();
79     }
80 
81 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM