比较浮点数到零

C ++ FAQ lite “[29.17]为什么我的浮点比较不起作用? build议这个平等testing:

#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 } 
  1. 这是否正确,这意味着等于零的唯一数字是+0-0
  2. 是否应该在testing零时使用这个函数,或者像是|x| < epsilon这样的testing |x| < epsilon

更新

正如Daniel Daranas所指出的那样,函数应该更好地称为isNearlyEqual (这是我所关心的)。

有人指出了这个环节 ,我想分享一下。

你的观察是正确的。

如果x == 0.0 ,那么abs(x) * epsilon是零,您正在testingabs(y) <= 0.0

如果y == 0.0那么你正在testingabs(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

在这种情况下,常见问题的build议是有用的。 没有“一刀切”的浮点比较。 您必须考虑variables的语义,值的可接受范围以及计算引入的错误的大小。 甚至FAQ也提到了一个警告,称这个function通常不是一个问题,“当x和y的幅度明显大于epsilon,但是你的里程可能会有所不同”。

没有。

平等是平等的。

这个函数不会testing两个平等的双打,因为它的名字承诺。 它只会testing两个双打“足够接近”对方。

如果你真的想testing两个平等的双打,使用这一个:

 inline bool isEqual(double x, double y) { return x == y; } 

编码标准通常build议不要将两个双打比较。 但这是一个不同的主题。 如果你真的想比较两个双精确的相等, x == y是你想要的代码。

无论他们告诉你什么,10.00000000000000001都不等于10.0。

使用精确相等的一个例子是当double的特定值被用作某个特定状态的同义词时,例如“等待调用”或“无数据可用”。 只有在等待计算之后的实际数值只是双精度值可能值的一个子集时,才有可能。 最典型的特殊情况是,当这个值非负时,你使用-1.0作为“等待计算”或“无数据可用”的(精确)表示。 你可以用一个常数来表示:

 const double NO_DATA = -1.0; double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters); if (myData != -1.0) { ... } 

我不是捍卫这个完美的devise,但它是一个工程。 关键在于getSomeDataWhichIsAlwaysNonNegative将返回相同的常量NO_DATA ,这正好是-1.0,所以你的两个-1.0将是相同的。

你可以使用std::nextafter作为像下面这样一个值的epsilon的固定factor

 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”。

对于当前的浮点math运算, 不能保证(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个组合。

常见问题解答中描述的解决scheme基本上是应用粒度约束 – 来testingif (a == b +/- epsilon)

另一种方法是完全避免使用定点精度或使用所需粒度作为存储的基本单位。 例如,如果你想以纳秒级的精度存储时间,那么使用纳秒作为你的存储单元。

C ++ 11引入了std :: ratio作为不同时间单位之间定点转换的基础。

像@Exceptyon指出的那样,这个函数与你正在比较的值是“相对的”。 Epsilon * abs(x)度量将基于x的值进行缩放,以便您可以获得与epsilon一样精确的比较结果,而不考虑x或y中值的范围。

如果您将零( y )与另一个非常小的值( x )进行比较,比如说1e-8,那么abs(xy) = 1e-8仍然会比epsilon *abs(x) = 1e-13大得多。 所以,除非你处理的数字非常小,不能用doubletypes来表示,否则这个函数应该完成这个工作,并且只能匹配+0-0

该function似乎完全有效的零比较。 如果你打算使用它,我build议你在任何涉及浮点数的地方使用它,而不是像零点这样的特殊情况,只是为了使代码具有统一性。

ps:这是一个整洁的function。 感谢您指出它。

如果您只对+0.0-0.0感兴趣,则可以使用<cmath> fpclassify 。 例如:

if( FP_ZERO == fpclassify(x) ) do_something;

FP编号的简单比较有其自身的特定性,关键是FP格式的理解(请参阅https://en.wikipedia.org/wiki/IEEE_floating_point

当FP编号以不同的方式计算时,一个通过sin(),另一个通过exp(),严格的相等不会起作用,尽pipemath上的数字可能是相等的。 同样的方式将不会与常数保持一致。 实际上,在许多情况下,FP数字不能用严格的等式(==)来比较

在这种情况下应该使用DBL_EPSIPON常量,这是最小值不要改变表示1.0被添加到1.0以上的数字。 对于超过2.0个DBL_EPSIPON的浮点数根本不存在。 同时,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上的“相对误差”是<=ε,而不是绝对差值