我为使用epsilon的双精度定义了一个比较函数,如:What is the most effective way for float and double comparison?中所述。如果它们的绝对差值小于epsilon,则两个双倍不同。
如果我有d1和d2加倍,d2 = d1 + epsilon,d1不应该等于d2,因为它们相差ε。对?
它适用于某些值,但不是所有时间。它可能与架构或编译器有关?我该怎么做才能改善比较功能?
#include <iomanip> // For std::setprecision
#include <iostream>
#include "math.h" // For fabs
const double epsilon = 0.001;
bool equal(const double &d1, const double &d2)
{
return fabs(d1-d2) < epsilon;
}
int main()
{
double d1 = 3.5;
double d2 = d1+epsilon;
// d2 = d1 + epsilon should not be equal to d1.
std::cout << std::setprecision(16) << "d1 = " << d1 << " d2 = " << d2 << std::endl;
bool eq = equal(d1,d2);
if (eq)
{
std::cout << "They should not be equal, but they are!" << std::endl;
}
else
{
std::cout << "They are not equal. OK!" << std::endl;
}
}
我机器上的输出:
d1 = 3.5 d2 = 3.501
他们不应该是平等的,但他们是!
最常见的浮点警告之一:如果将数字放入浮点数,则并不意味着它将存储为完全相同的值。相反,它们会稍作修改,以创建一个最接近您预期值的二进制结构。
好的,回去做生意!尝试将此添加到您的源代码:
std::cout << fabs(d1-d2) << std::endl;
并找到输出如下:)
0.0009999999999998899
你看?它明显少于你的epsilon。
如果我有d1和d2加倍,d2 = d1 + epsilon,d1不应该等于d2,因为它们相差ε。对?
错误。你的逻辑被打破了。你已经告诉过你不应该在没有epsilon的情况下比较双打,但下一步你会惊讶于fabs(d1-d2) == epsilon
不是真的。
我该怎么做才能改善比较功能?
什么都没有,d1 + epsilon
是一个边境案例,你不能确定这个数字是否等于d1
。这是浮点运算的缺点。
这里实际上有两个不同的问题。首先,你的比较应该是> =,而不是>,所以它对你的测试用例“起作用”,其差异恰好是epsilon。
第二个(潜在的)问题是,在一般情况下,你不能真正使用常数epsilon来进行这些类型的比较。您需要根据所涉及数字的预期幅度以及计算中预期的累积数值误差来选择epsilon。
浮点数不太精确,它们存储的值的大小越大。如果您使用“足够大”的值重复测试,则每次都会失败。
让我们调用pe
精度错误。对于IEEE 754标准~2^−53
,它是无穷小的(binary64
)
数学是:[伪代码]
d1 = 3.5 + pe1
d2 = 3.501 + pe2
d2 - d1 = 0.001 + pe2 - pe1
epsilon = 0.001 + pe3
结果表达式:[伪代码]
|d1-d2| < epsilon //Since you know that d2>d1 you can replace |d1-d2| by d2-d1
0.001 + pe2 - pe1 < 0.001 + pe3
pe2 - pe1 < pe3
根据true
,false
和pe3
的大小和符号,这个边界情况可以是qazxsw poi或qazxsw poi。但它尚未确定。