一、BigDecimal描述
BigDecimal类位于java.math.BigDecimal包下。是Java中用于表示任意精度数字的类,它可以表示无限长度的小数,BigDecimal 通常支持任意位数的小数部分,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。而且也可以使用此类进行精确的四舍五入,这一点在开发中经常使用。
二、BigDecimal常用构造函数
方法 | 描述 |
---|---|
BigDecimal(int val) | 将int类型转换成BigDecimal类型数据。 |
BigDecimal(double val) | 将double类型转换成BigDecimal类型数据。不推荐使用,存在丢失精度问题。 |
BigDecimal(long val) | 将long类型转换成BigDecimal类型数据。 |
BigDecimal(String val) | 将String类型转换成BigDecimal类型数据。推荐使用 |
BigDecimal bigDecimal1 = new BigDecimal(1);
BigDecimal bigDecimal2 = new BigDecimal(0.1); // 不推荐使用,对丢失精度。
BigDecimal bigDecimal3 = new BigDecimal(1L);
BigDecimal bigDecimal4 = new BigDecimal("0.1"); // 推荐使用
System.out.println("int转BigDecimal结果" + bigDecimal1);
System.out.println("double转BigDecimal结果" + bigDecimal2);
System.out.println("long转BigDecimal结果" + bigDecimal3);
System.out.println("String转BigDecimal结果" + bigDecimal4);
三、BIgDecimal常用方法
方法 | 描述 |
---|---|
add(BigDecimal) | BigDecimal对象中的值相加,返回BigDecimal对象。 |
subtract(BigDecimal) | BigDecimal对象中的值相减,返回BigDecimal对象。 |
multiply(BigDecimal) | BigDecimal对象中的值相乘,返回BigDecimal对象。 |
divide(BigDecimal) | BigDecimal对象中的值相除,返回BigDecimal对象。该方法可能会遇到无限精度问题,会抛出异常,使用时需注意。 |
compareTo(BigDecimal val) | 比较大小,返回int类型。0(相等) 1(大于) -1(小于)。a、b均不能为null,否则会报空指针。 |
max(BigDecimal) | 两值比较,返回最大值。 |
min(BigDecimal val) | 两值比较,返回最小值。 |
setScale(位数,舍弃规则) | 用于格式化小数点。 |
remainder(BigDecimal) | 求余数,求BigDecimal类型数据除以divisor的余数。 |
doubleValue() | 将BigDecimal对象中的值转换成双精度数double类型。 |
floatValue() | 将BigDecimal对象中的值转换成单精度数float类型。 |
longValue() | 将BigDecimal对象中的值转换成长整数long类型。 |
intValue() | 将BigDecimal对象中的值转换成整数int类型。 |
negate() | 求BigDecimal类型数据的相反数。 |
abs() | 将BigDecimal对象中的值转换成绝对值。 |
1、加法运算(add)
- 使用 add 方法将两个 BigDecimal 对象相加。
BigDecimal bigDecimal1 = new BigDecimal("0.1");
BigDecimal bigDecimal2 = new BigDecimal("0.2");
BigDecimal add = bigDecimal1.add(bigDecimal2);
System.out.println("输出结果======================");
System.out.println("加法运算的结果:" + add);
System.out.println("输出结果======================");
2、减法运算(subtract)
- 使用 subtract 方法将一个 BigDecimal 对象减去另一个。
BigDecimal bigDecimal1 = new BigDecimal("0.2");
BigDecimal bigDecimal2 = new BigDecimal("0.1");
BigDecimal subtract = bigDecimal1.subtract(bigDecimal2);
System.out.println("输出结果======================");
System.out.println("减法运算的结果:" + subtract);
System.out.println("输出结果======================");
3、乘法运算(multiply)
- 使用 multiply 方法将两个 BigDecimal 对象相乘。
BigDecimal bigDecimal1 = new BigDecimal("0.2");
BigDecimal bigDecimal2 = new BigDecimal("0.1");
BigDecimal multiply = bigDecimal1.multiply(bigDecimal2);
System.out.println("输出结果======================");
System.out.println("乘法运算的结果:" + multiply);
System.out.println("输出结果======================");
4、除法运算(divide)
- 使用 divide 方法将一个 BigDecimal 对象除以另一个。
- 注意:该方法可能会遇到无限精度问题,会抛出异常。需要指定舍入规则。
错误示范:因1/3 会无限循环小时,所以程序会抛出异常。需要指定舍入规则。
BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = new BigDecimal("3");
BigDecimal divide = bigDecimal1.divide(bigDecimal2);
正确使用方式:指定舍入规则
BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = new BigDecimal("3");
BigDecimal divide = bigDecimal1.divide(bigDecimal2,2,BigDecimal.ROUND_HALF_DOWN);
System.out.println("输出结果======================");
System.out.println("除法运算的结果-截取2位并四舍五入:" + divide);
System.out.println("输出结果======================");
5、比较大小(compareTo)
- 使用 compareTo 方法比较两个 BigDecimal 对象的大小。
- a、b均不能为null,否则会报空指针。
- 返回值:0(相等) 1(大于) -1(小于)
方式一:
BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = new BigDecimal("3");
int i = bigDecimal1.compareTo(bigDecimal2);
// 0(相等) 1(大于) -1(小于)
System.out.println("输出结果======================");
System.out.println("比较大小的结果:" + i);
System.out.println("输出结果======================");
方式二:
- a小于b:< 0
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
boolean bool = a.compareTo(b) < 0;
System.out.println("输出结果======================");
System.out.println("比较大小的结果 a小于b:" + bool);
System.out.println("输出结果======================");
- a等于b:== 0
BigDecimal a = new BigDecimal("2");
BigDecimal b = new BigDecimal("2");
boolean bool = a.compareTo(b) == 0;
System.out.println("输出结果======================");
System.out.println("比较大小的结果 a等于b:" + bool);
System.out.println("输出结果======================");
- a大于b:> 0
BigDecimal a = new BigDecimal("3");
BigDecimal b = new BigDecimal("1");
boolean bool = a.compareTo(b) > 0;
System.out.println("输出结果======================");
System.out.println("比较大小的结果 a大于b:" + bool);
System.out.println("输出结果======================");
- a大于等于b:> -1
BigDecimal a = new BigDecimal("3");
BigDecimal b = new BigDecimal("1");
BigDecimal c = new BigDecimal("1");
boolean bool1 = a.compareTo(c) > -1;
boolean bool2 = b.compareTo(c) > -1;
System.out.println("输出结果======================");
System.out.println("比较大小的结果 a大于等于c:" + bool1);
System.out.println("比较大小的结果 b大于等于c:" + bool2);
System.out.println("输出结果======================");
- a小于等于b:< 1
BigDecimal a = new BigDecimal("2");
BigDecimal b = new BigDecimal("5");
BigDecimal c = new BigDecimal("5");
boolean bool1 = a.compareTo(c) < 1;
boolean bool2 = b.compareTo(c) < 1;
System.out.println("输出结果======================");
System.out.println("比较大小的结果 a小于等于c:" + bool1);
System.out.println("比较大小的结果 b小于等于c:" + bool2);
System.out.println("输出结果======================");
6、返回最大值(max)
- 从两个 BigDecimal 对象中选取最大的值。
BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = new BigDecimal("3");
BigDecimal max = bigDecimal1.max(bigDecimal2);
System.out.println("输出结果======================");
System.out.println("max比较的结果:" + max);
System.out.println("输出结果======================");
7、返回最小值(min)
- 从两个 BigDecimal 对象中选取最小的值。
BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = new BigDecimal("3");
BigDecimal min = bigDecimal1.min(bigDecimal2);
System.out.println("输出结果======================");
System.out.println("min比较的结果:" + min);
System.out.println("输出结果======================");
8、格式化小数点(setScale)
- 用于对BigDecimal对象进行格式化小数点。
BigDecimal bigDecimal1 = new BigDecimal("1.1423194");
BigDecimal scale = bigDecimal1.setScale(2,BigDecimal.ROUND_HALF_DOWN);
System.out.println("输出结果======================");
System.out.println("格式化的结果:" + scale); // 格式化的结果:1.14
System.out.println("输出结果======================");
9、求余数(remainder)
- 用于获取一个 BigDecimal 对象除以另一个的余数。
BigDecimal bigDecimal1 = new BigDecimal("10");
BigDecimal bigDecimal2 = new BigDecimal("3");
BigDecimal remainder = bigDecimal1.remainder(bigDecimal2);
System.out.println("输出结果======================");
System.out.println("取余数的结果:" + remainder); // 取余数的结果:1
System.out.println("输出结果======================");
10、取相反数(negate)
- 返回一个 BigDecimal 对象的负数
BigDecimal bigDecimal1 = new BigDecimal("10");
BigDecimal negate = bigDecimal1.negate();
System.out.println("输出结果======================");
System.out.println("取负数的结果:" + negate);
System.out.println("输出结果======================");
11、取绝对值(abs)
- 返回一个 BigDecimal 对象的绝对值。
BigDecimal bigDecimal1 = new BigDecimal("-10");
BigDecimal abs = bigDecimal1.abs();
System.out.println("输出结果======================");
System.out.println("取绝对值的结果:" + abs);
System.out.println("输出结果======================");
13、转换类型
- doubleValue() 将BigDecimal对象转换为double类型。
- floatValue() 将BigDecimal对象转换为float类型。
- longValue() 将BigDecimal对象转换为long类型。
- intValue() 将BigDecimal对象转换为int类型。
BigDecimal bigDecimal1 = new BigDecimal("12.325167");
double doubleValue = bigDecimal1.doubleValue();
float floatValue = bigDecimal1.floatValue();
long longValue = bigDecimal1.longValue();
int intValue = bigDecimal1.intValue();
System.out.println("输出结果======================");
System.out.println("转换double的结果:" + doubleValue);
System.out.println("转换float的结果:" + floatValue);
System.out.println("转换long的结果:" + longValue);
System.out.println("转换int的结果:" + intValue);
System.out.println("输出结果======================");
四、BigDecimal的舍入模式
方法 | 描述 |
---|---|
ROUND_UP | 远离0的方向舍入 |
ROUND_DOWN | 接近0的方向舍入 |
ROUND_CEILING | 无穷大的舍入方式 |
ROUND_FLOOR | 负无穷大的舍入方式 |
ROUND_HALF_UP | 四舍五入 |
ROUND_HALF_DOWN | 五舍六入 |
ROUND_HALF_EVEN | 银行家舍入法 |
ROUND_UNNECESSARY | 舍掉的位数不是0 |
ROUND_UP(远离0的方向舍入)
- 向远离0的方向舍入
BigDecimal bigDecimal1 = new BigDecimal("12.321167");
BigDecimal bigDecimal2 = new BigDecimal("-12.321167");
BigDecimal up1 = bigDecimal1.setScale(2, BigDecimal.ROUND_UP);
BigDecimal up2 = bigDecimal2.setScale(2, BigDecimal.ROUND_UP);
System.out.println("正数:" + up1); // 向上取舍-正数:12.33
System.out.println("负数:" + up2); // 向上取舍-负数:-12.33
ROUND_DOWN (接近0的方向舍入)
- 向接近0的方向舍入
BigDecimal bigDecimal1 = new BigDecimal("12.321167");
BigDecimal bigDecimal2 = new BigDecimal("-12.321167");
BigDecimal DOWN1 = bigDecimal1.setScale(2, BigDecimal.ROUND_DOWN);
BigDecimal DOWN2 = bigDecimal2.setScale(2, BigDecimal.ROUND_DOWN);
System.out.println("正数:" + DOWN1); // 正数:12.32
System.out.println("负数:" + DOWN2); // 负数:-12.32
ROUND_CEILING(无穷大的舍入)
- 无穷大的舍入方式
BigDecimal bigDecimal1 = new BigDecimal("12.321167");
BigDecimal bigDecimal2 = new BigDecimal("-12.321167");
BigDecimal CEILING1 = bigDecimal1.setScale(2, BigDecimal.ROUND_CEILING);
BigDecimal CEILING2 = bigDecimal2.setScale(2, BigDecimal.ROUND_CEILING);
System.out.println("正数:" + CEILING1); // 正数:12.33
System.out.println("负数:" + CEILING2); // 负数:-12.32
ROUND_FLOOR(负无穷大的舍入)
- 负无穷大的舍入方式
BigDecimal bigDecimal1 = new BigDecimal("12.321167");
BigDecimal bigDecimal2 = new BigDecimal("-12.321167");
BigDecimal FLOOR1 = bigDecimal1.setScale(2, BigDecimal.ROUND_FLOOR);
BigDecimal FLOOR2 = bigDecimal2.setScale(2, BigDecimal.ROUND_FLOOR);
System.out.println("正数:" + FLOOR1); // 正数:12.32
System.out.println("负数:" + FLOOR2); // 负数:-12.33
ROUND_HALF_UP(四舍五入)
- 四舍五入
BigDecimal bigDecimal1 = new BigDecimal("12.321167");
BigDecimal bigDecimal2 = new BigDecimal("-12.321167");
BigDecimal HALF_UP1 = bigDecimal1.setScale(2, BigDecimal.ROUND_HALF_UP);
BigDecimal HALF_UP2 = bigDecimal2.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println("正数:" + HALF_UP1); // 正数:12.32
System.out.println("负数:" + HALF_UP2); // 负数:-12.32
ROUND_HALF_DOWN(五舍六入)
- 五舍六入
BigDecimal bigDecimal1 = new BigDecimal("12.325");
BigDecimal bigDecimal2 = new BigDecimal("-12.325");
BigDecimal HALF_DOWN1 = bigDecimal1.setScale(2, BigDecimal.ROUND_HALF_DOWN);
BigDecimal HALF_DOWN2 = bigDecimal2.setScale(2, BigDecimal.ROUND_HALF_DOWN);
System.out.println("正数:" + HALF_DOWN1); // 正数:12.32
System.out.println("负数:" + HALF_DOWN2); // 负数:-12.32
ROUND_HALF_EVEN(银行家舍入法)
- 银行家舍入法,目标是偶数,可以使用四舍五入,也可以适用五舍六入,具体看哪边更接近一个偶数
BigDecimal bigDecimal1 = new BigDecimal("12.329");
BigDecimal bigDecimal2 = new BigDecimal("-12.322");
BigDecimal HALF_EVEN1 = bigDecimal1.setScale(2, BigDecimal.ROUND_HALF_EVEN);
BigDecimal HALF_EVEN2 = bigDecimal2.setScale(2, BigDecimal.ROUND_HALF_EVEN);
System.out.println("正数:" + HALF_EVEN1); // 正数:12.33
System.out.println("负数:" + HALF_EVEN2); // 负数:-12.32
ROUND_UNNECESSARY(舍掉的位数不是0)
- 需要舍掉的位数不是0,就报异常 ArithmeticException。如果需要舍掉的位数是0,就返回已结舍掉的正常结果。
BigDecimal bigDecimal1 = new BigDecimal("12.329");
BigDecimal unnecessary = bigDecimal1.setScale(2, BigDecimal.ROUND_UNNECESSARY);
System.out.println(unnecessary);
五、BigDecimal常见问题
踩坑一:创建BigDecimal精度丢失
在BigDecimal中提供了很多创建方式,可以通过new 直接创建,也可以通过 BigDecimal#valueOf 创建。这两种方式使用不当,也会导致精度问题。如下:
BigDecimal bigDecimal1 = new BigDecimal(0.123912214890);
System.out.println(bigDecimal1);
BigDecimal bigDecimal2 = BigDecimal.valueOf(0.318283929989232910293889129273729932);
System.out.println(bigDecimal2);
执行结果:
0.1000000000000000055511151231257827021181583404541015625
0.3182839299892329
结论:
- 第一,在使用BigDecimal构造函数时,尽量传递字符串而非浮点类型,不然会出现精度问题;
- 第二,如果无法满足第一条,则可采用BigDecimal#valueOf方法来构造初始化值。但是valueOf受double类型精度影响,当传入参数小数点后的位数超过double允许的16位精度还是可能会出现问题的
踩坑二:无限精度问题
BigDecimal并不代表无限精度,当在两个数除不尽的时候,就会出现无限精度的坑。
BigDecimal bigDecimal1 = new BigDecimal("0.1");
BigDecimal bigDecimal2 = new BigDecimal("0.3");
System.out.println(bigDecimal1.divide(bigDecimal2));
执行结果:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1693)
at com.kss.ewaterapi.TestCon.main(TestCon.java:131)
因为在除法(divide)运算过程中,商是一个无限小数,那么就会抛出ArithmeticException异常。这种情况可以在使用divide方式的时候指定舍入方式即可,如下:
BigDecimal bigDecimal1 = new BigDecimal("0.1");
BigDecimal bigDecimal2 = new BigDecimal("0.3");
// 执行结果:0.33
System.out.println(bigDecimal1.divide(bigDecimal2,2,BigDecimal.ROUND_HALF_UP));
踩坑三:使用BigDecimal进行计算的时参数不能为null
在使用BigDecimal类型进行计算时,进行加、减、乘、除、比较大小时,一定要保证参与计算的两个值不能为空,否则会抛出java.lang.NullPointerException异常。如下:
BigDecimal bigDecimal1 = new BigDecimal("0.1");
BigDecimal bigDecimal2 = null ;
System.out.println(bigDecimal1.divide(bigDecimal2,2,BigDecimal.ROUND_HALF_UP));
执行结果:
Exception in thread "main" java.lang.NullPointerException
at java.math.BigDecimal.divide(BigDecimal.java:1563)
at com.kss.ewaterapi.TestCon.main(TestCon.java:131)
踩坑四:数值比较
一般在比较两个值是否相等时,都是用equals 方法,但是,在BigDecimal 中使用equals可能会导致结果错误,BigDecimal 中提供了 compareTo 方法,在很多时候需要使用compareTo 比较两个值。如下所示:
BigDecimal bigDecimal1 = new BigDecimal("1.00");
BigDecimal bigDecimal2 = new BigDecimal("1");
System.out.println(bigDecimal1.equals(bigDecimal2));
System.out.println(bigDecimal1.compareTo(bigDecimal2));
执行结果:
false
0
结论:
由于equals不仅比较了值是否相等,还比较了精度是否相同。因为两个值的精度不同,所有结果也就不相同。而 compareTo 是只比较值的大小。返回的值为-1(小于),0(等于),1(大于)。
踩坑五:除法计算时被除数不能为0
代码实例:
BigDecimal bigDecimal1 = new BigDecimal("1.00");
BigDecimal bigDecimal2 = new BigDecimal("0.00");
System.out.println(bigDecimal1.divide(bigDecimal2, 2, BigDecimal.ROUND_HALF_UP));
执行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at java.math.BigDecimal.divideAndRound(BigDecimal.java:4137)
at java.math.BigDecimal.divide(BigDecimal.java:5214)
at java.math.BigDecimal.divide(BigDecimal.java:1564)
at com.kss.ewaterapi.TestCon.main(TestCon.java:143)
踩坑六:执行顺序不能调换(乘法交换律失效)
乘法满足交换律是一个常识,但是在计算机的世界里,会出现不满足乘法交换律的情况。如下:
BigDecimal bigDecimal1 = new BigDecimal("1.00");
BigDecimal bigDecimal2 = new BigDecimal("3.00");
BigDecimal bigDecimal3 = new BigDecimal("3.0");
System.out.println(bigDecimal1.multiply(bigDecimal2).divide(bigDecimal3,2,BigDecimal.ROUND_HALF_UP));
System.out.println(bigDecimal1.divide(bigDecimal3,2,BigDecimal.ROUND_HALF_UP).multiply(bigDecimal2));
执行结果:
1.00
0.9900
结论:
执行顺序交换后,产生的结果可能不同,会导致一定的问题,使用顺序建议先乘后除。