C 标准正态分布米尔斯比的高效精确计算

C 标准正态分布米尔斯比的高效精确计算,c,math,floating-point,floating-accuracy,C,Math,Floating Point,Floating Accuracy,M(x)由John Mills引入,用于表示分布的累积分布函数与其概率密度函数之间的关系: 米尔斯,“比例表:法向曲线任何部分的面积和边界纵坐标”。《生物计量学》,第18卷,第3/4号(1926年11月),第395-400页。() 米尔斯比的平均值为(1-D(x))/p(x),其中D表示分布函数,p(x)表示概率密度函数。在标准正态分布的特定情况下,我们得到M(x)=(1-Φ(x))/ν(x)=Φ(-x)/ν(x),或者当通过互补误差函数表示时,M(x)=ex2√(π/2)erfc(x/√2)

M(x)由John Mills引入,用于表示分布的累积分布函数与其概率密度函数之间的关系:

米尔斯,“比例表:法向曲线任何部分的面积和边界纵坐标”。《生物计量学》,第18卷,第3/4号(1926年11月),第395-400页。()

米尔斯比的平均值为(1-D(x))/p(x),其中D表示分布函数,p(x)表示概率密度函数。在标准正态分布的特定情况下,我们得到M(x)=(1-Φ(x))/ν(x)=Φ(-x)/ν(x),或者当通过互补误差函数表示时,M(x)=ex2√(π/2)erfc(x/√2) = √(π/2)erfcx(x/√2)


之前的问题涉及数学环境中的磨机比计算,如和,但是这些环境中复杂的计算工具在C中没有等价物。如何仅使用C标准数学库就可以准确有效地计算标准正态分布的Mills比率?

在前面的回答中,我已经展示了如何使用C标准数学库来高效准确地计算标准正态分布的Mills比率计算,
normpdf()
normcdf()
,以及
erfcx()
。基于这三种实现方式,可以通过以下两种方式之一,以简单的方式轻松编码磨机比的计算:

双倍我的磨坊比(双倍a)
{
返回my_normcdf(-a)/my_normcdf(a);
}
双倍my_mills_比率_2(双倍a)
{
常量双SQRT_HALF_HI=0x1.6a09e667f3bccp-01;//1/SQRT(2),msbs;
const double SQRT_HALF_LO=0x1.21165f626cdd5p-54;//1/SQRT(2),LSB;
常量双SQRT_PIO2_HI=0x1.40d931ff62705p+00;//SQRT(pi/2),msbs;
const double SQRT_PIO2_LO=0x1.2caf9483f5ce4p-53;//SQRT(pi/2),LSB;
双r;
a=fma(SQRT_HALF_HI,a,SQRT_HALF_LO*a);
r=我的(a);
返回fma(SQRT_PIO2_HI,r,SQRT_PIO2_LO*r);
}
然而,这两种方法在数值上都有缺陷。对于
mills\u ratio\u 1()。在IEEE-754中,双精度都在
a
=38附近变为零,由于零除以零,导致NaN结果。关于
my_mills_ratio_2()
,负半平面中的指数增长会导致错误放大,从而导致较大的ulp错误。解决这一问题的一种方法是简单地将两种近似值中表现良好的部分结合起来:

双倍my\u-mills\u比率\u 3(双倍a)
{
回报率(a<0)?我的磨坊率(a):我的磨坊率(a);
}
这相当有效。使用英特尔编译器13.1.3.198版,使用40亿个测试向量,构建我在先前答案中给出的代码,在正半平面中观察到最大错误为2.79346 ulps,而在负半平面中观察到最大错误为6.81248 ulps。对于接近溢出的大型结果,负半平面中会出现更大的错误,因为此时PDF的值非常小,它们被表示为精度降低的低于正常值的双精度数字

另一种解决方案是解决影响负半平面中的
my\u mills\u ratio\u 2()
的错误放大问题。可以通过将
erfcx()
的参数计算到比双精度更好的精度,并将此参数的低阶位用于
erfcx()
结果的线性插值来实现这一点

为此,还需要erfcx(x)的斜率,即erfcx'(x)=2xerfcx(x)-2/√π. 通过C标准数学函数
FMA()
提供FMA(融合乘法加法)操作,可有效实现这种准双精度计算。在中间计算期间,可通过局部重缩放避免坡度大小溢出的风险

结果实现在整个输入域中的错误小于4个ULP:

/*计算标准正态分布的比率:
*
*M(x)=normcdf(-x)/normfdfd(x)=sqrt(pi/2)*erfcx(x/sqrt(2))
*
*正半平面中的最大ulp误差:2.79346
*负半平面中的最大ulp误差:3.90753
*/ 
双倍my_-mills_比率(双倍a)
{
双s,t,r,h,l;
常量双SQRT_HALF_HI=0x1.6a09e667f3bccp-01;//1/SQRT(2),msbs
const double SQRT_HALF_LO=0x1.21165f626cdd5p-54;//1/SQRT(2),LSB
常量双SQRT_PIO2_HI=0x1.40d931ff62705p+00;//SQRT(pi/2),msbs
常数双SQRT_PIO2_LO=0x1.2caf9483f5ce4p-53;//SQRT(pi/2),LSB
常数双二次方π=0x1.20dd750429b6dp+00;//2/sqrt(π)
const double MAX_IEEE_DBL=0x1.ffffffffffffp+1023;
const double SCALE_DOWN=0.03125;//在中间计算中防止ovrfl
常数双刻度向上=1.0/刻度向下;
//将参数a/sqrt(2)计算为双精度h:l的头尾对
h=fma(SQRT_HALF_HI,a,SQRT_HALF_LO*a);
l=fma(-SQRT_HALF_LO,a,fma(-SQRT_HALF_HI,a,h));
//计算参数“head”的比例互补误差函数
t=我的(h);
//若在负半平面,若结果未溢出,则提高精度

如果((a<-1.0)和&(t在前面的答案中,我已经演示了如何使用C标准数学库高效准确地计算,
normpdf()
normcdf()
,以及
erfcx())
。基于这三种实现方式,可以通过以下两种方式之一简单地对磨机比的计算进行编码:

双倍我的磨坊比(双倍a)
{
返回my_normcdf(-a)/my_normcdf(a);
}
双倍my_mills_比率_2(双倍a)
{
常数双SQRT_半高=0x1.6a09e6