添加两个对数时防止下溢

时间:2016-05-23 12:04:07

标签: c++ floating-point precision numerical-methods

我正在使用维基百科log probability文章中描述的对数空间方程中的加法,但在计算非常大的负数对数的exp时,我得到了下溢。结果,我的程序崩溃了。

示例输入为a = -2b = -1033.4391885529124

我的代码直接来自维基百科文章,如下所示:

double log_sum(double a, double b)
{
  double min_ab = std::min(a, b);
  a = std::max(a, b);
  b = min_ab;
  if (isinf(a) && isinf(b)) {
    return -std::numeric_limits<double>::infinity();
  } else if (isinf(a)) {
    return b;
  } else if (isinf(b)) {
    return a;
  } else {
    return a + log2(1 + exp2(b - a));
  }
}

我想出了以下想法,但无法确定哪个是最好的:

  • 评估前检查超出范围的输入。
  • 禁用(某种程度上)异常,并在评估后刷新或钳制输出
  • 实现不会抛出异常并自动刷新或限制结果的自定义日志和exp函数。
  • 其他一些方法?

另外,我有兴趣知道对数基数的选择对计算有什么影响。我选择了第二个基数,因为我相信其他对数基数将从log_n(x) = log_2(x) / log_2(n)计算出来,并且会因分割而遭受精确损失。这是对的吗?

2 个答案:

答案 0 :(得分:1)

根据http://en.cppreference.com/w/cpp/numeric/math/exp

  

对于IEEE兼容类型double,如果709.8&lt;如果arg&lt; arg,则保证下溢。 -708.4

因此,您无法阻止下溢。但是:

  

如果由于下溢而发生范围错误,则返回正确的结果(舍入后)。

所以不应该有任何程序崩溃 - &#34;只是&#34;失去精确度。

但请注意

1.0

将更快地松开精度,即已经在n = -53。这是因为1.0 + 2^-52之后的下一个可表示的数字是exp

1.0 + exp(...)引起的精度损失远小于添加{{1}}时的精度损失

答案 1 :(得分:0)

这里的问题是准确计算表达式log(1+exp(x))而没有中间的/溢出。幸运的是,Martin Maechler(R核心开发人员之一)详细说明了如何在section 3 of this vignette中完成。

他使用自然基函数:应该可以通过适当缩放函数将其转换为base-2,但它在一个部分中使用log1p函数,而我不知道任何函数提供base-2变体的数学库。

基数的选择不太可能对准确性(或性能)产生任何影响,并且大多数合理的数学库能够为两个函数提供子1-ulp保证(即,您将使两个浮点值中的一个最接近确切的答案)。一种非常常见的方法是将浮点数分解为其基数2指数k和有效数1+f,例如1/sqrt(2) < 1+f < sqrt(2),然后使用多项式近似来计算{{1}由于一些数学怪癖(基本上,泰勒级数的第二项可以精确表示的事实),事实证明在自然基础而不是基数2中更准确,所以典型的实现看起来像是:

log(1+f)

(例如,在openlibm中查看loglog2),因此使用其中一个没有任何实际好处。