使用epsilon将double比较为零

时间:2012-12-04 08:41:30

标签: c++ double

今天,我正在查看一些C ++代码(由其他人编写)并找到了这一部分:

double someValue = ...
if (someValue <  std::numeric_limits<double>::epsilon() && 
    someValue > -std::numeric_limits<double>::epsilon()) {
  someValue = 0.0;
}

我试图弄清楚这是否有意义。

epsilon()的文档说:

  

该函数返回1和大于1的最小值之间的差值[可以用双倍]表示。

这是否也适用于0,即epsilon()是大于0的最小值?或00 + epsilon之间的数字是否可由double表示?

如果没有,那么这个比较是否等同于someValue == 0.0

11 个答案:

答案 0 :(得分:188)

假设64位IEEE双,则有52位尾数和11位指数。让我们把它分解为位:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^0 = 1

大于1的最小可表示数字:

1.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^0 = 1 + 2^-52

因此:

epsilon = (1 + 2^-52) - 1 = 2^-52

0和epsilon之间有数字吗?很多...例如最小正可表示(正常)数字是:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^-1022 = 2^-1022

事实上,在0和epsilon之间有(1022 - 52 + 1)×2^52 = 4372995238176751616个数字,占所有可表示数字的47%......

答案 1 :(得分:17)

测试肯定与someValue == 0不同。浮点数的整个概念是它们存储指数和有效数。因此,它们表示具有一定数量的二进制有效精度数值的值(在IEEE双精度的情况下为53)。可表示的值在0附近比在1附近密集得多。

要使用更熟悉的十进制系统,假设您使用指数存储小数值“到4个有效数字”。然后,大于1的下一个可表示值为1.001 * 10^0epsilon1.000 * 10^-3。但是1.000 * 10^-4也是可表示的,假设指数可以存储-4。您可以接受我的说法,IEEE双可以存储小于epsilon指数的指数。

您无法单独从此代码中了解是否有意义地使用epsilon作为边界,您需要查看上下文。可能epsilon是对产生someValue的计算中的错误的合理估计,并且可能是它不是。

答案 2 :(得分:12)

0和epsilon之间存在数字,因为epsilon是1和可以在1之上表示的下一个最高数字之间的差异,而不是0和可以在0之上表示的下一个最高数字之间的差异(如果它那些代码会做的很少): -

#include <limits>

int main ()
{
  struct Doubles
  {
      double one;
      double epsilon;
      double half_epsilon;
  } values;

  values.one = 1.0;
  values.epsilon = std::numeric_limits<double>::epsilon();
  values.half_epsilon = values.epsilon / 2.0;
}

使用调试器,在main结束时停止程序并查看结果,您将看到epsilon / 2与epsilon,0和1不同。

所以这个函数取值+/- epsilon并使它们为零。

答案 3 :(得分:5)

可以使用以下程序打印数字(1.0,0.0,...)周围的epsilon(最小可能差异)的近似值。它打印以下输出:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
一点思考清楚地表明,epsilon越小,我们用来查看其epsilon值的数字越小,因为指数可以调整到该数字的大小。

#include <stdio.h>
#include <assert.h>
double getEps (double m) {
  double approx=1.0;
  double lastApprox=0.0;
  while (m+approx!=m) {
    lastApprox=approx;
    approx/=2.0;
  }
  assert (lastApprox!=0);
  return lastApprox;
}
int main () {
  printf ("epsilon for 0.0 is %e\n", getEps (0.0));
  printf ("epsilon for 1.0 is %e\n", getEps (1.0));
  return 0;
}

答案 4 :(得分:3)

假设我们正在使用适合16位寄存器的玩具浮点数。有一个符号位,一个5位指数和一个10位尾数。

此浮点数的值是尾数,被解释为二进制十进制值,是指数幂的两倍。

大约1指数等于零。所以尾数的最小数字是1024的一部分。

指数的1/2附近是负1,因此尾数的最小部分是一半。使用五位指数,它可以达到负16,此时尾数的最小部分值得在32米中的一个部分。在负16指数时,该值约为32k的一部分,比我们上面计算的epsilon更接近于零!

现在这是一个玩具浮点模型,它不能反映真实浮点系统的所有怪癖,但反映小于epsilon的值的能力与实际浮点值非常相似。

答案 5 :(得分:3)

XX的下一个值之间的差异因X而异。
epsilon()只是11的下一个值之间的差异 00的下一个值之间的差异不是epsilon()

相反,您可以使用std::nextafter将双倍值与0进行比较,如下所示:

bool same(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

double someValue = ...
if (same (someValue, 0.0)) {
  someValue = 0.0;
}

答案 6 :(得分:2)

我认为这取决于您计算机的precision。 看看这个table:你可以看到,如果你的epsilon用double表示,但你的精度更高,那么比较不等于

someValue == 0.0

反正好问题!

答案 7 :(得分:2)

由于尾​​数和指数部分,您不能将此值应用于0。 由于指数,你可以存储非常少的数字,小于epsilon, 但是当你尝试做类似的事情(1.0 - “非常小的数字”)时你会得到1.0。 Epsilon是一个不是有价值的指标,而是价值精度的指标,它是尾数。 它显示了我们可以存储多少个正确的数字后续十进制数字。

答案 8 :(得分:1)

因此,假设系统无法区分1.000000000000000000000和1.000000000000000000001。这是1.0和1.0 + 1e-20。你认为还有一些值可以在-1e-20和+ 1e-20之间表示吗?

答案 9 :(得分:1)

对于IEEE浮点,在最小的非零正值和最小的非零负值之间,存在两个值:正零和负零。测试值是否在最小的非零值之间等同于测试等于零的值;但是,分配可能会产生影响,因为它会将负零变为正零。

可以想象浮点格式在最小有限正值和负值之间可能有三个值:正无穷小,无符号零和负无穷小。我不熟悉实际上以这种方式工作的任何浮点格式,但这种行为完全合理且可以说比IEEE更好(可能不足以值得添加额外的硬件来支持它,但在数学上1 /(1 / INF),1 /( - 1 / INF)和1 /(1-1)应代表三个不同的情况,说明三个不同的零)。我不知道任何C标准是否会强制签署的无穷小,如果它们存在,则必须比较等于零。如果他们不这样做,那么像上面这样的代码可以有效地确保例如将一个数字重复除以2最终会产生零而不是被卡在“无穷小”上。

答案 10 :(得分:0)

此外,具有此类功能的良好原因是删除“非正规”(那些非常小的数字,不能再使用隐含的前导“1”并具有特殊的FP表示)。你为什么想做这个?因为有些机器(特别是一些较旧的Pentium 4)在处理非正规数时会变得非常非常慢。其他人只是变慢了一些。如果您的应用程序确实不需要这些非常小的数字,则将它们刷新为零是一个很好的解决方案。考虑这一点的好地方是任何IIR滤波器或衰减功能的最后步骤。

另请参阅:Why does changing 0.1f to 0 slow down performance by 10x?

http://en.wikipedia.org/wiki/Denormal_number