阿里云面试指南:使用BigDecimal进行浮点数运算以避免精度丢失的重要性及最佳实践
为什么在阿里云面试中建议使用BigDecimal进行浮点数运算
在最近的技术面试中,BigDecimal 成为了大厂面试中的常考知识点之一。根据《阿里巴巴 Java 开发手册》的指导:“为了避免精度丢失,建议使用 BigDecimal 来进行浮点数的运算”。
浮点数运算真的有可能导致精度丢失吗?答案是肯定的!
精度丢失的示例
以下代码展示了浮点数计算可能导致的精度问题:
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a); // 0.100000024
System.out.println(b); // 0.099999905
System.out.println(a == b); // false
那么,为什么在使用 float
或 double
进行运算时会导致精度丢失呢?
这与计算机存储浮点数的方式密切相关。计算机内部使用二进制表示数字,并且在存储时其宽度是有限的。当遇到无限循环的小数时,计算机会截断这些小数,从而引发精度损失。这就是为什么浮点数无法在二进制中精确表示的原因。例如,十进制中的 0.2 无法准确转换为二进制小数。
浮点数的二进制转换过程示例
// 0.2 转换为二进制数的过程如下:
0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
0.2 * 2 = 0.4 -> 0(循环开始)
...
欲了解更多关于浮点数的知识,建议阅读《计算机系统基础(四)浮点数》这篇文章。
BigDecimal 的优势
BigDecimal 的最大优点在于它可以进行浮点数运算而不会造成精度丢失。在大部分需要精确浮点数结果的业务场景下,例如财务计算,通常会选择 BigDecimal。
根据《阿里巴巴 Java 开发手册》的建议,进行浮点数等值判断时,基本数据类型不应使用 ==
进行比较,而包装数据类型则不应使用 equals
方法。具体原因在前文已经详细阐述,这里不再赘述。
为了解决浮点数运算精度丢失的问题,可以通过 BigDecimal 来定义浮点数并进行运算。
使用 BigDecimal 进行浮点数运算的示例
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
System.out.println(x.compareTo(y)); // 0
BigDecimal 的常用方法介绍
创建 BigDecimal
为了防止精度丢失,我们推荐使用 BigDecimal(String val)
构造方法或 BigDecimal.valueOf(double val)
静态方法来创建 BigDecimal 对象。
基本运算:加减乘除
add
: 将两个 BigDecimal 对象相加subtract
: 将两个 BigDecimal 对象相减multiply
: 将两个 BigDecimal 对象相乘divide
: 将两个 BigDecimal 对象相除
示例代码如下:
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.add(b)); // 1.9
System.out.println(a.subtract(b)); // 0.1
System.out.println(a.multiply(b)); // 0.90
System.out.println(a.divide(b)); // 会抛出 ArithmeticException 异常
System.out.println(a.divide(b, 2, RoundingMode.HALF_UP)); // 1.11
注意:使用 divide
方法时,尽量选择带有三个参数的版本,并设置合适的 RoundingMode
,以避免 ArithmeticException
(当遇到无法除尽的无限循环小数时)。
大小比较
使用 compareTo
方法对两个 BigDecimal 进行比较:
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b)); // 1
设置保留小数位
通过 setScale
方法设置小数位数和保留规则。
BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3, RoundingMode.HALF_DOWN);
System.out.println(n); // 1.255
BigDecimal 的等值比较问题
在使用 equals()
方法进行比较时,可能会出现意想不到的结果:
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("1.0");
System.out.println(a.equals(b)); // false
这是因为 equals()
方法不仅比较数值,还比较精度(scale),而 compareTo()
方法只比较值。
例如,1.0 的 scale 是 1,而 1 的 scale 是 0,因此 a.equals(b)
返回 false。
而使用 compareTo()
方法比较时,若两个数相等则返回 0。
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("1.0");
System.out.println(a.compareTo(b)); // 0
总结
由于浮点数无法在二进制中精确表示,因此存在精度丢失的风险。然而,Java 提供了 BigDecimal 来安全地处理浮点数。BigDecimal 的实现依赖于 BigInteger,同时引入了小数位的概念,使得它在浮点数运算中成为更为合适的选择。
参考资料
[1] 计算机系统基础(四)浮点数: [http://kaito-kidd.com/2018/08/08/computer-system-float-point/](