Table des matières
- Date de la séance de TP : vendredi 25 octobre 2024 - 13h45
- Date limite de prise en compte de votre version sur votre dépôt GitLab : dimanche 10 novembre 2024 - 23h59
Utilisez les fichiers Number.hpp
, Number.cpp
et TestNumber.cpp
du dossier tp/clp_tp2
de votre dépôt Git, seul le contenu de ce dossier sera pris en compte pour l'évaluation de votre travail sur ce TP.
Pour cet exercice, et le suivant, Google Test est utilisé pour des tests unitaires. Voir sur cette page les possibilités que vous avez.
1. Présentation
Lors du TD2, nous avons conçu une solution permettant de représenter des naturels de taille quelconque.
Mais le type Number
proposé ne permet pas de manipuler ces nombres avec autant de facilité que des int
: il n'est pas possible de copier un nombre, on ne peut pas utiliser les opérateurs arithmétiques classiques, l'affichage ne se fait pas de la même façon...
L'objectif de ce TP est de concevoir un type Number
amélioré qui solutionne ces problèmes identifiés.
2. Nouveau type (1 point)
2.1. Approche objet
Pour utiliser un Number
de la même manière qu'un int
, Number
doit être une classe. Nous conserverons la liste simplement chainée de Digit
, mais cette classe devra être non visible des utilisateurs, c'est pourquoi elle sera définie en privée à l'intérieur de Number
.
Nous arrivons donc à la définition initiale suivante :
class Number { public: Number( unsigned long l ); ~Number(); private: using DigitType = unsigned int; // Un chiffre décimal par maillon : l'objectif ici n'est pas la performance // mais votre code doit être écrit en tenant compte de cet aspect static const DigitType number_base{ 10u }; struct Digit { DigitType digit_; Digit * next_; }; Digit * first_; };
- Définir le constructeur et le destructeur de
Number
.
Vous devez utiliser aussi une approche objet pour la classe Digit
, donc vous devez ajouter un constructeur, un destructeur et d'autres méthodes plus tard à celle-ci. Le non respect de cette conception sera pénalisé.
La récursion telle que mise en œuvre dans le TD N°2 se fera sur les méthodes de Digit
.
2.2. Affichage
- Définir dans
Number
une méthode
void print( std::ostream & out ) const;
qui affiche le nombre sur le flux de sortie.
Vous ne devez pas construire une chaîne de caractères et l'afficher ensuite. Il faut afficher directement les chiffres sur le flux avec une méthode dans Digit
.
- Ajouter dans
Number.hpp
la définition suivante qui permet d'afficher un nombre avec le protocole de la bibliothèque standard d'entrée/sortie C++ :
inline std::ostream & operator<<( std::ostream & out, const Number & n ) { n.print( out ); return out; }
2.3. Tests de construction et d'affichage
- Compiler et exécuter votre programme pour vérifier que les tests unitaires suivants (dans
TestNumber.cpp
) réussissent.
TEST( TestNumber, TestNumber0 ) { Number n{ 0 }; std::ostringstream os; os << n; EXPECT_EQ( os.str(), "0" ); } TEST( TestNumber, TestNumber12345678 ) { Number n{ 12345678 }; std::ostringstream os; os << n; EXPECT_EQ( os.str(), "12345678" ); } TEST( TestNumber, TestNumberBig ) { Number n{ 12345123451234512345UL }; std::ostringstream os; os << n; EXPECT_EQ( os.str(), "12345123451234512345" ); } TEST( TestNumber, TestNumberRandom ) { auto r{ std::rand() }; Number n{ static_cast< unsigned long >( r )}; std::ostringstream os; os << n; EXPECT_EQ( os.str(), std::to_string( r )); }
add, commit & push
3. Classe concrète (1 point)
- Rendre cette classe concrète (comportements de construction et d'affectation par copie).
Vous devez utiliser l'idiome du swap()
. Le non respect de cette conception sera pénalisé.
Avez-vous vraiment besoin de l'affectation par copie d'un Digit
?
- Écrire des tests unitaires pour ces comportements.
add, commit & push
4. Calculs (1 point)
Soit à calculer n!
. On sait que 13!
est trop grand pour pouvoir être représenté sur 32 bits. On choisit donc de représenter le résultat par un Number
. Nous avons pour cela besoin d'ajouter et de multiplier un Number
avec un unsigned int
.
- Ajouter les comportements d'addition et de multiplication d'un
Number
par ununsigned int
en définissant les méthodes de signature :
void add( unsigned int i ); void multiply( unsigned int i );
Ces méthodes modifient l'objet courant.
Votre code doit être écrit de façon à tenir compte du nombre de chiffres décimaux stockés dans un Digit
, et ne doit pas provoquer de débordements.
- Écrire des tests unitaires pour ces comportements.
- Écrire une fonction calculant la factorielle d'un entier, ayant comme signature :
Number factorial( unsigned int i );
- Voici un test unitaire pour ce comportement.
TEST( TestNumber, TestFactorial123 ) { std::ostringstream os; os << factorial( 123 );; EXPECT_EQ( os.str(), "121463043670253296757662432418812958554542170884833823153289181618292" "358923621676688311569606126402021707358352212940477825910915704116514" "72186029519906261646730733907419814952960000000000000000000000000000" ); }
5. Lecture (0,5 point)
Nous avons vu lors du TD n°2 que la lecture chiffre par chiffre en commençant par ceux de poids forts n'est pas adaptée dès que que l'on souhaite mettre plus d'un chiffre décimal par Digit
. Nous contournerons cette difficulté en permettant la construction d'un Number
à partir d'une chaîne de caractères.
- Définir un constructeur de signature
Number( std::string s );
Une exception std::invalid_argument
sera émise si la chaîne de caractères est vide ou contient un caractère qui n'est pas un chiffre décimal.
Vous pouvez utiliser std::isdigit
pour tester si un caractère est un chiffre décimal.
La valeur d
d'un chiffre qui s'écrit avec le caractère c
s'obtient par l'expression
unsigned int d{ static_cast< unsigned int >( c - '0' )}
Besoin d'aide ?
Vous pouvez ajouter un chiffre et multiplier par 10 votre nombre en construction.
- Écrire des tests unitaires pour ce comportement.
- Déclarer dans
Number.hpp
la fonction :
std::istream & operator>>( std::istream & in, Number & n );
- Définir dans
Number.cpp
cette fonction :
std::istream & operator>>( std::istream & in, Number & n ) { // TODO return in; }
permettant la lecture d'un Number
selon le protocole de la bibliothèque standard d'entrée/sortie de C++. Cette fonction ignore les blancs au départ, et modifie l'argument n
uniquement si elle a réussi à lire au moins un chiffre ensuite ; elle s'arrète dès qu'un caractère qui n'est pas un chiffre est rencontré.
Voici un extrait de code pour lire le flux d'entrée caractère par caractère :
// in est le flux d'entrée // ignore les blancs au début in >> std::ws; while( in.good() ) { int c{ in.get() }; if( std::isdigit( c )) { // ... à vous de l'utiliser ... } else { in.putback( c ); break; } }
- Écrire des tests unitaires pour ce comportement.
add, commit & push
6. Partie avancée (0,5 point)
La surcharge des opérateurs et les constructions et affectations par déplacement sont vus au dernier cours.
- Remplacez les méthodes
add()
etmultiply()
par les opérateurs+=
et*=
; ajouter les opérateurs+
et*
. - Ajouter les comportements de construction et d'affectation par déplacement et les tests unitaires correspondants.
add, commit & push