在静态分析的上下文中,我有兴趣在以下条件的then分支中确定x
的值:
double x;
x = …;
if (x + a == b)
{
…
a
和b
可以假设为双精度常量(推广到任意表达式是问题中最容易的部分),并且可以假设编译器严格遵循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次浮点加法更便宜。
答案 0 :(得分:5)
您已经在问题中提供了一个漂亮而优雅的解决方案:
如果有理性的计算是便宜的,那就很简单:价值观 for x将是理性中包含的双精度数 区间(b - a - 0.5 * ulp1(b)... b - a + 0.5 * ulp2(b))。界限 如果b是偶数,则应该包括,如果b是奇数,则排除,并且ulp1和 ulp2是可以采用的两种略有不同的“ULP”定义 相同的,如果一个人不介意失去一点精度的权力 2。
以下是根据本段对问题进行部分解决的半合理草图。希望我很快就有机会充实它。要获得真正的解决方案,您必须处理次正规,零,NaN以及所有其他有趣的东西。我假设a
和b
是1e-300 < |a| < 1e300
和1e-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 > 2a
,b - a > b/2
。 b - a
的计算值最多偏离半个ulp。一个ulp1
最多两个ulp,因为一个ulp2
,所以你给出的理性间隔最多为两个ulp宽。找出b-a
最接近的五个值中的哪一个。
如果a > 2b
,则b-a
的ulp至少与b
的ulp一样大;如果有效的话,我敢打赌它必须是b-a
最接近的三个值之一。我想象a
和b
具有不同符号的情况类似。
我写了一小堆实现这个想法的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;
}
}
}