BigDecimal淺析
為什么使用BigDecimal
首先看一個例子:
public class DoubleTest {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
}
}
輸出的結果:0.30000000000000004(我們的預期是0.3)
其實float和double類型的主要設計目標是為了科學計算和工程計算。他們執行二進制浮點運算,這是為了在廣域數值范圍上提供較為精確的快速近似計算而精心設計的。然而,它們沒有提供完全精確的結果,所以不應該被用于要求精確結果的場合。所有就出現了BigDecimal。
jdk文檔BigDecimal的描述:
不可變的、任意精度的有符號十進制數。BigDecimal 由任意精度的整數非標度值和32位的整數標度(scale)組成。BigDecimal 表示的數值是 (unscaledValue × 10-scale)。</pre>
BigDecial構造方法
下面是常用的四種構造方法:
BigDecimal(int)
BigDecimal(double)
BigDecimal(long)
BigDecimal(String)
public static void main(String[] args) {
double a = 123.11;
BigDecimal bigDecimal = new BigDecimal(a);
System.out.println(bigDecimal);
}
輸出結果:123.1099999999999994315658113919198513031005859375
public static void main(String[] args) {
String a = "123.11";
BigDecimal bigDecimal = new BigDecimal(a);
System.out.println(bigDecimal);
}
輸出結果:123.11
public static void main(String[] args) {
double a = 123.11;
System.out.println(BigDecimal.valueOf(a));
}
輸出結果:123.11
BigDecimal(double)這個構造方法為什么會輸出不是我們預期的值:
查看源碼的注釋得知: new BigDecimal(0.1)所創建的BigDecimal實際上等于 0.1000000000000000055511151231257827021181583404541015625,這是因為0.1無法準確地表示為 double(或者不能表示為任何有限長度的二進制小數),所以建議使用BigDecimal(String)構造方法。
valueOf其實也調用BigDecimal(String)構造方法
public static BigDecimal valueOf(double val) {
// Reminder: a zero double returns '0.0', so we cannot fastpath
// to use the constant ZERO. This might be important enough to
// justify a factory approach, a cache, or a few private
// constants, later.
return new BigDecimal(Double.toString(val));
}</pre>
BigDecimal基本運算
add(BigDecimal) BigDecimal對象中的值相加
subtract(BigDecimal) BigDecimal對象中的值相減
multiply(BigDecimal) BigDecimal對象中的值相乘
divide(BigDecimal) BigDecimal對象中的值相除
注意:+-* /運算返回的是新的BigDecimal對象
比較大小: compareTo()
-1、0、1,即左邊比右邊數大,返回1,相等返回0,比右邊小返回-1。注意不能使用equals方法來比較大小。
//add(BigDecimal)源碼
public BigDecimal add(BigDecimal augend) {
if (this.intCompact != INFLATED) {
if ((augend.intCompact != INFLATED)) {
return add(this.intCompact, this.scale, augend.intCompact, augend.scale);
} else {
return add(this.intCompact, this.scale, augend.intVal, augend.scale);
}
} else {
if ((augend.intCompact != INFLATED)) {
return add(augend.intCompact, augend.scale, this.intVal, this.scale);
} else {
return add(this.intVal, this.scale, augend.intVal, augend.scale);
}
}
}
//add
private static BigDecimal add(final long xs, int scale1, BigInteger snd, int scale2) {
int rscale = scale1;
long sdiff = (long)rscale - scale2;
boolean sameSigns = (Long.signum(xs) == snd.signum);
BigInteger sum;
if (sdiff < 0) {
int raise = checkScale(xs,-sdiff);
rscale = scale2;
long scaledX = longMultiplyPowerTen(xs, raise);
if (scaledX == INFLATED) {
sum = snd.add(bigMultiplyPowerTen(xs,raise));
} else {
sum = snd.add(scaledX);
}
} else { //if (sdiff > 0) {
int raise = checkScale(snd,sdiff);
snd = bigMultiplyPowerTen(snd,raise);
sum = snd.add(xs);
}
return (sameSigns) ?
//新對象
new BigDecimal(sum, INFLATED, rscale, 0) :
//valueOf()返回的也是新對象
valueOf(sum, rscale, 0);
}</pre>
BigDecimal格式化
如果我們要將String轉換為BigDecimal,然后保留兩位小數。
DecimalFormat format = new DecimalFormat("0.00");
System.out.println(format.format(new BigDecimal(str)));
//如果四舍五入呢
String str = "123.4444";
System.out.println(new BigDecimal(str).setScale(2,BigDecimal.ROUND_HALF_UP));
參數
直接刪除多余的小數位,如2.35會變成2.3 setScale(1,BigDecimal.ROUND_DOWN)
進位處理,2.35變成2.4 setScale(1,BigDecimal.ROUND_UP)
四舍五入,2.35變成2.4 setScale(1,BigDecimal.ROUND_HALF_UP)
四舍五入,2.35變成2.3,如果是5則向下舍setScaler(1,BigDecimal.ROUND_HALF_DOWN) BigDecimal使用時注意點
(1)盡量使用參數類型為String的構造函數。
(2) BigDecimal都是不可變的,每次計算會產生新的對象,所以+-*/后保存值,如:a.dd(b)要寫成a = a.add(b)。
BigDecimal 由任意精度的整數非標度值 和 32 位的整數標度 (scale) 組成理解
例如:-314和 3.1415
表示為:-314 × 10-0 和13412 × 10-4
這里用(非標度值 和 標度)表示分別為:[-314, 0]和[13412, 4]
BigDecimal amount = new BigDecimal("314");
System.out.println(amount.signum());//正負
System.out.println(amount.scale()); //標度
System.out.println(amount.stripTrailingZeros());
System.out.println(amount.stripTrailingZeros().scale());//去零后的標度(注意是末尾的0)
//結果
1
0
314
0
BigDecimal amount = new BigDecimal("3.1415");
System.out.println(amount.signum());//正負
System.out.println(amount.scale()); //標度
System.out.println(amount.stripTrailingZeros());
System.out.println(amount.stripTrailingZeros().scale());//去零后的標度
//結果
1
4
3.1415
4
//大家嘗試理解判斷是否是整數這段代碼
private boolean isIntegerValue(BigDecimal bd) {
return bd.signum() == 0 || bd.scale() <= 0 || bd.stripTrailingZeros().scale() <= 0;
}