今天,我在看一些c++代码(别人写的),发现了这一部分:

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

我在想这到底说得通不合理。

epsilon()的文档说:

该函数返回1与可[用双精度符号]表示的大于1的最小值之间的差值。

这是否也适用于0,即()的最小值大于0?或者有没有0到0 +之间的数可以用双精度数表示?

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


当前回答

X和X的下一个值之间的差值根据X而变化。 Epsilon()只是1和下一个1的值之间的差。 0和下一个0值之间的差不是()。

相反,你可以使用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;
}

其他回答

X和X的下一个值之间的差值根据X而变化。 Epsilon()只是1和下一个1的值之间的差。 0和下一个0值之间的差不是()。

相反,你可以使用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;
}

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

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

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

你不能仅仅从这段代码中判断用作为边界是否有意义,你需要看一下上下文。可能是对产生someValue的计算错误的合理估计,也可能不是。

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

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

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

在1附近,指数等于0。尾数中最小的数字是1024的1分之一。

接近1/2的指数是- 1,所以尾数最小的部分是一半大。如果是5位指数,它可以达到负16,此时尾数最小的部分值为3200万分之一。在- 16指数处,这个值大约是32k的1分之1,比我们上面计算的1附近更接近于0 !

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