从数学上讲,这是一个难题,您可以考虑一些解决方案。
Dislcaimer:前面的数学术语。
您可能已经知道,normalcdf 函数用于计算正态随机变量的概率。因为正态分布是连续的,所以相应的概率密度函数 (normalpdf) 本身并不给出概率,(与 二项式 或 几何 等离散分布相反em> 分布)。相反,曲线下的区域给出了正态随机变量落在 范围值内的概率。因此,您寻找的 normalcdf 函数是 normalpdf 函数的一部分下的区域。
在数学上,求连续曲线下的面积是微积分的一个基本问题。此类问题的解决方案称为 integral 和 integrating 一个函数在一个数字范围内意味着找到曲线下的面积和该范围内的最低值之间的面积最高的。
在大多数情况下,我们可以只集成 pdf 函数来获得 cdf 函数,然后在我们想要的任何地方对其进行评估。问题的核心,以及 Java 中的算法不像人们想象的那么简单的原因是 normalpdf 函数没有 闭式 积分——它是值不能在任何有限的步骤中计算。因此,normalcdf 函数的值特别难以捉摸。
这个问题有两种主要的解决方案。
1.数值积分技术
数值积分技术通过几何近似曲线下面积来解决问题。该区域被分成等宽或不同宽度的矩形或其他形状,每个形状的高度由 pdf 函数给出。矩形的面积之和是曲线下面积的近似值,即对应的概率。这些技术可用于计算任意精度的值,但比第 2 类计算成本更高。使用更好的近似值(例如辛普森规则)可以改进计算。下面是一个简单的数值积分方法。
public static double normCDF(double z)
{ double LeftEndpoint = -100;
int nRectangles = 100000;
double runningSum = 0;
double x;
for(int n = 0; n < nRectangles; n++){
x = LeftEndpoint + n*(z-LeftEndpoint)/nRectangles;
runningSum += Math.pow(Math.sqrt(2*Math.PI),-1)*Math.exp(-Math.pow(x,2)/2)*(z-LeftEndpoint)/nRectangles;
}
System.out.println(runningSum);
return runningSum;
}
2。分析技术
分析技术利用了这样一个事实,即虽然 normalpdf 没有封闭形式的积分,但 pdf 可以“转换”为称为 泰勒级数,然后逐项积分。基本上,它将 pdf 转换为无限多个简单函数的总和,然后对每个函数进行解析积分,然后将所有积分相加。由于这是一个解析过程,程序员只需在计算系数后将积分级数包含在程序中即可。结果的精度仅取决于计算中包含的总和项的数量,并且往往比数值积分技术更快地接近准确值。例如,Mohammad Alderawy 的解决方案只计算了五个系数。下面是一种包括系数计算的方法,因此您可以计算任意精度的值(实际上,normalcdf 系列不是直接计算的。相反,相关 error 的系数函数 被计算然后通过线性变换进行转换)。然而,由于系数的计算涉及阶乘函数,因此对于大量系数会遇到内存问题。值得庆幸的是,这种方法在前一类解决方案中的方法所需的迭代的一小部分中返回具有更高精度的值,以产生类似的结果。
public static double normalCDF(double x){
System.out.println(0.5*(1+erf(x/Math.sqrt(2))));
return 0.5*(1+erf(x/Math.sqrt(2)));
}
public static double erf(double z)
{
int nTerms = 315;
double runningSum = 0;
for(int n = 0; n < nTerms; n++){
runningSum += Math.pow(-1,n)*Math.pow(z,2*n+1)/(factorial(n)*(2*n+1));
}
return (2/Math.sqrt(Math.PI))*runningSum;
}
static double factorial(int n){
if(n == 0) return 1;
if(n == 1) return 1;
return n*factorial(n-1);
}
其他功能
对于逆函数,由于我们在normalCDF方法中使用了误差函数,所以我们可以类似的方式使用逆误差函数。同样,我们通过解析获得逆误差函数的系数,然后在方法中根据需要计算它们。
public static double invErf(double z)
{
int nTerms = 315;
double runningSum = 0;
double[] a = new double[nTerms + 1];
double[] c = new double[nTerms + 1];
c[0]=1;
for(int n = 1; n < nTerms; n++){
double runningSum2=0;
for (int k = 0; k <= n-1; k++){
runningSum2 += c[k]*c[n-1-k]/((k+1)*(2*k+1));
}
c[n] = runningSum2;
runningSum2 = 0;
}
for(int n = 0; n < nTerms; n++){
a[n] = c[n]/(2*n+1);
runningSum += a[n]*Math.pow((0.5)*Math.sqrt(Math.PI)*z,2*n+1);
}
return runningSum;
}
public static double invNorm(double A){
return (2/Math.sqrt(2))*invErf(2*A-1);
}
我没有对数正态函数的方法,但您可以使用相同的想法获得一个。