如何计算两个浮点数的平均值?

fde*_*hin 2 language-agnostic floating-point ieee-754

在浮点运算中计算两个数字的平均值的最准确方法是什么?让我们考虑一下最常见的双精度 64 位数字。

  1. (a + b) / 2

  2. a / 2 + b / 2

  3. a + (b - a) / 2

这些计算平均值的方法可能会给出不同的结果,如下面的 C++ 代码所示:

double a = 1.2;
double b = 3.6;
double mean1 = (a + b) / 2.0;
double mean2 = a / 2.0 + b / 2.0;
double mean3 = a + (b - a) / 2.0;
cout << fixed << setprecision(20);
cout << "mean1: " << mean1 << endl;
cout << "mean2: " << mean2 << endl;
cout << "mean3: " << mean3 << endl;
Run Code Online (Sandbox Code Playgroud)

产生结果

mean1: 2.39999999999999991118
mean2: 2.39999999999999991118
mean3: 2.40000000000000035527
Run Code Online (Sandbox Code Playgroud)

问题两个浮点数计算平均值的数学性质?比较情况1和情况2。简单的结论是精度是相同的,但是情况1可以溢出,而情况2可以下溢。

mean1并且mean2似乎提供了更准确的结果,因为它们比mean3真实值 2.4 更接近。似乎总是这样,mean3不太精确。直观上这是有道理的,因为mean3的计算需要3次运算,因此丢失精度的机会更多,而mean1的计算只需要2次运算。


更新: 1.2 和 3.6 存储为

1.1999999999999999555910790149937383830547332763671875
3.600000000000000088817841970012523233890533447265625
Run Code Online (Sandbox Code Playgroud)

所以他们的真实意思是

2.40000000000000002220446049250313080847263336181640625
Run Code Online (Sandbox Code Playgroud)

然而这个值不能用双精度数表示。最接近的双精度值恰好是mean1,其中mean1mean3

2.399999999999999911182158029987476766109466552734375 (difference 1.11e-16)
2.4000000000000003552713678800500929355621337890625   (difference 3.33e-16)
Run Code Online (Sandbox Code Playgroud)

我偶然发现了 Fortran 代码中的第三种情况https://www.cs.umd.edu/~oleary/LBFGS/FORTRAN/linesearch.f

1.1999999999999999555910790149937383830547332763671875
3.600000000000000088817841970012523233890533447265625
Run Code Online (Sandbox Code Playgroud)
2.40000000000000002220446049250313080847263336181640625
Run Code Online (Sandbox Code Playgroud)

哪里p5是 0.5

我不确定这种计算平均值的动机是什么。可能是 n=2 运行平均值的情况,如浮点值的数值稳定运行平均值https://diego.assencio.com/?index=c34d06f4f4de2375658ed41f70177d59中所述,但它仅提高了大数的准确性的元素。

如果这是为了避免溢出,那么它似乎不起作用。所有三种情况都可能产生上溢或下溢。

2.399999999999999911182158029987476766109466552734375 (difference 1.11e-16)
2.4000000000000003552713678800500929355621337890625   (difference 3.33e-16)
Run Code Online (Sandbox Code Playgroud)
stpf = stpc + (stpq - stpc)/2
Run Code Online (Sandbox Code Playgroud)

此外,第三种情况的性能稍差一些(但是应谨慎考虑结果,因为它们可能会根据上下文而有很大差异,但通常指令数量较少的代码运行速度更快)

stp = stx + p5*(sty - stx)
Run Code Online (Sandbox Code Playgroud)

https://quick-bench.com/q/V_STrIZ5CmIL0Q3KpbLX6G9-G90

也许我所缺少的选择背后有一些逻辑。上面的代码可能是要在不支持 IEEE 754 的不同硬件上运行?那么,有什么理由计算平均值a + (b - a) / 2为而不是(a + b) / 2?或者只是有点不准确,应该使用简单的方法来计算平均值?

Mat*_*ans 5

为了准确性,应该首选(a + b) / 2or a / 2 + b / 2,因为除以 2在浮点中是无损的,除非它“下溢”。just/ 2会递减指数,因此,实际上,错误只会在加法中引入。

如果你愿意的话,你可以* 0.5代替/ 2.0——这没有什么区别。

然而,该表格a + (b - a) / 2保证:

  • 如果 和a都是b非负有限值,则结果也是非负有限值。该(a + b)/2表格不提供此保证;和
  • 如果 和a都是b非负有限值,并且b >= a,则结果始终为>= a<= b。其他两种形式都没有提供这种保证。例如,如果您正在进行二分搜索,这可能很重要。