在Java中比较double值的相等性。

13

我希望能向更有经验的Java原始双精度浮点数相等性处理人员征求建议。由于可能存在舍入误差,使用d1 == d2来比较两个双精度浮点数d1d2是不够准确的。

我的问题如下:

  1. Java的Double.compare(d1,d2) == 0是否在一定程度上处理了舍入误差?如1.7文档所述,如果d1d2在数值上相等,则返回值为0。是否有人确定他们所说的“数值上相等”具体指什么?

  2. 使用相对误差计算与某个Δ值进行比较,您会推荐通用(非特定应用程序)的δ值吗?请参见下面的示例。

以下是一个通用函数,用于考虑相对误差检查相等性。您会推荐什么值的delta以捕获简单操作+、-、/、*操作中大部分的舍入误差?

public static boolean isEqual(double d1, double d2) {
    return d1 == d2 || isRelativelyEqual(d1,d2);
}

private static boolean isRelativelyEqual(double d1, double d2) {
    return delta > Math.abs(d1- d2) / Math.max(Math.abs(d1), Math.abs(d2));
}

可能是如何解决Java舍入Double问题的重复问题。 - om-nom-nom
1
你的isRelativelyEqual方法是解决这个问题的正确途径(除了NaN问题)。你可以根据浮点格式的精度位数选择“delta”,对于双精度而言,有效精度为53位或16个十进制数字,以及您希望达到的“接近程度”。或者,您可以在这些数字上使用doubleToLongBits函数,“向上取整”小数部分以排除1-3个小数位,然后进行相等比较。 - Hot Licks
3个回答

7
你可以尝试使用10的负15次方作为delta值进行实验,但你会发现一些计算结果存在较大的舍入误差。此外,你进行的操作越多,累积的舍入误差就会越大。
一个特别糟糕的情况是当你减去两个几乎相等的数字时,例如1.0000000001 - 1.0,并将结果与0.0000000001进行比较。
因此,在所有情况下都很难找到通用的方法。你必须始终计算在某种应用中可以期望的精度,然后如果它们更接近这个精度,就认为它们的结果是相等的。
例如,以下代码的输出:
public class Main {

    public static double delta(double d1, double d2) {
        return Math.abs(d1- d2) / Math.max(Math.abs(d1), Math.abs(d2));
    }

    public static void main(String[] args) {
        System.out.println(delta(0.1*0.1, 0.01));
        System.out.println(delta(1.0000000001 - 1.0, 0.0000000001));
    }

}

is

1.7347234759768068E-16
8.274036411668976E-8

区间算术可以用来跟踪累积的舍入误差。然而,在实践中,误差区间往往过于悲观,因为有时候舍入误差也会互相抵消。


如果期望值为0.0怎么办? - simon.watts
@simon.watts 没有什么特别的,就像我之前说的:“几乎没有希望找到适用于所有情况的通用方法”。 - Henry

2
您可以尝试以下代码(未经测试):

您可以尝试以下代码(未经测试):

public static int sortaClose(double d1, double d2, int bits) {
    long bitMask = 0xFFFFFFFFFFFFFFFFL << bits;
    long thisBits = Double.doubleToLongBits(d1) & bitMask;
    long anotherBits = Double.doubleToLongBits(d2) & bitMask;

    if (thisBits < anotherBits) return -1;
    if (thisBits > anotherBits) return 1;
    return 0;                        
}

"bits"通常是1到4位不等,具体取决于您需要截止的精度。
一种改进方法是在掩码前将第一个要归零的位的位置加1(进行“四舍五入”),但是这样还需要担心连锁反应直到最高有效位。

谢谢,这正是我在寻找的方法。 然而,这并不适用于“跨越0”——正+ε和负-ε并不被视为非常接近。这里有一个备选版本,它通过了我所做的测试(但如果它在某个地方出现了一些故障,我也不会感到震惊):return d1==d2 /* 热点路径;还处理无穷大和NaN。 */ || (Math.abs(d1-d2) < Math.max( Math.ulp(d1), Math.ulp(d2) ) * (0b1L << bits)); - not-just-yeti

2
从compareTo的javadoc中可以看出:
  • 此方法认为Double.NaN等于自身且大于所有其他double值(包括Double.POSITIVE_INFINITY)。
  • 此方法认为0.0d大于-0.0d。
您可能会发现这篇文章非常有用:

文章链接

如果需要,您可以像这样检查:
double epsilon = 0.0000001;
if      ( d <= ( 0 - epsilon ) ) { .. }
else if ( d >= ( 0 + epsilon ) ) { .. }
else { /* d "equals" zero */ }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接