二项式系数舍入误差

时间:2016-09-30 20:04:11

标签: c rounding binomial-coefficients

我必须计算表达式的c二项式系数 (x + y)** n,n非常大(500-1000的数量级)。我想到的计算二项式系数的第一个算法是multiplicative formula。所以我将它编码为我的程序

long double binomial(int k, int m)
{
    int i,j;
    long double num=1, den=1;
    j=m<(k-m)?m:(k-m);
    for(i=1;i<=j;i++)
    {
        num*=(k+1-i);
        den*=i;
    }
    return num/den; 
}

这个代码在单个核心线程上非常快,例如与recursive formula进行比较,尽管后者不太容易出现舍入错误,因为只涉及求和而不是除法。 因此,我想测试这些算法以获得很好的价值并尝试评估500选择250(订购10 ^ 160)。我发现&#34;相对错误&#34;小于10 ^( - 19),所以基本上它们是相同的数字,虽然它们有10 ^ 141的不同。

所以我想知道:有没有办法评估计算错误的顺序?是否有一些快速的方法来计算二项式系数,这比乘法公式更精确?由于我不知道算法的准确性,因此我不知道在哪里截断斯特林系列以获得更好的结果。

我已经搜索了一些二项式系数表,所以我可以从这些表中复制,但我发现的最好的一个在n = 100时停止......

4 个答案:

答案 0 :(得分:1)

要获得小km的精确整数结果,可能会有更好的解决方案(代码略有不同):

  unsigned long binomial(int k, int m)
  {
   int i,j; unsigned long num=1;
   j=m<(k-m)?m:(k-m);
   for(i=1;i<=j;i++)
   {
    num*=(k+1-i);
    num/=i;
   }
   return num;
  }

每次在进行除法num/=i后获得组合数字时,您都不会被截断。要获得较大km的近似结果,您的解决方案可能会很好。但要注意long double乘法已经比整数的乘法和除法慢得多(unsigned longsize_t)。如果你想要更精确地获得更大的数字,可能必须对一个大整数class进行编码或包含在库中。如果有n!极大整数n的快速因子算法,您也可以谷歌搜索。这对组合学也有帮助。当ln(n!)很大时,斯特林公式是n的良好近似值。这一切都取决于你想要的准确程度。

答案 1 :(得分:1)

如果你真的想使用乘法公式,我建议采用基于异常的方法。

  1. 使用大整数(例如long long)实现公式
  2. 尽快尝试分部行动(卓卓建议)
  3. 添加代码以检查每个除法和乘法的正确性
  4. 解决不正确的分割或乘法,例如
    • 尝试卓然提出的循环划分,但如果失败则采用初始算法(积累除数的乘积)
    • 将未解析的乘数,除数存储在其他长整数中,并尝试在下一个迭代循环中解决它们
  5. 如果你真的使用大数字,那么你的结果可能不适合长整数。那么在这种情况下你可以切换到long double或使用你的个人LongInteger存储。
  6. 这是一个骨架代码,可以给你一个想法:

    long long binomial_l(int k, int m)
    {
        int i,j;
        long long num=1, den=1;
        j=m<(k-m)?m:(k-m);
        for(i=1;i<=j;i++)
        {
            int multiplier=(k+1-i);
            int divisor=i;
            long long candidate_num=num*multiplier;
            //check multiplication
            if((candidate_num/multiplier)!=num)
            {
                //resolve exception...
            }
            else
            {
                num=candidate_num;
            }
    
            candidate_num=num/divisor;
            //check division
            if((candidate_num*divisor)==num)
            {
                num=candidate_num;
            }
            else
            {
                //resolve exception
                den*=divisor;
                //this multiplication should also be checked...
            }
        }
        long long candidate_result= num/den; 
        if((candidate_result*den)==num)
        {
            return candidate_result;
        }
        // you should not get here if all exceptions are resolved
        return 0;
    }
    

答案 2 :(得分:1)

如果您只计算单个二项式系数C(n,k)n相当大但不大于约1750,那么使用一个像样的C库最好的方法是使用{{3}标准库函数:

tgammal(n+1) / (tgammal(n-k+1) * tgammal(k+1))

使用libm的Gnu实现进行测试,该结果始终在精确值的几个ULP内产生结果,并且通常优于基于乘法和除法的解决方案。

如果k足够小(或大),二项式系数不会溢出64位精度,那么你可以通过交替乘法和除法得到精确的结果。

如果n如此之大以至于tgammal(n+1)超出了长双倍(超过1754)的范围,但又没有那么大以至于分子溢出,那么乘法解是最好的一个bignum图书馆。但是,您也可以使用

expl(lgammal(n+1) - lgammal(n-k+1) - lgammal(k+1))

不太精确但更容易编码。 (此外,如果系数的对数对您有用,则上述公式将适用于相当大的n和k范围。不必使用expl将提高准确性。)

如果您需要一系列具有相同n值的二项式系数,那么您最好的选择是迭代加法:

void binoms(unsigned n, long double* res) {
  // res must have (n+3)/2 elements
  res[0] = 1;
  for (unsigned i = 2, half = 0; i <= n; ++i) {
    res[half + 1] = res[half] * 2;
    for (int k = half; k > 0; --k)
      res[k] += res[k-1];
    if (i % 2 == 0)
      ++half;
  }
}

以上仅产生k从0到n / 2的系数。它具有比乘法算法略大的舍入误差(至少当k接近n / 2时),但如果你需要所有系数并且它具有更大范围的可接受输入,它会快得多。

答案 3 :(得分:0)

这可能不是OP正在寻找的,但是可以通过二元熵函数分析近似nCr的大n。它在

中提到