计算大数时的计数错误(例如 50!)
Counting error while calculating large number(e.g. 50!)
当我输入小数字时我的代码运行良好,例如 10
选择 2
,但是当涉及到 50
选择 10
时,它的结果是错误的,你能告诉我这里出了什么问题吗?
#include <stdio.h>
long long int factorial(int n);
long long int combn(int n, int k);
int main(void) {
int n = 0;
int k = 0;
printf("Enter n and k:\n");
scanf("%d %d", &n, &k);
combn(n, k);
}
long long int combn(int n, int k) {
long long int C = 0;
C = factorial(n) / (factorial(k) * factorial(n - k));
printf("C %d choose %d = %ld\n", n, k, C);
}
long long int factorial(int n) {
if (n == 1)
return 1;
else
return n * factorial(n - 1);
}
combn(50, 10)
应该是 10272278170
.
你溢出了 long long
容量(而且你的代码混合了 long long
和 int
顺便说一句)
你需要计算 50!这是 ~ 3.10^64,远远高于 max int
(~2^9) 和 max long long int
~9.10^18。
您需要使用一个特殊的大整数库或修改您的算法以不计算溢出值(或不使用大值...)
好像有算法可以计算适合long long而不溢出的组合;看:
Calculating the Amount of Combinations
50!
是一个非常大的数字,几乎需要 150 位来表示,long long
数据类型仅提供 64 位。因此,C 无法按照您的方式进行计算;它溢出了。
为此,您可以使用任意精度的算术包库。这种库表示具有可变位数的数字,并提供不会溢出的操作。
gmp -- the Gnu MP Bignum library,就是这样一个库的例子。还有其他人。以下是您可以如何使用 gmp 进行操作。 (未调试)。
#include "gmp.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(int argc, char * argv[]){
uint n;
uint m;
mpz_t nn;
mpz_t mmn;
mpz_t mmm;
mpz_t denom;
mpz_t result;
char * str;
if (argc <= 2){
printf ("Usage: %s <number> <number> \n", argv[0]);
return 1;
}
n = atoi(argv[1]);
m = atoi(argv[2]);
mpz_fac_ui (nn,n); /* nn = n! */
mpz_fac_ui (mmn,n-m); /* mmn = (n-m)! */
mpz_fac_ui (mmm,m); /* mmm = m! */
mpz_mul(denom, mmm, mmn); /* denom = mmn * mmm */
mpz_fdiv_q(result, nn, denom); /* result = nn / denom */
str = mpz_get_str (null, 10, const mpz_t result);
printf ("deal %d from %d: %s combinations\n", n,m, str);
free (str);
mpz_clear(nn);
mpz_clear(mmm);
mpz_clear(mmn);
mpz_clear(denom);
mpz_clear(result);
return 0;
}
另一种可能性:利用 (n!) / (n-m)!
等于 (m+1 到 n) 整数的乘积这一事实。例如 50!/ 47!
是 48 * 49 * 50
。在许多情况下,这应该使您的整数可以用 64 位表示。而且,更好的是,当你进行这种计算机运算时,你不必执行实际的除法运算,因为它不在公式中。
使用默认公式计算 n
为 n
的中等大值选择 k
涉及超出类型 long long
范围的中间结果。有 2 个解决方案可以解决此问题:
- 使用可以处理如此大数字的 bignum 包
- 枚举因数并消除除数以将计算减少为一系列乘法。
这是后一种方法的修改版本:
#include <limits.h>
#include <stdio.h>
unsigned long long int combn(int n, int k) {
if (k < 0 || n < 0 || k > n)
return 0;
// minimize computations
if (k > n - k)
k = n - k;
int factors[k];
// initialize factors of n! / (n - k)!
for (int i = 0; i < k; i++)
factors[i] = n - i;
for (int i = k; i > 1; i--) {
// find the multiple of i, divide it by i
for (int j = 0; j < k; j++) {
if (factors[j] % i == 0) {
factors[j] /= i;
break;
}
}
}
// compute result
unsigned long long int C = 1;
for (int i = 0; i < k; i++) {
if (C > ULLONG_MAX / factors[i])
return ULLONG_MAX;
C = C * factors[i];
}
return C;
}
int main(void) {
int n, k;
printf("Enter n and k: ");
if (scanf("%d %d", &n, &k) == 2) {
unsigned long long C = combn(n, k);
if (C == ULLONG_MAX)
printf("overflow\n");
else
printf("%d choose %d is %llu\n", n, k, C);
}
return 0;
}
输出:
50 choose 10 is 10272278170
当我输入小数字时我的代码运行良好,例如 10
选择 2
,但是当涉及到 50
选择 10
时,它的结果是错误的,你能告诉我这里出了什么问题吗?
#include <stdio.h>
long long int factorial(int n);
long long int combn(int n, int k);
int main(void) {
int n = 0;
int k = 0;
printf("Enter n and k:\n");
scanf("%d %d", &n, &k);
combn(n, k);
}
long long int combn(int n, int k) {
long long int C = 0;
C = factorial(n) / (factorial(k) * factorial(n - k));
printf("C %d choose %d = %ld\n", n, k, C);
}
long long int factorial(int n) {
if (n == 1)
return 1;
else
return n * factorial(n - 1);
}
combn(50, 10)
应该是 10272278170
.
你溢出了 long long
容量(而且你的代码混合了 long long
和 int
顺便说一句)
你需要计算 50!这是 ~ 3.10^64,远远高于 max int
(~2^9) 和 max long long int
~9.10^18。
您需要使用一个特殊的大整数库或修改您的算法以不计算溢出值(或不使用大值...)
好像有算法可以计算适合long long而不溢出的组合;看: Calculating the Amount of Combinations
50!
是一个非常大的数字,几乎需要 150 位来表示,long long
数据类型仅提供 64 位。因此,C 无法按照您的方式进行计算;它溢出了。
为此,您可以使用任意精度的算术包库。这种库表示具有可变位数的数字,并提供不会溢出的操作。
gmp -- the Gnu MP Bignum library,就是这样一个库的例子。还有其他人。以下是您可以如何使用 gmp 进行操作。 (未调试)。
#include "gmp.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(int argc, char * argv[]){
uint n;
uint m;
mpz_t nn;
mpz_t mmn;
mpz_t mmm;
mpz_t denom;
mpz_t result;
char * str;
if (argc <= 2){
printf ("Usage: %s <number> <number> \n", argv[0]);
return 1;
}
n = atoi(argv[1]);
m = atoi(argv[2]);
mpz_fac_ui (nn,n); /* nn = n! */
mpz_fac_ui (mmn,n-m); /* mmn = (n-m)! */
mpz_fac_ui (mmm,m); /* mmm = m! */
mpz_mul(denom, mmm, mmn); /* denom = mmn * mmm */
mpz_fdiv_q(result, nn, denom); /* result = nn / denom */
str = mpz_get_str (null, 10, const mpz_t result);
printf ("deal %d from %d: %s combinations\n", n,m, str);
free (str);
mpz_clear(nn);
mpz_clear(mmm);
mpz_clear(mmn);
mpz_clear(denom);
mpz_clear(result);
return 0;
}
另一种可能性:利用 (n!) / (n-m)!
等于 (m+1 到 n) 整数的乘积这一事实。例如 50!/ 47!
是 48 * 49 * 50
。在许多情况下,这应该使您的整数可以用 64 位表示。而且,更好的是,当你进行这种计算机运算时,你不必执行实际的除法运算,因为它不在公式中。
使用默认公式计算 n
为 n
的中等大值选择 k
涉及超出类型 long long
范围的中间结果。有 2 个解决方案可以解决此问题:
- 使用可以处理如此大数字的 bignum 包
- 枚举因数并消除除数以将计算减少为一系列乘法。
这是后一种方法的修改版本:
#include <limits.h>
#include <stdio.h>
unsigned long long int combn(int n, int k) {
if (k < 0 || n < 0 || k > n)
return 0;
// minimize computations
if (k > n - k)
k = n - k;
int factors[k];
// initialize factors of n! / (n - k)!
for (int i = 0; i < k; i++)
factors[i] = n - i;
for (int i = k; i > 1; i--) {
// find the multiple of i, divide it by i
for (int j = 0; j < k; j++) {
if (factors[j] % i == 0) {
factors[j] /= i;
break;
}
}
}
// compute result
unsigned long long int C = 1;
for (int i = 0; i < k; i++) {
if (C > ULLONG_MAX / factors[i])
return ULLONG_MAX;
C = C * factors[i];
}
return C;
}
int main(void) {
int n, k;
printf("Enter n and k: ");
if (scanf("%d %d", &n, &k) == 2) {
unsigned long long C = combn(n, k);
if (C == ULLONG_MAX)
printf("overflow\n");
else
printf("%d choose %d is %llu\n", n, k, C);
}
return 0;
}
输出:
50 choose 10 is 10272278170