引言
在进行精确计算时,Java的基本浮点类型(如float和double)无法避免精度损失,这对于财务、科学等需要高精度计算的应用是不可接受的。BigDecimal
类提供了精确的浮点数计算功能,能够处理这类问题。
精度丢失
浮点数进行计算会产生丢失精度的现象,这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数 1/10,从而导致计算误差。
示例
| System.out.println(2.0 - 1.9);
|
以上程序运行,控制台打印结果为0.10000000000000009
,而不是0.1
。
为解决以上问题,Java专门提供了一个类BigDecimal
,用于需要高精度计算的场景。
BigDecimal
常用构造方法
BigDecimal(int val)
BigDecimal(long val)
BigDecimal(String val)
| BigDecimal bd1 = new BigDecimal(1); BigDecimal bd3 = new BigDecimal(3L); BigDecimal bd4 = new BigDecimal("4.5");
|
不推荐使用double
类型进行构造,因为浮点数无法准确的表示一个数,如1/10,前面已经说过,如
| BigDecimal bigDecimal = new BigDecimal(0.1); System.out.println(bigDecimal);
|
输出结果为0.1000000000000000055511151231257827021181583404541015625
如果需要使用浮点数构造BigDecimal
,则需要使用另一个构造方法
BigDecimal valueOf(double val)
| BigDecimal bd5 = BigDecimal.valueOf(0.1); System.out.println(bd5);
|
比较大小
对于BigDecimal
对象,一般使用compareTo
方法比较两个数的大小
| BigDecimal bd1 = new BigDecimal("1"); BigDecimal bd2 = new BigDecimal("2"); BigDecimal bd3 = new BigDecimal("2.0");
System.out.println(bd1.compareTo(bd2)); System.out.println(bd2.compareTo(bd1)); System.out.println(bd2.compareTo(bd3));
|
常用计算
| BigDecimal bd1 = new BigDecimal("4.000"); BigDecimal bd2 = new BigDecimal("3");
BigDecimal addRes = bd1.add(bd2);
BigDecimal subRes = bd1.subtract(bd2);
BigDecimal mulRes = bd1.multiply(bd2);
BigDecimal divRes = bd1.divide(bd2, RoundingMode.HALF_DOWN);
System.out.println(addRes); System.out.println(subRes); System.out.println(mulRes); System.out.println(divRes);
|
注意除法较为特殊,需要指定另外一个参数“舍入规则”,如向正无穷大靠近、向负无穷大靠近、四舍五入,否则遇到无限循环小数时会抛出异常
其他加减乘方法可指定可不指定舍入规则
| throw new ArithmeticException("Non-terminating decimal expansion; " + "no exact representable decimal result.");
|
转换结果
BigDecimal
对象可以转换为字符串、整形、浮点型,如
| BigDecimal bd = new BigDecimal("0.1"); int i = bd.intValue(); long l = bd.longValue(); double v = bd.doubleValue(); String s = bd.toString();
System.out.println(i); System.out.println(l); System.out.println(v); System.out.println(s);
|
注意:浮点型转换为整形时会出现精度损失!
格式化
可使用类 DecimalFormat
对 BigDecimal
对象进行格式化以及设置舍入规则
占位符 0
与占位符 #
| DecimalFormat df1 = new DecimalFormat("00.00"); DecimalFormat df2 = new DecimalFormat("#.00"); DecimalFormat df3 = new DecimalFormat("##.##");
BigDecimal bd = new BigDecimal("0.3"); System.out.println(df1.format(bd)); System.out.println(df2.format(bd)); System.out.println(df3.format(bd));
|
从以上可以看出,占位符0
有多少个,最后的结果就有多少位,而#
则只保留有效数字
实际应用示例
金钱格式化:小数点左边3为分隔,只保留有效数字,但最少为一位,右边则是保留两位数字
| DecimalFormat df4 = new DecimalFormat(",##0.00"); df4.setRoundingMode(RoundingMode.UP); BigDecimal bd2 = new BigDecimal("1233.230"); BigDecimal bd3 = new BigDecimal("13.232"); BigDecimal bd4 = new BigDecimal("0.232"); System.out.println(df4.format(bd2)); System.out.println(df4.format(bd3)); System.out.println(df4.format(bd4));
|
其他
- 性能考虑:虽然
BigDecimal
提供了高精度,但其运算速度慢于原始类型。在不需要极高精度的场景下,考虑性能和需求之间的平衡。
- 内存使用:
BigDecimal
由于其复杂性,相比原始类型会占用更多内存。适当时进行优化,例如在进行大量计算时复用对象。
总结
BigDecimal
是处理需要高度精确数值的理想选择。通过其各种方法,可以进行精确的数学运算,避免了常规浮点类型的局限。虽然BigDecimal
的操作相比原生类型更为复杂和耗时,但在精确度至关重要的情况下,这种性能的牺牲是合理的。使用时需要特别注意其与BigInteger
的不同,以及如何选择合适的数学运算方法来满足具体需求。