> Théorie |
PrograZine issue #9 - http://www.citeweb.net/discase/9/GDNOMBRE.htm | Edito | Sommaire | Contribution | Contacts |
Calcul avec les grands nombres
par Loubail - loubail@club-internet-fr http://www.multimania.com/loubail/ |
Initié | C |
Salut a tous, Dans cet article je vais vous expliquer comment effectuer des calculs avec les grands nombres ( plutot les tres grands nombres). La grandeur de ces nombres sera seulement limitee par la memoire disponible sur votre ordinateur ! ;) J’en vois deja qui se disent : super je vais pouvoir calculer les 1000 premieres decimales de PI. ;) Certains d’entre vous peuvent se demander pourquoi j’ai decide d’elaborer un tel article ? Tout d’abord il est tres interessant de mettre en place une librairie permettant d’effectuer ce type de calcul. D’autre part lorsque j’aurai termine ma serie d’article sur ce sujet je vais ecrire une serie d’articles sur la cryptographie afin de vous apprendre a proteger vos programmes. Bon rentrons dans le vif du sujet. Pour ce premier article j’ai decide de commencer par les bases : soit l’addition, la soustraction, la multiplication, la division ( reste + diviseur) avec des nombres positifs.( ce qui represente deja beaucoup) Je suppose que certains d’entre vous ont deja essaye de mettre en place une telle librarie et je suis a peu pres sur que de nombreuses personnes ont tout simplement utilise une librairie adaptee comme Bignum sans rien y comprendre. Cette article va donc, je l’espere, vous permettre de comprendre et de realiser ce type de librairie.
De nos jours les registres de calcul de nos processeurs sont limites a 32Bits. Certains compilateurs permettent de realiser des calculs sur 64Bits. Mais selon mon point de vue la limite de 64 Bits peut facilement devenir un inconvenient ( bien que 2^64=18446744073709551616 ). Dans la vie de tous les jours nous representons nos nombres avec des chiffres appartenant à [0 ; 9]. On parle alors de nombres ecrits en base 10. Soit n un entier naturel ( c’est à dire un entier positif : 0 1 2 3 4 5 6 7 8 9 10 11 ... ) En base N un nombre s’ecrit : ![]() Avec ai un chiffre defini en fonction de i appartenant à [0 ; N [ et j un nombre tel que tous les ai associés à un i > j soit egaux à 0. Exemple : 1121= 1.10^0 + 2.10^1 + 1.10^2 + 1.10^3 (d’ou son ecriture en base 10) Maintenant la notion de base est definie ! C’etait pas tres complique. Pour permettre a un maximum de type de processeurs d’effectuer des calculs avec les grands nombres j’ai decide d’utiliser la base 256. Ainsi tous les processeurs pouvant realiser des calculs sur 16 Bits pourront etre utilise. (Oui 16 Bits et non 8 Bits vous comprendrez plus loin pourquoi). Ainsi les grands nombres seront sous la forme : ![]() Avec ai appartenant à [0 ; 255] Comme nous avons determine de quelle maniere nous allons representer les grands nombres nous allons pouvoir passer a la premiere operation arithmetique : l’addition. Je tiens a signaler que je vais tout d’abord m’occuper des nombres positifs. Puis lorsque j’aurai elabore des algorithmes permettant d’effectuer des additions, soustractions, multiplications et divisions je generaliserais ces operations aux entiers relatifs (nombres postifis et negatifs). (pour les nombres negatifs se sera pour le prochain numero).
Du point de vue de la programmation dans cet article les listings seront rediges en C et pour une meilleur comprehension je n’utiliserai pas de pointeurs mais des tableaux " predefinis ". Evidemment je vous conseille de passer, une fois tout l’article compris, aux pointeurs car le programme sera plus rapide ( ce qui est tres important ;) ;) ) et ne sera pas limite en ce qui concerne la taille des grands nombres ! J’ai donc decide de mettre en place un type ( et non une classe : j’elaborerai une classe lorsque toute la serie d’articles sur ce sujet sera " terminee ") . Donc voici le type bignb :
L’addition est une operation elementaire. Elle represente une excellente initiation au calcul avec les grands nombres compte tenu de sa grande simplicite.
Tout d’abord je tiens a vous dire que j’ai du trouve par moi-meme l’ensemble des algorithmes utilises dans cet article : j’ai essaye d’elaborer des algorithmes suffisamment rapide mais il existe surement des algorithmes plus rapides que ceux-ci. Si vous connaissez des algorithmes plus performant faites les moi parvenir à loubail@club-internet.fr (LIEN) L’algorithme utilise pour l’addition est veritablement simple. Il suffit d’additionner les chiffres de 2 nombres en base 256 un a un de la meme maniere qu’en base 10. Evidemment il y a le probleme avec la retenue. Comment proceder ? C’est ici que la remarque faite precedemment : (Oui 16 Bits et non 8 Bits vous comprendrez plus loin pourquoi) prend son importance. Soit nb1 et nb2 deux grands nombres. Soit nb3 un grand nombre tel que nb3=nb1+nb2 On effectue tout d’abord l’addition membre a membre des chiffres de nb1 et de nb2 ce qui donne : Avec nb1i et nb2i les chiffres de nb1 et nb2 associes a i et j le nombre de chiffres du nombre le plus grand entre nb1 et nb2. ![]() Ensuite il faut effectuer une sorte de remise en forme. Je m’explique : lorsque vous additionnez deux nombres appartenant à [0 ; 255] (8Bits) le résultat appartient à [0 ; 510]. (16 Bits). (D’où l’importance de la remarque citée precedemment !) Comment effectuer une telle remise en forme ? Pour se faire je vais utiliser les congruences. Quoi les congruences c’est quoi ce machin ? Eh bien c’est simple (si on veut ! ;) ) Avant tout pour comprendre ce qu’est la congruence il faut parler de division euclidienne. Je ne vais effectuer aucune demonstration mathematique, vous devrez admettre ceci :
Maintenant voici ce que vous devez admettre en ce qui concerne les congruences :
Bon ben on fait quoi avec ca ? Eh ben on va pouvoir remettre en forme nb3. Comme nb3 doit etre ecrit en base 256 on va effectuer des congruences modulo 256. Chaque chiffre sera verra affectee une valeur V tel que V ![]() ![]() Apres avoir applique cet algorithme il se peut que le nombre de chiffre de nb3 est change s’il y a eu une retenu sur le dernier chiffre. Donc on effectue le test suivant : ![]()
Je vais effectuer un exemple avec des nombres petits (mais pas trop quand meme) pour que vous puissiez verifier sur votre calculatrice (Ti, Casio, HP (j’utilise une HP48 ;) )) ou un logiciel de calcul mathematique (gardez Maple pour plus tard j’en parlerai plus loin dans cet article !)) Bon je pose nb1= 138.256^0 + 18.256^1 + 121.256^2 + 221.256^3 Et nb2= 47.256^0 + 123.256^1 + 145.256^2 Donc nb1 dispose de 4 chiffres et nb2 de 3 chiffres. Ainsi je vais effectuer deja 4 calculs.
Et ensuite la remise en forme
Donc nb3=185.256^0 + 141.256^1 + 10.256^2 + 222.256^3
Je vais desormais optimiser cet algorithme en fusionnant les deux processus : celui d’addition puis celui de remise en forme en un seul processus. (Je parle d’optimisation mais je tiens a preciser que ceci n’est pas une veritable optimisation au sens premier du mot !) On garde toujours nb1 et nb2, et nb3 comme resultat de nb1 + nb2 Et bien on applique ceci : ![]() Comme vous pouvez le constater il faut affecter à nb3 la valeur 0 avant l’application de cet algorithme comme on ajoute a la valeur nb3i une autre valeur.
Bon nous allons desormais passer a quelque chose d’un petit peu plus complique.
Voilà cette derniere ligne de calcul est tres interessante afin de mieux comprendre le processus ( si vous ne l’avez pas encore compris ;) ) je vais vous presenter le calcul sous une autre forme. ![]() Selon mon point de vue cette representation est plus simple a comprendre et resume bien le principe que nous allons incorpore dans notre algorithme. L’algorithme que nous allons utilise va donc tout d’abord elaborer j " tableaux ". Chaque tableau representera un grand nombre. Soit Tab le groupe de tableau et i l’indice du tableau considere. On aura ainsi nb3 = nb1 * nb2 = Tab0 + Tab1 + Tab2 + Tab3 ... Avec Tab0 = a0 * nb2 , Tab1 = a1.256 * nb2, Tab2 = a2.256^2 * nb2, Tab3 = a3.256^3 * nb2, ... Ceci effectue il faudra remettre en forme les nombres associe aux tableaux comme lors de l’addition puis additionner ces nombres entre eux. On obtiendra alors nb3, le resultat de nb1 * nb2. Combien de tableaux doivent etre utilises pour un tel calcul ? Il evident que nous envisageons d’utiliser le moins de tableaux possibles. Donc on pose j le nombre de chiffres du nombre le plus petit entre nb1 et nb2. On utilisera alors j tableaux. Pourquoi j tableaux ? Et bien tout simplement parce que j calculs prendront place ! Je ne vais pas perdre mon temps a vous expliquer pour quelle raison j calculs prennent place car je considere que ceci est evident ( et meme si toute evidence merite d’etre demontree ;) ). Donc apres avoir etabli nos tableaux il faut faire en sorte, comme lors de l’addition, que tous les chiffres du nombre associe au tableau appartiennent a [0 ; 255]. Etant donne que j’ai deja parle de cette methode plus haut il n’est pas necessaire de rentrer dans les details. Je rappelle seulement pour les personnes qui l’auraient oublie qu’on utilise des congruences modulo 256. Enfin on effectue l’addition de tous les tableaux apres avoir effectue leur remise en forme. L’algorithme que nous allons utilise sera legerement different de celui ci car il effectuera a la fois les multiplications intermediaires et les remises en forme comme lors de l’addition.
Ouf ! ! ! ;) C’est termine ! Nous pouvons desormais passe de la " theorie " a la pratique par la programmation. (Enfin ! ;) )
Etant donne que je viens de vous presente l’algorithme a utiliser je vais seulement annote succintement les lignes de code.
On passe desormais a une autre operation elementaire : la soustraction. Comme, pour le moment on ne s’occupe que de nombres positifs nous allons faire en sorte que le resultat soit positif. Le gros probleme de cette operation reside dans les retenues : dans certains cas il ne faut pas oublier de " propager " une retenue. Soit nb1 et nb2 deux nombres tels que nb1>=nb2 et nb3 le resultat de nb1 - nb2 nb1 = a0 + a1.256 + a2.256^2 + a3.256^3 ... et nb2 = b0 + b1.256 + b2.256^2 + b3.256^3 ... nb3 = (a0-b0) + (a1-b1).256 + (a2-b2).256^2 + (a3-b3).256^3 ... Durant le calcul de nb3 differents se presentent :
A ceux qui n’ont rien compris : ne vous affolez pas, je vais donner des exemples concrets.
Premier cas :
Deuxieme cas :
Troisieme cas :
Ici a0<b0 et a1<b1 et a2<b2 et a3>=b3 Une propagation de retenue est necessaire !
J’espere que ces exemples vous ont permi de mieux assimile l’algorithme utilise.
Desormais nous pouvons passer au cas general, qui est beaucoup plus interessant et complique. ;) On a nb1>=nb2. Soit vald un nombre tel que vald>nb2.nomb[nb2.nb] (je rappelle au passage que nb2.nb=nombre de chiffre de nb2 - 1). Plus precisement val est tel que vald=(nb2.nomb[nb2.nb]+1). Par exemple si nb2=13.256^2 + 125.256 + 11 alors vald=14. On pose : nb3=nb1. Ensuite on effectue la difference entre le nombre de chiffre de nb3 et le nombre de chiffre de nb2. (Vous allez comprendre pourquoi !). Soit j3 cette difference. Soit muld un nombre qui representera le quotient. Debut de l’algorithme : Soit mula un nombre defini par mula=(nb3.nomb[nb3.nb]/vald)*256^j3. On a desormais des valeurs tres interessantes. Pourquoi, me direz-vous ? Lorsque vous divisez nb1 par nb2 et que nb1 > nb2 alors on peut au moins soustraire a nb1 z fois la valeur de nb2. (z etant defini comme la division de nb1 par un nombre plus grand que nb2). En partant de ce principe on calcule un z assez proche de la valeur de a que l’on cherche d’ou l’utilite de vald puis de mula. Lorqu’on a calcule mula plusieurs cas se presentent :
Ensuite on pose nb5=mula*nb2
J’ai defini un type nouveau pour le resultat d’une division : le type bigdiv
Une exemple semble etre le bien venu ! ;)
J’espere que cet exemple vous aura permis de mieux comprendre cet algorithme.
Cette fois-ci je vous met le code sans de nombreuses annotations car je pense qu’il n’y a rien a ajoute quand a la comprehension de l’algorithme et donc du code Le seul point qui pourrait vous paraître obscure est la forme de mula et muld : comme mula et muld doivent etre des grands nombres il faut definir leur nombre de chiffre et mettre des 0 ou il faut ! ;)
La comparaison de deux grands est relativement aisee. Soit nb1 et nb2 deux grands nombres
Voici la fonction a laquelle je faisais reference precedemment.
Un nombre egal a 0 est positif et comporte 1 chiffre. Notre grand nombre prendra donc les parametres suivants :
Et on devra mettre a zero tous les chiffres du tableau nomb
Ce premier article sur les calculs avec les grands nombres est termine. Nous avons ainsi elabore des fonctions permettant d’effectuer les 4 operations arithmetiques les plus elementaires avec des nombres positifs. Les prochains articles generaliseront ces calculs aux nombres negatifs decriront un algorithme qui permet d’afficher les grands nombres a l’ecran (passage de la base 256 a la base 10), s’interesseront au pgcd, a bezout, a l’inverse modulo, a la puissance modulo b, etc ... J’espere avoir ete suffisamment clair. Si vous voulez me poser des questions, si vous avez des remarques, si vous voulez me donner votre avis sur cet article ecrivez-moi a : loubail@club-internet.fr(LIEN) ou bailsite@hotmail.com(LIEN) Vous pouvez aussi visiter mon site : http://www.multimania.com/loubail/ (Site en francais - LIEN) ou http://www.geocities.com/SiliconValley/Chip/8195 (Site en anglais - LIEN). |
Cet article est la propriété de Loubail. La copie et la diffusion sont libres sauf dans un but lucratif sans accord explicite de l'auteur. |