使用epsilon将double比较为零

问题描述 投票:207回答:11

今天,我正在查看一些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相当的比较?

c++ double
11个回答
189
投票

假设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
投票

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

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


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


17
投票

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

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

你无法从这个代码中单独判断是否有意义地使用epsilon作为绑定,你需要查看上下文。可能是epsilon是计算中产生someValue的误差的合理估计,并且它可能不是。


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之间的值并使它们为零。


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;
}

3
投票

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

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

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

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

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


3
投票

XX的下一个值之间的差异根据X而有所不同。 epsilon()只是11的下一个值之间的差异。 00的下一个值之间的差异不是epsilon()

相反,您可以使用std::nextafter将double值与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;
}

2
投票

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

someValue == 0.0

反正好问题!


2
投票

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


1
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.