使用epsilon比较双零


214

今天,我浏览了一些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


3
大约1的epsilon很有可能比大约0的epsilon高得多,因此可能会有介于0和0 + epsilon_at_1之间的值。我猜这部分的作者想使用小的东西,但是他不想使用魔术常数,因此他只是使用了这个本质上任意的值。
enobayram

2
比较浮点数很困难,甚至鼓励使用epsilon或阈值。请参阅:cs.princeton.edu/introcs/91floatcygnus-software.com/papers/comparingfloats/comparingfloats.htm
Aditya Kumar Pandey

40
第一链接是403.99999999
graham.reeds 2012年

6
IMO,在这种情况下,的使用具有numeric_limits<>::epsilon误导性且无关紧要。如果实际值与0相差不超过ε,我们要假设0。应该根据问题说明而不是与机器有关的值来选择ε。我怀疑当前的epsilon是无用的,因为即使只有几个FP操作也会累积比该错误大的错误。
Andrey Vihrov

1
+1。epsilon并非最小,但如果您知道您需要什么精度以及您正在做什么,则可以在大多数实际工程任务中达到指定的目的。
SChepurin

Answers:


192

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

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


27
所以很奇怪,您可以说“正数的47%” :)
configurator

13
@configurator:不,您不能这么说(不存在“自然的”有限度量)。但是,你可以说“正的47%表示的数字”。
Yakov Galka

1
@ybungalobill我不知道。指数有11位:1个符号位和10个值位。为什么最小的正数是2 ^ -1022而不是2 ^ -1024?
帕夫洛·迪班

3
@PavloDyban:仅仅因为指数没有符号位。它们被编码为偏移量:如果编码的指数为,0 <= e < 2048则尾数乘以2的幂e - 1023。例如,的指数2^0编码为e=10232^1as e=10242^-1022as e=1。的值e=0保留给次法线和实零。
Yakov Galka

2
@PavloDyban:也是2^-1022最小的普通数。最小的数目是0.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^-1022 = 2^-1074。这是次标准的,这意味着尾数部分小于1,因此使用指数进行编码e=0
Yakov Galka

17

测试当然与相同someValue == 0。浮点数的整体思想是,它们存储指数和有效位数。因此,它们代表具有一定数量的二进制有效数字的值(在IEEE double情况下为53)。可表示的值在0附近比在1附近更加密集。

要使用更熟悉的十进制系统,假设您存储带有指数的十进制值“至4个有效数字”。那么接下来的表示值大于11.001 * 10^0,和epsilon1.000 * 10^-3。但是1.000 * 10^-4也可以表示,假设指数可以存储-4。您可以相信,IEEE double 可以存储小于的指数epsilon

您不能仅通过此代码来判断是否epsilon专门将其用作边界是否有意义,您需要查看上下文。可能是对epsilon产生的计算中的错误的合理估计someValue,也可能不是。


2
很好,但即使是这种情况,更好的做法是将错误限制在合理命名的变量中,并在比较中使用它。就目前而言,它与魔术常数没有什么不同。
enobayram

也许我在这个问题上应该更清楚一些:我不怀疑epsilon是否足够大以覆盖计算误差,但是这种比较是否相等someValue == 0.0
塞巴斯蒂安·克里斯曼斯基2012年

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,零和一是不同的。

因此,此函数采用+/-ε之间的值并使它们为零。


5

可以用以下程序打印数字(1.0、0.0,...)附近的ε近似值(尽可能小的差异)。它输出以下输出:
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;
}

2
您检查了哪些实现?对于GCC 4.7绝对不是这种情况。
安东·戈洛夫

3

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

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

约1的指数等于零。因此,尾数的最小位数为1024的一位。

接近1/2的指数为负一,因此尾数的最小部分为一半。用五位指数可以达到负16,这时尾数的最小部分相当于32m的一部分。在16指数为负时,该值约为32k的一分之一,比我们上面计算的约一的epsilon更接近零!

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


3

的差值X与下一个值之差X根据决定X
epsilon()只是和之间1的差值1
与之间0的差值0不是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

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

someValue == 0.0

反正是个好问题!


2

由于尾数和指数部分,您不能将其应用于0。由于指数的原因,您只能存储很少的数字,该数字小于epsilon,但是当您尝试执行类似(1.0-“非常小的数字”)的操作时,您将得到1.0。Epsilon并非指标,而是指标精度(以尾数表示)。它显示了我们可以存储多少个正确的数字位数。


2

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

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


“ 1 /(1-1)”(从您的示例中得出)不是无穷大而不是零吗?
塞巴斯蒂安·克莱斯曼斯基

数量(1-1),(1 / INF)和(-1 / INF)都表示零,但理论上除以它们的数量应在理论上产生三个不同的结果(IEEE数学将前两个视为相同)。
supercat 2012年

1

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


除了零外,我认为-1e-20和+ 1e-20之间没有值。但是仅仅因为我认为这没有实现。
塞巴斯蒂安·克莱斯曼斯基

@SebastianKrysmanski:并非如此,0和之间有很多浮点值epsilon。因为它是浮点,而不是固定点。
Steve Jessop

不同于零的最小可表示值受分配用于表示指数的位数限制。因此,如果double具有11位指数,则最小数字为1e-1023。
cababunga 2012年

0

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

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

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


1
这不仅去除了非正规化的数字,还去除了更多的数字。它将普朗克常数或电子质量更改为零,如果使用这些数字,将给您非常非常错误的结果。
gnasher729
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.