web-dev-qa-db-fra.com

Calcul de factorielle de grands nombres en C

Dans mon code C, je veux calculer la factorielle pour les nombres compris entre 1 et 100. Pour les petits nombres, la fonction fonctionne, mais pour les plus grands nombres, par exemple 100! il retourne un résultat incorrect. Tout moyen de gérer factorielle de grands nombres dans C?. Le compilateur qui utilise est gcc v4.3.3. Mon code est le suivant:

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

double print_solution(int);

int main(void)
{
        int no_of_inputs,n ;

        int ctr = 1;

        scanf("%d",&no_of_inputs); //Read no of inputs

        do
        {
                scanf("%d",&n); //Read the input

                printf("%.0f\n",print_solution(n));

                ctr++;  

        }while(ctr <= no_of_inputs);


        return 0;       
}

double print_solution(int n)
{
        if(n == 0 || n == 1)
                return 1;
        else
                return n*print_solution(n-1);


}
18
ronk

Aucun type de données C standard ne gérera avec précision des nombres allant jusqu'à 100 !. Votre seule option si vous utilisez arithmétique entière de précision arbitraire , via une bibliothèque ou par vous-même.

S'il ne s'agit que d'un projet de loisir, je vous conseillerais de l'essayer vous-même. C'est un exercice amusant. Si cela est lié au travail, utilisez une bibliothèque préexistante.

Le type de données C le plus important que vous obtiendrez normalement est un entier de 64 bits. 100! est de l'ordre de 10157, qui prend la meilleure partie de 500 bits pour stocker avec précision comme un entier.

37
cletus

100 factorial est énorme, pour être précis c'est 93326215443944152681699238856266700490715968264381621468592963895217

Vous devriez peut-être utiliser une bibliothèque bignum comme GMP . Il y a de beaux documents, une interface assez cohérente, rapide et si vous êtes sous Linux, votre distribution a probablement un paquet (je pense que le mien l'installe par défaut)

17
varzan

Pour calculer approximativement les factorielles de grands nombres, vous pouvez procéder de la manière suivante:

 n! = n * (n-1)! 
 alors log (n!) = log (n) + log (n-1!) 

Maintenant, vous pouvez utiliser la programmation dynamique pour calculer log (n!) Et calculer
n! as (base) ^ (valeur du journal)

14
sud03r

Si vous ne souhaitez pas utiliser une bibliothèque bigint, stdlib se contente d'utiliser long double et tgammal() à partir de math.h:

long double fact(unsigned n)
{
    return tgammal(n + 1);
}

Cela vous donnera 100! avec une précision de 18 décimales sur x86 (ie 80 bits long double).

Une implémentation exacte n'est pas si compliquée non plus:

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

void multd(char * s, size_t len, unsigned n)
{
    unsigned values[len];
    memset(values, 0, sizeof(unsigned) * len);
    for(size_t i = len; i--; )
    {
        unsigned x = values[i] + (s[i] - '0') * n;
        s[i] = '0' + x % 10;
        if(i) values[i - 1] += x / 10;
    }
}

void factd(char * s, size_t len, unsigned n)
{
    memset(s, '0', len - 1);
    s[len - 1] = '1';
    for(; n > 1; --n) multd(s, len, n);
}

int main(void)
{
    unsigned n = 100;
    size_t len = ceill(log10l(tgammal(n + 1)));
    char dstr[len + 1];
    dstr[len] = 0;
    factd(dstr, len, n);
    puts(dstr);
}
9
Christoph

Tout le monde vous dit la bonne réponse, mais quelques points supplémentaires.

  1. Votre idée initiale d'utiliser un double pour obtenir une plage plus large ne fonctionne pas car un double ne peut pas stocker ces données avec précision. Il peut faire les calculs mais avec beaucoup d’arrondi. C'est pourquoi les librairies bigint existent.

  2. Je sais que ceci est probablement un exemple tiré d'un site de tutoriel ou d'exemples, mais une récursion sans limite vous mordra à un moment donné. Vous avez une solution récursive pour ce qui est essentiellement un processus itératif. Vous comprendrez pourquoi ce site porte le même nom lorsque vous essayez d'exécuter votre programme avec des valeurs plus grandes (Try 10000).

Une approche itérative simple est la suivante

  int answer, idx;

  for (answer = 1, idx = 1; idx <= no_of_inputs; idx++ ) {
    answer = answer * idx;
  }
  printf("Factorial of %3d =  %d\n", no_of_inputs, answer);
5
Steve Weet

voici ce que j’ai fait pour résoudre une énigme Google il ya quelques années, elle utilise la bibliothèque GMP http://gmplib.org/ :

#include <stdio.h>
#include "gmp.h"

void fact(mpz_t r,int n){
    unsigned int i;
    mpz_t temp;
    mpz_init(temp);
    mpz_set_ui(r,1);
    for(i=1;i<=n;i++){
        mpz_set_ui(temp,i);
        mpz_mul(r,r,temp);
    }
    mpz_clear(temp);
}
int main(void) {
    mpz_t r;
    mpz_init(r);
    fact(r,188315);
    /* fact(r,100); */
    gmp_printf("%Zd\n",r);
    mpz_clear(r);
    return(0);
}

gcc -lgmp -o fact fact.c

./fait

2
solsTiCe

Des moyens de gérer factorielle de grands nombres en C?

Comme les factorielles peuvent rapidement dépasser la plage des entiers standard à largeur fixe et même des types à virgule flottante tels que double, Code doit envisager un type d'utilisateur permettant une précision entière sans bornes pour une réponse exacte .

Il existe diverses bibliothèques de précision pour les nombres entiers, mais si le code nécessite une solution simple, envisagez l’utilisation de string .

Le ci-dessous n'est pas rapide, ni conscient des limites des tableaux, mais illustre bien l'idée. La conversion de '0'-'9' en/à partir de 0-9 est une perte de temps, mais elle permet un débogage facile, étape par étape.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

static char *strfact_mult(char *s, unsigned x) {
  unsigned sum = 0;
  size_t len = strlen(s);
  size_t i = len;
  while (i > 0) {
    sum += (s[--i] - '0') * x;
    s[i] = sum % 10 + '0';
    sum /= 10;
  }
  while (sum) {
    len++;
    memmove(&s[1], s, len);
    s[i] = sum % 10 + '0';
    sum /= 10;
  }
  return s;
}

char *str_fact(char *dest, unsigned n) {
  strcpy(dest, "1");
  while (n > 1) {
    strfact_mult(dest, n--);
  }
  return dest;
}

void test_fact(unsigned n) { 
  char s[1000];
  printf("%3u %s\n", n, str_fact(s, n));
}

int main(void) {
  test_fact(0);
  test_fact(4);
  test_fact(54);
  test_fact(100);
}

Sortie

  0 1
  4 24
 54 230843697339241380472092742683027581083278564571807941132288000000000000
100 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
1
chux

Si vous souhaitez utiliser uniquement les types de données standard et que vous n'avez pas besoin de la réponse exacte, calculez le logarithme de n! au lieu de n! lui-même. Le logarithme de n! s'intègre facilement dans une double (sauf si n est énorme).

1
Jitse Niesen

vous pouvez essayer le type "unsigned long long", mais c’est le maximum que vous pouvez obtenir avec les types intégrés. Je suggérerais (comme Cletus l’a déjà mentionné) soit d’utiliser une implémentation connue de grands nombres, soit d’en écrire un vous-même. "c'est un bel exercice" x 2.

1
rmn

En plus des conseils des autres, je vous conseillerais de vous familiariser avec les limites de stockage des types de base (int, long, long long, ...) pour l'ordinateur/la plate-forme que vous utilisez réellement. ("En cas de doute, imprimez plus!")

Une précédente affiche faisait référence à une limite de précision de 80 bits, mais cela est propre à un processeur x86. 

Une autre personne a cité ISO C90 à plusieurs reprises, bien que C99 soit la dernière norme en date; même si de nombreux compilateurs n'ont pas complètement implémenté C99, vous constaterez probablement qu'ils ont au moins un support très long, ce qui devrait correspondre à une précision> = 64 bits.

0
Garen

Calculer de grands factoriels sans aucune bibliothèque externe

C'est vraiment un vieux problème. Je vois la plupart des réponses suggérant bibliothèque externe ou résultat approximatif , indiquant la limitation de la mémoire. Mais pensez un peu différemment - vous ne devez pas toujours utiliser integer ou double ou unsigned long long dans la programmation pour faire des mathématiques!


J'ai utilisé int[] pour calculer Big Factorials . Ce petit Java code peut (théoriquement) trouver factoriel de un nombre quelconque -

public class BigFactorial {
    public static int[] calculateFactorial(int inputNumber) {
        int[] factorial = initializeFactorial(inputNumber);

        for(int i=inputNumber-1, j, k; i>0; i--){
            for(j=factorial.length-1, k=0; factorial[j] >= 0; j--){
                k += i*factorial[j];
                factorial[j] = k%10;
                k /= 10;
            }

            factorial[j] = k%10;
            k /= 10;
            factorial[j-1] = k;

            for(j=0; factorial[j]<1; j++){
                factorial[j] = -1;
            }
        }

        return factorial;
    }

    private static int[] initializeFactorial(int inputNumber){

        int digits = (int) Math.ceil(inputNumber*Math.log10(inputNumber/2))+2;
        int[] factorial = new int[digits+1];

        for(int i=0; i<factorial.length; i++){
            factorial[i] = -1;
        }

        for(int j=factorial.length-1, i=inputNumber; i>0; j--){
            factorial[j] = i%10;
            i /= 10;
        }

        return factorial;
    }

    public static void showOutput(int[] factorial){
        int i=0;
        while(factorial[i]<1){
            i++;
        }

        for(; i<factorial.length; i++){
            System.out.print(factorial[i]);
        }
    }

    ///test
    public static void main(String[] args) {
        int inputNumber = 5000;
        showOutput(calculateFactorial(inputNumber));
    }
}
0
Minhas Kamal

Je suppose que c’est parce que vous débordez de la plage int, qui est jusqu’à env. 2 milliards Vous pouvez obtenir jusqu'à 4 milliards si vous utilisez unsigned int, mais au-delà, vous devez utiliser la bibliothèque bigint .

0
Stefano Borini

100! = 933262154439441526816992388562667004907159282643816216628985929

Vous ne pouvez pas représenter un nombre aussi grand avec un int ou un long.

0
gshauger

Ceci est certainement dû à un débordement. Vous avez besoin d'un moyen de représenter de grands nombres (unsigned long long ne couvrira même pas jusqu'à 25!).

0
Michael Foukarakis