|
|
T
itre : Mathématiques et Grands Nombres.A
uteur : Emmanuel PIERRE
Tout ceux qui, un jour, ont eu à rendre un programme, ou pire à faire pour sa propre satisfaction, un programme utilisant des fonctions mathématiques utilisant des grands nombres, se sont peut-être heurtés au problème des limitations de type du Turbo Pascal. Grande nouveauté avec le Borland Pascal 7, le format
COMP, mais les nombres sont encore très limités.Suite à un projet de recherche de grands nombres premiers, qui sont tous positifs, car je ne parlerai que de grands nombres positifs, la première chose que mon binôme à faite, à été d'utiliser les listes, dont il avait l'habitude. Mais tout ceux qui sont habitués à utiliser le Pascal sur des stations VAX/VMS se retrouvent marris devant leurs pauvres PC quand ils s'aperçoivent de la terrible limitation de leur système en ressource dynamique : le Pascal ne désallouant pas après un appel ( j'envoie 2 listes, et j'en renvoie une dans la 1e i.e
. NA := add( NA , NB ), l'ancien NA reste en mémoire ).Dans ce cas-là, que faire ? Passer en mode protégé est une solution, mais quand on utilise des routines asm, tout recalculer c'est la galère... , ou tout bêtement créer de nouvelles fonctions de grands nombres.
C'est donc cette méthode que j'ai choisie pour contourner cet écueil, et dont la seule limitation était la mémoire dynamique du système ( on peut toujours y appliquer le mode protégé ). L'idée est simple, deux tableaux en l'occurrence pour une suite de fibonnacci, tous deux de 32000 cases de
byte, puis créer de nouvelles fonctions d’addition, de soustraction... Avantage immédiat : une case de liste fait 5 byte, une de tableau 1, d'où un gain conséquent de mémoire.Le type est défini juste ici :
type Numtab = array[1..32000] of byte; BIGnum = record t : ^numtab; s : integer; end;
Le record Bignum a deux champs, le premier T le pointeur sur tableau de byte, et le deuxième, la taille du nombre contenu dans T, très utile pour optimiser le calcul.
Pour les utiliser, on les crée par :
var NA : BIGnum; (* les définit *) begin new(NA.T);(* crée ce nombre, A NE JAMAIS OUBLIER SOUS PEINE DE MORT *) init_BN(NA);(* on le met à 0 *) add_c(NA,2); (* on met 2 dans NA, c.f. plus loin... ); release(NA.T);(* détruit NA *) end;Voyons les fonctions mathématiques de base :
On initialise en mettant des zéro partout et en mettant la taille à 0 :
procedure init_BN(var ptr:BIGnum); (* Initialise un grand nombre a zéro *) var i : integer; begin for i := 1 to 32000 do ptr.t^ [i] := 0; ptr.s := 0; end;
Renvoyer le plus grand est souvent nécessaire pour deux Integers :
function max(i,j:integer):integer; (* Renvoie le plus grand des Integers *) begin if i>j then max := i else max := j; end;
Il faut déjà implémenter une fonction pour savoir si on a affaire à un nombre nul, on sait jamais, mais ça va servir, je vous le promets :
function est_nul(NA:BIGnum):boolean; begin est_nul := (NA.s=0) OR ( (NA.s=1) AND (NA.t^[1]=0) ) end;
Première procédure intéressante après l'initialisation, l'addition de base, c'est à dire celle de plus bas niveau, l'addition par une constante inférieure à 10. (C.f. cours de numération ). J'appelle par la suite BN un Big Number, un grand nombre.
procedure add_c(var NA:BIGnum;cst:byte); var ret : byte; (* la retenue *) i : integer; (* le compteur *) c : integer; (* le calcul intermédiaire *) Begin ret := 0; (* pas de retenue au départ *) if not((cst=0) or est_nul(NA)) then (* zéros, rien a traiter *) for i := 1 to na.s+1 do (* on va de 1 a n+1, en cas de retenue *) begin if i>1 then cst := 0; (* idiot, mais utile pour optimiser *) c := NA.t^[i] + (cst + ret); (* valeur actuelle plus cst si*) (* premier rang, et retenue si il y a lieu *) ret := 0; (* on l'a utilisée, on la retire *) if (c>=10) then (* si il y a retenue dans notre calcul *) begin ret := 1; (* on la met pour le prochain tour *) c := c-10; (* on retire de c 10 *) end; NA.t^[i] := c;(* on met dans la position du BN, la valeur c*) if (ret=0) then (* plus de retenue, on peut sortir *) exit; (* allez, hop on sort, c'est fini *) end; (* fin du FOR BEGIN *) if (NA.t^[na.s+1]<>0) then inc(NA.s); (* si le BN a grandi *) end;
Et maintenant l'addition complète :
procedure add(var NA, NB, ND : BIGnum); var ret : byte; i : integer; c : integer; Begin ret := 0; (* pas de retenue *) if not(est_nul(NB) or est_nul(NA)) then (* des 0, on sort *) begin for i := 1 to max(NA.s,NB.s)+1 do(* on calcule au plus grand *) (* des deux+1 si retenue *) begin c := NA.t^[i] + NB.t^[i] + ret; ret := 0; if c>=10 then begin ret := 1; c := c - 10; end; ND.t^[i] := c; end; if (ND.t^[i]<>0) then ND.s := i(* si il y a du monde en n+1 *) else if (i>1)and(ND.t^[i-1]<>0) then ND.s := i - 1;(* sinon*) end else ND:=NA; end;
Rien de bien nouveau et pas d'astuces, mais voyons la soustraction d'une constante :
procedure sous_c(var NA : BIGnum;cst : byte); var ret : byte; i : integer; c : integer; Begin ret := 0; (* pas de retenue *) if not est_nul(NA)and(cst<>0) then (* si NA est nul, rien a faire *) for i := 1 to NA.s do begin if i>1 then cst := 0; (* c.f. add_c *) if (NA.t^[i]<(cst+ret)) and (i<na.s) then begin (* si on retire plus qu'il n'y a, retenue *) NA.t^[i] := 10 + NA.t^[i] - ( cst + ret ); ret := 1; end else begin (* sinon simple soustraction, et fin de calcul *) (* car plus rien a retirer*) NA.t^[i] := NA.t^[i] - ( ret + cst ); ret := 0; if (na.s=i) then (* dernière case *) if (i>0) and (NA.t^[i]=0) then dec(NA.s); (* si pas exit; end end; if (NA.t^[i]<>0) then NA.s := i (* si taille inchangée *) else if (i>1) and (NA.t^[i-1]<>0) then NA.s := i-1;(* sinon *) end;
Ici une optimisation, renvoie le plus grand, comme on travaille seulement en nombres positifs, donc on soustrait le plus grand au plus petit :
function greater(NA,NB:BIGnum):boolean; var i ret : byte; i : integer; c : integer; Begin if NA.s>NB.s then (* on commence par regarder la taille *) greater:=true else if NA.s<NB.s then greater:=false else (* et après comparaison case a case *) begin i:=max(na.s,nb.s); (* taille max *) while (NA.t^[i]=NB.t^[i])and(i>0) do (* boucle tant qu'egaux*) dec(i); (* sens décroissant, c'est plus rapide *) if (NA.t^[i]>=NB.t^[i]) then greater:=true else greater:=false; end; end;
Et maintenant la soustraction, sous vos applaudissements:
procedure sous(var NA,NB,ND:BIGnum); var ret : byte; i : integer; c : integer; Begin ret := 0; if est_nul(NB) then ND:=NA else for i := 1 to max(na.s,nb.s) do (* le parcours *) begin if (NA.t^[i]< (NB.t^[i]+ret) ) then (* A<B+retenue *) begin ND.t^[i] := 10 + NA.t^[i] - ( NB.t^[i] + ret ); ret := 1; end else (* A>B+retenue *) begin ND.t^[i] := NA.t^[i] - ( NB.t^[i] + ret ); ret := 0; end; end; if (ND.t^[i]<>0) then ND.s := i else if (i>1) and (ND.t^[i-1]<>0) then ND.s := i - 1; end;
Voyons maintenant d’autres fonctions plus diverses, par exemple pour savoir si il y a reste dans la division de A par B :
function modulozero(var NA,NB:BIGnum):boolean; var naa : BIGnum; Begin new(NAa.t); (* on crée une nouvelle *) init_BN(NAa); (* on l'initialise *) add(NA,NAa,NAa); (* on met NA=NAa *) while greater(NAa,NB) do (* puis on soustrait NB a Naa tant que *) sous(NAa,NB,NAa); (* Naa>NB *) modulozero:= not (is_void(NAa)); dispose(NAa.t); (* on nettoie avant de quitter *) end;
Et maintenant la division par 2, mon cours de numération me dit que diviser un nombre par 2, c’est trouver le nombre de 2 dans celui-ci, je vais donc soustraire (A div 2) fois le nombre 2 pour trouver A div 2 :
procedure div2(var NAA:BIGnum); var t : BIGnum; m : longint; i : integer; Begin m := 0; if (naa.s<=10) and (naa.t^[10]<=2) then begin (* taille < 10, on peut utiliser des longint, compat. tp6 *) for i := naa.s downto 1 do m := m*10 + naa.t^[i]; (* on passe de BN a longint *) m := m div 2; (* on divise par 2 *) init_BN(naa); (* on passe de longint a BN *) i := 0; while (m<>0) do begin inc(i); naa.t^[i] := m mod 10; (* on prend le dernier *) m := m div 10; (* et on le retire *) end; naa.s := i; (* on ajuste la taille *) end else (* on est condamne a des calculs très longs ... *) begin new(t.t); init_BN(t); repeat sous_c(NAA,2); (* on retire 2 *) add_c(t,1); (* et on le comptabilise *) until (NAA.s<=2) and (NAA.t^[1]<=1) and (NAA.t^[2]=0); init_BN(NAA); add(NAA,t,NAA); (* on renvoie t qui est naa div 2 *) dispose(t.t); end; end;
Et après ça, il faut les afficher ces fichus nombres :
procedure show(ptr:BIGnum); var i : integer; s : string; begin for i := ptr.s downto 1 do begin str(ptr.t^[i],s); affiche(s); (* on affiche un digit, une fonction de votre cru *) end; end;
Limitations :
Conclusion
Vous voici tout clef en main, vous n'avez plus qu'à faire du copié-collé ...
L'exemple est le programme FIB.EXE.
Bibliographie
Dr Dobb's #226 janvier 95, avec un article sur les grands nombres en C++.
Principes fondamentaux de l'informatique, Aho,J.Ullman ed.DUNOD que je vous recommande vivement.
Remerciements
HP Mâd, pour son cours d'assembleur Flash qui me " sert " de modèle,
Murdoc, mon binôme, qui malgré ses défauts n'en est pas moins sympa,
et Ecureuil pour ses conseils, et ses routines, sans qui certains de mes programmes ne seraient pas ce qu'ils sont.
Et toi, hypocrite lecteur, mon ami, mon frère. ( Baudelaire )
Pour tout commentaire, webmaster@e-nef.com Dernière MaJ 05/02/2023 |