使用C中的置换(nPr,nCr)函数避免整数溢出

时间:2012-06-13 13:38:08

标签: c math statistics

我正在尝试做一些与统计相关的功能,所以我可以执行一些相关的程序(即:概率统计计算,生成任意深度的Pascal三角形等)。

我遇到了一个问题,我可能会遇到溢出问题。例如,如果我想计算nPr(n = 30,p = 1),我知道我可以将它减少到:

30P1 = 30! / (30 - 1)!
     = 30! / (29)!
     = 30! / 29!
     = 30

但是,在使用下面的函数进行计算时,由于整数溢出,我看起来总是会得到无效值。是否有任何变通方法不需要使用库来支持任意大数字?我已经在其他关于伽玛函数的帖子中读过一些内容,但找不到具体的例子。

int factorial(int n) {
   return (n == 1 || n == 0) ? 1 : factorial(n - 1) * n;
}


int nCr(int n, int r) {
   return (nPr(n,r) / factorial(r));
   //return factorial(n) / factorial(r) / factorial(n-r));
}


int nPr(int n, int r) {
   return (factorial(n) / factorial(n-r));
}

5 个答案:

答案 0 :(得分:2)

我认为你有两个选择:

  1. 使用大整数库。这样你就不会失去精确度(浮点数可能适用于某些情况,但不是很好的替代品)。

  2. 重构您的功能,因此它们不会达到高中间值。例如。 factorial(x)/factorial(y)y+1x的所有数字的乘积。所以只需编写一个循环并相乘。这样,如果最终结果溢出,您将只会溢出。

答案 1 :(得分:1)

如果您不必处理签名值(并且看起来没有这样做),您可以尝试使用更大的整数类型,例如unsigned long long。如果这还不够,则需要使用支持任意长整数的非标准库。请注意,使用long long类型需要C99编译器支持(如果使用GCC,可能必须使用-std=c99进行编译)。

编辑:您可以在long double中使用更多内容,在某些系统上为80位。

答案 2 :(得分:1)

我可能会很紧张,但在我看来,去double并且伽玛函数在这里是过度的。

  

是否有任何变通办法不需要使用库来支持任意大数字?

当然有。你完全知道你在处理什么 - 整数范围的产品。一系列整数是有限整数列表的特例。我不知道用C表示列表的惯用方法是什么,所以我会坚持使用C-ish伪代码:

make_list(from, to)
    return a list containing from, from+1, ..., to

concatenate_lists(list1, list2)
    return a list with all the elements from list1 and list2

calculate_list_product(list)
    return list[0] * list[1] * ... * list[last]

calculate_list_quotient(numerator_list, denominator_list)
    /* 
    be a bit clever here: form the product of the elements of numerator_list, but
    any time the running product is divisible by an element of denominator_list,
    divide the running product by that element and remove it from consideration
    */

n_P_r(n, r)
   /* nPr is n! / (n-r)! 
      which simplifies to n * n-1 * ... * r+1
       so we can just: */
   return calculate_list_product(make_list(r+1, n)) 

n_C_r(n, r)
   /* nCr is n! / (r! (n-r)!) */
    return calculate_list_quotient(
        make_list(1, n), 
        concatenate_lists(make_list(1, r), make_list(1, n-r))
    )

请注意,我们实际上从未计算过阶乘!

答案 3 :(得分:1)

这是一种不使用伽玛函数计算的方法。 它依赖于n_C_r =(n / r)*((n-1) C (r-1))的事实 对于任何正值,n_C_0 = 1 所以我们可以用它写一个recusrive函数,如下面的

public long combination(long n, long r) {
    if(r==0)
        return 1;
    else {
        long num = n * combination(n - 1, r - 1);
        return num/r;
    }
}

答案 4 :(得分:0)

你看起来像是在正确的轨道上,所以你走了:

#include <math.h>
#include <stdio.h>

int nCr(int n, int r) {
   if(r>n) {
      printf("FATAL ERROR"); return 0;
     }       
   if(n==0 || r==0 || n==r) {
      return 1;
   } else {
      return (int)lround( ((double)n/(double)(n-r)/(double)r) * exp(lgamma(n) - lgamma(n-r) - lgamma(r)));
   }
}


int nPr(int n, int r) {
   if(r>n) {printf("FATAL ERROR"; return 0;}
   if(n==0 || r==0) {
      return 1;
   } else {
      if (n==r) {
         r = n - 1;
      }
      return (int)lround( ((double)n/(double)(n-r)) * exp(lgamma(n) - lgamma(n-r)));
   }
}

要编译,请执行:gcc -lm myFile.c && ./a.out

请注意,结果的准确性受double数据类型的位深度限制。您应该能够获得良好的效果,但要注意:将int上面的long long unsigned替换为n,r,可能无法保证{{1}}更大值的准确结果。在某些时候,您仍然需要一些数学库来处理任意大的值,但这可以帮助您避免使用较小的输入值。