我使用的代码审查工具在我使用等号操作符比较两个浮点数值时会出现以下警告。正确的方法是什么,如何做呢?是否有可以重复使用的帮助函数(commons-*)?
Description
不能使用等于(==)运算符来比较浮点数值。
Explanation
由于浮点数的舍入误差,使用等于(==)或不等于(!=)运算符比较浮点数值并不总是准确的。
Recommendation
比较这两个浮点数值,看它们的值是否接近。
float a;
float b;
if(a==b)
{
..
}
我使用的代码审查工具在我使用等号操作符比较两个浮点数值时会出现以下警告。正确的方法是什么,如何做呢?是否有可以重复使用的帮助函数(commons-*)?
Description
不能使用等于(==)运算符来比较浮点数值。
Explanation
由于浮点数的舍入误差,使用等于(==)或不等于(!=)运算符比较浮点数值并不总是准确的。
Recommendation
比较这两个浮点数值,看它们的值是否接近。
float a;
float b;
if(a==b)
{
..
}
IBM提供了一种比较两个浮点数的建议,使用除法而不是减法 - 这使得选择适用于所有输入范围的epsilon更容易。
if (abs(a/b - 1) < epsilon)
关于epsilon的值,我会使用这个维基百科表格中给出的5.96e-08
,或者可能使用两倍该值。
if (abs(b) <= 1.18e−38)
条件进行检查,如果为真,则检查 a
是否满足相同的条件以查看它是否相等。 - Mark Ransom5.96e-08
,对于实际价值来说太小了:没有任何一对不同的单精度IEEE 754浮点数a
和b
满足abs(a / b - 1) < 5.96e-08
,因此使用这个epsilon值进行测试只是一种缓慢测试相等性的方式。 - Mark Dickinson它希望您将它们进行所需的精度比较。例如,如果您需要浮点数的前4个小数位相等,则可以使用:
if(-0.00001 <= a-b && a-b <= 0.00001)
{
..
}
或者:
if(Math.abs(a-b) < 0.00001){ ... }
你需要将所需精度添加到两个数字的差中,并将其与所需精度的两倍进行比较。
任何你认为更易读的方式都可以。我个人更喜欢第一种方法,因为它清楚地显示了你在两侧允许的精度。
a = 5.43421
和 b = 5.434205
将通过比较。
a
是 2 而 b
是 3,那么第一次比较会失败(正确),但第二次比较会通过(不正确)。 - StriplingWarriorprivate static final float EPSILON = <very small positive number>;
if (Math.abs(a-b) < EPSILON)
...
使用commons-lang库
org.apache.commons.lang.math.NumberUtils#compare
http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double)
System.out.println((0.6 / 0.2) == 3); // false
false
,而在数学上应该是true
。static float e = 0.00000000000001f;
if (Math.abs(a - b) < e)
如果两个参数相等或在允许误差范围内(包括边界),则返回 true。如果两个浮点数之间有 (maxUlps - 1) 个或更少的浮点数,则认为它们相等,即相邻的浮点数被认为是相等的。
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);
}
根据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);
}
return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0x7fffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0x7fffffffffffffe0L);
有许多情况下,只有在两个浮点数绝对等效时才希望将它们视为相等,而“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,即使一个值是直接从另一个值分配的。
虽然有许多情况需要检查浮点数的精确等价性,但我不确定是否有任何情况应该考虑实际行为==
更可取。可以说,所有等价性测试都应通过实际测试等价性的函数(例如通过比较位形式)进行。
首先,有几个需要注意的事项:
float.h
中的 epsilon 的平方根 sqrt(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;
}
{符号位、偏置指数、尾数}
的情况下,如果将这些数字作为有符号的整数解释,则它们按字典顺序排序。也就是说,符号位成为符号位,而指数始终完全优先于尾数,因为它首先确定解释为整数的数字的数量级。
float
并涉及到货币(或者通常表示为小数的任何数字),你应该考虑使用BigDecimal
。 - Kirk Woll