用epsilon比较double和zero

今天,我正在浏览一些C ++代码(由其他人编写),并发现这一部分:

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

我试图弄清楚这是否有道理。

epsilon()的文档说:

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

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

如果不是,那么是不是相当于someValue == 0.0的比较?

假设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和ε之间,约占所有可表示数字的47%。

testing肯定是不一样的someValue == 0 。 浮点数的整个概念是它们存储一个指数和一个有效数。 因此它们代表了一定数量的二进制有效数字的精度(在IEEE的情况下为53)。 可接受的值比0更接近于1。

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

你不能从这个代码单独判断是否有意义地使用epsilon作为边界,你需要看上下文。 这可能是epsilon是产生某个值的计算中错误的合理估计,也可能不是。

有0和epsilon之间存在的数字,因为ε是1和高于1的下一个最高数字之间的差异,而不是0和高于0的下一个最高数字之间的差异(如果是,那代码会做的很less): –

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

使用一个debugging器,停止在主结束程序,看看结果,你会看到epsilon / 2是不同于epsilon,零和一个。

所以这个函数的值在+/-ε之间,并使其为零。

可以用下面的程序打印围绕数字(1.0,0.0,…)的epsilon(最小可能的差异)的aproximation。 它打印下面的输出:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
一个小小的想法表明,小数越小,我们用它来观察其ε值就越小,因为指数可以调整到这个数的大小。

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

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

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

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

指数的1/2附近是负1,所以尾数的最小部分是一半大。 用五位指数可以达到负16,在这一点上尾数的最小部分相当于32m中的一部分。 而在负16指数处,这个值在32k左右,比我们上面计算的ε值更接近零。

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

我认为这取决于你的电脑的精度 。 看看这个表格 :你可以看到,如果你的epsilon由double表示,但是你的精度更高,那么这个比较就不等于

 someValue == 0.0 

好的问题无论如何!

由于尾数和指数部分,您不能将其应用于0。 由于指数,你可以存储很less的数字,这比epsilon小,但是当你试图做一些像(1.0 – “非常小的数字”),你会得到1.0。 Epsilon不是价值的指标,而是价值精确度的指标,这是尾数。 它显示了我们可以存储多less个正确的随机数。

所以我们假设系统不能区分1.000000000000000000000和1.000000000000000000001。 即1.0和1.0 + 1e-20。 你认为在-1e-20和+ 1e-20之间还有一些值吗?

对于IEEE浮点,在最小的非零正值和最小的非零负值之间,存在两个值:正零和负零。 testing一个值是否在最小的非零值之间相当于testing与零相等; 但是,这项任务可能会产生影响,因为它会将负零变成正零。

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

XX的下一个值之间的差异根据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; } 

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

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

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