BigDecimal

本文最后更新于:4 年前

引言

在进行精确计算时,Java的基本浮点类型(如float和double)无法避免精度损失,这对于财务、科学等需要高精度计算的应用是不可接受的。BigDecimal类提供了精确的浮点数计算功能,能够处理这类问题。

精度丢失

浮点数进行计算会产生丢失精度的现象,这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数 1/10,从而导致计算误差。

示例

1
System.out.println(2.0 - 1.9);

以上程序运行,控制台打印结果为0.10000000000000009,而不是0.1

为解决以上问题,Java专门提供了一个类BigDecimal,用于需要高精度计算的场景。

BigDecimal

常用构造方法

  • BigDecimal(int val)
  • BigDecimal(long val)
  • BigDecimal(String val)
1
2
3
BigDecimal bd1 = new BigDecimal(1);
BigDecimal bd3 = new BigDecimal(3L);
BigDecimal bd4 = new BigDecimal("4.5");

不推荐使用double类型进行构造,因为浮点数无法准确的表示一个数,如1/10,前面已经说过,如

1
2
BigDecimal bigDecimal = new BigDecimal(0.1);
System.out.println(bigDecimal);

输出结果为0.1000000000000000055511151231257827021181583404541015625

如果需要使用浮点数构造BigDecimal,则需要使用另一个构造方法

  • BigDecimal valueOf(double val)
1
2
BigDecimal bd5 = BigDecimal.valueOf(0.1);
System.out.println(bd5);

比较大小

对于BigDecimal对象,一般使用compareTo方法比较两个数的大小

1
2
3
4
5
6
7
BigDecimal bd1 = new BigDecimal("1");
BigDecimal bd2 = new BigDecimal("2");
BigDecimal bd3 = new BigDecimal("2.0");

System.out.println(bd1.compareTo(bd2)); // -1:小于
System.out.println(bd2.compareTo(bd1)); // 1 :大于
System.out.println(bd2.compareTo(bd3)); // 0 :等于

常用计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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); // 7.000
System.out.println(subRes); // 1.000
System.out.println(mulRes); // 12.000
System.out.println(divRes); // 1.333

注意除法较为特殊,需要指定另外一个参数“舍入规则”,如向正无穷大靠近、向负无穷大靠近、四舍五入,否则遇到无限循环小数时会抛出异常

其他加减乘方法可指定可不指定舍入规则

1
2
throw new ArithmeticException("Non-terminating decimal expansion; " +
"no exact representable decimal result.");

转换结果

BigDecimal对象可以转换为字符串、整形、浮点型,如

1
2
3
4
5
6
7
8
9
10
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); // 0
System.out.println(l); // 0
System.out.println(v); // 0.1
System.out.println(s); // 0.1

注意:浮点型转换为整形时会出现精度损失!

格式化

可使用类 DecimalFormatBigDecimal 对象进行格式化以及设置舍入规则

占位符 0 与占位符 #

1
2
3
4
5
6
7
8
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)); // 00.30
System.out.println(df2.format(bd)); // .30
System.out.println(df3.format(bd)); // 0.3

从以上可以看出,占位符0有多少个,最后的结果就有多少位,而#则只保留有效数字

实际应用示例

金钱格式化:小数点左边3为分隔,只保留有效数字,但最少为一位,右边则是保留两位数字

1
2
3
4
5
6
7
8
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)); // 1,233.23
System.out.println(df4.format(bd3)); // 13.23
System.out.println(df4.format(bd4)); // 0.23

其他

  • 性能考虑:虽然 BigDecimal 提供了高精度,但其运算速度慢于原始类型。在不需要极高精度的场景下,考虑性能和需求之间的平衡。
  • 内存使用BigDecimal 由于其复杂性,相比原始类型会占用更多内存。适当时进行优化,例如在进行大量计算时复用对象。

总结

BigDecimal是处理需要高度精确数值的理想选择。通过其各种方法,可以进行精确的数学运算,避免了常规浮点类型的局限。虽然BigDecimal的操作相比原生类型更为复杂和耗时,但在精确度至关重要的情况下,这种性能的牺牲是合理的。使用时需要特别注意其与BigInteger的不同,以及如何选择合适的数学运算方法来满足具体需求。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!