以前就有想法花一點時間學習一下Java中用于表示高精度運算的類BigDecimal
了解之後,總結一下以示學習
1. BigDecimal是什麼
先看一組例子:
public static void main(String[] s) {
double a = 0.1, b = 0.2;
System.out.println(a + b);
}
輸出結果:
0.30000000000000004
事與願違,結果并不是我們想象的0.3;
原因很簡單,計算機在做運算的時候,會先将十進制變成二進制,再對二進制進行運算;在十進制轉換為二進制的時候,有時候不能使用有限的一個二進制來表達,尤其是遇到有小數的情況,即出現了精度丢失問題,進而導緻運算的時候出現了事與願違的一幕。如0.1轉換為二進制是
0.000110011001100110011001100…………
既然有問題,那就必有解決辦法;在Java中是使用BigDecimal 來解決;是java.math包中提供的API類,用來對超過16位有效位的數進行精确的運算。除了加減乘除運算,還提供了格式化,舍入等方法。
2. BigDecimal常用構造方法
-
BigDecimal(int)
建立一個具有參數所指定整數值的對象
-
BigDecimal(double)
建立一個具有參數所指定雙精度值的對象
-
BigDecimal(long)
建立一個具有參數所指定長整數值的對象
-
BigDecimal(String)
建立一個具有參數所指定以字元串表示的數值的對象
問題分析:
public static void main(String[] s) {
BigDecimal bigDecimal = new BigDecimal("0.1");
BigDecimal bigDecimal2 = new BigDecimal("0.2");
BigDecimal bigDecimal3 = new BigDecimal(0.1); // 傳double構造方法
BigDecimal bigDecimal4 = new BigDecimal(0.2); // 傳double構造方法
System.out.println(bigDecimal.add(bigDecimal2));
System.out.println(bigDecimal3.add(bigDecimal4));
}
輸出結果:
0.3
0.3000000000000000166533453693773481063544750213623046875
原因:
使用BigDecimal類構造方法傳入double類型時,計算的結果也是不精确的!因為不是所有的浮點數都能夠被精确的表示成一個double
類型值,有些浮點數值不能夠被精确的表示成 double 類型值,是以它會被表示成與它最接近的 double 類型的值。
- String 構造方法是完全可預知的:寫入 newBigDecimal(“0.1”) 将建立一個 BigDecimal,它正好等于預期的 0.1。是以,通常建議優先使用String構造方法。也是官方文檔推薦的。
3. BigDecimal常用方法
BigDecimal bigDecimal = new BigDecimal("0.1");
BigDecimal bigDecimal2 = new BigDecimal("0.2");
BigDecimal bigDecimal3 = new BigDecimal("-0.2");
// 加
System.out.println(bigDecimal.add(bigDecimal2));
// 乘
System.out.println(bigDecimal.multiply(bigDecimal2));
// 減
System.out.println(bigDecimal.subtract(bigDecimal2));
// 除
System.out.println(bigDecimal.divide(bigDecimal2));
// 絕對值
System.out.println(bigDecimal3.abs());
// 比較大小,-1小于,0等于,1大于
System.out.println(bigDecimal.compareTo(bigDecimal2));
運作結果:
0.3
0.02
-0.1
0.5
0.2
-1
4. BigDecimal根據舍入模式保留小數位
運算的時候保留小數位,并指定某種具體的保留模式
/*
* 保留小數位(2位),以及舍入模式( RoundingMode.HALF_UP:四舍五入)
*/
BigDecimal bigDecimal = new BigDecimal("0.333");
BigDecimal bigDecimal2 = new BigDecimal("0.111");
// 加
System.out.println(bigDecimal.add(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
// 乘
System.out.println(bigDecimal.multiply(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
// 減
System.out.println(bigDecimal.subtract(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
// 除(三分之一四舍五入模式保留二位小數)
System.out.println(bigDecimal2.divide(bigDecimal, 2, RoundingMode.HALF_UP));
運作結果:
0.44
0.04
0.22
0.33
注意:
值得一提的是,運用除法的時候,如果出現無限循環小數時,就會抛異常:
java.lang.ArithmeticException:Non-terminating decimal expansion; no exact representable decimal result.
解決異常的方法就是設定小數點位數
- 加減乘和除設定保留小數方式不一樣
- 其他的舍入模式可以參見jdk文檔(RoundingMode類),這裡不做介紹,傳送門:http://www.matools.com/api/java8
5. BigDecimal格式化
由于NumberFormat類的format()方法可以使用BigDecimal對象作為其參數,可以利用BigDecimal對超出16位有效數字的貨币值,百分值,以及一般數值進行格式化控制
// 建立貨币格式化引用
NumberFormat currency = NumberFormat.getCurrencyInstance();
// 建立百分比格式化引用
NumberFormat percent = NumberFormat.getPercentInstance();
BigDecimal a= new BigDecimal("15.483");
BigDecimal b = new BigDecimal("0.1235");
BigDecimal c= a.multiply(b);
System.out.println(currency.format(a));
System.out.println(percent.format(b));
System.out.println(currency.format(c));
System.out.println(percent.format(c));
運作結果:
¥15.48
12%
¥1.91
191%
注意:
選擇系統預設的貨币格式,且預設保留兩位小數(錢就這樣哈哈),采用四舍五入模式;
如需選擇其他的貨币格式,可選擇性的傳入Locale裡面的常量,如
NumberFormat currency1 = NumberFormat.getCurrencyInstance(Locale.US) // $美元
- 選擇系統預設的百分比格式,且預設不保留小數位,采用四舍五入模式;
如需指定保留小數位,可使用
setMaximumFractionDigits
和
getMinimumFractionDigits
設定最大最小的小數位
// 建立貨币格式化引用
NumberFormat currency = NumberFormat.getCurrencyInstance();
// 建立百分比格式化引用
NumberFormat percent = NumberFormat.getPercentInstance();
// 小數點最多三位,不夠也不用補零
percent.setMaximumFractionDigits(3);
BigDecimal a = new BigDecimal("15.483");
BigDecimal b = new BigDecimal("0.1235");
BigDecimal c = a.multiply(b);
System.out.println(currency.format(a));
System.out.println(percent.format(b));
System.out.println(currency.format(c));
System.out.println(percent.format(c));
運作結果:
¥15.48
12.35%
¥1.91
191.215%
換成:
// 小數點最少三位,不夠補零
percent.setMinimumFractionDigits(3);
運作結果:
¥15.48
12.350%
¥1.91
191.215%
提示:
- 也可以使用setMaximumIntegerDigits和setMinimumIntegerDigits設定最多和最少整數位,用法和小數位設定一緻
除了NumberFormat,也可以使用它的子類DecimalFormat,對Decimal數值進行格式化(保留小數位)
/*
* 0:少了用0補,多了切掉,四舍五入
* #:少了也不補(就空着),多了切掉,四舍五入
* 注意:使用#時數值0不能表示一個位置
*/
DecimalFormat df = new DecimalFormat("0.00");
System.out.println(df.format(new BigDecimal("3.435")));
System.out.println(df.format(new BigDecimal(0)));
System.out.println(df.format(new BigDecimal("0.00")));
System.out.println(df.format(new BigDecimal("0.001")));
System.out.println(df.format(new BigDecimal("0.006")));
System.out.println(df.format(new BigDecimal("10.206")));
System.out.println("===========");
DecimalFormat df1 = new DecimalFormat("0.##");
System.out.println(df1.format(new BigDecimal("3.435")));
System.out.println(df1.format(new BigDecimal(0)));
System.out.println(df1.format(new BigDecimal("0.00")));
System.out.println(df1.format(new BigDecimal("0.001")));
System.out.println(df1.format(new BigDecimal("0.006")));
System.out.println(df1.format(new BigDecimal("10.206")));
運作結果:
3.44
0.00
0.00
0.00
0.01
10.21
=========
3.44
0
0
0
0.01
10.21
注意:
- 0和#都是占位作用,是最多位數的作用,差別在于不夠時#不補,而0補零
#後面不能有0,因為如果位數不夠,#表示不補,0表示補0,這是沖突的,會報異常
java.lang.IllegalArgumentException: Unexpected ‘0’ in pattern “0.#0”
6. BigDecimal總結
- 盡量使用參數類型為String的構造函數
- 系統涉及高精度運算則一定要使用BigDecimal,如果對精度沒有要求,則推薦使用double或float;因為BigDecimal性能低于double和float
- BigDecimal 在進行每一次四則運算時,都會産生一個新的對象 ,是以在做加減乘除運算時要記得要儲存操作後的值
- 使用除法的時候,最好設定好要保留的位數,不能很容易因為無限循環出現異常,導緻程式終止
7. BigDecimal工具類推薦
來源于網絡,我也在使用的一個BigDecimal工具類
import java.math.BigDecimal;
/**
* 用于高精确處理常用的數學運算
*/
public class ArithmeticUtils {
//預設除法保留小數位數
private static final int DEF_DIV_SCALE = 10;
/**
* 提供精确的加法運算
* @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 BigDecimal add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2);
}
/**
* 提供精确的加法運算
* @param v1 被加數
* @param v2 加數
* @param scale 保留scale 位小數
* @return 兩個參數的和
*/
public static String add(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的減法運算
* @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 BigDecimal sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2);
}
/**
* 提供精确的減法運算
* @param v1 被減數
* @param v2 減數
* @param scale 保留scale 位小數
* @return 兩個參數的差
*/
public static String sub(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的乘法運算
* @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();
}
/**
* 提供精确的乘法運算
* @param v1 被乘數
* @param v2 乘數
* @return 兩個參數的積
*/
public static BigDecimal mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2);
}
/**
* 提供精确的乘法運算
* @param v1 被乘數
* @param v2 乘數
* @param scale 保留scale 位小數
* @return 兩個參數的積
*/
public static double mul(double v1, double v2, int scale) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return round(b1.multiply(b2).doubleValue(), scale);
}
/**
* 提供精确的乘法運算
* @param v1 被乘數
* @param v2 乘數
* @param scale 保留scale 位小數
* @return 兩個參數的積
*/
public static String mul(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供(相對)精确的除法運算,當發生除不盡的情況時,精确到
* 小數點以後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();
}
/**
* 提供(相對)精确的除法運算。當發生除不盡的情況時,由scale參數指
* 定精度,以後的數字四舍五入
* @param v1 被除數
* @param v2 除數
* @param scale 表示需要精确到小數點以後幾位
* @return 兩個參數的商
*/
public static String div(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v1);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的小數位四舍五入處理
* @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));
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小數位四舍五入處理
* @param v 需要四舍五入的數字
* @param scale 小數點後保留幾位
* @return 四舍五入後的結果
*/
public static String round(String v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取餘數
* @param v1 被除數
* @param v2 除數
* @param scale 小數點後保留幾位
* @return 餘數
*/
public static String remainder(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取餘數 BigDecimal
* @param v1 被除數
* @param v2 除數
* @param scale 小數點後保留幾位
* @return 餘數
*/
public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 比較大小
* @param v1 被比較數
* @param v2 比較數
* @return 如果v1 大于v2 則 傳回true 否則false
*/
public static boolean compare(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
int bj = b1.compareTo(b2);
boolean res;
if (bj > 0)
res = true;
else
res = false;
return res;
}
}