前言:
之前在項目裏遇到一個問題:169/100=1.0
int nPtCount = 169;
final int MAX_ROUTE_PT = 100;
double lfDivide = 0;
if (nPtCount > MAX_ROUTE_PT) {
lfDivide = nPtCount / MAX_ROUTE_PT;
nPtCount = MAX_ROUTE_PT;
Log.d("TraceList","航跡被劃分:"+lfDivide );
}
log打印出來是1.0,後來查閱資料,發現用BigDecimal除法才能精確。
BigDecimal 構造方法
- public BigDecimal(double val) //將double表示形式轉換爲BigDecimal *不建議使用
- public BigDecimal(int val) //將int表示形式轉換成BigDecimal
- public BigDecimal(String val) //將String表示形式轉換成BigDecimal
爲什麼不建議採用第一種構造方法呢?來看例子
public static void main(String[] args)
{
BigDecimal bigDecimal = new BigDecimal(2);
BigDecimal bDouble = new BigDecimal(2.3);
BigDecimal bString = new BigDecimal("2.3");
System.out.println("bigDecimal=" + bigDecimal);
System.out.println("bDouble=" + bDouble);
System.out.println("bString=" + bString);
}
運行結果如下:
爲什麼會出現這種情況呢?
JDK的描述:
1、參數類型爲double的構造方法的結果有一定的不可預知性。有人可能認爲在Java中寫入newBigDecimal(0.1)所創建的BigDecimal正好等於 0.1(非標度值 1,其標度爲 1),但是它實際上等於0.1000000000000000055511151231257827021181583404541015625。這是因爲0.1無法準確地表示爲 double(或者說對於該情況,不能表示爲任何有限長度的二進制小數)。這樣,傳入到構造方法的值不會正好等於 0.1(雖然表面上等於該值)。
2、另一方面,String 構造方法是完全可預知的:寫入 newBigDecimal("0.1") 將創建一個 BigDecimal,它正好等於預期的 0.1。因此,比較而言,通常建議優先使用String構造方法。
當double必須用作BigDecimal的源時,請使用Double.toString(double)
轉成String,然後
使用String構造方法,或使用BigDecimal的靜態方法valueOf,如下:
public static void main(String[] args)
{
BigDecimal bDouble1 = BigDecimal.valueOf(2.3);
BigDecimal bDouble2 = new BigDecimal(Double.toString(2.3));
System.out.println("bDouble1=" + bDouble1);
System.out.println("bDouble2=" + bDouble2);
}
結果如下:
BigDecimal加減乘除運算
對於常用的加,減,乘,除,BigDecimal類提供了相應的成員方法。
public BigDecimal add(BigDecimal value); //加法
public BigDecimal subtract(BigDecimal value); //減法
public BigDecimal multiply(BigDecimal value); //乘法
public BigDecimal divide(BigDecimal value); //除法
大概的用法如下:
public static void main(String[] args)
{
BigDecimal a = new BigDecimal("4.5");
BigDecimal b = new BigDecimal("1.5");
System.out.println("a + b =" + a.add(b));
System.out.println("a - b =" + a.subtract(b));
System.out.println("a * b =" + a.multiply(b));
System.out.println("a / b =" + a.divide(b));
}
運行結果:
這裏有一點需要注意的是除法運算divide.
BigDecimal除法可能出現不能整除的情況,比如 4.5/1.3,這時會報錯java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
其實divide方法有可以傳三個參數
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
第一參數表示除數, 二個參數表示小數點後保留位數,第三個參數表示舍入模式。
只有在作除法運算或四捨五入時纔用到舍入模式,有下面這幾種:
- ROUND_UP :向遠離零的方向舍入。捨棄非零部分,並將非零捨棄部分相鄰的一位數字加一。
- ROUND_DOWN :向接近零的方向舍入。捨棄非零部分,同時不會非零捨棄部分相鄰的一位數字加一,採取截取行爲。
- ROUND_CEILING :向正無窮的方向舍入。如果爲正數,舍入結果同ROUND_UP一致;如果爲負數,舍入結果同ROUND_DOWN一致。注意:此模式不會減少數值大小。
- ROUND_FLOOR :向負無窮的方向舍入。如果爲正數,舍入結果同ROUND_DOWN一致;如果爲負數,舍入結果同ROUND_UP一致。注意:此模式不會增加數值大小。
- ROUND_HALF_UP :向“最接近”的數字舍入,如果與兩個相鄰數字的距離相等,則爲向上舍入的舍入模式。如果捨棄部分>= 0.5,則舍入行爲與ROUND_UP相同;否則舍入行爲與ROUND_DOWN相同。這種模式也就是我們常說的我們的“四捨五入”。
- ROUND_HALF_DOWN :向“最接近”的數字舍入,如果與兩個相鄰數字的距離相等,則爲向下舍入的舍入模式。如果捨棄部分> 0.5,則舍入行爲與ROUND_UP相同;否則舍入行爲與ROUND_DOWN相同。這種模式也就是我們常說的我們的“五舍六入”。
- ROUND_HALF_EVEN :向“最接近”的數字舍入,如果與兩個相鄰數字的距離相等,則相鄰的偶數舍入。如果捨棄部分左邊的數字奇數,則舍入行爲與 ROUND_HALF_UP 相同;如果爲偶數,則舍入行爲與 ROUND_HALF_DOWN 相同。注意:在重複進行一系列計算時,此舍入模式可以將累加錯誤減到最小。此舍入模式也稱爲“銀行家舍入法”,主要在美國使用。四捨六入,五分兩種情況,如果前一位爲奇數,則入位,否則捨去。
- ROUND_UNNECESSARY :斷言請求的操作具有精確的結果,因此不需要舍入。如果對獲得精確結果的操作指定此舍入模式,則拋出ArithmeticException。
按照各自的需要,可傳入合適的第三個參數。四捨五入採用 ROUND_HALF_UP
需要對BigDecimal進行截斷和四捨五入可用setScale方法,例:
public static void main(String[] args)
{
BigDecimal a = new BigDecimal("4.5635");
a = a.setScale(3, RoundingMode.HALF_UP); //保留3位小數,且四捨五入
System.out.println(a);
}
減乘除其實最終都返回的是一個新的BigDecimal對象,因爲BigInteger與BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象:
public static void main(String[] args)
{
BigDecimal a = new BigDecimal("4.5");
BigDecimal b = new BigDecimal("1.5");
a.add(b);
System.out.println(a); //輸出4.5. 加減乘除方法會返回一個新的BigDecimal對象,原來的a不變
}
BigDecimal比較大小
BigDecimal a = new BigDecimal (101);
BigDecimal b = new BigDecimal (111);
//使用compareTo方法比較
//注意:a、b均不能爲null,否則會報空指針
if(a.compareTo(b) == -1){
System.out.println("a小於b");
}
if(a.compareTo(b) == 0){
System.out.println("a等於b");
}
if(a.compareTo(b) == 1){
System.out.println("a大於b");
}
if(a.compareTo(b) > -1){
System.out.println("a大於等於b");
}
if(a.compareTo(b) < 1){
System.out.println("a小於等於b");
}
BigDecimal轉String
public static void main(String[] args) {
// 浮點數的打印
System.out.println(new BigDecimal("10000000000").toString());
// 普通的數字字符串
System.out.println(new BigDecimal("100.000").toString());
// 去除末尾多餘的0
System.out.println(new BigDecimal("100.000").stripTrailingZeros().toString());
// 避免輸出科學計數法
System.out.println(new BigDecimal("100.000").stripTrailingZeros().toPlainString());
}
// output
100.000
1E+2
- 用toString()方法輸出的就是普通的數字字符串。
- stripTrailingZeros()函數就是用於去除末尾多餘的0的,
- 用toPlainString()函數代替toString(),避免輸出科學計數法的字符串。
總結
- 商業計算使用BigDecimal。
- 儘量使用參數類型爲String的構造函數。
- BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,所以在做加減乘除運算時千萬要保存操作後的值。