使用==运算符比较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.43421
和b = 5.434205
将通过比较
private static final float EPSILON = <very small positive number>; if (Math.abs(ab) < EPSILON) ...
由于浮点数提供了variables但不可控的精度(也就是说,除了使用double
和float
之间进行select时,不能设置精度),所以必须select自己的固定精度进行比较。
请注意,这不是一个真正的等价操作符,因为它不是传递的。 你可以很容易地得到a
等于b
和b
等于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)
float
types是一个近似值 – 有一个指数部分和有限精度的值部分。
例如:
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)。