【问题标题】:Overflow When Calculating Average?计算平均值时溢出?
【发布时间】:2021-09-04 02:49:59
【问题描述】:

给定 2 个整数,我们可以这样计算它们的平均值:

return (a+b)/2;

这是不安全的,因为 (a+b) 会导致溢出(旁注:有人可以告诉我这种情况下的正确术语可能是内存溢出吗?)

所以我们写:

return a+(b-a)/2;

可以在n 数字上实现相同的技巧吗?如何实现?

【问题讨论】:

  • 缓冲区溢出,这是正确的术语吗?
  • 整数溢出通常是术语
  • @dratenik 不过注意评论很重要:this algorithm will have severe underflow issues
  • @dratenik 对于整数,您会遇到与问题中的两个有值示例相同的溢出问题。

标签: c++ math overflow


【解决方案1】:

请注意,有几种不同的平均值。我假设你问的是算术平均值。

溢出(旁注:有人可以告诉我这种情况的正确术语可能是内存溢出吗?)

正确的说法是算术溢出,或者只是溢出。不是内存溢出。

a+(b-a)/2;

b-a 也可以溢出。这并不像看起来那么容易解决。

标准库有一个函数模板可以正确执行此操作而不会溢出:std::midpoint

我检查了std::midpoint 的实现,它们按照您对整数的建议执行,除了操作数首先转换为相应的无符号类型。然后将结果转换回来。数学家可能会解释它是如何工作的,但我想这与模运算的魔力有关。

对于浮点数,它们执行a / 2 + b / 2(如果输入正常)。

可以在 n 个数字上实现相同的技巧吗?如何实现?

适用于所有输入而不会溢出和不精确的最简单解决方案可能是使用任意精度算术。

【讨论】:

  • std::midpoint 不能处理超过 2 个参数,对吧?
  • @mattlangford midpoint 仅适用于 2 个参数
  • 所以a / 2 + b / 2不能溢出整数?
  • @john 它不会溢出,但是如果你正在寻找算术平均值,你会得到许多值的错误结果。例如考虑1 / 2 + 1 / 2 == 0。 0是数组[1, 1]的平均值吗?
  • 这是一个解决方案 res = a/2.0 + b/2.0 @eerorika
【解决方案2】:

获取多个数字的平均数的一种方法是找到累积移动平均线或 CMA:

您的代码 a + (b - a) / 2 也可以从 n + 1 == 2 的这个等式推导出来。


将上面的等式转换为代码,你会得到类似于:

std::vector<int> vec{10, 5, 8, 3, 2, 8}; // average is 6

double average = 0.0;

for(auto n = 0; n < vec.size(); ++n)
{
    average += (vec[n] - average) / (n + 1);
}

std::cout << average; // prints 6

或者,您也可以使用std::accumulate

std::cout << std::accumulate(vec.begin(), vec.end(), 0.0, 
                             [n = 0](auto cma, auto i) mutable {
                                 return cma + (i - cma) / ++n;
                             });

请注意,任何时候使用浮动除法都可能导致结果不精确,尤其是当您尝试多次这样做时。有关不精确的更多信息,您可以查看:Is floating point math broken?

【讨论】:

  • 我认为您应该解释 CMA 是如何初始化的,以及 CMA 的类型——如果它是不可分割的,上述内容并不总是有效。确实,可以编译的 sn-p 代码是最好的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-14
  • 2012-06-19
  • 1970-01-01
相关资源
最近更新 更多