当前位置: 首页 > 知识库问答 >
问题:

使用 C 标准数学库精确计算标准正态分布的 PDF

邹斌
2023-03-14

标准正态分布的概率密度函数定义为e < sup >-x < sup > 2 /2 /√(2π)。这可以用简单的方式转换成C代码。单精度实现的示例可能是:

float my_normpdff (float a)
{
    return 0x1.988454p-2f * my_expf (-0.5f * a * a); /* 1/sqrt(2*pi) */
}

虽然此代码没有过早的下限溢位,但存在精度问题,因为计算2/2时产生的误差会被后续的幂运算放大。人们可以通过针对更高精度引用的测试轻松证明这一点。确切的误差将根据所使用的exp()expf()实现的准确性而有所不同;对于忠实四舍五入的幂函数,人们通常会观察到IEEE-754binary32单精度的最大误差约为26ulps,IEEE-754binary64双精度的最大误差约为29ulps。

如何以合理有效的方式解决准确性问题?一种简单的方法是采用更高精度的中间计算,例如对< code>float实现使用< code>double计算。但是,如果不容易获得更高精度的浮点运算,这种方法不适用于< code>double实现,并且如果< code>double运算比< code>float计算昂贵得多,例如在许多GPU上,这种方法对于< code>float实现可能是低效的。

共有1个答案

楮乐邦
2023-03-14

问题中提出的精度问题可以通过使用有限数量的double-float或double--doublecomputer来有效地解决,这得益于融合乘法加法(FMA)运算的使用。

从< code>C99开始,此操作可通过标准数学函数< code>fmaf(a,b,c)和< code>fma(a,b,c)进行,它们计算a*b c,无需对中间产品进行舍入。虽然这些函数直接映射到几乎所有现代处理器上的快速硬件操作,但它们可能使用旧平台上的仿真代码,在这种情况下,它们可能非常慢。

这允许仅使用两个操作以两倍于正常精度的精度计算乘积,从而产生一对头:尾的本机精度数字:

prod_hi = a * b            // head
prod_lo = FMA (a, b, -hi)  // tail

结果的高阶位可以传递给求幂运算,而低阶位则用于通过线性插值提高结果的精度,利用ex是其自身导数的事实:

e = exp (prod_hi) + exp (prod_hi) * prod_lo  // exp (a*b)

这使我们能够消除原始实现的大部分错误。另一个较小的计算误差来源是常数1的有限精度/√表示为(2π)。通过对提供两倍本机精度的html" target="_blank">常量使用head:tail表示法,并计算:

r = FMA (const_hi, x, const_lo * x)  // const * x

下面的论文指出,这种技术甚至可以对某些任意精度的常数产生正确舍入的乘法:

Nicolas Brisebarre和Jean Michel Muller,“任意精度常数的正确四舍五入乘法”,《IEEE计算机汇刊》,第57卷,第2期,2008年2月,第165-174页

结合这两种技术,并考虑到一些涉及NaN的角落案例,我们得出了以下基于IEEE-754二进制32的float实现:

float my_normpdff (float a)
{
    const float RCP_SQRT_2PI_HI =  0x1.988454p-02f; /* 1/sqrt(2*pi), msbs */
    const float RCP_SQRT_2PI_LO = -0x1.857936p-27f; /* 1/sqrt(2*pi), lsbs */
    float ah, sh, sl, ea;

    ah = -0.5f * a;
    sh = a * ah;
    sl = fmaf (a, ah, 0.5f * a * a); /* don't flip "sign bit" of NaN argument */
    ea = expf (sh);
    if (ea != 0.0f) ea = fmaf (sl, ea, ea); /* avoid creation of NaN */
    return fmaf (RCP_SQRT_2PI_HI, ea, RCP_SQRT_2PI_LO * ea);
}

基于IEEE-754 binary64的相应双重实现看起来几乎相同,除了使用不同的常量值:

double my_normpdf (double a)
{
    const double RCP_SQRT_2PI_HI =  0x1.9884533d436510p-02; /* 1/sqrt(2*pi), msbs */
    const double RCP_SQRT_2PI_LO = -0x1.cbc0d30ebfd150p-56; /* 1/sqrt(2*pi), lsbs */
    double ah, sh, sl, ea;

    ah = -0.5 * a;
    sh = a * ah;
    sl = fma (a, ah, 0.5 * a * a); /* don't flip "sign bit" of NaN argument */
    ea = exp (sh);
    if (ea != 0.0) ea = fma (sl, ea, ea); /* avoid creation of NaN */
    return fma (RCP_SQRT_2PI_HI, ea, RCP_SQRT_2PI_LO * ea);
}

这些实现的准确性分别取决于标准数学函数< code>expf()和< code>exp()的准确性。在C数学库提供精确舍入版本的情况下,上述两种实现的最大误差通常小于2.5 ulps。

 类似资料:
  • 标准c数学函数 -> 详解 标准c数学函数 abs 语法: #include <stdlib.h> int abs( int num ); 功能: 函数返回参数num.的绝对值。例如: int magic_number = 10; cout << "Enter a guess: "; cin >> x; cout << "Your guess was " << abs( magic_n

  • C++ 标准库可以分为两部分: 标准函数库: 这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自 C 语言。 面向对象类库: 这个库是类及其相关函数的集合。 C++ 标准库包含了所有的 C 标准库,为了支持类型安全,做了一定的添加和修改。 标准函数库 标准函数库分为以下几类: 输入/输出 I/O 字符串和字符处理 数学 时间、日期和本地化 动态分配 其他 宽字符函数 面向对象类库 标

  • 掌握标准 C++的基础知识和技能是使用 Qt 进行编程的前提,虽然 Qt 也支持其他的语言 扩展(比如 Java、Python 等),但 Qt 的基础和努力方向仍然是以 C++语言为主,所以读者 朋友一定要掌握标准 C++。 3.1.1 程序设计语言介绍 1.软件 计算机内部所有能够存储的各种数据和能够执行的各种程序都称为软件。而程序一词 经常有两种理解:(1)由程序员编写的源代码;(2)可执行的

  • Python标准库是Python强大的动力所在,我们已经在前文中有所介绍。由于标准库所涉及的应用很广,所以需要学习一定的背景知识。 硬件原理 这一部份需要了解内存,CPU,磁盘存储以及IO的功能和性能,了解计算机工作的流程,了解指令的概念。这些内容基础而重要。 Python标准库的一部份是为了提高系统的性能(比如mmap),所以有必要了解基本的计算机各个组成部分的性能。 操作系统 在了解操作系统时

  • 基本上,在每次打印之前,在windows的代码块中我有“fflush(stdin);”这很有效。当我将代码复制到Linux时,它不起作用,“fflush(stdin);”的任何替代方案也不起作用我找到了。无论我用哪种方式做,输入在缓冲区中似乎没有被清除,或者我的代码中的某些内容不正确。

  • C++ 程序由类(class)和函数(function)组成。可以用多个小的软件模块构成C++程序,但大多数C++程序员会利用C++标准库中已有的类和函数来编程。这样,C++“世界”中实际要学习两方面的知识,第一是学习C++语言本身,第二是学习如何利用C++标准库中现有的类和函数(本书将介绍许多类和函数)。 Plauger(见参考文献P192)的著作是程序员必读的.可以帮助程序员深入了解 C++