使用==运算符比较float / double值

时间:2011-07-26 21:46:14

标签: java floating-point

当我开始使用相等运算符比较两个浮点值时,我使用的代码审查工具会引用以下内容。什么是正确的方法以及如何做到这一点?是否有一个帮助函数(commons- *),我可以重用它?

描述

无法使用equals(==)运算符

比较浮点值

解释

使用等式(==)或不等式(!=)运算符比较浮点值并不总是准确的,因为舍入错误。

建议

比较两个浮点值以查看它们是否接近值。

float a;
float b;

if(a==b)
{
..
}

9 个答案:

答案 0 :(得分:32)

IBM has a recommendation用于比较两个浮点数,使用除法而不是减法 - 这样可以更容易地选择适用于所有输入范围的epsilon。

if (abs(a/b - 1) < epsilon)

关于epsilon的值,我会使用this Wikipedia table中给出的5.96e-08,或者可能是该值的2倍。

答案 1 :(得分:10)

它希望您将它们与您需要的准确度进行比较。例如,如果您要求浮点数的前4个十进制数字相等,那么您将使用:

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

或者:

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

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

无论你认为什么都更具可读性。我自己更喜欢第一个,因为它清楚地显示了你允许双方的精确度。

a = 5.43421b = 5.434205将通过比较

答案 2 :(得分:6)

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

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

由于浮点数为您提供可变但无法控制的精度(也就是说,除了在使用doublefloat之间选择时,您无法设置精度),您必须选择自己的固定精确比较。

请注意,这不再是真正的等价运算符,因为它不是传递的。您可以轻松获得a等于bb等于c,但a不等于c

编辑:还要注意,如果a为负且b是一个非常大的正数,则减法可能会溢出,结果将为负无穷大,但测试仍然有效,因为负无穷大的绝对值是正无穷大,它将大于EPSILON

答案 3 :(得分: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)

答案 4 :(得分: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)(或更少)浮点数,则认为两个浮点数相等,即两个相邻的浮点数被认为相等。

答案 5 :(得分: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);
}

这为您提供了当前比例下两个值之间可以表示的浮点数,它应该比绝对值更好。

答案 6 :(得分:1)

我根据java实现== for double的方式对此进行了尝试。它首先转换为IEEE 754长整数形式,然后进行逐位比较。 Double还提供静态doubleToLongBits()来获取整数形式。使用bit fiddling,您可以通过添加1/2(一位)和截断来“舍入”双尾数的尾数。

为了与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);

答案 7 :(得分:1)

首先,需要注意几点:

  • &#34;标准&#34;这样做的方法是选择一个常数epsilon,但常数epsilons不能正确地适用于所有数字范围。
  • 如果你想使用一个常数epsilon sqrt(EPSILON),那么来自float.h的epsilon的平方根通常被认为是一个很好的值。 (这来自一本臭名昭着的橙色书&#34;此刻的名字让我逃脱了。)
  • 浮点除法会很慢,所以你可能想避免它进行比较,即使它的行为就像选择一个为数字定制的epsilon&#39;量值。

你真的想做什么?这样的事情:
比较值相差多少可表示的浮点数。

此代码来自this布鲁斯道森的精彩文章。该文章已经更新here。主要区别在于旧文章打破了严格别名规则。 (将浮动指针转换为int指针,解除引用,写入,转换)。虽然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浮点格式{sign-bit, biased-exponent, mantissa},如果解释为有符号大小的整数,则数字按字典顺序排序。也就是说,符号位成为符号位,并且指数总是在定义浮点数的大小时完全超过尾数,因为它首先确定解释为int的数字的大小。

因此,我们将浮点数的位表示解释为有符号大小的int。然后,如果数字为负,我们通过从0x80000000减去它们将有符号幅度的int转换为2的补码整数。然后我们只比较两个值,就像我们任何有符号的两个补码整数一样,并看看它们有多少不同的值。如果这个数量小于你选择的数量阈值,你可以选择多少可表示的浮点值并且仍然被认为是相等的,那么你就说它们​​相等。&#34;等等。请注意,此方法正确地让#34;等于&#34;对于较大幅度的浮点数,数值会有较大的值,对于较小幅度的浮点数,数值会因较小的值而不同。

答案 8 :(得分: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,即使一个值直接从另一个值分配,比较也总是会产生错误。

虽然在很多情况下检查确切等效的浮点数是正确的,但我不确定==的实际行为应该被认为是更好的情况。可以说,所有等价测试都应该通过实际测试等价的函数来完成(例如通过比较按位形式)。