JAVA精確計算
double型資料不能進行精確計算
JAVA的double型資料不能進行精確計算,許多程式語言也是一樣。請看下面的例子:
從這個例子可以看出計算的結果出現了誤差,所以用double型資料進行金額計算是不可靠的。
四捨五入**(截斷)
上面這個例子中,如果把計算結果按照指定的位數進行四捨五入,似乎就是我們想要的結果,但是在數學計算中四捨五入意味著誤差,並且JDK中沒有提供保留指定位數的四捨五入的API。
有一種方式是使用java.text.DecimalFormat類,它可以把數字格式化為指定的格式,但是它的舍入模式並不是我們數學上的四捨五入。請看下面的例子:
從這個例子可以看到,DecimalFormat類把1.145保留兩位小數之後是1.14而不是1.15,所以不能
double型數值在整數部分超過7位時就自動轉化為科學記數法表示,請看下面的例子:
通常情況下,我們不需要用科學記數法表示,從科學記數法轉回普通的記數方法也是比較繁瑣的。
BigDecimal是一個不變的、任意精度的有符號的十進位制數物件。它提供了四個構造方法,其中有兩個是用BigInteger構造的,在這裡重點講解用double和String構造的構造方法。
Constructor Summary |
BigDecimal(double val) Translates a double into a BigDecimal. |
BigDecimal(String val) Translates the String representation of a BigDecimal into a BigDecimal. |
BigDecimal(double)是把一個double型十進位制數構造為一個BigDecimal例項。
BigDecimal(String)是把一個以String表示的十進位制數構造為BigDecimal例項。
通常情況下,對於浮點數我們都會定義為double型別,但是在JDK的API文件中有這樣一段描述:BigDecimal(double)這個構造方法可能會有不可預知的結果。有人可能設想new BigDecimal(0.1)
然而(String)構造方法則是完全可預知的,new BigDecimal(“0.1”)精確的等於0.1,因此,new BigDecimal(String)構造方法是被優先推薦使用的。
請看下面的例子:
從這個例子可以看出,要進行精確的計算,必須使用BigDecimal(String val)。
用BigDecimal可以進行精確的四則運算,請看下面三個例子:
上面三個例子演示了用BigDecimal進行加法、減法、乘法運算,進行計算的資料與本文第一個例子是一樣的,而計算的結果都是正確的。用BigDecimal進行除法運算時涉及到保留有效位數和舍入模式問題,這也是BigDecimal類使用的重點,在下面的章節介紹。
BigDecimal定義了以下舍入模式,只有在做除法運算或四捨五入時才會用到舍入模式。下面的各小節的例子是應用各種舍入模式把double數值保留小數點後兩位:
Field Summary |
|
static int |
ROUND_CEILING Rounding mode to round towards positive infinity. |
static int |
ROUND_DOWN Rounding mode to round towards zero. |
static int |
ROUND_FLOOR Rounding mode to round towards negative infinity. |
static int |
ROUND_HALF_DOWN Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. |
static int |
ROUND_HALF_EVEN Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor. |
static int |
ROUND_HALF_UP Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up. |
static int |
ROUND_UNNECESSARY Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary. |
static int |
ROUND_UP Rounding mode to round away from zero. |
ROUND_CEILING模式是向正無窮大方向舍入。請看下面的例子:
在數軸上,對於正數,如果數值與1.02之間的距離不到0.01就進位到1.02,所以除非數值等於1.01不用進位,否則所有大於1.01且小於1.02的數值都要進位到1.02,因為在數軸上1.02比1.01更靠近正無窮大;對於負數,所有小於等於-1.01且大於-1.02的數值都要捨棄從小數點後第三位開始的所有數字,因為在數軸上-1.01比-1.02更靠近正無窮大,這就是ROUND_CEILING模式——向正無窮大方向舍入。
ROUND_FLOOR模式是向負無窮大方向舍入。請看下面的例子:
在數軸上,1.01比1.02更靠近負無窮大,所以,所有大於等於1.01且小於1.02的數值都要捨棄從小數點後第三位開始的所有數字;-1.02比-1.01更靠近負無窮大,所以,所有大於等於-1.02且小於-1.01的數值都要捨棄從小數點後第三位開始的所有數字,這就是ROUND_FLOOR模式——向靠近負無窮大的方向舍入。
ROUND_DOWN模式是向靠近零的方向舍入。請看下面的例子:
在數軸上,1.01比1.02更靠近零,所以,所有大於等於1.01且小於1.02的數值都要捨棄從小數點後第三位開始的所有數字;-1.01比-1.02更靠近零,所以,所有大於-1.02且小於等於-1.01的數值都要捨棄從小數點後第三位開始的所有數字,這就是ROUND_DOWN模式——向靠近零的方向舍入。
ROUND_UP模式是向遠離零的方向舍入。請看下面的例子:
ROUND_UP模式實際上對於正數就是向正無窮大方向舍入ROUND_CEILING,對於負數就是向負無窮大方向舍入ROUND_FLOOR,這就是ROUND_UP模式——向遠離零的方向舍入。
ROUND_ UNNECESSARY模式是不使用舍入模式。如果可以確保計算結果是精確的,則可以指定此模式,否則如果指定了使用此模式卻遇到了不精確的計算結果,則丟擲ArithmeticException。請看下面的例子:
在不使用舍入模式的情況下,1.01和1.01000都可以精確的保留到小數點後第二位,而1.01001卻不能,所以程式丟擲了異常。
ROUND_HALF_DOWN模式是向距離最近的一邊舍入,如果兩邊距離相等,就向靠近零的方向舍入。請看下面的例子:
在數軸上,1.01501與1.02之間的距離比1.01501與1.01之間的距離近,所以1.01501進位到1.02;1.01500和1.01之間的距離與1.01500和1.02之間的距離相等,而1.01比1.02更靠近零,所以1.01500保留兩位小數為1.01,這就是ROUND_HALF_DOWN模式——向距離最近的一邊舍入,如果兩邊距離相等,就向靠近零的方向舍入。
前面章節提到的java.text.DecimalFormat類使用的就是ROUND_HALF_DOWN模式。
(日常計算中使用)ROUND_HALF_UP
ROUND_HALF_UP模式是向距離最近的一邊舍入,如果兩邊距離相等,就向遠離零的方向舍入。請看下面的例子:
解釋:一個數1.015如保留兩2位小數,則舍入後是1.01或1.02,ROUND_HALF_UP此模式為向距離1.01和1.02近的方向舍;如果距離一樣向0方向舍。便為1.02 。
在數軸上,1.01501與1.02之間的距離比1.01501與1.01之間的距離近,所以1.01501進位到1.02;1.01500和1.01之間的距離與1.01500和1.02之間的距離相等,而1.02比1.01更遠離零,所以1.01500保留兩位小數為1.02,這就是ROUND_HALF_UP模式——向距離最近的一邊舍入,如果兩邊距離相等,就向遠離零的方向舍入。
從例子中可以看出:ROUND_HALF_UP模式就是我們數學上的四捨五入。
ROUND_HALF_UP模式是向距離最近的一邊舍入,如果兩邊距離相等且小數點後保留的位數是奇數,就使用ROUND_HALF_UP模式;如果兩邊距離相等且小數點後保留的位數是偶數,就使用ROUND_HALF_DOWN模式。請看下面的例子:
在以上8種舍入模式中,我們日常計算只需要使用ROUND_HALF_UP模式。
從結果可以看出,雖然指定了保留小數點後10位,但由於151.3除以100的商可以精確到小數點後3位,所以b3的值就是1.513。