我正在尝试做一些与统计相关的功能,所以我可以执行一些相关的程序(即:概率统计计算,生成任意深度的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));
}
答案 0 :(得分:2)
我认为你有两个选择:
使用大整数库。这样你就不会失去精确度(浮点数可能适用于某些情况,但不是很好的替代品)。
重构您的功能,因此它们不会达到高中间值。例如。 factorial(x)/factorial(y)
是y+1
到x
的所有数字的乘积。所以只需编写一个循环并相乘。这样,如果最终结果溢出,您将只会溢出。
答案 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}}更大值的准确结果。在某些时候,您仍然需要一些数学库来处理任意大的值,但这可以帮助您避免使用较小的输入值。