【发布时间】:2016-05-27 05:28:25
【问题描述】:
在各种情况下,例如对于数学函数的参数缩减,需要计算(a - K) / (a + K),其中a 是一个正变量参数,K 是一个常数。在许多情况下,K 是 2 的幂,这是与我的工作相关的用例。我正在寻找比直接除法更准确地计算这个商的有效方法。可以假设硬件支持融合乘加 (FMA),因为此时所有主要 CPU 和 GPU 架构都提供此操作,并且可通过函数fma() 和 fmaf() 在 C/C++ 中使用。
为了便于探索,我正在试验float 算术。由于我还计划将该方法移植到double 算术,因此不得使用使用高于参数和结果的本机精度的任何操作。到目前为止,我最好的解决方案是:
/* Compute q = (a - K) / (a + K) with improved accuracy. Variant 1 */
m = a - K;
p = a + K;
r = 1.0f / p;
q = m * r;
t = fmaf (q, -2.0f*K, m);
e = fmaf (q, -m, t);
q = fmaf (r, e, q);
对于区间 [K/2, 4.23*K] 中的参数 a,如果 K 是 2 的幂,则上面的代码计算所有输入的商几乎正确四舍五入(最大误差非常接近 0.5 ulps),并且在中间结果中没有上溢或下溢。对于K 不是 2 的幂,这段代码仍然比基于除法的朴素算法更准确。就性能而言,此代码可以更快在平台上计算浮点倒数比浮点除法更快的幼稚方法。
当K = 2n时我做了以下观察:当工作区间的上限增加到8*K,16*K,...最大误差逐渐增加并开始从下面慢慢逼近朴素计算的最大误差。不幸的是,对于区间的下限,情况似乎并非如此。如果下界下降到0.25*K,则上述改进方法的最大误差等于朴素方法的最大误差。
有没有一种计算 q = (a - K) / (a + K) 的方法,与这两种朴素方法相比,可以实现更小的最大误差(以 ulp 测量相对于数学结果)和上面的代码序列,在更宽的区间上,特别是对于下限小于0.5*K的区间?效率很重要,但是比上面代码中使用的操作可能更多可以容忍。
在下面的一个答案中,有人指出我可以通过将商作为两个操作数的未评估和返回来提高准确性,即作为头尾对q:qlo,即类似于众所周知的双-float 和 double-double 格式。在我上面的代码中,这意味着将最后一行更改为qlo = r * e。
这种方法当然很有用,我已经考虑过将其用于pow() 中的扩展精度对数。但它并不能从根本上帮助扩大增强计算提供更准确商的区间。在我正在查看的特定情况下,我想使用K=2(用于单精度)或K=4(用于双精度)来保持主要近似区间较窄,a 的区间大致为 [0 ,28]。我面临的实际问题是,对于
【问题讨论】:
-
您是否尝试过为您的算法建模平均误差曲线并将其添加到结果中?
-
我不确定您所说的“平均误差曲线”是什么意思。我对最小化最大误差感兴趣,以 ulps 为单位。我通过在测试间隔内进行详尽的测试来确定错误,这就是我在探索性工作中使用单精度算术的原因。
-
不知道是否值得看一下:
(a / (a + k)) - (k / (a + k))的相对错误? -
@BrettHale 以这种方式重写表达式将导致最大 ulp 错误爆炸,因为当
a接近K时会进行减法抵消。 -
不幸的是,在某些平台上,
double操作的成本要高得多(高达float操作的 32 倍)。由于我也想对double使用相同的算法,因此没有可以在那里使用的廉价“四重”操作。因此要求只使用“原生”宽度操作(这也使得矢量化更容易)。
标签: c algorithm floating-point floating-accuracy