1. 程式人生 > >使用BigDecimal實現精確加減乘除運算

使用BigDecimal實現精確加減乘除運算

  • 商業計算不要使用浮點數

在前幾期的每日一練題目中,有一道題目是,需要精確計算的時候一定要使用BigDecimal。在本科時候其實計算機原理就曾經學過計算機的浮點數表示,但是從小數學的慣性思維上,總覺得乘法就移一下小數點,不會出問題。在商業計算中遇到帶小數點的“元”轉換為“分”的時候,進行*100的操作總覺得double根本不會出問題,於是怒踩了一坑。

  • 簡單理解為什麼會有精度問題

大家都知道計算機要用二進位制表示,基本的十進位制和二進位制的換算應該都清楚。例如:
0.5(十進位制) = 0.1(二進位制),因為2^-1是0.5。
0.25(十進位制) = 0.01(二進位制),因為2^-2是0.25。

那0.2呢,怎麼表示呢,表示不出來啊。那就只能找個最接近的近似值來代表。
所以0.2的二進位制小數是0.0011001100110011.......,也就是(2^-3)+(2^-4)+(2^-7)+(2^-8)+.....

  • 簡單介紹IEEE754表示法

以Java中的double值0.2為例,double大小為32位。

System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(0.2)));

執行以上程式碼可以看到double型別0.2在機器中的二進位制表示為:11111111001001100110011001100110011001100110011001100110011010。

需要將其補齊64位,在前面加兩個0:
0011111111001001100110011001100110011001100110011001100110011010。

按照IEEE754表示法,我們將其分為三段:
符號(64位):0,
階碼(63-53位):01111111100,
尾數(51-1位):001100110011001100110011001100110011001100110011010。

System.out.println(Integer.valueOf("01111111100",2));

執行以上程式碼可以看到階碼的十進位制值為1020。當階碼不為0或2047時,需要減去1023這個偏移量,並且尾數前面隱藏了”1.”。因此,調整後的表示為:
符號:0,
階碼:-3(十進位制),
尾數:1.1001100110011001100110011001100110011001100110011010。

最終,按照-1^符號 尾數 2^階碼的計算方式,0.2真正的二進位制值如下所示,和真正的0.2明顯是有偏差的:
0.0011001100110011001100110011001100110011001100110011010

  • Java中正確使用BigDecimal

在Java中遇到商業計算,可以方便的使用BigDecimal來解決精度問題。但是使用中也存在需要注意的地方。

doubled =0.2; System.out.println(newBigDecimal(d));

執行以上程式碼可以看到,java中表示的0.2的值為:
0.200000000000000011102230246251565404236316680908203125,是有偏差的。

但是雖然用了BigDecimal,為什麼資料還是有問題?這是因為在使用BigDecimal時,不要使用double引數的構造方法,而是要使用String引數的構造方法,或是靜態方法valueOf,才能夠保證資料的準確性。

//兩種正確的使用方式 

System.out.println(newBigDecimal("0.2")); 

System.out.println(BigDecimal.valueOf(0.2));

BigDecimal類建立的是物件,不能使用傳統的+、-、*、/等算術運算子直接對其進行數學運算,而必須呼叫其對應的方法.方法的引數也必須是BigDecimal型別的物件.

  • 構造BigDecimal 物件常用方法

  • 方法一

BigDecimal BigDecimal(double d); //不允許使用
  • 方法二 

BigDecimal BigDecimal(String s); //常用,推薦使用
  • 方法三

static BigDecimal valueOf(double d); //常用,推薦使用
  • 注意:

  1. double 引數的構造方法,不允許使用!!!!因為它不能精確的得到相應的值,值會變大;


  2. String 構造方法是完全可預知的: 寫入 new BigDecimal("0.1") 將建立一個 BigDecimal,它正好等於預期的0.1; 因此,通常建議優先使用 String 構造方法;


  3. 靜態方法 valueOf(double val) 內部實現,仍是將 double 型別轉為 String 型別; 這通常是將 double(或float)轉化為 BigDecimal 的首選方法。