221

今天,我浏览了一些 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?或者0和之间是否有0 + epsilon可以用 a 表示的数字double

如果不是,那么比较不等于someValue == 0.0

4

11 回答 11

203

假设 64 位 IEEE double,则有 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

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

于 2012-12-04T09:17:49.973 回答
17

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

要使用更熟悉的十进制系统,假设您将十进制值存储为“4 位有效数字”和指数。然后下一个可表示的值大于1is 1.001 * 10^0,并且epsilonis 1.000 * 10^-3。但1.000 * 10^-4也是可表示的,假设指数可以存储 -4。你可以相信我的话,IEEE double可以存储的指数小于epsilon.

您无法仅从这段代码中判断将其epsilon专门用作绑定是否有意义,您需要查看上下文。这可能是epsilon对产生的计算中的错误的合理估计,someValue也可能不是。

于 2012-12-04T08:52:16.453 回答
13

有些数字存在于 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 之间的值并将它们设为零。

于 2012-12-04T09:11:52.723 回答
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;
}
于 2012-12-04T09:37:14.160 回答
4

X与 的下一个值之间的差值X根据 变化X
epsilon()只是1和 的下一个值之间的差1。和 的下一个值
之间的差不是。00epsilon()

相反,您可以使用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;
}
于 2016-02-06T18:29:34.800 回答
3

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

这个浮点数的值是尾数,解释为二进制十进制值,乘以 2 的指数次方。

在 1 附近,指数等于 0。所以尾数的最小位数是 1024 的一部分。

接近 1/2 的指数是负一,所以尾数的最小部分是原来的一半。使用 5 位指数可以达到负 16,此时尾数的最小部分值 32m 中的一部分。在负 16 指数处,该值大约是 32k 的一部分,比我们上面计算的大约 1 的 epsilon 更接近于零!

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

于 2012-12-04T09:21:14.407 回答
2

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

someValue == 0.0

总之是个好问题!

于 2012-12-04T08:50:32.263 回答
2

由于尾数和指数部分,您不能将此应用于 0。由于指数,您可以存储比 epsilon 小的数字,但是当您尝试执行类似 (1.0 - "very small number") 之类的操作时,您将获得 1.0。Epsilon 不是值的指标,而是值精度的指标,以尾数表示。它显示了我们可以存储多少个正确的后继十进制数字。

于 2012-12-04T08:57:11.990 回答
2

对于 IEEE 浮点,在最小的非零正值和最小的非零负值之间,存在两个值:正零和负零。测试一个值是否在最小的非零值之间,相当于测试是否与零相等;然而,赋值可能会产生影响,因为它会将负零变为正零。

可以想象,浮点格式可能具有介于最小有限正负值之间的三个值:正无穷小、无符号零和负无穷小。我不熟悉实际上以这种方式工作的任何浮点格式,但这样的行为将是完全合理的,并且可以说比 IEEE 的更好(也许不够好到值得添加额外的硬件来支持它,但在数学上 1 /(1/INF)、1/(-1/INF) 和 1/(1-1) 应该代表三种不同的情况,说明三个不同的零)。我不知道是否有任何 C 标准会要求有符号的无穷小,如果它们存在,则必须比较等于零。如果他们不这样做,上面的代码可以有效地确保例如

于 2012-12-04T16:05:49.223 回答
1

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

于 2012-12-04T08:50:36.327 回答
0

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

另请参阅:为什么将 0.1f 更改为 0 会使性能降低 10 倍?

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

于 2012-12-22T15:49:09.980 回答