CHAPITRE II
Impl´
ementation de grands entiers en C++
Die ganzen Zahlen hat der liebe Gott gemacht,
alles andere ist Menschenwerk.
Leopold Kronecker
Objectifs
Reconsid´erer les algorithmes d’arithm´etique connus de l’´ecole.
Comprendre leur formulation en C++, n´ecessairement tr`es d´etaill´ee.
Exp´erimenter empiriquement les premiers ph´enom`enes de complexit´e.
La probl´
ematique. Tr`es souvent en programmation, les types primitifs ne mod´elisent pas ce que l’on
veut. Par exemple le type int ne repr´esente que les «petits »entiers : sa m´emoire ´etant fix´ee `a 4 octets
(32 bits), la plage des valeurs possibles (avec signe) va de 231 `a 231 1, ce qui correspond `a l’intervalle
[[2147483648,2147483647]]. Pour illustration, reprenons un exemple d´ej`a rencontr´e au chapitre I:
Exemple 0.1. On veut ´ecrire un programme qui affiche les valeurs n! pour n=1,2,...,50.
`
A titre d’exemple, on souhaite que le programme suivant calcule correctement la factorielle :
Integer factorielle= 1;
for( Integer facteur= 1; facteur <= 50; ++facteur )
{
factorielle*= facteur;
cout << "La factorielle " << facteur << "! vaut " << factorielle << endl;
}
Comme on a vu au chapitre I, les types primitifs du C++ n’y suffisent pas, et l’utilisation na¨ıve du type
int cr´eera des r´esultats catastrophiques. Pour r´esoudre ce genre de probl`eme assez fr´equent, on est men´e
`a construire des nouveaux types, traditionnellement appel´es classes, qui sont mieux adapt´es au probl`eme.
Dans notre cas on voudrait disposer d’une classe, appel´ee Integer , qui impl´emente les grands entiers
avec une utilisation intuitive.
L’approche g´
en´
erale. Les types primitifs n’admettent qu’une allocation de m´emoire «statique »
(c’est-`a-dire de taille fix´ee d’avance), ce qui restreint forc´ement la plage des valeurs. Pour les grands en-
tiers il est avantageux d’allouer la m´emoire de mani`ere «dynamique », en adaptant la taille d’un tableau
au cours du programme selon les besoins du calcul. On discutera au §1une telle solution «faite maison »,
mod´elis´ee directement sur la num´eration d´ecimale, et le §2discute bri`evement des questions de complexit´e.
Le chapitre III pr´esente une solution «professionnelle », issue de la biblioth`eque GMP. N’importe
quelle des deux impl´ementations permettra de r´esoudre le probl`eme de l’exemple 0.1 d’une mani`ere facile
est naturelle; elles se distinguent seulement par leur niveau d’optimisation : une journ´ee de programmation
pour notre classe Naturel contre plusieurs ann´ees de d´eveloppement pour la biblioth`eque GMP .
Sommaire
1. Une impl´
ementation «faite maison »des nombres naturels. 1.1. Num´eration d´ecimale `a
position. 1.2. Impl´ementation en C++. 1.3. Incr´ementer et decr´ementer. 1.4. Comparaison.
1.5. Addition et soustraction. 1.6. Multiplication. 1.7. Division euclidienne.
2. Questions de complexit´
e. 2.1. Le coˆut des calculs. 2.2. Complexit´e lin´eaire vs quadratique.
3. Exercices suppl´
ementaires. 3.1. Num´eration romaine. 3.2. Partitions.
35
36 Chapitre II Impl´ementation de grands entiers en C++
1. Une impl´
ementation «faite maison »des nombres naturels
1.1. Num´
eration d´
ecimale `
a position. Comme le C++ ne pr´evoit pas de type primitif pour les
grands nombres entiers, nous allons impl´ementer nous-mˆemes un tel type, appel´e Naturel , dans le fi-
chier naturel.cc . C’est un excellent exercice de programmation. Si cependant vous ˆetes impatient, il
suffira de survoler ce chapitre pour passer directement au chapitre III qui pr´epare l’usage pratique.
Avertissement. — Le seul but de notre impl´
ementation est d’illustrer le principe.
Elle restera dilettante dans le sens qu’aucune optimisation s´
erieuse ne sera faite.
`
A part cette n´
egligence, toute autre impl´
ementation suivrait une approche similaire.
On reprend ici une id´ee vielle de 2000 ans, qui est aussi simple que g´eniale : la num´eration d´ecimale `a
position. Afin de r´ealiser ce programme en C++, on utilisera un tableau pour stocker une suite de chiffres.
(Voir le chap. I,§6.1 pour l’utilisation des tableaux sous forme de la classe g´en´erique vector .) Ainsi notre
impl´ementation sera une traduction directe des algorithmes scolaires bien connus.
`
A noter toutefois qu’une telle impl´ementation doit ˆetre tr`es pr´ecise : en particulier on doit manipuler
les vecteurs avec soin, r´eserver toujours de la m´emoire de taille suffisante, puis rallonger ou raccourcir le
cas ´ech´eant. On discutera tour `a tour les diff´erentes fonctions de base, commenc¸ant par la repr´esentation
des donn´ees et l’entr´ee-sortie, ensuite l’incr´ementation/decr´ementation et la comparaison, puis l’addi-
tion/soustraction, la multiplication, et finalement la division euclidienne.
Rappel de notation. Pour l’axiomatique des nombres naturels et leurs principales propri´et´es, nous
renvoyons `a l’annexe de ce chapitre. En voici un r´esultat fondamental :
D´
efinition 1.1. Nous fixons un nombre naturel b2, que nous appelons la base. (Dans tout ce paragraphe
on pourra choisir b=10 pour fixer les id´ees.) Par rapport `a la base b, toute suite finie (an,...,a1,a0)de
nombres naturels akNrepr´esente un nombre naturel, `a savoir
han,...,a1,a0ib:=anbn+···+a1b1+a0=
n
k=0akbk.
Si an6=0 et 0 ak<bpour tout k=0,1,...,n, nous appelons la suite (an,...,a1,a0)une repr´
esentation
normale par rapport `a la base b, ou aussi un d´
eveloppement en base b.
Proposition 1.2. Tout nombre naturel a Npeut ˆ
etre ´
ecrit de mani`
ere unique comme a =han,...,a1,a0ib
avec 0<an<b et 0ak<b pour tout k =0,1,...,n1. L’application (an,...,a1,a0)7→ a=n
k=0akbk
´
etablit donc une bijection entre nombres naturels a et d´
eveloppements (an,...,a1,a0)en base b. Sous cette
bijection on appelle akle ki`
eme chiffre de a dans son d´
eveloppement en base b.
Dans le cas b=10 on appelle chiffres d´
ecimaux les symboles 0,1,2,3,4,5,6,7,8,9, que l’on
identifie avec les dix premiers nombres naturels. Ainsi tout nombre naturel apeut ˆetre repr´esent´e par une
suite de chiffres d´ecimaux (an,...,a1,a0), appel´ee le d´
eveloppement d´
ecimal de a.`
A noter, en particulier,
qu’avec cette convention le nombre naturel 0 sera repr´esent´e par la suite vide.
Exercice/M 1.3. ´
Etant donn´es deux nombres naturels a=a
n,...,a
1,a
0bet a′′ =a′′
n,...,a′′
1,a′′
0b, leur
somme a=a+a′′ peut ˆetre repr´esent´ee comme a
n+a′′
n,...,a
1+a′′
1,a
0+a′′
0b. Seul petit probl`eme : cette
repr´esentation n’est en g´en´eral pas normale. Pour ce faire on doit encore propager les retenues. L’algorithme
II.1 ci-dessous fait exactement ceci. Essayez de justifier sa correction.
Algorithme II.1 Normalisation par rapport `a une base b
Entr´
ee: Une repr´esentation (am,...,a1,a0)Nm+1d’un entier aen base b2
Sortie: La repr´esentation normale (an,...,a1,a0)Nn+1de aen base b
Initialiser retenue 0, nm
pour kde 0`
anfaire akak+retenue,retenue akdivb,akakmodb
tant que retenue >0faire nn+1, anretenuemodb,retenue retenuedivb
tant que n0 et an=0faire nn1
retourner (an,...,a1,a0)
MAE 22 juin 2009
§
1 — Une impl´ementation
«
faite maison
»
des nombres naturels 37
1.2. Impl´
ementation en C++. On d´efinit le type Chiffre pour repr´esenter un chiffre, ou un petit
entier `a deux chiffres au plus. Le type Naturel est un vecteur de chiffres. Techniquementil est avantageux
de tout formuler sous forme d’une classe, mais c’est ici un d´etail sans importance : notre impl´ementation
restera assez facile `a comprendre, mˆeme sans explication d´etaill´ee de la programmation «orient´ee objet ».
Programme II.1 D´efinition du type Naturel , conversion de int , et op´erateur de sortie
typedef int Indice; // type pour stocker un indice
typedef short int Chiffre; // type pour stocker un chiffre (ou deux)
const Chiffre base= 10; // base entre 2 et 16, ici on choisit 10
const char chiffre[]= "0123456789abcdef"; // chiffres pour l’affichage
class Naturel
{
public:
vector<Chiffre> chiffres; // La suite des chiffres dans la base donn´ee
void raccourcir() // Raccourcir en effa¸cant d’´eventuels eros terminaux
{while( !chiffres.empty() && chiffres.back()==0 ) chiffres.pop_back(); };
void normaliser() // Normaliser pour n’avoir que de chiffres 0,...,b-1
{
Chiffre retenue= 0;
for( Indice i=0; i<chiffres.size(); ++i )
{chiffres[i]+= retenue; retenue= chiffres[i]/base; chiffres[i]%= base; };
while( retenue > 0 ) {chiffres.push_back( retenue % base ); retenue/= base; };
raccourcir();
};
Naturel( int n= 0 ) // Constructeur par conversion d’un petit entier
{for( ; n>0; n/=base ) chiffres.push_back( Chiffre( n % base ) ); };
Naturel& operator= ( const Naturel& n ) // Affectation (par copie des chiffres)
{chiffres= n.chiffres; return *this; };
void clear() // Remettre `a ero (= suite vide)
{chiffres.clear(); };
bool est_zero() const // Tester si z´ero (= suite vide)
{return chiffres.empty(); };
Indice size() const // D´eterminer la taille (= longueur de la suite)
{return chiffres.size(); };
Chiffre operator[] ( Indice i ) const // Lire le chiffre `a la position i
{return ( i<0 || i>=chiffres.size() ? Chiffre(0) : chiffres[i] ); };
};
// Conversion valeur -> symbole pour la sortie
char symbole( Chiffre valeur )
{return ( valeur>=0 && valeur<base ? chiffre[valeur] : ’?’ ); }
// Op´erateur de sortie (afficher les chiffres, ou bien "0" pour une suite vide)
ostream& operator<< ( ostream& out, const Naturel& n )
{
Indice i= n.chiffres.size();
if ( i == 0 ) return ( out << 0 );
for( --i; i>=0; --i ) out << symbole( n.chiffres[i] );
return out;
}
Avec le d´ebut de notre impl´ementation on peut d´ej`a ´ecrire le programme II.2 suivant :
MAE 22 juin 2009
38 Chapitre II Impl´ementation de grands entiers en C++
Programme II.2 Un exemple d’utilisation naturel-exemple.cc
1#include <iostream> // d´eclarer l’entr´ee-sortie standard
2#include "naturel.cc" // inclure notre impl´ementation de la classe Naturel
3using namespace std; // acc`es direct aux fonctions standard
4
5int main()
6{
7Naturel a; // d´efinition d’une variable (initialis´ee `a z´ero)
8Naturel b(123); // efinition avec initialisation `a la valeur 123
9cout << "a = " << a << ", b = " << b << endl;
10 a= b; // affectation (copie des chiffres de b vers a)
11 cout << "a = " << a << ", b = " << b << endl;
12 a.clear(); // effacer la valeur stock´ee (remettre `a ero)
13 cout << "a = " << a << ", b = " << b << endl;
14 a= 42; // affecter une valeur (conversion implicite)
15 cout << "a = " << a << ", b = " << b << endl;
16 cout << "d´eveloppement de longueur " << a.size();
17 for( int i=0; i<=10; ++i ) cout << ", a[" << i << "]=" << a[i];
18 cout << endl;
19 }
Repr´
esentation interne: Tout d’abord, on veut que tout nombre naturel puisse ˆetre repr´esent´e comme
une valeur de type Naturel . Ceci est r´ealis´e ici en stockant les chiffres du d´eveloppement
d´ecimal dans un vecteur appel´e chiffres . Par convention on stocke un d´eveloppement d´ecimal
(an,...,a1,a0)comme un vecteur a[0],a[1],...,a[n] de longueur n+1, dans le sens de
poids croissant. C’est juste une convention commode, mais une fois cette d´ecision est prise, il
faut s’y conformer dans les fonctions ult´erieures, sans aucune exception.
Normalisation: Par pr´ecaution on a d´ej`a impl´ement´e deux fonctions auxiliaires : raccourcir()
qui supprime d’´eventuels z´ero terminaux, ainsi que normaliser() qui propage d’´eventuelles
retenues comme expliqu´e plus haut afin de n’avoir que des chiffres 0,...,9. Ces deux fonctions
pourraient servir dans des fonctions ult´erieures qui doivent renvoyer un r´esultat normalis´e.
Constructeur: On peut d´efinir une variable ade type Naturel par Naturel a; elle sera initialis´ee
`a z´ero (la valeur par d´efaut dans le constructeur). La d´efinition Naturel b(123) entraˆıne une
initialisation `a la valeur d´ecimale 123 : la suite des chiffr´es stock´ee sera donc 3,2,1 (sic !). `
A
noter que l’op´erateur de sortie se chargera d’un affichage dans l’ordre usuel.
Op´
erateur d’affectation: On voudra utiliser l’op´erateur d’affectation =avec la syntaxe et la s´emantique
usuelle : il prend deux arguments, une variable de type Naturel `a gauche et une valeur de type
Naturel `a droite, puis il affecte la valeur `a la variable. Dans notre cas, l’instruction a= b;
copie le d´eveloppement d´ecimal stock´e dans bdans la variable a.
Conversion de type: La construction par conversion permet d’affecter une valeur de type int `a une
variable de type Naturel : on pourra ´ecrire a= Naturel(42) pour une conversion explicite
ou bien a= 42 pour une conversion implicite. Les deux passent par le constructeur fourni ci-
dessus, et produisent un objet anonyme temporaire de type Naturel contenant les chiffres 2,4
du d´eveloppement d´ecimal de 42 ; celui-ci est ensuite copi´e dans apar l’op´erateur d’affectation.
R´
einitialisation: La fonction clear() permet d’effacer le d´eveloppement d´ecimal; le r´esultat (la
suite vide) repr´esente z´ero. R´eciproquement la fonction est zero() permet de d´eterminer si un
nombre vaut z´ero : il suffit de tester si le d´eveloppement est de longueur 0. `
A noter que l’on sup-
pose toujours une repr´esentation normalis´ee; par exemple h0,0,0idec n’est pas une repr´esentation
normale de z´ero!
Acc`
es aux chiffres: On fournit aussi un op´erateur d’indexation s´
ecuris´
e: pour un indice l´egitime on
renvoie le chiffre correspondant stock´e dans le vecteur, et pour tout indice en dehors de cette plage
on renvoie 0, la seule valeur math´ematiquement raisonnable. (Le justifier.) On peut ainsi acc´eder
aux chiffres du d´eveloppement d´ecimal sans se soucier de la longueur exacte de la repr´esentation
interne. (Quant `a l’indexation des vecteurs, relire les avertissements du chapitre I,§6.1.)
MAE 22 juin 2009
§
1 — Une impl´ementation
«
faite maison
»
des nombres naturels 39
Entr´
ee-sortie: Les op´erateurs d’entr´ee >> et de sortie << permettent de lire et d’´ecrire des valeurs de
type Naturel ; ils traduisent alors entre la repr´esentation externe (l’´ecriture d´ecimale usuelle)
et la repr´esentation interne (qui peut en diff´erer). On n’a reproduit ci-dessus que la sortie, qui est
plus facile. Vous trouverez l’op´erateur d’entr´ee dans le fichier naturel.cc .
Les chiffres sont affich´es dans l’ordre usuel an,...,a1,a0, alors qu’ils sont stock´es dans
l’ordre inverse. Ceci illustre un principe important : le stockage interne et l’´ecriture externe sont
ind´ependants. C’est la tˆache des op´erateurs d’entr´ee-sortie de traduire entre les deux.
Exercice/P 1.4. Avec ces quelques lignes de code, notre impl´ementation est d´ej`a op´erationnelle. Bien
´evidemment, il manque encore l’arithm´etique, que nous allons impl´ementer dans la suite. Si vous ˆetes
courageux vous pouvez essayer de programmer une solution vous-mˆeme, ou au moins esquisser une
impl´ementation. Vous verrez que ce n’est pas trivial. Voici ce que l’on souhaite faire :
Incr´
ementation et decr´
ementation: On voudra utiliser l’incr´ementation ++ et la decr´ementation --
avec la syntaxe usuelle, afin de «compter »avec le type Naturel .
Op´
erateurs de comparaison: On voudra utiliser les op´erateurs de comparaison == ,!= , ainsi que <,
<= ,>,>= comme d’habitude.
Op´
erateurs arithm´
etiques: On voudra utiliser les op´erateurs arithm´etiques +,-,*,/,%avec la
syntaxe et la s´emantique usuelles.
Op´
erateurs mixtes: Finalement, les op´erateurs mixtes += ,-= ,*= ,/= ,%= devront s’utiliser d’une
mani`ere naturelle, de sorte que a+=b ´equivaille `a a=a+b etc.
1.3. Incr´
ementer et decr´
ementer. Comme premi`ere op´eration arithm´etique nous commenc¸ons par
le processus de compter, c’est-`a-dire augmenter n7→ n+1 ou diminuer n7→ n1 (pour n>0). Essayer de
comprendre leur fonctionnement (sur quelques exemples) puis de justifier leur correction.
Programme II.3 Incr´ementation et decr´ementation
// Incr´ementer : la fonction renvoie toujours ‘true’ (= ex´ecution sans erreur).
bool incrementer( Naturel& n )
{
for( Indice i=0; i<n.size(); ++i )
if ( n.chiffres[i] == Chiffre(base-1) ) n.chiffres[i]= Chiffre(0);
else {++(n.chiffres[i]); return true; };
n.chiffres.push_back( Chiffre(1) );
return true;
}
// Incr´ementation sous forme d’op´erateur
Naturel& operator++ ( Naturel& n )
{incrementer(n); return n; }
// Decr´ementer : si n=0 la fonction renvoie ‘false’ (= erreur).
bool decrementer( Naturel& n )
{
for( Indice i=0; i<n.size(); ++i )
if ( n.chiffres[i] == Chiffre(0) ) n.chiffres[i]= Chiffre(base-1);
else {--(n.chiffres[i]); n.raccourcir(); return true; };
return false;
}
// Decr´ementation sous forme d’op´erateur
Naturel& operator-- ( Naturel& n )
{
if ( !decrementer(n) )
cerr << "Erreur : on ne peut decr´ementer 0." << endl;
return n;
}
MAE 22 juin 2009
1 / 23 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !