1. 程式人生 > >Java中double轉BigDecimal的注意事項

Java中double轉BigDecimal的注意事項

先上結論:不要直接用double變數作為構造BigDecimal的引數。

線上有這麼一段Java程式碼邏輯:

1,介面傳來一個JSON串,裡面有個數字:57.3。

2,解析JSON並把這個數字儲存在一個float變數。

3,把這個float變數賦值給一個 BigDecimal物件,用的是BigDecimal的double引數的構造:

   new BigDecimal(double val)

4,把這個BigDecimal儲存到MySQL資料庫,欄位型別是decimal(15,2)。

這段程式碼邏輯在線上跑了好久了,資料庫儲存的值是57.3也沒什麼問題,但是在今天debug的時候發現,第三步的BigDecimal物件儲存的值並不是57.3,而是57.299999237060546875,很明顯,出現了精度的問題。

至於資料庫最終儲存了正確的57.3完全是因為欄位型別設定為2位小數,超過2位小數就四捨五入,所以才得到了正確的結果,相當於MySQL給我們把這個精度問題掩蓋了。

總覺得這是個坑,所以研究了一下相關的知識。

首先是BigDecimal的double引數構造,在官方JDK文件中對這個構造是這麼描述的:

public BigDecimal(double val)

Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value. The scale of the returned BigDecimal is the smallest value such that (10scale × val) is an integer.

Notes:

The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a 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 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 String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.

When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor. To get that result, use the static valueOf(double) method.

Parameters:

val - double value to be converted to BigDecimal.

Throws:

NumberFormatException - if val is infinite or NaN.

翻譯一下大概是這樣的:

1,BigDecimal(double val)構造,用double當引數來構造一個BigDecimal物件。

2,但是這個構造不太靠譜(unpredictable),你可能以為BigDecimal(0.1)就是妥妥的等於0.1,但是你以為你以為的就是你以為的?還真不是,BigDecimal(0.1)這貨實際上等於0.1000000000000000055511151231257827021181583404541015625,因為準確的來說0.1本身不能算是一個double(其實0.1不能代表任何一個定長二進位制分數)。

3,BigDecimal(String val)構造是靠譜的,BigDecimal(“0.1”)就是妥妥的等於0.1,推薦大家用這個構造。

4,如果你非得用一個double變數來構造一個BigDecimal,沒問題,我們貼心的提供了靜態方法valueOf(double),這個方法跟new Decimal(Double.toString(double))效果是一樣的。

說白了就是別直接拿double變數做引數,最好使用String型別做引數或者使用靜態方法valueOf(double),我寫了個例子試了一下:

         public static void main(String[] args) {



                   float a=57.3f;

                   BigDecimal decimalA=new BigDecimal(a);

                   System.out.println(decimalA);

                  

                   double b=57.3;

                   BigDecimal decimalB=new BigDecimal(b);

                   System.out.println(decimalB);

                  

                   double c=57.3;

                   BigDecimal decimalC=new BigDecimal(Double.toString(c));

                   System.out.println(decimalC);

                  

                   double d=57.3;

                   BigDecimal decimalD=BigDecimal.valueOf(d);

                   System.out.println(decimalD);

         }

輸出結果:

57.299999237060546875

57.2999999999999971578290569595992565155029296875

57.3

57.3

以後還是儘量按照官方推薦的套路來,否則不知道什麼時候又給自己挖坑了。