首页 > 解决方案 > 使用 exp 控制舍入是否存在错误?

问题描述

我在某些平台上观察到不正确的(IMO)舍入行为,如下所示:

计算log(2)下舍入模式到FE_DOWNWARD和的值FE_UPWARD(参见<fenv.h>)。在我见过的所有情况下,向下舍入的结果都小于向上舍入的结果,这是我所期望的(结果不准确)。

现在使用相同的舍入模式,调用exp每个结果。由于exp是单调递增的(感兴趣区域的斜率约为 2),我预计这些结果会相差更远。但在一些测试平台上,这两个结果exp是等价的。

这是一个简单的测试程序:

#include <stdio.h>
#include <math.h>
#include <fenv.h>

int main(void) {
  fesetround(FE_DOWNWARD);
  double xlo = log(2);
  double xxlo = exp(xlo);
  printf("xlo=%.16e xxlo=%.16e\n",xlo,xxlo);

  fesetround(FE_UPWARD);
  double xhi = log(2);
  double xxhi = exp(xhi);
  printf("xhi=%.16e xxhi=%.16e\n",xhi,xxhi); 

  printf("Delta x=%.16e\n",xhi-xlo);
  if (xxlo == xxhi) 
    printf("FAIL\n");
  else 
    printf("Delta xx=%.16e\n",xxhi-xxlo);

  return 0;
}

Xeon E5520 @ 2.27GHz 的一个(良好)结果:

xlo=6.9314718055994528e-01 xxlo=1.9999999999999997e+00
xhi=6.9314718055994540e-01 xxhi=2.0000000000000005e+00
Delta x=1.1102230246251566e-16
Delta xx=6.6613381477509393e-16

其他通过测试的处理器:AMD Ryzen 9 3950X、Intel i7-5557U、Intel i7-3770、Intel m7-6Y75。

但据报道有一对失败了(xxlo==xxhi):Intel E5-2650 和 AMD EPYC 7401,巧合的是,这两款机器都是服务器级机器。我无法直接访问这些,但在https://repl.it/languages/c上运行测试也失败了:

clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final)
 clang-7 -pthread -lm -o main main.c
 ./main
xlo=6.93147180559945286e-01 xxlo=2.00000000000000000e+00
xhi=6.93147180559945398e-01 xxhi=2.00000000000000000e+00
Delta x=1.11022302462515655e-16
FAIL

我对结果的实际精度不太感兴趣,但对相对值更感兴趣。所以我错过了什么,或者这只是错误的?如果它错了,是硬件舍入错误还是软件数学库问题,甚至是编译器/设置问题?我也有兴趣收集运行简单测试的任何结果,看看是否可以从这些数据中得出任何额外的结论。

标签: cfloating-pointroundingieee-754

解决方案


分布(向下舍入到向上舍入)被函数的导数放大。因此,如果您的原始界限是 1ULP,我预计 2.0 处的 exp(x) 的价差为 3。那是(原始价差)*(导数)+1。+1 是因为您正在进行另一个向下舍入和围捕。

如果导数是 2.5,我预计输出价差在 3 到 4 之间。

请提供数字的十六进制;这应该通过避免 bin<->dec 转换来确定(这在某些机器上似乎也存在缺陷)。

我所说的都不能使您的结论无效,即服务器的硬件不一致,因此 IEEE 实施存在缺陷。

哦,还有一件事;检查优化器是否工作太努力——通过在查看日志后消除 exp。或者也许使用 FMA,它有时会给你更好的舍入误差。


推荐阅读