有没有关于Boost的erf函数背后算法的详细信息?该模块的文档并不是很精确。我发现有多种方法被混合使用,但是具体是什么我并不确定。对我来说,它看起来像是Abramowitz和Stegun的变化。
- 哪些方法被混合使用了?
- 这些方法是如何混合使用的?
- erf函数的复杂度是什么(常数时间)?
Sebastian
有没有关于Boost的erf函数背后算法的详细信息?该模块的文档并不是很精确。我发现有多种方法被混合使用,但是具体是什么我并不确定。对我来说,它看起来像是Abramowitz和Stegun的变化。
Sebastian
Boost Math Toolkit 的文档中有一个很长的参考文献列表,其中包括 Abramowitz 和 Stegun。erf-function 接口包含一个 policy 模板参数,可用于控制数值精度(因此影响其运行时复杂度)。
#include <boost/math/special_functions/erf.hpp>
namespace boost{ namespace math{
template <class T>
calculated-result-type erf(T z);
template <class T, class Policy>
calculated-result-type erf(T z, const Policy&);
template <class T>
calculated-result-type erfc(T z);
template <class T, class Policy>
calculated-result-type erfc(T z, const Policy&);
}} // namespaces
更新:
以下是早期提供的误差函数参考文献中“实现”部分的逐字复制:
实现
所有这些函数的版本都首先使用通常的反射公式使它们的参数为正:
erf(-z) = 1 - erf(z);
erfc(-z) = 2 - erfc(z); // preferred when -z < -0.5
erfc(-z) = 1 + erf(z); // preferred when -0.5 <= -z < 0
这些函数的通用版本是基于不完全伽玛函数实现的。
当识别到有效数字(尾数)大小时(目前适用于53、64和113位实数,以及通过提升为双精度处理的单精度24位),则使用JM设计的一系列有理逼近。
对于z <= 0.5,则使用erf的有理逼近,基于erf是奇函数的观察结果,因此使用以下公式计算erf:
erf(z) = z * (C + R(z*z));
针对绝对误差进行优化的有理近似R(z*z),只要其绝对误差与常数C相比足够小,那么在计算R(z*z)时产生的任何舍入误差都将有效地从结果中消失。因此,在这个区域内erf和erfc的误差非常低:最后一位在极少数情况下不正确。
对于z > 0.5,我们观察到在一个小区间[a, b)内:
erfc(z) * exp(z*z) * z ~ c
针对某个常数c,因此当z > 0.5时,我们使用以下方法计算erfc:
erfc(z) = exp(-z*z) * (C + R(z - B)) / z;
再次强调,R(z - B)的优化目标是绝对误差,常数C是在范围端点处计算erfc(z) * exp(z*z) * z的平均值。只要R(z - B)的绝对误差与c相比很小,那么c + R(z - B)就会被正确地舍入,结果的误差仅取决于exp函数的准确性。在实践中,除了极少数情况外,误差都限制在结果的最后一位。常数B的选择使得有理逼近的左端点为0。
对于大范围[a, +∞]内的大z,上述逼近将被修改为:
erfc(z) = exp(-z*z) * (C + R(1 / z)) / z;