计算大数(例如50!)时计数错误

时间:2019-04-14 12:55:08

标签: c long-integer factorial

当我输入小的数字(例如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

3 个答案:

答案 0 :(得分:1)

您溢出了long long的容量(并且您的代码正在混合long longint BTW)

您需要计算50!这是〜3.10 ^ 64,因此远高于max int(〜2 ^ 9)和max long long int,后者约为〜9.10 ^ 18。 您需要使用特殊的大整数库或重新计算算法,以免计算溢出值(或不使用大值...)

似乎有一种算法可以计算很长很长时间而不会溢出的组合;看到: Calculating the Amount of Combinations

答案 1 :(得分:1)

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位。而且,当您执行这种计算机算术时,甚至更好,您不必执行实际的除法运算,因为它不符合公式。

答案 2 :(得分:0)

计算n时,对于k的较大值,请选择n,其默认公式的中间结果超出类型long long的范围。有两种解决此问题的方法:

  • 使用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