在Java中直接比较两个浮点数/双精度浮点数是否安全?

5

如果我使用这样的比较(a是int,b和c是float/double),那么是否安全:

a == b
b == c

也许听起来很荒谬,但在我以前的编程语言中,有时候 1 + 2 == 3 是错误的(因为左侧返回的是 2.99999999999...)。那么这个呢:

Math.sqrt(b) == Math.sqrt(c)
b / 3 == 10 / 3 //In case b = 10, does it return true?

3
如果1.0 + 2.0不等于3.0,无论精度如何,那么您的计算机出了问题。但是如果0.1 + 0.2不等于0.3,这就更让人能够理解了。 - cHao
10个回答

14

一般来说,由于许多十进制数不能被准确地表示为 floatdouble 值,因此通常情况下不安全。经常提到的解决方案是测试这些数字之间的差异是否小于某个“小”值(在数学文献中通常用希腊字母“epsilon”符号表示)。

但是,你需要注意如何进行测试。例如,如果你写成:

if (Math.abs(a - b) < 0.000001) {
    System.err.println("equal");
}

如果在测试中,ab应该是“相同的”,则应该使用绝对误差。但这样做可能会遇到麻烦,例如当ab分别为1,999,999.992,000,000.00等值时,这两个数字之间的差异比一个 float 数据类型表示的最小值(在那个级别上)还要小,但它仍然远大于我们选择的 epsilon。

可以说,更好的方法是使用相对误差;例如,以以下方式进行(防御性地编码)

if (a == b ||
    Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b)) < 0.000001) {
    System.err.println("close enough to be equal");
}

但是,即使如此仍然不是完整的答案,因为它没有考虑到某些计算方式会导致误差累积到难以控制的程度。请查看这个维基百科链接了解更多详细信息。

底线是,在浮点数计算中处理错误比表面上看起来要困难得多。


还有一点需要注意(就像其他人已经解释过的那样),整数算术在某些方面与浮点算术表现非常不同:

  • 如果结果不是整数,则整数除法将截断小数部分
  • 整数加减乘运算会溢出。

这两种情况都会在没有任何警告的情况下发生,无论是在编译时还是运行时。


一个完美无瑕的答案!非常感谢! - Luke Vo

5

您需要注意一些细节。

1.0 + 2.0 == 3.0

这是正确的,因为整数可以被精确地表示。

Math.sqrt(b) == Math.sqrt(c) 

如果b等于c。
b / 3.0 == 10.0 / 3.0

如果b == 10.0,这是我认为你的意思。

最后两个示例比较了同一计算的两个不同实例。当您具有不可表示数字的不同计算时,精确相等测试会失败。

如果您正在测试浮点逼近的计算结果,则应该进行容差的相等性测试。

您有任何具体的现实世界的例子吗?我认为您会发现希望使用浮点数进行相等测试是很少的。


由于1和2是可表示的,很难解释。 - David Heffernan
实际上,2来自于10/5(确切地说是10/5),但是不知何故VB.NET中的Double返回1.9999999,所以当加1时,它变成了2.9999999999999。 - Luke Vo
虽然你对浮点数的工作方式保持谨慎是明智的,但你不应该过于担心。在基于IEEE754浮点数的VB.NET和其他环境中,10.0/5.0的结果为2.0,并且可以精确表示。此外,在这样的环境中,1.0+2.0==3.0。了解你能做什么以及不能做什么,以及边界在哪里非常重要。 - David Heffernan
嗯,我对5不是很确定,只确定2是由10除以一个number(来自另一位队友编写的函数)得出的结果,在理论/算法上,它应该是整数(尽管返回类型是double)。所以问题可能来自于那个函数。 - Luke Vo
实际上,分母为2的幂次方的有理数是可以精确表示的,只要使用float或double具有足够的精度来表示特定的有理数。考虑(2 ^ 100 -1)/ 2 ^ 100。 - Stephen C
显示剩余2条评论

4

在比较浮点数/双精度浮点数时,最安全的方法实际上是检查它们之间的差是否为一个小数。

例如:

Math.abs(a - b) < EPS

EPS可以是0.0000001之类的数字。

这样做可以确保精度误差不会影响您的结果。


是的,如果不安全的话,这就是我打算做的。 - Luke Vo

4

b / 3 != 10 / 3 - 如果b是一个浮点变量,如b = 10.0f,那么b / 3是3.3333,而10 / 3是整数除法,所以等于3。

如果b == c,那么Math.sqrt(b) == Math.sqrt(c) - 这是因为sqrt函数总是返回double类型。

通常来说,你不应该直接比较双精度/浮点数的相等性,因为它们是浮点数,可能会出现误差。你几乎总是希望使用给定的精度进行比较,例如:

b - c < 0.000001


+1 这里的第一条语句绝对是需要理解的重要内容。 - Jesse Webb

2

在基本上任何语言中,对于双精度浮点数来说,“==”比较并不是特别安全的。最好采用一个epsilon比较方法(即检查两个浮点数之间的差距是否足够小)。

例如:

Math.sqrt(b) == Math.sqrt(c)

我不确定为什么你不直接比较b和c,但在这里使用epsilon比较也可以。
对于:
b / 3 == 10 / 3

因为整数除法的原因,10/3=3,所以这不一定会给出你想要的结果。你可以使用10.0/3,但我仍然不确定为什么你不直接比较b和10(在任何情况下都使用epsilon比较方法)。


这只是一个例子,有时您需要一个返回双精度/浮点值以进行比较的函数(而不是sqrt)。 - Luke Vo
明白了。无论如何,您应该使用Ionel在他的答案中演示的比较方法。 - Jodaka

1
浮点数包装类的比较方法可以用于比较两个浮点数值。
Float.compare(float1,float2)==0

它将比较与每个浮点对象对应的整数位。

1
但是为什么?float1 == float2完全做同样的事情! - Stephen C

0
b是浮点数,10是整数,如果你用这个标准来比较它们,那么结果会是false,因为...
b = flaot (meance ans 3.33333333333333333333333)
10 is integer so that make sence.

@Rasel,你可以将其与相同的语言进行比较。 - Siten

0
在Java中,您可以比较一个浮点数与另一个浮点数,一个双精度浮点数与另一个双精度浮点数。当双精度和浮点数的精度相等时,将比较双精度和浮点数会得到true。

0

0

2位小数的高效方法:

public static void main(String[] args) {
    double number = 3.14999965359;
    float number2 = 3.14909265359f;
    Date date = new Date();
    System.out.println(equalsDoubleWithFloat(number, number2));
    System.out.println(isDoubleBigFromFloat(number, number2));
    System.out.println("delay: " + (new Date().getTime() - date.getTime()) + " ms");
}

private static boolean equalsDoubleWithFloat(double db, float fl) {
    return Float.compare((float) db, fl) == 0 || Math.abs(db - fl) < 0.01;
}

private static boolean isDoubleBigFromFloat(double db, float fl) {
    return Math.abs(db - fl) > 0.01 && Float.compare((float) db, fl) > 0;
}
public static boolean isDoubleSmallFromFloat(double db, float fl) {
        return Math.abs(db - fl) > 0.01 && Float.compare((float) db, fl) < 0;
}

输出结果为:

true
false
delay: 1 ms

其他解决方案:

private static final DecimalFormat TWO_DIGITS = new DecimalFormat("0.00");

public static double formatDouble(double value) {
    return Double.parseDouble(TWO_DIGITS.format(value));
}

public static float formatFloat(float value) {
    return Float.parseFloat(TWO_DIGITS.format(value));
}

public static int compareDoubleWithFloat(double db, float fl) {
    double roundedDouble = formatDouble(db);
    float roundedFloat = formatFloat(fl);
    if (Math.abs(roundedDouble - roundedFloat) < 0.01) {
        return 0;
    } else if (roundedDouble > roundedFloat) {
        return 1;
    } else {
        return -1;
    }
}

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