[虽然这是一个自我回答的问题,但我很乐意投票并接受任何替代答案,无论是在相同的计算工作量下提供上级准确度,还是在保持相同准确度的情况下减少计算工作量。]
我以前有demonstrated如何计算互补误差函数erfcf()
,其最大误差小于3个ulps。这可以作为其他函数的构建块,例如标准正态分布的CDF Φ(x)= 1/2 erfc(-λ 1/2 x)或高斯Q函数,Q(x)= 1-Φ(x)= 1/2 erfc(λ 1/2 x)。然而,对于某些使用情况,不需要完全精确到单精度的计算,而erfc()
评估对总运行时间的贡献不可忽略。
文献提供了对互补误差函数的各种低精度近似,但它们要么限于完整输入域的子集,要么针对绝对误差进行了优化,要么计算过于复杂,例如,需要多次调用超越函数。如何才能实现erfcf()
具有高性能和大约5位十进制数的相对精度 * 在整个输入域中 *?
2条答案
按热度按时间6yt4nkrj1#
以下假设平台符合IEEE-754(2008)浮点标准,在该平台上
float
被Map到IEEE-754binary32
,并且在32位整数和float
之间使用相同的字节序。(如果需要,通过设置适当的命令行开关)保留IEEE-754语义。我使用的是带有开关-march=skylake-avx152 -O3 -fp-model=precise
的Intel C/C++编译器。由于互补误差函数关于(0,1)对称,因此可以关注正半平面中的函数输入。这里函数大致像exp(-x2)一样衰减,并且对于x > 10.5的参数,
float
计算下溢到零。如果将erfc(x)/ exp(-x2)绘制在[0,10.5]形状表明,用多项式近似有点困难,但应该很容易用有理函数近似,即,两个多项式的比率。一些初始实验表明,两个多项式的次数各为3应该足以达到五位数的精度。虽然有许多工具可以生成多项式近似,但不幸的是,有理近似并非如此。我使用Remez算法的修改来生成初始极大极小近似R(x)= P(x)/Q(x)到erfc(x)/ exp(-x2),但必须进行相当广泛的启发式搜索,以获得一个近似值,该近似值提供 * 接近 * 误差峰值的等振荡,实现了10-5的相对误差,其余的差异对于我的需要来说可以忽略不计。
通过计算erfc(x)= exp(-x2)R(x),所获得的精度显然取决于平台
expf()
实现的精度。此功能的忠实全面的实现(最大误差<= 1ulp)是常见的。虽然英特尔编译器附带了一个高度精确的数学库,可以提供近乎正确的四舍五入实现(最大误差非常接近0.5 ulps),我也尝试了我自己的忠实四舍五入的替代my_expf()
,误差更大,只观察到对fast_erfcf()
精度的影响非常小。字符串
exdqitrt2#
OP提供了用于评估性能测试的范围的注解:1 ULP步骤中的0.0至10.5。
由于所有
float
[-1.6e-08...+1.6e-08]中约有40%返回值约为1.0,代码可以使用以下内容。对我来说,整体性能至少 * 翻了一番 *。字符串
由于
erfc(x)
中float
的大范围可以返回1.0,因此我建议进行这种预先测试。优点包括:
float
的40%来说,速度要快得多。erfc(x_near_0)
预期返回1.0。OP的fast_erfcf(0.0)
返回1.00000834。考虑到可以容忍的大ULP,可以接受。1.0仍然很好。fast_erfcf(small negative value)
返回0.999991536。可以接受,因为可以容忍较大的ULP,但在可能的情况下,希望有erfc(x) >= erfc(next x towards + infinity)
。此外:建议在[0.1.... 10.0]范围内对最感兴趣的值进行性能测试。
这个建议对OP没有多大帮助,所以把它移到这个答案的底部。
与其用
my_expf(float a)
执行 ex,不如用my_exp2f(float a)
执行 2x(这会影响有理函数的常数)。j = fmaf(1.442695f, a, 12582912.f);
被删除。使用base 2比base e 执行取幂稍微容易一些。
我估计代码会快几个百分点(~3%),而ULP结果不会有太大变化。
确实,这是一个微优化,但它实际上是免费的,因为它不会给链接答案的方法增加任何真实的复杂性。