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

当我开始使用相等运算符来比较两个浮点值时,我使用的代码审查工具与下面的投诉。 什么是正确的方法和如何做到这一点? 是否有帮助function(commons- *)在那里,我可以重用?

描述

无法使用equals(==)运算符比较浮点值

说明

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

build议

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

float a; float b; if(a==b) { .. } 

IBM推荐比较两个浮点数,使用除法而不是减法 – 这使得select适用于所有input范围的epsilon变得更加容易。

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

至于epsilon的价值,我会使用这个维基百科表中给出的5.96e-08 ,或者可能是这个值的2 5.96e-08

它希望你能够在你所需要的精度内进行比较。 例如,如果你需要浮点数的前4位十进制数相等,那么你可以使用:

 if(-0.00001 <= ab && ab <= 0.00001) { .. } 

要么:

 if(Math.abs(ab) < 0.00001){ ... } 

在哪里你添加所需的精度的差异的两个数字,并将其比较两倍所需的精度。

无论你怎么想,都是更可读的。 我更喜欢第一个,因为它清楚地显示了你在双方允许的精度。

a = 5.43421b = 5.434205将通过比较

 private static final float EPSILON = <very small positive number>; if (Math.abs(ab) < EPSILON) ... 

由于浮点数提供了variables但不可控的精度(也就是说,除了使用doublefloat之间进行select时,不能设置精度),所以必须select自己的固定精度进行比较。

请注意,这不是一个真正的等价操作符,因为它不是传递的。 你可以很容易地得到a等于bb等于c但是不等于c

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

使用commons-lang

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

还有commons-math(在你的情况下更合适的解决scheme):

 http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double) 

floattypes是一个近似值 – 有一个指数部分和有限精度的值部分。
例如:

 System.out.println((0.6 / 0.2) == 3); // false 

风险是一个微小的舍入误差可以使比较false ,当在math上它应该是true

解决方法是比较浮动允许一个小的差异仍然是“平等”:

 static float e = 0.00000000000001f; if (Math.abs(a - b) < e) 

Apache 公共math拯救: MathUtils。(double x,double y,int maxUlps)

如果两个参数相等或在允许的错误范围内(包含),则返回true。 如果两个浮点数之间存在(maxUlps – 1)(或更less)浮点数,即两个相邻的浮点数被认为相等,则两个浮点数被视为相等。

下面是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更好。

我根据java实现==的方式对此进行了刺探。 它首先转换为IEEE 754长整型,然后进行按位比较。 Double还提供了静态doubleToLongBits()来获得整数forms。 使用位摆弄你可以通过添加1/2(一位)和截断来“舍入”双数的尾数。

为了跟上supercat的观察,函数首先尝试一个简单的==比较,如果失败,则只进行四舍五入。 这是我想出了一些(希望)有用的意见。

我做了一些有限的testing,但不能说我已经尝试了所有的边缘情况。 另外,我没有testing性能。 这不应该太糟糕。

我刚刚意识到这与Dmitri提供的解决scheme基本相同。 也许更简洁一点。

 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 // eg 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); 

首先,需要注意一些事情:

  • 这样做的“标准”方法是select一个恒定的epsilon,但是恒定的epsilon不能在所有数字范围内正常工作。
  • 如果要使用一个不变的epsilon sqrt(EPSILON) ,那么float.h中的ε的平方根通常被认为是一个很好的值。 (这是来自臭名昭着的“橙皮书”,现在谁的名字就逃脱了我)。
  • 浮点除法将会很慢,所以你可能想要避免它进行比较,即使它的行为就像是为数字的大小定制一个小数点。

你真的想做什么? 像这样的东西:
比较多less可表示的浮点数的值不同。

这段代码来自Bruce Dawson的这篇非常棒的文章。 这篇文章已经在这里更新了 。 主要的区别在于旧文章打破了严格的别名规则。 (将浮点指针转换为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} ,数字按字典顺序排列,如果解释为有符号数量级整数。 这就是符号位成为符号位,指数总是在定义浮点数的尾数时完全超过尾数,因为它首先确定被解释为整数的数量的大小。

所以,我们将浮点数的位表示解释为一个有符号的整数。 然后,如果数字为负数,则将有符号数的整数转换为二进制补码整数,从0x80000000中减去它们。 然后,我们只是比较两个值,就像我们对任何有二进制补码的符号进行比较,看看它们有多less个值不同。 如果这个数量小于你select多less浮点数的阈值,这个数值可能会有所不同,但仍然被认为是相等的,那么你就说它们​​是“相等的”。 请注意,这种方法正确地让“相等”的数字在较大幅度的浮点数上有较大的值,对于较小幅度的浮点数则为较小的值。

有很多情况下,只要两个浮点数完全等价就可以把两个浮点数等同起来,而“三angular”比较就是错的。 例如,如果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,则即使一个值直接从另一个值中分配,比较也总是会产生错误。

虽然有很多情况下检查浮点数的确切等价是正确的,但我不确定任何情况下==的实际行为应该被认为是更可取的。 可以说,所有的等价testing都应该通过一个实际testing等价性的函数来完成(例如通过比较按位forms)。