比较两个双精度浮点数或两个浮点数最有效的方法是什么?
简单地这样做是不正确的:
bool CompareDoubles1 (double A, double B)
{
return A == B;
}
比如:
bool CompareDoubles2 (double A, double B)
{
diff = A - B;
return (diff < EPSILON) && (-diff < EPSILON);
}
似乎是浪费加工。
有人知道更聪明的浮点比较器吗?
在这个版本中,你可以检查,这些数字之间的差异并不比某些分数(比如,0.0001%)更大:
bool floatApproximatelyEquals(const float a, const float b) {
if (b == 0.) return a == 0.; // preventing division by zero
return abs(1. - a / b) < 1e-6;
}
请注意Sneftel关于浮动可能的分数限制的评论。
还要注意的是,它不同于使用绝对的epsilon的方法——这里你不需要担心“数量级”——数字可能是,比如说1e100,或者1e-100,它们总是会被一致地比较,而且你不必为每一种情况更新epsilon。
'返回fabs(a - b) < EPSILON;
这是可以的,如果:
输入的数量级变化不大
极少数相反的符号可以被视为相等
否则就会给你带来麻烦。双精度数的分辨率约为小数点后16位。如果您正在比较的两个数字在量级上大于EPSILON*1.0E16,那么您可能会说:
return a==b;
我将研究一种不同的方法,假设您需要担心第一个问题,并假设第二个问题对您的应用程序很好。解决方案应该是这样的:
#define VERYSMALL (1.0E-150)
#define EPSILON (1.0E-8)
bool AreSame(double a, double b)
{
double absDiff = fabs(a - b);
if (absDiff < VERYSMALL)
{
return true;
}
double maxAbs = max(fabs(a) - fabs(b));
return (absDiff/maxAbs) < EPSILON;
}
这在计算上是昂贵的,但有时是需要的。这就是我们公司必须做的事情,因为我们要处理一个工程库,输入可能相差几十个数量级。
无论如何,关键在于(并且适用于几乎所有的编程问题):评估你的需求是什么,然后想出一个解决方案来满足你的需求——不要认为简单的答案就能满足你的需求。如果在您的评估后,您发现fabs(a-b) < EPSILON将足够,完美-使用它!但也要注意它的缺点和其他可能的解决方案。
我为java编写这篇文章,但是您可能会发现它很有用。它使用长变量而不是双变量,但会处理nan、亚法线等。
public static boolean equal(double a, double b) {
final long fm = 0xFFFFFFFFFFFFFL; // fraction mask
final long sm = 0x8000000000000000L; // sign mask
final long cm = 0x8000000000000L; // most significant decimal bit mask
long c = Double.doubleToLongBits(a), d = Double.doubleToLongBits(b);
int ea = (int) (c >> 52 & 2047), eb = (int) (d >> 52 & 2047);
if (ea == 2047 && (c & fm) != 0 || eb == 2047 && (d & fm) != 0) return false; // NaN
if (c == d) return true; // identical - fast check
if (ea == 0 && eb == 0) return true; // ±0 or subnormals
if ((c & sm) != (d & sm)) return false; // different signs
if (abs(ea - eb) > 1) return false; // b > 2*a or a > 2*b
d <<= 12; c <<= 12;
if (ea < eb) c = c >> 1 | sm;
else if (ea > eb) d = d >> 1 | sm;
c -= d;
return c < 65536 && c > -65536; // don't use abs(), because:
// There is a posibility c=0x8000000000000000 which cannot be converted to positive
}
public static boolean zero(double a) { return (Double.doubleToLongBits(a) >> 52 & 2047) < 3; }
请记住,在一些浮点运算之后,number可能与我们期望的非常不同。没有代码可以解决这个问题。
我使用这个代码。不像上面的答案,这允许一个人
给出一个在代码注释中解释的abs_relative_error。
第一个版本比较复数,使错误
可以用两个矢量之间的夹角来解释
在复平面上具有相同的长度(这给出了一点
洞察力)。然后是2实数的正确公式
数字。
https://github.com/CarloWood/ai-utils/blob/master/almost_equal.h
后者是
template<class T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
almost_equal(T x, T y, T const abs_relative_error)
{
return 2 * std::abs(x - y) <= abs_relative_error * std::abs(x + y);
}
其中abs_relative_error基本上(两倍)是文献中最接近定义的绝对值:相对错误。但这只是名字的选择。
我认为在复平面中最明显的是。如果|x| = 1, y在x周围形成一个直径为abs_relative_error的圆,则认为两者相等。
比较浮点数取决于上下文。因为即使改变操作的顺序也会产生不同的结果,所以知道你希望这些数字有多“相等”是很重要的。
在研究浮点数比较时,比较Bruce Dawson编写的浮点数是一个很好的开始。
以下定义来自Knuth的《The art of computer programming》:
bool approximatelyEqual(float a, float b, float epsilon)
{
return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool essentiallyEqual(float a, float b, float epsilon)
{
return fabs(a - b) <= ( (fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool definitelyGreaterThan(float a, float b, float epsilon)
{
return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool definitelyLessThan(float a, float b, float epsilon)
{
return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
当然,选择取决于上下文,并决定你想要的数字有多相等。
比较浮点数的另一种方法是查看数字的ULP(最后位置的单位)。虽然没有专门处理比较,但“每个计算机科学家都应该知道浮点数”这篇论文是了解浮点数如何工作以及陷阱是什么,包括什么是ULP的很好的资源。