boost::math::erf算法

6

有没有关于Boost的erf函数背后算法的详细信息?该模块的文档并不是很精确。我发现有多种方法被混合使用,但是具体是什么我并不确定。对我来说,它看起来像是Abramowitz和Stegun的变化。

  • 哪些方法被混合使用了?
  • 这些方法是如何混合使用的?
  • erf函数的复杂度是什么(常数时间)?

Sebastian

1个回答

6

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;

理性逼近在非常详细地解释。如果您需要更多细节,您可以查看源代码

谢谢您的回答。通过您对策略模板的提示和对代码的简短审查,我发现可以设置迭代次数,从而实现O(1)的实现(对于固定的迭代次数)。但其他问题仍然存在。当然,参考文献包括Abramowitz/Stegun,但这本书不仅仅包含erf。阅读代码始终是一种选择,但它会消耗太多时间,算法应以一种使其无需阅读代码的方式进行记录。 - Euphoriewelle
@Euphoriewelle 我更新了我的答案,并附上了 Boost 手册中的实现部分副本。我认为它应该能回答你关于实现问题的大部分疑问。如果需要更多细节,恐怕源代码确实是最终指南。 - TemplateRex
谢谢您的更新!对于大多数用户来说,您的答案应该已经足够了。我想我别无选择,只能去阅读代码以获取细节信息。 - Euphoriewelle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接