约翰米尔斯引入Mills ratio M(x)来表达分布的累积分布函数与其概率密度函数之间的关系:
学家P. Mills,“比率表:正常曲线的任何部分的面积与边界的纵坐标”。 Biometrika ,Vol。 18,No。3/4(1926年11月),第395-400页。 (online)
Mills比率的definition是(1-D(x))/ P(x),其中D表示分布函数,P(x)是概率密度函数。在标准正态分布的特定情况下,我们得到M(x)=(1 - Φ(x))/φ(x)=Φ(-x)/φ(x),或者当通过互补误差表示时函数,M(x)= e x 2 √(π/ 2)erfc(x/√2)=√(π/ 2)erfcx(x /√2 )。
之前的问题已经涉及像R和Matlab这样的数学环境中Mills比率的计算,但是这些环境的复杂计算设施在C中没有等效。如何计算Mills仅使用C标准数学库准确有效地标准正态分布的比率?
答案 0 :(得分:2)
在之前的回答中,我展示了如何使用C标准数学库高效准确地计算PDF of the standard normal distribution,normpdf()
,CDF of the standard normal distribution,normcdf()
和scaled complementary error function erfcx()
。基于这三种实现,可以通过以下两种方式之一以简单的方式轻松编写Mills比率的计算:
double my_mills_ratio_1 (double a)
{
return my_normcdf (-a) / my_normpdf (a);
}
double my_mills_ratio_2 (double a)
{
const double SQRT_HALF_HI = 0x1.6a09e667f3bccp-01; // 1/sqrt(2), msbs;
const double SQRT_HALF_LO = 0x1.21165f626cdd5p-54; // 1/sqrt(2), lsbs;
const double SQRT_PIO2_HI = 0x1.40d931ff62705p+00; // sqrt(pi/2), msbs;
const double SQRT_PIO2_LO = 0x1.2caf9483f5ce4p-53; // sqrt(pi/2), lsbs;
double r;
a = fma (SQRT_HALF_HI, a, SQRT_HALF_LO * a);
r = my_erfcx (a);
return fma (SQRT_PIO2_HI, r, SQRT_PIO2_LO * r);
}
然而,这两种方法都存在数值缺陷。对于mills_ratio_1()
,随着论证幅度的增加,PDF术语和CDF术语在正半平面上迅速消失。在IEEE-754中,双精度在a
= 38附近变为零,由于零除零而导致NaN结果。关于my_mills_ratio_2()
,负半平面的指数增长导致误差放大,因此导致大的ulp误差。解决这个问题的一种方法是简单地组合两个近似值中每一个的表现良好的部分:
double my_mills_ratio_3 (double a)
{
return (a < 0) ? my_mills_ratio_1 (a) : my_mills_ratio_2 (a);
}
这种方法运作得相当好。使用英特尔编译器版本13.1.3.198构建我在之前的答案中提出的代码,使用40亿个测试向量,在正半平面中观察到最大误差为2.79346 ulps,而在最大误差为6.81248 ulps时观察到负半平面。对于接近溢出的大结果,负半平面出现稍大的误差,因为在这一点上,PDF的值非常小,以至于它们被表示为精度降低的次正规双精度数。
一种替代解决方案是解决在负半平面中影响my_mills_ratio_2()
的误差放大问题。可以通过将erfcx()
的参数计算为优于双精度,并使用该参数的低位进行erfcx()
结果的线性插值来实现。
为此,还需要erfcx(x)的斜率,即erfcx'(x)= 2x erfcx(x) - 2 /√π。通过C标准数学函数fma()
提供FMA(融合乘法 - 加法)运算可以有效地实现这种准双倍计算。通过局部重新缩放可以避免在中间计算期间斜率大小溢出的风险。
结果实现在整个输入域中的误差小于4 ulps:
/* Compute Mills ratio of the standard normal distribution:
*
* M(x) = normcdf(-x)/normpdf(x) = sqrt(pi/2) * erfcx(x/sqrt(2))
*
* maximum ulp error in positive half-plane: 2.79346
* maximum ulp error in negative half-plane: 3.90753
*/
double my_mills_ratio (double a)
{
double s, t, r, h, l;
const double SQRT_HALF_HI = 0x1.6a09e667f3bccp-01; // 1/sqrt(2), msbs
const double SQRT_HALF_LO = 0x1.21165f626cdd5p-54; // 1/sqrt(2), lsbs
const double SQRT_PIO2_HI = 0x1.40d931ff62705p+00; // sqrt(pi/2), msbs
const double SQRT_PIO2_LO = 0x1.2caf9483f5ce4p-53; // sqrt(pi/2), lsbs
const double TWO_RSQRT_PI = 0x1.20dd750429b6dp+00; // 2/sqrt(pi)
const double MAX_IEEE_DBL = 0x1.fffffffffffffp+1023;
const double SCALE_DOWN = 0.03125; // prevent ovrfl in intermed. computation
const double SCALE_UP = 1.0 / SCALE_DOWN;
// Compute argument a/sqrt(2) as a head-tail pair of doubles h:l
h = fma (SQRT_HALF_HI, a, SQRT_HALF_LO * a);
l = fma (-SQRT_HALF_LO, a, fma (-SQRT_HALF_HI, a, h));
// Compute scaled complementary error function for argument "head"
t = my_erfcx (h);
// Enhance accuracy if in negative half-plane, if result has not overflowed
if ((a < -1.0) && (t <= MAX_IEEE_DBL)) {
// Compute slope: erfcx'(x) = 2x * erfcx(x) - 2/sqrt(pi)
s = fma (h, t * SCALE_DOWN, -TWO_RSQRT_PI * SCALE_DOWN); // slope
// Linearly interpolate result based on derivative and argument "tail"
t = fma (s, -2.0 * SCALE_UP * l, t);
}
// Scale by sqrt(pi/2) for final result
r = fma (SQRT_PIO2_HI, t, SQRT_PIO2_LO * t);
return r;
}
除了涉及的常量外,单精度实现几乎完全相同:
/* Compute Mills ratio of the standard normal distribution:
*
* M(x) = normcdf(-x)/normpdf(x) = sqrt(pi/2) * erfcx(x/sqrt(2))
*
* maximum ulp error in positive half-plane: 2.41987
* maximum ulp error in negative half-plane: 3.39521
*/
float my_mills_ratio_f (float a)
{
float h, l, r, s, t;
const float SQRT_HALF_HI = 0x1.6a09e6p-01f; // sqrt(1/2), msbs
const float SQRT_HALF_LO = 0x1.9fcef4p-27f; // sqrt(1/2), lsbs
const float SQRT_PIO2_HI = 0x1.40d930p+00f; // sqrt(pi/2), msbs
const float SQRT_PIO2_LO = 0x1.ff6270p-24f; // sqrt(pi/2), lsbs
const float TWO_RSQRT_PI = 0x1.20dd76p+00f; // 2/sqrt(pi)
const float MAX_IEEE_FLT = 0x1.fffffep+127f;
const float SCALE_DOWN = 0.0625f; // prevent ovrfl in intermed. computation
const float SCALE_UP = 1.0f / SCALE_DOWN;
// Compute argument a/sqrt(2) as a head-tail pair of floats h:l
h = fmaf (SQRT_HALF_HI, a, SQRT_HALF_LO * a);
l = fmaf (-SQRT_HALF_LO, a, fmaf (-SQRT_HALF_HI, a, h));
// Compute scaled complementary error function for argument "head"
t = my_erfcxf (h);
// Enhance accuracy if in negative half-plane, if result has not overflowed
if ((a < -1.0f) && (t <= MAX_IEEE_FLT)) {
// Compute slope: erfcx'(x) = 2x * erfcx(x) - 2/sqrt(pi)
s = fmaf (h, t * SCALE_DOWN, -TWO_RSQRT_PI * SCALE_DOWN);
// Linearly interpolate result based on derivative and argument "tail"
t = fmaf (s, -2.0f * SCALE_UP * l, t);
}
// Scale by sqrt(pi/2) for final result
r = fmaf (SQRT_PIO2_HI, t, SQRT_PIO2_LO * t);
return r;
}