std::uniform_real_distribution<T> 和 std::numeric_limits<T>::max() 对于 T == long double 在 Valgrind 下的问题

Problem with std::uniform_real_distribution<T> and std::numeric_limits<T>::max() for T == long double under Valgrind

我对以下代码有疑问:

#include <cassert>
#include <limits>
#include <random>

int main() {
  using T = long double;
  std::mt19937 engine{ std::random_device{}() };
  const auto max = std::numeric_limits<T>::max();
  const auto res = std::uniform_real_distribution<T>(0., max)(engine);
  assert(res < max);
}

此代码作为独立程序正确执行。不幸的是,在 Valgrind 下,断言是不满足的。该问题仅发生在 T == long double,即 doublefloat 情况下没有问题。

你能解释一下问题的本质吗?

(我在 GNU/Linux x86_64 上使用 GCC 9.3.0 和 Valgrind 3.15.0。我为 GCC 使用了 -O0 -g 标志, --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all --运行-cxx-freeres=yes对于 Valgrind。)

这里的问题是 Valgrind 不完全支持 long double。手册 reads:

Valgrind has the following limitations in its implementation of x86/AMD64 floating point relative to IEEE754.

There is no support for 80 bit arithmetic. Internally, Valgrind represents all such "long double" numbers in 64 bits, and so there may be some differences in results. ...

考虑这个简单的代码:

int main() {
  auto max = static_cast<long double>(std::numeric_limits<double>::max());
  std::cout << std::hexfloat << max << std::endl;
}

如果 运行 独立和在 Valgrind 下,输出是相同的:

0xf.ffffffffffff8p+1020

现在让我们尝试通过添加行

来打印下一个可表示的值
max = std::nextafter(max, std::numeric_limits<long double>::max());

我们在没有 Valgrind 的情况下得到了预期的输出,但在 Valgrind 下得到了不同的输出:

0xf.ffffffffffff801p+1020    // no Valgrind
0xf.ffffffffffff8p+1020      // under Valgrind

如果我们尝试打印 std::numeric_limits<long double>::max() 值本身,则会观察到类似的不一致:

0xf.fffffffffffffffp+16380   // no Valgrind
0x8p+16381                   // under Valgrind

难怪 std::uniform_real_distribution<long double> 以某种依赖于实现的方式失败,std::numeric_limits<long double>::max()