使用“==”运算符比较浮点数/双精度值

21

我使用的代码审查工具在我使用等号操作符比较两个浮点数值时会出现以下警告。正确的方法是什么,如何做呢?是否有可以重复使用的帮助函数(commons-*)?

Description

不能使用等于(==)运算符来比较浮点数值。

Explanation

由于浮点数的舍入误差,使用等于(==)或不等于(!=)运算符比较浮点数值并不总是准确的。

Recommendation

比较这两个浮点数值,看它们的值是否接近。

float a;
float b;

if(a==b)
{
..
}

4
顺便提一下,如果你正在使用float并涉及到货币(或者通常表示为小数的任何数字),你应该考虑使用BigDecimal - Kirk Woll
好评论。我知道这一点,并且在处理货币相关的所有事务时都使用BigDecimal。但是对于其他所有情况,我会采用不同的方法。 - Aravind Yarram
使用java.lang.Float.compare(float1, float2); //返回整数值,但不能用于if条件语句中代替float1 == float2 - Ajay Takur
“Uhhh,'if ( java.lang.Float.compare(float1, float2) == 0) { ... }'有什么问题吗?”这看起来像是一个有效的if条件语句啊! - ingyhere
1
@Hearen,你的理解是反过来了。Float1.compareTo(Float2) 是使用 Float.compare(f1.f2) 的。不管怎样,返回值都有些尴尬。我猜这取决于开发人员是否需要为他们特定的用例对输入进行消毒处理。我不反对你的看法。 - ingyhere
显示剩余2条评论
9个回答

36

1
@DrewNoakes,好问题。您可以使用 if (abs(b) <= 1.18e−38) 条件进行检查,如果为真,则检查 a 是否满足相同的条件以查看它是否相等。 - Mark Ransom
2
糟糕。那段代码不仅运行缓慢,而且在很多情况下可能是错误的。如果比较 x 和 (y-z),公差需要基于 y 和 z 的大小,而不是差异的大小,并且上述代码无法考虑到这一点。如果例如计算了 f(x) 并想知道是否应该计算 f(y) [或者只需使用 f(x) 的值],则通常适用于 x 和 y 的精确相等测试。不幸的是,精确相等测试通常很困难,尽管“==”运算符在大多数情况下已足够好。 - supercat
1
不确定Java的Math.abs()方法是如何实现的,但可以通过位移操作非常快地计算绝对值:http://www.musicdsp.org/showone.php?id=132。 - ingyhere
1
建议使用的epsilon值为5.96e-08,对于实际价值来说太小了:没有任何一对不同的单精度IEEE 754浮点数ab满足abs(a / b - 1) < 5.96e-08,因此使用这个epsilon值进行测试只是一种缓慢测试相等性的方式。 - Mark Dickinson

10

它希望您将它们进行所需的精度比较。例如,如果您需要浮点数的前4个小数位相等,则可以使用:

if(-0.00001 <= a-b && a-b <= 0.00001)
{
..
}

或者:

if(Math.abs(a-b) < 0.00001){ ... }

你需要将所需精度添加到两个数字的差中,并将其与所需精度的两倍进行比较。

任何你认为更易读的方式都可以。我个人更喜欢第一种方法,因为它清楚地显示了你在两侧允许的精度。

a = 5.43421b = 5.434205 将通过比较。


1
如果 a 是 2 而 b 是 3,那么第一次比较会失败(正确),但第二次比较会通过(不正确)。 - StriplingWarrior

6
private static final float EPSILON = <very small positive number>;

if (Math.abs(a-b) < EPSILON)
   ...

作为浮点数提供可变但不可控制的精度(也就是说,您不能设置除使用double和float之外的精度),因此您必须为比较选择自己的固定精度。
请注意,这不再是一个真正的等价运算符,因为它不具有传递性。你很容易得到a等于b和b等于c但a不等于c。
编辑:还要注意,如果a是负数而b是非常大的正数,减法可能会溢出并且结果将是负无穷大,但测试仍将起作用,因为负无穷大的绝对值是正无穷大,大于EPSILON。

请问EPSILON的实际值应该是多少? - Aravind Yarram
1
个人而言,我会将EPSILON定义为应用程序特定的最大容差。如果要比较的数字非常大,它实际上可以是一个相当大的数字! :) - Affe
@Affe 没错。这将取决于应用程序本身。 - biziclop

3

使用commons-lang库

org.apache.commons.lang.math.NumberUtils#compare

同时,commons-math(在您的情况下更为适用):
http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double)

该函数对缓解他的工具引发的担忧没有任何作用。 - Affe
@Affe 哪一个?commons-math 中的 compare - agree; equals 函数与其他答案所述的函数相似,同时 MathUtils 中还提供了很酷的重载版本。 - Andrey
是的,在MathUtils#equals中使用的方法通常是最好的解决方案,除非应用程序域定义了更粗糙的内容。第一条评论是针对您的预编辑答案的 :) - Affe

2
“float”类型是一个“近似”的值——它有一个指数部分和一个具有有限精度的值部分。例如:
System.out.println((0.6 / 0.2) == 3);  // false

风险在于,一个微小的四舍五入误差可能会使比较结果为false,而在数学上应该是true
解决方法是允许浮点数之间存在一定的差异仍然被视为“相等”进行比较。
static float e = 0.00000000000001f;
if (Math.abs(a - b) < e)

Apache commons-math 来拯救:MathUtils.(double x, double y, int maxUlps)

如果两个参数相等或在允许误差范围内(包括边界),则返回 true。如果两个浮点数之间有 (maxUlps - 1) 个或更少的浮点数,则认为它们相等,即相邻的浮点数被认为是相等的。


2
以下是Commons Math实现的实际代码:

private static final int SGN_MASK_FLOAT = 0x80000000;

public static boolean equals(float x, float y, int maxUlps) {
    int xInt = Float.floatToIntBits(x);
    int yInt = Float.floatToIntBits(y);

    if (xInt < 0)
        xInt = SGN_MASK_FLOAT - xInt;

    if (yInt < 0)
        yInt = SGN_MASK_FLOAT - yInt;

    final boolean isEqual = Math.abs(xInt - yInt) <= maxUlps;

    return isEqual && !Float.isNaN(x) && !Float.isNaN(y);
}

这将为您提供当前比例尺下可表示在两个值之间的浮点数的数量,这应该比绝对epsilon更有效。

1

根据Java实现double类型的==运算的方式,我尝试了一下。它首先将其转换为IEEE 754长整型形式,然后进行位比较。Double还提供了静态doubleToLongBits()方法来获取整数形式。通过位操作,您可以通过添加1/2(一个位)并截断来“四舍五入” double 的尾数。

遵循supercat的观察,该函数首先尝试简单的==比较,仅在失败时才进行四舍五入。以下是我想出的一些(希望有用的)注释。

我进行了一些有限的测试,但不能说我尝试过所有边缘情况。此外,我没有测试性能。它不应该太糟糕。

我刚意识到这实际上与 Dmitri 提供的解决方案相同。可能更加简明。

static public boolean nearlyEqual(double lhs, double rhs){
    // This rounds to the 6th mantissa bit from the end. So the numbers must have the same sign and exponent and the mantissas (as integers)
    // need to be within 32 of each other (bottom 5 bits of 52 bits can be different). 
    // To allow 'n' bits of difference create an additive value of 1<<(n-1) and a mask of 0xffffffffffffffffL<<n
    // e.g. 4 bits are: additive: 0x10L = 0x1L << 4 and mask: 0xffffffffffffffe0L = 0xffffffffffffffffL << 5
    //int bitsToIgnore = 5;
    //long additive = 1L << (bitsToIgnore - 1);
    //long mask = ~0x0L << bitsToIgnore; 
    //return ((Double.doubleToLongBits(lhs)+additive) & mask) == ((Double.doubleToLongBits(rhs)+additive) & mask);
    return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0xffffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0xffffffffffffffe0L);
}

以下修改处理了当值在0的两侧时,符号发生变化的情况。
return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0x7fffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0x7fffffffffffffe0L);

0

有许多情况下,只有在两个浮点数绝对等效时才希望将它们视为相等,而“delta”比较是错误的。例如,如果f是一个纯函数,并且知道q=f(x)和y===x,则应该知道q=f(y)而无需计算它。不幸的是,在这方面==有两个缺陷。

  • 如果一个值是正零,另一个值是负零,则它们将被比较为相等,即使它们不一定等效。例如,如果f(d)=1/d,a=0,b=-1*a,则a==b但f(a)!=f(b)。

  • 如果任何一个值是NaN,则比较总是返回false,即使一个值是直接从另一个值分配的。

虽然有许多情况需要检查浮点数的精确等价性,但我不确定是否有任何情况应该考虑实际行为==更可取。可以说,所有等价性测试都应通过实际测试等价性的函数(例如通过比较位形式)进行。


0

首先,有几个需要注意的事项:

  • “标准”的方法是选择一个固定的 epsilon 值,但是对于所有数字范围,常量 epsilon 并不总是正确的。
  • 如果您想使用一个恒定的 epsilon,则 float.h 中的 epsilon 的平方根 sqrt(EPSILON) 通常被认为是一个好值。(这来自于一本著名的“橙皮书”,我目前无法想起它的名字。)
  • 浮点除法会很慢,因此即使它像选择专门为数字幅度量身定制的 epsilon 那样运行,您也可能希望避免将其用于比较。

你真正想做什么?像这样:
比较数值差异的可表示浮点数数量。

这段代码来自于 Bruce Dawson 的this非常棒的文章。该文章已经进行了更新。主要区别在于旧文章违反了严格别名规则(将浮点指针强制转换为整型指针,解引用、写入、强制类型转换回去)。虽然 C/C++ 纯粹主义者会很快指出这个缺陷,但在实践中它确实有效,并且我认为这样的代码更易读。但是,新的文章使用了联合体,C/C++ 也保持了其尊严。为了简洁起见,下面是违反严格别名规则的代码。

// Usable AlmostEqual function
bool AlmostEqual2sComplement(float A, float B, int maxUlps)
{
    // Make sure maxUlps is non-negative and small enough that the
    // default NAN won't compare as equal to anything.
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);
    int aInt = *(int*)&A;
    // Make aInt lexicographically ordered as a twos-complement int
    if (aInt < 0)
        aInt = 0x80000000 - aInt;
    // Make bInt lexicographically ordered as a twos-complement int
    int bInt = *(int*)&B;
    if (bInt < 0)
        bInt = 0x80000000 - bInt;
    int intDiff = abs(aInt - bInt);
    if (intDiff <= maxUlps)
        return true;
    return false;
}

上面代码的基本思想是首先注意到在给定 IEEE 754 浮点格式 {符号位、偏置指数、尾数} 的情况下,如果将这些数字作为有符号的整数解释,则它们按字典顺序排序。也就是说,符号位成为符号位,而指数始终完全优先于尾数,因为它首先确定解释为整数的数字的数量级。
因此,我们将浮点数的位表示解释为有符号整数。然后通过从0x80000000中减去它们(如果该数为负数)将其转换为二进制补码整数。然后,我们将两个值像任何有符号的二进制补码整数一样进行比较,并看它们相差多少。如果这个量小于您选择的表示相等的可表示浮点数的数量阈值,则说明它们是“相等”的。请注意,对于更大的幅度浮点数,“相等”的数字可以以更大的值不同,而对于更小的幅度浮点数,“相等”的数字可能以更小的值不同。

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