最快的算法,用于识别制作双精度方程x + a == b true的最小和最大x

时间:2014-06-14 18:12:14

标签: c floating-point ieee-754

在静态分析的上下文中,我有兴趣在以下条件的then分支中确定x的值:

double x;
x = …;
if (x + a == b)
{
  …

ab可以假设为双精度常量(推广到任意表达式是问题中最容易的部分),并且可以假设编译器严格遵循IEEE 754({ {1}}是0)。运行时的舍入模式可以假设为最接近均匀。

如果使用有理数计算是便宜的,那就很简单:FLT_EVAL_METHOD的值将是理性区间中包含的双精度数(b - a - 0.5 * ulp1(b)... b - a + 0.5 * ulp2(b))。如果x是偶数,则应该包括边界,如果b是奇数,则排除边界,而ulp1和ulp2是两个稍微不同的“ULP”定义,如果不介意丢失一点,可以采用相同的定义对两个权力的精确度。

不幸的是,使用有理数进行计算可能会很昂贵。考虑另一种可能性是通过二分法获得每个边界,在64个双精度加法中(每个操作决定结果的一位)。获得下限和上限的128个浮点加法可能比任何基于数学的解都快。

我想知道是否有办法改进“128浮点添加”的想法。实际上我有自己的解决方案,涉及舍入模式和b调用的更改,但我不想让任何人的风格痉挛,导致他们错过比我现有的更优雅的解决方案。另外,我不确定两次更改舍入模式实际上比64次浮点加法更便宜。

1 个答案:

答案 0 :(得分:5)

您已经在问题中提供了一个漂亮而优雅的解决方案:

  

如果有理性的计算是便宜的,那就很简单:价值观   for x将是理性中包含的双精度数   区间(b - a - 0.5 * ulp1(b)... b - a + 0.5 * ulp2(b))。界限   如果b是偶数,则应该包括,如果b是奇数,则排除,并且ulp1和   ulp2是可以采用的两种略有不同的“ULP”定义   相同的,如果一个人不介意失去一点精度的权力   2。

以下是根据本段对问题进行部分解决的半合理草图。希望我很快就有机会充实它。要获得真正的解决方案,您必须处理次正规,零,NaN以及所有其他有趣的东西。我假设ab1e-300 < |a| < 1e3001e-300 < |b| < 1e300,以便在任何时候都不会发生疯狂。

如果没有溢出和下溢,您可以从ulp1(b)获取b - nextafter(b, -1.0/0.0)。您可以从ulp2(b)获得nextafter(b, 1.0/0.0) - b

如果b/2 <= a <= 2b,那么Sterbenz定理会告诉您b - a是准确的。因此,(b - a) - ulp1 / 2将是与下限最接近的double(b - a) + ulp2 / 2将是与上限最接近的double。尝试这些值,以及前后的值,并选择有效的最宽区间。

如果b > 2ab - a > b/2b - a的计算值最多偏离半个ulp。一个ulp1最多两个ulp,因为一个ulp2,所以你给出的理性间隔最多为两个ulp宽。找出b-a最接近的五个值中的哪一个。

如果a > 2b,则b-a的ulp至少与b的ulp一样大;如果有效的话,我敢打赌它必须是b-a最接近的三个值之一。我想象ab具有不同符号的情况类似。

我写了一小堆实现这个想法的C ++代码。在我厌倦等待之前,它没有通过随机模糊测试(在几个不同的范围内)。这是:

void addeq_range(double a, double b, double &xlo, double &xhi) {
  if (a != a) return; // empty interval
  if (b != b) {
    if (a-a != 0) { xlo = xhi = -a; return; }
    else return; // empty interval
  }
  if (b-b != 0) {
    // TODO: handle me.
  }

  // b is now guaranteed to be finite.
  if (a-a != 0) return; // empty interval

  if (b < 0) {
    addeq_range(-a, -b, xlo, xhi);
    xlo = -xlo;
    xhi = -xhi;
    return;
  }

  // b is now guaranteed to be zero or positive finite and a is finite.
  if (a >= b/2 && a <= 2*b) {
    double upulp = nextafter(b, 1.0/0.0) - b;
    double downulp = b - nextafter(b, -1.0/0.0);
    xlo = (b-a) - downulp/2;
    xhi = (b-a) + upulp/2;
    if (xlo + a == b) {
      xlo = nextafter(xlo, -1.0/0.0);
      if (xlo + a != b) xlo = nextafter(xlo, 1.0/0.0);
    } else xlo = nextafter(xlo, 1.0/0.0);
    if (xhi + a == b) {
      xhi = nextafter(xhi, 1.0/0.0);
      if (xhi + a != b) xhi = nextafter(xhi, -1.0/0.0);
    } else xhi = nextafter(xhi, -1.0/0.0);
  } else {
    double xmid = b-a;
    if (xmid + a < b) {
      xhi = xlo = nextafter(xmid, 1.0/0.0);
      if (xhi + a != b) xhi = xmid;
    } else if (xmid + a == b) {
      xlo = nextafter(xmid, -1.0/0.0);
      xhi = nextafter(xmid, 1.0/0.0);
      if (xlo + a != b) xlo = xmid;
      if (xhi + a != b) xhi = xmid;
    } else {
      xlo = xhi = nextafter(xmid, -1.0/0.0);
      if (xlo + a != b) xlo = xmid;
    }
  }
}