【问题标题】:Poisson calculation (erlang C)泊松计算(erlang C)
【发布时间】:2016-08-23 20:32:29
【问题描述】:

我之前发布过这个,用户告诉我在 codereview 上发布。我做到了,他们关闭了它......所以在这里再一次:(我删除了旧问题)

我有这些公式:

我需要 erlangC 公式的泊松公式:

我尝试在 C 中重建公式:

double getPoisson(double m, double u, bool cumu)
{
    double ret = 0;
    if(!cumu)
    {
        ret = (exp(-u)*pow(u,m)) / (factorial(m));
    }
    else
    {
        double facto = 1;
        double ehu = exp(-u);
        for(int i = 0; i < m; i++)
        {
            ret = ret + (ehu * pow(u,i)) / facto;
            facto *= (i+1);
        }
     }
     return ret;
}

Erlang C 公式:

double getErlangC(double m, double u, double p)
{
    double numerator = getPoisson(m, u, false);
    double denominator = getPoisson(m, u, false) + (1-p) * getPoisson(m, u, true);
    return numerator/denominator;
}

主要问题是,getPoisson 中的m 参数值很大(>170) 所以它要计算>170!但它无法处理它。我认为原始数据类型太小而无法正常工作,或者你说什么?

顺便说一句:这是我用于第一个泊松的阶乘函数:

double factorial(double n)
{
    if(n >= 1)
        return n*factorial(n-1);
    else
        return 1;
}

一些示例:

输入:

double l = getErlangC(50, 48, 0.96);
printf("%g", l);

输出:

0.694456 (correct)

输入:

double l = getErlangC(100, 96, 0.96);
printf("%g", l);

输出:

0.5872811 (correct)

如果我对 getErlangC 的第一个参数 (m) 使用高于 170 的值,例如:

输入:

double l = getErlangC(500, 487, 0.974);
printf("%g", l);

输出:

naN (incorrect)

例外:

0.45269

我的方法如何?有没有更好的方法来计算 Poisson 和 erlangC?

一些信息:Excel 具有 POISSON 函数,并且在 Excel 上它可以完美运行...是否有办法查看 EXCEL 用于 POISSON 的算法(代码)?

【问题讨论】:

标签: c poisson


【解决方案1】:

(pow(u, m)/factorial(m)) 可以表示为一个递归循环,其中每个元素显示为 u/n,其中每个 n 是 m! 的一个元素。

double ratio(double u, int n)
{
    if(n > 0)
     {
        // Avoid the ratio overflow by calculating each ratio element
        double val;
        val = u/n;
        return val*ratio(u, n-1);
      }
    else
      {
         // Avoid division by 0 as power and factorial of 0 are 1
        return 1;
      }
}

请注意,如果您想避免递归,也可以将其作为循环来实现

double ratio(double u, int n)
{
    int i;
    // Avoid the ratio overflow by calculating each ratio element
    // default the ratio to 1 for n == 0
    double val = 1;
    // calculate the next n-1 ratios and put them into the total
    for (i = 1; i<=n; i++)
      {
        // Put in the next element of the ratio 
        val *=  u/i;
      }
    // return the final value of the ratio
    return val;
}

【讨论】:

  • 无限下降、堆栈溢出、在不确定时使用具有自动存储持续时间的对象的值的未定义行为、误导性缩进。
  • 感谢您的帮助,我使用了您的方式,现在我可以处理大值了
  • @EOF 感谢您指出省略括号的错字。加上括号,结果不是无限的。 Sice val 仅在 if 内部使用,我将定义移至该范围,并且值不再是不确定的。
  • 递归的控制变量通常是整数类型。我看不出有什么理由在这里使用浮点数,但有很好的理由不这样做。
  • @Olaf 好点。既然它是幂和阶乘,那么你是对的。我会解决的。
【解决方案2】:

为了处理超出double 范围的值,重新编码以使用值的log。缺点 - 一些精度损失。

可以通过改进的代码重新获得精度,但这里至少可以解决范围问题。

OP 代码的轻微变体如下:用于比较。

long double factorial(unsigned m) {
  long double f = 1.0;
  while (m > 0) {
    f *= m;
    m--;
  }
  return f;
}

double getPoisson(unsigned m, double u, bool cumu) {
  double ret = 0;
  if (!cumu) {
    ret = (double) ((exp(-u) * pow(u, m)) / (factorial(m)));
  } else {
    double facto = 1;
    double ehu = exp(-u);
    for (unsigned i = 0; i < m; i++) {
      ret = ret + (ehu * pow(u, i)) / facto;
      facto *= (i + 1);
    }
  }
  return ret;
}

double getErlang(unsigned m, double u, double p) {
  double numerator = getPoisson(m, u, false);
  double denominator = numerator + (1.0 - p) * getPoisson(m, u, true);
  return numerator / denominator;
}

建议的更改

#ifdef M_PI
  #define  MY_PI M_PI
#else
  #define  MY_PI 3.1415926535897932384626433832795
#endif

// log of n!
//
// Gosper Approximation of Stirling's Approximation
// http://mathworld.wolfram.com/StirlingsApproximation.html
// n! about= sqrt(pi*(2*n + 1/3.)) * pow(n,n)  * exp(-n)
static double ln_factorial(unsigned n) {
  if (n <= 1) return 0.0;
  double x = n;
  return log(sqrt(MY_PI * (2 * x + 1 / 3.0))) + log(x) * x - x;
}


double getPoisson_2(unsigned m, double u, bool cumu) {
  double ret = 0.0;
  if (cumu) {
    // Simplify term calculation.  `mul` does not get too large nor small.
    double mul = exp(-u);
    for (unsigned i = 0; i < m; i++) {
      ret += mul;
      mul *= u/(i + 1);
      // printf("ret:% 10e  mul:% 10e\n", ret, mul);
    }
  } else {
    // ret = (exp(-u) * pow(u, m)) / (factorial(m));
    double ln_ret = -u + log(u) * m - ln_factorial(m);
    return exp(ln_ret);
  }
  return ret;
}

double getErlang_2(unsigned m, double u, double p) {
  double numerator = getPoisson_2(m, u, false);
  double denominator = numerator + (1 - p) * getPoisson_2(m, u, true);
  return numerator / denominator;
}

测试代码

void ErTest(unsigned m, double u, double p, double expect) {
  printf("m:%4u  u:% 14e  p:% 14e", m, u, p);
  printf("  E0:% 14e", expect);
  double y1 = getErlang(m, u, p);
  printf("  E1:% 14e", y1);
  double y2 = getErlang_2(m, u, p);
  printf("  E2:% 14e", y2);
  puts("");
}

int main(void) {
  ErTest(50, 48, 0.96, 0.694456);
  ErTest(100, 96, 0.96, 0.5872811);
  ErTest(500, 487, 0.974, 0.45269);
}

m:  50  u:  4.800000e+01  p:  9.600000e-01  E0:  6.944560e-01  E1:  6.944556e-01  E2:  6.944562e-01
m: 100  u:  9.600000e+01  p:  9.600000e-01  E0:  5.872811e-01  E1:  5.872811e-01  E2:  5.872813e-01
m: 500  u:  4.870000e+02  p:  9.740000e-01  E0:  4.526900e-01  E1:           nan  E2:  4.464746e-01

【讨论】:

  • 哇,感谢您的帮助。你的算法很棒,我刚刚尝试了“sabbahillel”的算法,它比你的短,但给出的结果完全相同。我要了解你们做了什么,然后我要实施它!再次感谢!!!
【解决方案3】:

您的大型递归factorial 是一个问题,因为它可能会产生堆栈溢出以及值溢出。 pow 也可能变大。

这是一种逐步组合事物的方法:

double
getPoisson(double m, double u, bool cumu)
{
    double sum = 0;
    double facto = 1;
    double u_i = 1;
    double ehu = exp(-u);
    double cur = ehu;

    // u_i -- pow(u,i)
    // cur -- current/last term in series
    // sum -- sum of terms

    for (int i = 0; i < m; i++) {
        cur = (ehu * u_i) / facto;
        sum += cur;

        u_i *= u;
        facto *= (i + 1);
    }

    return cumu ? sum : cur;
}

以上内容“没问题”,但由于u_ifacto 条款,仍然可能会溢出一些值。

这是一个将术语组合为比率的替代方法。它不太可能溢出:

double
getPoisson(double m, double u, bool cumu)
{
    double sum = 0;
    double ehu = exp(-u);
    double cur = ehu;
    double ratio = 1;

    // cur -- current/last term in series
    // sum -- sum of terms
    // ratio -- u^i / factorial(i)

    for (int i = 0; i < m; i++) {
        cur = ehu * ratio;
        sum += cur;

        ratio *= u;
        ratio /= (i + 1);
    }

    return cumu ? sum : cur;
}

以上可能仍然产生一些较大的值。如果是这样,您可能必须使用long doublequadmath 或多精度算术。或者,想出一个方程式/算法的“模拟”。

【讨论】:

  • 我试过用你的getPoisson() 代替 OP 的getPoisson()getErlang(50, 48, .96),折扣大约 1.2%,还不错。虽然我期待更好 - 也许 OP 的数字已经关闭?也许我的答案的测试工具可能有助于比较。
  • @chux 谢谢[感谢星星]。我根据我为泰勒级数完成的一些代码编写了它 [pre-coffee :-)]。我将其视为参考示例并没有对其进行测试。我在办公桌上检查了几次,所以我很确定它,但很高兴知道它“开箱即用”。
  • 嘿 Craig Estey,感谢您的帮助 :) 正如 chux 所说,返回值有点不同 :)) 无论如何。谢谢! @CraigEstey
  • ratio *= u; ratio /= (i + 1); --> ratio *= u/(i + 1); 可能有助于解决范围问题。
  • @chux 在ratio calc 上,优化器可能会处理它。但是,我会试一试。此外,您评论cumu 与否。我看了看,代码可能需要在非 cumu 情况的循环之前增加m。我要出去喝[急需的]咖啡,所以我稍后会解决这个问题。你的想法?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-01
  • 2022-08-04
  • 2018-02-10
  • 2020-12-09
  • 1970-01-01
  • 2015-05-08
相关资源
最近更新 更多