用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的最小值? 或者有0
和0 + 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^0
, epsilon
为1.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标准是否会要求那些有符号的无穷小(如果存在的话)必须等于零。 如果他们不这样做,像上面这样的代码可以有效地确保例如将数字重复两次,最终将产生零,而不是被卡在“无穷小”上。
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; }
此外,具有这种function的一个好的理由是去除“非正常”(非常小的数字,不能再使用隐含的前导“1”并具有特殊的FP表示)。 你为什么想做这个? 因为有些机器(特别是一些较旧的Pentium 4)在处理非正常模式时真的很慢。 其他人只是有点慢。 如果您的应用程序不需要这些非常小的数字,将它们清零将是一个很好的解决scheme。 好的地方考虑这是任何IIR滤波器或衰减function的最后一步。
另请参阅: 为什么将0.1f更改为0会使性能下降10倍?