1. 程式人生 > >java小數運算,內附現成的工具類。不能用double和float,要用decimal

java小數運算,內附現成的工具類。不能用double和float,要用decimal

https://www.cnblogs.com/xujishou/p/7491932.h

為什麼會出現這個問題呢,就這是java和其它計算機語言都會出現的問題,下面我們分析一下為什麼會出現這個問題:
float和double型別主要是為了科學計算和工程計算而設計的。他們執行二進位制浮點運算,這是為了在廣泛的數字範圍上提供較為精確的快速近似計算而精心設計的。然而,它們並沒有提供完全精確的結果,所以我們不應該用於精確計算的場合。float和double型別尤其不適合用於貨幣運算,因為要讓一個float或double精確的表示0.1或者10的任何其他負數次方值是不可能的(其實道理很簡單,十進位制系統中能不能準確表示出1/3呢?同樣二進位制系統也無法準確表示1/10)。

浮點運算很少是精確的,只要是超過精度能表示的範圍就會產生誤差。往往產生誤差不是因為數的大小,而是因為數的精度。因此,產生的結果接近但不等於想要的結果。尤其在使用 float 和 double 作精確運算的時候要特別小心。

現在我們就詳細剖析一下浮點型運算為什麼會造成精度丟失?

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

首先我們要搞清楚下面兩個問題:

 

     (1) 十進位制整數如何轉化為二進位制數

 

           演算法很簡單。舉個例子,11表示成二進位制數:

 

                     11/2=5 餘   1

 

                       

5/2=2   餘   1

 

                       2/2=1   餘   0

 

                       1/2=0   餘   1

 

                          0結束         11二進位制表示為(從下往上):1011

 

          這裡提一點:只要遇到除以後的結果為0了就結束了,大家想一想,所有的整數除以2是不是一定能夠最終得到0。換句話說,所有的整數轉變為二進位制數的演算法會不會無限迴圈下去呢?絕對不會,整數永遠可以用二進位制精確表示 ,但小數就不一定了。

 

      (2) 十進位制小數如何轉化為二進位制數

 

           演算法是乘以2直到沒有了小數為止。舉個例子,0.9表示成二進位制數

 

                     0.9*2=1.8   取整數部分 1

 

                     0.8(1.8的小數部分)*2=1.6    取整數部分 1

 

                     0.6*2=1.2   取整數部分 1

 

                     0.2*2=0.4   取整數部分 0

 

                     0.4*2=0.8   取整數部分 0

 

                     0.8*2=1.6 取整數部分 1

 

                     0.6*2=1.2   取整數部分 0

 

                              .........      0.9二進位制表示為(從上往下): 1100100100100......

 

           注意:上面的計算過程迴圈了,也就是說*2永遠不可能消滅小數部分,這樣演算法將無限下去。很顯然,小數的二進位制表示有時是不可能精確的 。其實道理很簡單,十進位制系統中能不能準確表示出1/3呢?同樣二進位制系統也無法準確表示1/10。這也就解釋了為什麼浮點型減法出現了"減不盡"的精度丟失問題。

解決方法

使用BigDecmal,而且需要在構造引數使用String型別。

在《Effective Java》這本書中就給出了一個解決方法。該書中也指出,float和double只能用來做科學計算或者是工程計算,在商業計算等精確計算中,我們要用java.math.BigDecimal。

    BigDecimal類有4個構造方法,我們只關心對我們解決浮點型資料進行精確計算有用的方法,即

BigDecimal(double value) // 將double型資料轉換成BigDecimal型資料

    思路很簡單,我們先通過BigDecimal(double value)方法,將double型資料轉換成BigDecimal資料,然後就可以正常進行精確計算了。等計算完畢後,我們可以對結果做一些處理,比如 對除不盡的結果可以進行四捨五入。最後,再把結果由BigDecimal型資料轉換回double型資料。

    這個思路很正確,但是如果你仔細看看API裡關於BigDecimal的詳細說明,你就會知道,如果需要精確計算,我們不能直接用double,而非要用 String來構造BigDecimal不可!所以,我們又開始關心BigDecimal類的另一個方法,即能夠幫助我們正確完成精確計算的 BigDecimal(String value)方法。

// BigDecimal(String value)能夠將String型資料轉換成BigDecimal型資料

    那麼問題來了,想像一下吧,如果我們要做一個浮點型資料的加法運算,需要先將兩個浮點數轉為String型資料,然後用 BigDecimal(String value)構造成BigDecimal,之後要在其中一個上呼叫add方法,傳入另一個作為引數,然後把運算的結果(BigDecimal)再轉換為浮 點數。如果每次做浮點型資料的計算都要如此,你能夠忍受這麼煩瑣的過程嗎?至少我不能。所以最好的辦法,就是寫一個類,在類中完成這些繁瑣的轉換過程。這 樣,在我們需要進行浮點型資料計算的時候,只要呼叫這個類就可以了。網上已經有高手為我們提供了一個工具類Arith來完成這些轉換操作。它提供以下靜態 方法,可以完成浮點型資料的加減乘除運算和對其結果進行四捨五入的操作:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

package com.util;

import java.math.BigDecimal;

 

/**

 * 由於Java的簡單型別不能夠精確的對浮點數進行運算,這個工具類提供精確的浮點數運算,包括加減乘除和四捨五入。

 */

public class Arith {

 

    //預設吃吃飯運算精度

    private static final int DEF_DIV_SCALE = 10;

 

    //這個類不能例項化

    private Arith() {

         

    }

 

    /**

     * 提供精確的加法運算

     *

     * @param v1

     *            被加數

     * @param v2

     *            加數

     * @return 兩個引數的和

     */

    public static double add(double v1, double v2) {

        BigDecimal b1 = new BigDecimal(Double.toString(v1));

        BigDecimal b2 = new BigDecimal(Double.toString(v2));

        return b1.add(b2).doubleValue();

    }

 

    /**

     * 提供精確的減法運算

     * @param v1

     *            被減數

     * @param v2

     *            減數

     * @return兩個引數的差

     */

    public static double sub(double v1, double v2) {

        BigDecimal b1 = new BigDecimal(Double.toString(v1));

        BigDecimal b2 = new BigDecimal(Double.toString(v2));

        return b1.subtract(b2).doubleValue();

    }

 

    /**

     * 提供精確的乘法運算

     *

     * @param v1

     *            被乘數

     * @param v2

     *            乘數

     * @return 兩個引數的積

     */

    public static double mul(double v1, double v2) {

        BigDecimal b1 = new BigDecimal(Double.toString(v1));

        BigDecimal b2 = new BigDecimal(Double.toString(v2));

        return b1.multiply(b2).doubleValue();

    }

 

    /**

     * 提供(相對)精確的除非運算,當發生除不盡的情況時,精確到小數點以後10位,以後的數字四捨五入

     * @param v1

     *            被除數

     * @param v2

     *            除數

     * @return 兩個引數的商

     */

    public static double div(double v1, double v2) {

        return div(v1, v2, DEF_DIV_SCALE);

    }

 

    /**

     * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale引數指定精度,以後的數字四捨五入

     * @param v1

     *            被除數

     * @param v2

     *            除數

     * @param scale

     *            表示表示需要精確到小數點以後位數。

     * @return 兩個引數的商

     */

    public static double div(double v1, double v2, int scale) {

        if (scale < 0) {

            throw new IllegalArgumentException(

                    "The scale must be a positive integer or zero");

        }

        BigDecimal b1 = new BigDecimal(Double.toString(v1));

        BigDecimal b2 = new BigDecimal(Double.toString(v2));

        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();

    }

 

    /**

     * 提供精確的小數位四捨五入處理。

     * 提供精確的小數位四捨五入處理

     *

     * @param v

     *            需要四捨五入的數位

     * @param scale

     *            小數點後保留幾位

     * @return 四捨五入後的結果

     */

    public static double round(double v, int scale) {

        if (scale < 0) {

            throw new IllegalArgumentException(

                    "The scale must be a positive integer or zero");

        }

        BigDecimal b = new BigDecimal(Double.toString(v));

        BigDecimal one = new BigDecimal("1");

        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();

    }

}

  

附上Arith的原始碼,大家只要把它編譯儲存好,要進行浮點數計算的時候,在你的源程式中匯入Arith類就可以使用以上靜態方法來進行浮點數的精確計算了

tml