[英]Why comparing a small floating-point number with zero yields random result?
[英]Comparing floating point number to zero
C++ FAQ 精简版“[29.17] 为什么我的浮点比较不起作用?” 推荐这个平等测试:
#include <cmath> /* for std::abs(double) */
inline bool isEqual(double x, double y)
{
const double epsilon = /* some small number such as 1e-5 */;
return std::abs(x - y) <= epsilon * std::abs(x);
// see Knuth section 4.2.2 pages 217-218
}
+0
和-0
?|x| < epsilon
类的测试时,是否也应该使用此功能? |x| < epsilon
?更新
正如 Daniel Daranas 所指出的那样,该函数最好称为isNearlyEqual
(这是我关心的情况)。
有人指出“比较浮点数” ,我想更突出地分享。
你的观察是正确的。
如果x == 0.0
,则abs(x) * epsilon
为零,并且您正在测试abs(y) <= 0.0
。
如果y == 0.0
则您正在测试abs(x) <= abs(x) * epsilon
这意味着epsilon >= 1
(不是)或x == 0.0
。
所以is_equal(val, 0.0)
或is_equal(0.0, val)
都是没有意义的,你可以说val == 0.0
。 如果您只想完全接受+0.0
和-0.0
。
在这种情况下,FAQ 的建议作用有限。 没有“一刀切”的浮点比较。 您必须考虑变量的语义、可接受的值范围以及计算引入的误差大小。 甚至 FAQ 也提到了一个警告,说这个函数通常不是问题,“当 x 和 y 的大小明显大于 epsilon 时,但你的里程可能会有所不同”。
不。
平等就是平等。
正如其名称所承诺的那样,您编写的函数不会测试两个双精度数是否相等。 它只会测试两个双打是否彼此“足够接近”。
如果你真的想测试两个双打是否相等,请使用这个:
inline bool isEqual(double x, double y)
{
return x == y;
}
编码标准通常建议不要比较两个双精度值以确保完全相等。 但这是一个不同的主题。 如果你真的想比较两个双精度值是否完全相等, x == y
就是你想要的代码。
10.000000000000001 不等于 10.0,不管他们告诉你什么。
使用精确相等的一个例子是当 double 的特定值用作某些特殊状态的同义词时,例如“等待计算”或“无可用数据”。 仅当挂起计算之后的实际数值只是双精度值的可能值的子集时,这才是可能的。 最典型的特殊情况是该值是非负的,并且您使用 -1.0 作为“未决计算”或“无可用数据”的(精确)表示。 你可以用一个常量来表示:
const double NO_DATA = -1.0;
double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);
if (myData != NO_DATA)
{
...
}
如果您只对+0.0
和-0.0
感兴趣,则可以使用<cmath>
fpclassify
。 例如:
if( FP_ZERO == fpclassify(x) ) do_something;
您可以将std::nextafter
与一个固定factor
的epsilon
,如下所示:
bool isNearlyEqual(double a, double b)
{
int factor = /* a fixed factor of epsilon */;
double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;
return min_a <= b && max_a >= b;
}
2 + 2 = 5(*)
(对于某些 2 的浮点精度值)
当我们将“浮点”作为提高精度的一种方式时,经常会出现这个问题。 然后我们与“浮动”部分发生冲突,这意味着无法保证可以表示哪些数字。
因此,虽然我们可以很容易地表示“1.0、-1.0、0.1、-0.1”,但随着我们获得更大的数字,我们开始看到近似值——或者我们应该看到,除非我们经常通过截断数字来隐藏它们以进行显示。
因此,我们可能认为计算机正在存储“0.003”,但实际上它可能存储“0.0033333333334”。
如果执行“0.0003 - 0.0002”会发生什么? 我们期望 .0001,但实际存储的值可能更像是 "0.00033" - "0.00029",产生 "0.000004",或者最接近的可表示值,可能是 0,也可能是 "0.000006"。
对于当前的浮点数学运算,不能保证 (a / b) * b == a 。
#include <stdio.h>
// defeat inline optimizations of 'a / b * b' to 'a'
extern double bodge(int base, int divisor) {
return static_cast<double>(base) / static_cast<double>(divisor);
}
int main() {
int errors = 0;
for (int b = 1; b < 100; ++b) {
for (int d = 1; d < 100; ++d) {
// b / d * d ... should == b
double res = bodge(b, d) * static_cast<double>(d);
// but it doesn't always
if (res != static_cast<double>(b))
++errors;
}
}
printf("errors: %d\n", errors);
}
ideone 报告了 599 个实例,其中 (b * d) / d != b 仅使用 1 <= b <= 100 和 1 <= d <= 100 的 10,000 个组合。
常见问题解答中描述的解决方案本质上是应用粒度约束 - 测试if (a == b +/- epsilon)
。
另一种方法是通过使用定点精度或使用所需的粒度作为存储的基本单位来完全避免该问题。 例如,如果您希望以纳秒精度存储时间,请使用纳秒作为您的存储单位。
C++11 引入了std::ratio作为不同时间单位之间定点转换的基础。
就像@Exceptyon 指出的那样,此函数与您要比较的值“相关”。 Epsilon * abs(x)
度量将根据 x 的值进行缩放,因此无论 x 或 y 中的值范围如何,您都将获得与epsilon
一样准确的比较结果。
如果您将零 ( y
) 与另一个非常小的值 ( x
) 进行比较,例如 1e-8, abs(xy) = 1e-8
仍将远大于epsilon *abs(x) = 1e-13
。 因此,除非您正在处理无法用 double 类型表示的极小数字,否则此函数应该可以完成这项工作,并且只会将零与+0
和-0
匹配。
该函数对于零比较似乎完全有效。 如果您打算使用它,我建议您在涉及浮点数的任何地方使用它,并且对于零之类的东西没有特殊情况,只是为了代码的一致性。
ps:这是一个整洁的功能。 感谢您指出它。
FP 数字的简单比较有其特定性,关键是对 FP 格式的理解(参见https://en.wikipedia.org/wiki/IEEE_floating_point )
当 FP 数以不同的方式计算时,一个通过 sin(),另一个通过 exp(),严格相等将不起作用,即使数学上的数字可能相等。 同样的方式不会与常量相等。 实际上,在许多情况下,不能使用严格相等 (==) 来比较 FP 数
在这种情况下应该使用 DBL_EPSPON 常量,它是最小值不要改变表示 1.0 被添加到超过 1.0 的数字。 对于超过 2.0 DBL_EPSPON 根本不存在的浮点数。 同时,DBL_EPSILON 的指数为 -16,这意味着所有数字(假设指数为 -34)与 DBL_EPSILON 相比绝对相等。
另外,请参见示例,为什么 10.0 == 10.0000000000000001
比较两个浮点数取决于这些数字的性质,我们应该为它们计算 DBL_EPSILON 对比较有意义。 简单地说,我们应该将 DBL_EPSILON 乘以这些数字之一。 他们中的哪一个? 当然最大
bool close_enough(double a, double b){
if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b)))
{
return true;
}
return false;
}
所有其他方式都会给你带来不平等的错误,这可能很难捕捉
请注意,该代码是:
std::abs((x - y)/x) <= epsilon
您要求 var 上的“相对误差”是 <= epsilon,而不是绝对差异是
考虑这个例子:
bool isEqual = (23.42f == 23.42);
什么是isEqual
? 10 人中有 9 人会说“这是true
,当然”,10 人中有 9 人是错的: https : //rextester.com/RVL15906
那是因为浮点数不是精确的数字表示。
作为二进制数,它们甚至不能精确地表示所有可以精确表示为十进制数的数字。 例如,虽然0.1
可以精确地表示为十进制数(它恰好是1
的十分之一),但不能使用浮点数表示,因为它是0.00011001100110011...
作为二进制周期。 0.1
是浮点数, 1/3
是十进制数(即0.33333...
作为十进制数)
结果是像0.3 + 0.6
这样的计算可能会导致0.89999999999999991
,这不是0.9
,尽管它接近于此。 因此测试0.1 + 0.2 - 0.3 == 0.0
可能会失败,因为计算结果可能不是0
,尽管它会非常接近0
。
==
是一个精确的测试,对不精确的数字进行精确的测试通常意义不大。 由于许多浮点计算包括舍入误差,您通常希望比较也允许小错误,这就是您发布的测试代码的全部内容。 它不是测试“ A 是否等于 B ”,而是测试“ A 是否非常接近 B ”,因为非常接近通常是您可以从浮点计算中获得的最佳结果。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.