Calcul factoriel rapide.

Résolu/Fermé
tony624 Messages postés 128 Date d'inscription dimanche 27 avril 2008 Statut Membre Dernière intervention 14 septembre 2011 - Modifié par tony624 le 6/03/2011 à 18:54
tony624 Messages postés 128 Date d'inscription dimanche 27 avril 2008 Statut Membre Dernière intervention 14 septembre 2011 - 7 mars 2011 à 21:11
Bonjour,

Voilà, je suis en train de faire un projet sur les calculs en précision infinie, je peux donc gérer des nombres très très grand à l'aide d'un stockage des chiffres dans une liste de caractère. J'ai donc redéfini chaque opérateur. (peut être que le problème vient de là).
Mais voilà, lorsque je calcul une factorielle très grande (je suis allé jusqu'à 17000!) c'est vraiment très long, pour cela je fais juste une boucle toute simple.
Quelqu'un connaîtrait un algorithme plus rapide pour effectuer ce calcul ou alors une méthode de stockage plus performante.

Merci.

Ps: j'ai quelques connaissances en informatique n'hésitait pas à aller dans les détails.

6 réponses

KX Messages postés 16752 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 août 2024 3 018
Modifié par KX le 6/03/2011 à 23:57
Première remarque : on ne sait pas dans quel langage tu codes !
Ensuite pour commencer, regardes mon post (ici) écrit il y a deux semaines.

Sinon une idée pourrait être de ne pas calculer (n+2)! = (n+2)*[(n+1)*[n!]]
Mais plutôt de calculer (n+2)! = [(n+2)*(n+1)]*n!

La différence est subtile mais on peut gagner pas mal de calculs comme ça, en effet n étant un "petit" nombre (un long suffit) alors (n+2)*(n+1) se calcule rapidement, ainsi au lieu de faire deux multiplications de grands nombres on ne fait plus qu'une multiplication de grand nombre et une multiplication de petit nombre.

Maintenant je vais manipuler un peu les maths pour gagner encore plus en remplaçant des opérations sur des grands nombres par des opérations moins coûteuses sur des petits nombres.

(n+4)! = (n+4)*[(n+3)*[(n+2)*[(n+1)*[n!]]]] // 4 additions de petits nombres
                                            // 4 multiplications de gds nombres
       = [[(n+4)*(n+1)]*[(n+2)*(n+3)]]*n!
       = [(n²+5n+4)*(n²+5n+6)]*n!
       = [[n*(n+5)+4]*[n*(n+5)+6)]]*n!

     m = n*(n+5) // 1 addition et 1 multiplication de petits nombres
(n+4)! = [(m+4)*(m+6)]*n! // 2 additions et une multiplication de petits nombres
                          // 1 seule multiplication de grands nombres


Limites de ma méthode :

Quand je dis des petits nombres, je parle de long en Java (ou long int en C), ce qui nous laisse la liberté de calculer n! avec n allant jusqu'à N=2^63, voire N=2^64 si on considère les types non signés...

Vu les calculs que j'ai fait au-dessus, je dois m'assurer que tous les résultats obtenus par mes multiplications de petits nombres soient inférieurs à cette limite N.
Donc : (n+4)*(n+3)*(n+2)*(n+1) < N
Du coup cette méthode nous limite à ne pas dépasser n=2^15.75 pour N=2^63 et n=2^16 pour N=2^64.

Au delà il faudra se limiter à ma première simplification (n+2)!=(n+1)*(n+2)
Et ce jusqu'à n=2^31.5 pour N=2^63 et jusqu'à n=2^32 pour N=2^64

Evidemment, on peut également implémenter une méthode intermédiaire :
(n+3)! = [(n+3)*(n+2)*(n+1)]*n!
Dans ce cas on a n=2^21 pour N=2^63 et n=2^21.33 pour N=2^64
La confiance n'exclut pas le contrôle
4
ccm81 Messages postés 10893 Date d'inscription lundi 18 octobre 2010 Statut Membre Dernière intervention 29 septembre 2024 2 421
Modifié par ccm81 le 6/03/2011 à 21:29
bonsoir,
un debut peut être
on doit pouvoir recupérer le nombre de 0 provenant de multiples de 10 dans n, par exemple si n = 100, il y a 11 zéros provenant des multiples de 10
on peut aller plus loin avec des 0 provenant des 5x2
bon courage
1
tony624 Messages postés 128 Date d'inscription dimanche 27 avril 2008 Statut Membre Dernière intervention 14 septembre 2011
6 mars 2011 à 20:24
Merci, mais j'ai déjà testé.

Pour information, j'ai mis 300s pour 6000!.
0
tony624 Messages postés 128 Date d'inscription dimanche 27 avril 2008 Statut Membre Dernière intervention 14 septembre 2011
Modifié par tony624 le 6/03/2011 à 22:32
Oui, c'est vrai que je gagnerais du temps si je n'avais pas les zéros à gérer.

Je vais chercher de ce coté mais je suis ouvert à d'autres propositions.

Merci.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
tony624 Messages postés 128 Date d'inscription dimanche 27 avril 2008 Statut Membre Dernière intervention 14 septembre 2011
Modifié par tony624 le 6/03/2011 à 23:32
Désolé KX, ta réponse n'apparaissait pas. Dommage parce qu'elle est très intéressante. Je viens de finir la gestion des zéros et je sais pas pourquoi le calcul est beaucoup plus long. Ce qui n'est pas logique. Mais bon de toute façon ta méthode à l'air très bien je vais y regarder de plus prés.

Je code en C++. Et j'utilise ma propre classe d'entier non signé.

J'étais déjà passé par ce post.

Merci.
0
KX Messages postés 16752 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 août 2024 3 018
6 mars 2011 à 23:50
Je pense que ma réponse n'apparaissait pas car je n'ai pas arrêté de la modifier pour essayer d'être le plus précis et le plus complet possible. Mais ma réponse telle qu'elle est maintenant me convient et je ne devrais plus y toucher ;-)

Evidemment toute ma méthode repose sur l'alternance entre les calculs avec ta classe d'entier (plus lente mais dont les valeurs sont bien plus grandes) et les calculs standards (très rapides mais assez vite limités en amplitude) et sur quelques astuces de maths élémentaires...
0
tony624 Messages postés 128 Date d'inscription dimanche 27 avril 2008 Statut Membre Dernière intervention 14 septembre 2011
Modifié par tony624 le 6/03/2011 à 23:56
Je n'avais pas compris pour l'alternance. Ma classe ne peut pas interagir avec les types primitifs.
Du coup ça mets le même temps voir même un peu plus lent 327 au lieu de 300 pour 6000
0
KX Messages postés 16752 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 août 2024 3 018
Modifié par KX le 7/03/2011 à 00:22
Je ne sais pas comment tu as fait ta classe, mais il doit bien y avoir une méthode ou mieux un constructeur qui te créé un grand nombre à partir d'une valeur.
J'avoue que ça me paraît être un minimum pour une classe de grand nombre...

Remarque : si ta méthode n'existe pas tu peux toujours la créer, c'est très facile.
Je suppose que tu as tes deux valeurs "Zero" et "Un" (c'est généralement comme ça que l'on fait)

const Entier Deux = Un + Un; 

Entier convertir(const unsigned long int n) 
{ 
  if (n<2) 
    return (n==0) ? Zero : Un; 
  else 
    return Deux*convertir(n/2) + (n%2==0) ? Zero : Un; 
}

Entier e = convertir(12);
0
tony624 Messages postés 128 Date d'inscription dimanche 27 avril 2008 Statut Membre Dernière intervention 14 septembre 2011
Modifié par tony624 le 7/03/2011 à 01:24
Je viens de tester cette méthode et elle fonctionne mais je pense que je serai limité quand même.
0
KX Messages postés 16752 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 août 2024 3 018
Modifié par KX le 7/03/2011 à 07:37
Tout dépend après de ton implémentation des grands nombres, il faut tout optimiser !
En général quand on parle de grand nombres on parle de grandes bases, dans ta liste utilises-tu tous les caractères (base 256) ou juste les 10 premiers ?
Pour le produit as-tu penser à utiliser un algoritme efficace (Karatsuba, ou Toom-Cook par exemple) ?
Et ta somme ? C'est moins important pour le calcul de la factorielle, mais il faut y penser...

Remarque : pour l'algo de 'Entier convertir(long)' on peut encore l'optimiser en utilisant une méthode 'Entier doubler(Entier)' optimisée plutôt que d'utiliser la multiplication générique par Deux.
0
Patrice33740 Messages postés 8556 Date d'inscription dimanche 13 juin 2010 Statut Membre Dernière intervention 2 mars 2023 1 778
6 mars 2011 à 19:09
On peut utiliser un calcul récursif :
n! = n x (n-1)! (avec si n=0, n!=1)
-1
KX Messages postés 16752 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 août 2024 3 018
Modifié par KX le 6/03/2011 à 23:12
Le calcul récursif est mauvais pour de très grand nombres, la pile d'appel récursive n'est pas suffisamment grande pour se permettre d'aller trop loin !
En Java par exemple on ne peux guère dépasser n=25000 (voir mon post : ici)
0