1.概述
在本教程中,我们将讨论在Java中比较双精度值的不同方法。特别是,它不像比较其他原始类型那样容易。事实上,它在许多其他语言中都是有问题的,不仅是Java。
首先,我们将解释为什么使用简单的==运算符不准确,并且可能导致在运行时难以跟踪错误。然后,我们将展示如何正确比较普通Java库和常见第三方库中的double。
2.使用==运算符
使用==运算符进行比较时的不准确性是由于将双精度值存储在计算机内存中的方式引起的。我们需要记住,在有限的内存空间(通常为64位)中必须容纳无数个值。结果,我们无法在计算机中精确表示大多数double值。必须将它们四舍五入以保存。
由于四舍五入的准确性,可能会发生有趣的错误:
double d1 = 0; for (int i = 1; i <= 8; i++) { d1 += 0.1; } double d2 = 0.1 * 8; System.out.println(d1); System.out.println(d2);
d1
和d2,
这两个变量均应等于0.8。但是,当我们运行上面的代码时,我们将看到以下结果:
0.7999999999999999 0.8
在这种情况下,将两个值与==运算符进行比较将产生错误的结果。因此,我们必须使用更复杂的比较算法。
如果我们想获得最佳精度并控制舍入机制,则可以使用java.math.BigDecimal类。
3.在纯Java中比较双打
在纯Java中比较双值的推荐算法是阈值比较方法。在这种情况下,我们需要检查两个数字之间的差异是否在指定的公差范围内,通常称为**epsilon** :
double epsilon = 0.000001d; assertThat(Math.abs(d1 - d2) < epsilon).isTrue();
epsilon值越小,比较精度越高。但是,如果我们指定的公差值太小,则会得到与简单==比较中相同的错误结果。通常, epsilon的小数点后5位和6位的值通常是一个很好的起点。
不幸的是,标准JDK中没有实用程序可用于以推荐的精确方式比较双精度值。幸运的是,我们不需要自己编写。我们可以使用免费的和广为人知的第三方库提供的各种专用方法。
4.使用Apache Commons Math
Apache Commons Math是致力于数学和统计组件的最大的开源库之一。从各种不同的类和方法中,我们将org.apache.commons.math3.util.Precision类。它包含2个有用的equals()方法来正确比较双精度值:
double epsilon = 0.000001d; assertThat(Precision.equals(d1, d2, epsilon)).isTrue(); assertThat(Precision.equals(d1, d2)).isTrue();
这里使用的epsilon变量与前面的示例具有相同的含义。这是允许的绝对误差量。但是,这并不是与阈值算法的唯一相似之处。特别是,两种equals方法在后台使用相同的方法。
两参数函数版本只是equals(d1, d2, 1)方法调用的快捷方式。该版本中的epsilon值很高。因此,我们不应该使用它,而总是自己指定公差值。
5.使用番石榴
Google的Guava是一大堆核心Java库,它们扩展了标准JDK功能。 com.google.common.math软件包中包含大量有用的数学工具。为了在Guava中正确比较double值,让我们实现DoubleMath类中**fuzzyEquals()**方法:
double epsilon = 0.000001d; assertThat(DoubleMath.fuzzyEquals(d1, d2, epsilon)).isTrue();
方法名称与Apache Commons Math中的方法名称不同,但实际上在后台运行相同。唯一的区别是没有使用epsilon的默认值的重载方法。
6.使用JUnit
JUnit是Java中使用最广泛的单元测试框架之一。通常,每个单元测试通常以分析预期值与实际值之间的差异结束。因此,测试框架必须具有正确而精确的比较算法。实际上,JUnit提供了一组用于常见对象,集合和原始类型的比较方法,包括专用的方法来检查双精度值是否相等:
double epsilon = 0.000001d; assertEquals(d1, d2, epsilon);
实际上,它的工作原理与之前描述的Guava和Apache Commons的方法相同。
重要的是要指出,还有一个不推荐使用的包含两个参数的版本,没有epsilon参数。但是,如果要确保结果始终正确,则应坚持使用三参数版本。
7.结论
在本文中,我们探索了在Java中比较双精度值的不同方法。
我们已经解释了为什么简单比较可能导致在运行时难以跟踪错误。然后,我们展示了如何正确地比较普通Java库和通用库中的值。
0 评论