Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.
ratio [2015/09/20 19:35] 193.110.198.7 |
ratio [2016/07/05 18:53] (Version actuelle) gbdivers |
||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
- | ^ [[complex|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[bitset|Chapitre suivant]] ^ | + | ^ [[complex|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[rvalue_et_lvalue|Chapitre suivant]] ^ |
__ syntaxe full compile time, risque de confusion à voir tout de suite les 2 syntaxes aussi en détails __ | __ syntaxe full compile time, risque de confusion à voir tout de suite les 2 syntaxes aussi en détails __ | ||
- | ====== Les nombres rationnels ====== | + | ====== [Aller plus loin] Les nombres rationnels ====== |
- | ===== Rappel sur la représentation des nombres ===== | + | ===== Rappel sur la représentation des nombres rationnels ===== |
Les nombres rationnels sont des nombres qui s'écrivent sous la forme d'une division de deux nombres entiers : un numérateur divisé par un dénominateur non nul. | Les nombres rationnels sont des nombres qui s'écrivent sous la forme d'une division de deux nombres entiers : un numérateur divisé par un dénominateur non nul. | ||
Ligne 33: | Ligne 33: | ||
Vous avez vu dans le chapitre [[programme_minimal|]] qu'il y avait deux étapes pour obtenir le résultat d'un programme : une première phase de compilation (//compile-time//), qui permet de générer un programme à partir du code source C++, et une seconde phase d'exécution (//runtime//), qui exécute le programme. Cette distinction est intéressante, puisque la phase de compilation sera généralement réalisée une seule fois, pour ensuite exécuter plusieurs fois le programme. Donc tout ce qui est fait lors de cette première étape sera du temps de gagné sur l'exécution du programme. | Vous avez vu dans le chapitre [[programme_minimal|]] qu'il y avait deux étapes pour obtenir le résultat d'un programme : une première phase de compilation (//compile-time//), qui permet de générer un programme à partir du code source C++, et une seconde phase d'exécution (//runtime//), qui exécute le programme. Cette distinction est intéressante, puisque la phase de compilation sera généralement réalisée une seule fois, pour ensuite exécuter plusieurs fois le programme. Donc tout ce qui est fait lors de cette première étape sera du temps de gagné sur l'exécution du programme. | ||
- | Et c'est bien la distinction majeure entre ''std::complex'' et ''std::ratio''. La première permet de réaliser des calculs lors de l'exécution, alors que la second travaille uniquement lors de la compilation. (Il est possible de créer une classe en C++ permettant de manipuler des nombres rationnels lors de l'exécution, vous ferez cela en exercice. Mais ce n'est pas l'approche utilisée par ''std::ratio''). | + | <note>**Optimisation à la compilation** |
- | + | ||
- | La conséquence est que la syntaxe pour utiliser ''std::ratio'' est totalement différente de celle de ''std::complex''. En particulier, il n'est pas possible d'utiliser les opérateurs que vous avez déjà utilisé pour les nombres et ''std::complex'' (comme ''+'' ou ''==''). | + | |
- | <note>En règle générale, le compilateur essayera au mieux d'optimiser les calculs à la compilation. Par exemple : | + | En règle générale, le compilateur essayera au mieux d'optimiser les calculs à la compilation. Par exemple : |
<code cpp> | <code cpp> | ||
Ligne 52: | Ligne 50: | ||
La classe ''std::ratio'' est un cas particulier, puisqu'elle ne peut être utilisée que lors de la compilation. C'est une forme de limitation, puisque cela n'est pas utilisable tout le temps. Mais dans certains cas, il est préférable d'avoir une solution qui est performante et qui ne fonctionne qu'à la compilation ou qu'à l'exécution, plutôt que d'avoir une solution plus généraliste, mais moins performante.</note> | La classe ''std::ratio'' est un cas particulier, puisqu'elle ne peut être utilisée que lors de la compilation. C'est une forme de limitation, puisque cela n'est pas utilisable tout le temps. Mais dans certains cas, il est préférable d'avoir une solution qui est performante et qui ne fonctionne qu'à la compilation ou qu'à l'exécution, plutôt que d'avoir une solution plus généraliste, mais moins performante.</note> | ||
+ | |||
+ | Et c'est bien la distinction majeure entre ''std::complex'' et ''std::ratio''. La première permet de réaliser des calculs lors de l'exécution, alors que la second travaille uniquement lors de la compilation. (Il est possible de créer une classe en C++ permettant de manipuler des nombres rationnels lors de l'exécution, vous ferez cela en exercice. Mais ce n'est pas l'approche utilisée par ''std::ratio''). | ||
+ | |||
+ | La conséquence est que la syntaxe pour utiliser ''std::ratio'' est totalement différente de celle de ''std::complex''. En particulier, il n'est pas possible d'utiliser les opérateurs que vous avez déjà utilisé pour les nombres et ''std::complex'' (comme ''+'' ou ''==''). | ||
+ | |||
+ | <note>Notez bien la distinction faite entre **l'opération** (calculer une addition, une soustraction, etc.) et les **opérateurs** (''+'', ''-'', etc). | ||
+ | |||
+ | En général, une opération représente un concept, qui est défini de façon unique, souvent par une définition mathématique (comme c'est la cas par exemple ici avec l'addition entre deux nombres rationnels). | ||
+ | |||
+ | Au contraire, un opérateur est un moyen dans le code C++ de réaliser cette opération. Une opération peut être définie pour une classe, sans que l'opérateur habituel correspondant ne soit définie (comme c'est le cas ici avec ''std::ratio'', qui permet de calculer une addition, mais sans utiliser l'opérateur ''+''). Il est également possible d'avoir plusieurs syntaxes permettant de réaliser une opération (par exemple que l'on puisse utiliser ''+'' et ''std::add'' pour calculer une addition). | ||
+ | |||
+ | Il est important que les fonctionnalités proposées par une classe soient cohérentes et quelque soit la méthode utilisée pour calculer une addition par exemple, le résultat soit toujours le même.</note> | ||
+ | |||
La classe ''std::ratio'' utilise la notation avec chevrons, que vous avez déjà rencontré rapidement. Mais pas d'inquiétude, la syntaxe est simple, il suffit d'écrire : ''std::ratio<numérateur, dénominateur>''. Pour écrire la fraction $ \frac{1}{3} $ par exemple, il faudra donc écrire : | La classe ''std::ratio'' utilise la notation avec chevrons, que vous avez déjà rencontré rapidement. Mais pas d'inquiétude, la syntaxe est simple, il suffit d'écrire : ''std::ratio<numérateur, dénominateur>''. Pour écrire la fraction $ \frac{1}{3} $ par exemple, il faudra donc écrire : | ||
<code cpp> | <code cpp> | ||
+ | #include <ratio> | ||
+ | |||
std::ratio<1, 3> | std::ratio<1, 3> | ||
</code> | </code> | ||
+ | |||
+ | Il existe un certain nombre de ''std::ratio'' prédéfinie dans la norme C++. La liste complète est donnée dans la page de documentation : [[http://en.cppreference.com/w/cpp/numeric/ratio/ratio|std::ratio]]. Ces valeurs correspondent aux préfixes du système international d'unités (voir [[https://fr.wikipedia.org/wiki/Pr%C3%A9fixes_du_syst%C3%A8me_international_d%27unit%C3%A9s|Wikipédia]] pour les détails). Par exemple : | ||
+ | |||
+ | * nano = ''std::ratio<1, 1000000000>'' ; | ||
+ | * micro = ''std::ratio<1, 1000000>'' ; | ||
+ | * milli = ''std::ratio<1, 1000>'' ; | ||
+ | * kilo = ''std::ratio<1000, 1>'' ; | ||
+ | * mega = ''std::ratio<1000000, 1>'' ; | ||
+ | * giga = ''std::ratio<1000000000, 1>''. | ||
+ | |||
** Exercices ** | ** Exercices ** | ||
- | Ecrire les fractions : 2/3, 1/2, 3/3. | + | * Ecrire les fractions : 2/3, 1/2, 3/3. |
- | __ ratio prédéfinies : mega, kilo, etc __ | ||
- | __ division par 0 __ | + | ===== Afficher un std::ratio ===== |
- | __ afficher une valeur __ | + | Si vous compilez le code précédent, vous obtiendrez l'avertissement suivant : |
+ | |||
+ | <code> | ||
+ | main.cpp:4:5: warning: declaration does not declare anything [-Wmissing-declarations] | ||
+ | std::ratio<1, 3>; | ||
+ | ^~~~~~~~~~~~~~~~ | ||
+ | 1 warning generated. | ||
+ | </code> | ||
+ | |||
+ | Ce message indique en fait que la ligne contenant ''std::ratio'' ne fait rien. En effet, c'est le cas, le code déclare simplement un ''std::ratio'', et ne fait rien avec. | ||
+ | |||
+ | Essayons d'afficher un ''std::ratio'' avec ''std::cout'' : | ||
+ | |||
+ | <code cpp> | ||
+ | #include <iostream> | ||
+ | #include <ratio> | ||
+ | |||
+ | int main() { | ||
+ | std::cout << std::ratio<1, 3> << std::endl; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Malheureusement, ce code ne fonctionne pas directement et produit une erreur : | ||
+ | |||
+ | <code> | ||
+ | main.cpp:5:35: error: expected '(' for function-style cast or type construction | ||
+ | std::cout << std::ratio<1, 3> << std::endl; | ||
+ | ~~~~~~~~~~~~~~~~ ^ | ||
+ | 1 error generated. | ||
+ | </code> | ||
+ | |||
+ | Le message est un peu plus complexe a comprendre, mais au final, cela veut dire qu'il ne comprend pas cette syntaxe. La raison est en fait très simple : la classe ''std::ratio'' ne contient pas de fonctionnalités pour être affichée. Il faut récupérer directement les valeurs du numérateur et du dénominateur et les afficher. | ||
+ | |||
+ | Pour cela, il faut utiliser ''num'' et ''den'' avec la syntaxe suivante : | ||
+ | |||
+ | <code cpp> | ||
+ | std::ratio<1, 3>::num // numérateur | ||
+ | std::ratio<1, 3>::den // dénominateur | ||
+ | </code> | ||
+ | |||
+ | Au final, pour afficher un ''std::ratio'', il faut donc écrire : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <iostream> | ||
+ | #include <ratio> | ||
+ | |||
+ | int main() { | ||
+ | std::cout << std::ratio<1, 3>::num << std::endl; | ||
+ | std::cout << std::ratio<1, 3>::den << std::endl; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | affiche : | ||
+ | |||
+ | <code> | ||
+ | 1 | ||
+ | 3 | ||
+ | </code> | ||
+ | |||
+ | Une fraction est valide pour n'importe quelle paire d'entiers, avec un dénominateur non nul. Si on essaie de créer un ''std::ratio'' avec un dénominateur nul, on obtient une erreur assez explicite "denominator cannot be zero" : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <iostream> | ||
+ | #include <ratio> | ||
+ | |||
+ | int main() { | ||
+ | std::cout << std::ratio<1, 0>::num << std::endl; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | affiche : | ||
+ | |||
+ | <code> | ||
+ | In file included from main.cpp:2: | ||
+ | /usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/ratio:265:7: error: | ||
+ | static_assert failed "denominator cannot be zero" | ||
+ | static_assert(_Den != 0, "denominator cannot be zero"); | ||
+ | ^ ~~~~~~~~~ | ||
+ | main.cpp:5:23: note: in instantiation of template class 'std::ratio<1, 0>' requested here | ||
+ | std::cout << std::ratio<1, 0>::num << std::endl; | ||
+ | ^ | ||
+ | 1 error generated. | ||
+ | </code> | ||
+ | |||
+ | Un nombre rationnel peut être représenté par une infinité de paire d'entiers, en multipliant le numérateur et le dénominateur par un même nombre non nul. Par exemple, toutes les fractions suivantes sont des représentations du même nombre rationnel : | ||
+ | |||
+ | $$ \frac{2}{3} = \frac{4}{6} = \frac{6}{9} = \frac{12}{18} = \frac{22}{33} ... $$ | ||
+ | |||
+ | La représentation utilisant un numérateur et un dénominateur qui ne possèdent pas de diviseurs commun est appelée forme standardisée. Par exemple, "4/6" n'est pas une forme standardisée, puisque "4" et "6" ont un diviseur commun (on peut diviser 4 et 6 par 2). Par contre, "2/3" est une forme standardisée. Pour chaque nombre rationnel, il existe une et une seule forme standardisée. | ||
+ | |||
+ | La classe ''std::ratio'' simplifie les fractions et utilise la forme standardisée. Lorsque vous utiliser ''num'' et ''den'', cela correspond au numérateur et dénominateur de la forme standardisée. | ||
+ | |||
+ | <code> | ||
+ | #include <iostream> | ||
+ | #include <ratio> | ||
+ | |||
+ | int main() { | ||
+ | std::cout << std::ratio<2, 3>::num << " / " << std::ratio<2, 3>::den << std::endl; | ||
+ | std::cout << std::ratio<4, 6>::num << " / " << std::ratio<4, 6>::den << std::endl; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | affiche | ||
+ | |||
+ | <code> | ||
+ | 2 / 3 | ||
+ | 2 / 3 | ||
+ | </code> | ||
===== Les opérateurs arithmétiques de std:ratio ===== | ===== Les opérateurs arithmétiques de std:ratio ===== | ||
- | Les quatre opérations de base sont fournies avec la classe ''std::ratio'' : addition, soustraction, multiplication et division. Cependant, il n'est pas possible ici d'utiliser les opérateurs habituels ''+'', ''-'', ''*'' et ''/'' (pour des raisons techniques concernant la syntaxe de ces opérateurs, mais ce n'est pas important pour le moment). Ces opérations sont donc implémentées dans des fonctionalités : | + | Les quatre opérations de base sont fournies avec la classe ''std::ratio'' : addition, soustraction, multiplication et division. Cependant, il n'est pas possible ici d'utiliser les opérateurs habituels ''+'', ''-'', ''*'' et ''/'' (pour des raisons techniques concernant la syntaxe de ces opérateurs, mais ce n'est pas important pour le moment). Ces opérations sont donc implémentées dans des fonctionnalités : |
* ''ratio_add'' pour l'addition ; | * ''ratio_add'' pour l'addition ; | ||
Ligne 78: | Ligne 207: | ||
* ''ratio_divide'' pour la divison. | * ''ratio_divide'' pour la divison. | ||
- | Ces fonctionnalités utilisent aussi la syntaxe avec chevros, il faut donc écrire : | + | Ces fonctionnalités utilisent aussi la syntaxe avec chevrons, il faut donc écrire : |
<code cpp> | <code cpp> | ||
Ligne 86: | Ligne 215: | ||
En remplaçant "première fraction" et "seconde fraction" par des appels à ''std::ratio''. Donc, concrètement, si on veut additionner par exemple 2/3 et 3/4, il faudra écrire : | En remplaçant "première fraction" et "seconde fraction" par des appels à ''std::ratio''. Donc, concrètement, si on veut additionner par exemple 2/3 et 3/4, il faudra écrire : | ||
- | <code cpp> | + | <code cpp main.cpp> |
- | std::ratio_add<std::ratio<2, 3>, std::ratio<3, 4>> | + | #include <iostream> |
+ | #include <ratio> | ||
- | std::cout << std::ratio_add<std::ratio<2, 3>, std::ratio<3, 4>>::num << std::endl; | + | int main() { |
- | std::cout << std::ratio_add<std::ratio<2, 3>, std::ratio<3, 4>>::den << std::endl; | + | std::cout << std::ratio_add<std::ratio<2, 3>, std::ratio<3, 4>>::num << " / "; |
+ | std::cout << std::ratio_add<std::ratio<2, 3>, std::ratio<3, 4>>::den << std::endl; | ||
+ | } | ||
</code> | </code> | ||
- | <note>Vous réalisez sans doute l'intérêt d'utiliser les opérateurs habituels ''+'', ''-'', ''*'' et ''/'' avec ce simple code. Même si le résultat est le même, l'utilisation des opérateurs habituel permet d'avoir une syntaxe plus simple et plus lisible.</note> | + | affiche : |
+ | <code> | ||
+ | 17 / 12 | ||
+ | </code> | ||
+ | |||
+ | <note>Vous réalisez sans doute l'intérêt d'utiliser les opérateurs habituels ''+'', ''-'', ''*'' et ''/'' avec ce simple code. Même si le résultat est le même, l'utilisation des opérateurs habituels permet d'avoir une syntaxe plus simple et plus lisible.</note> | ||
+ | |||
+ | Pour terminer, la classe ''std::ratio'' propose également les opérations de comparaison habituels, également avec des noms spécifiques au lieu des opérateurs : | ||
+ | |||
+ | * égalité = ''ratio_equal'' ; | ||
+ | * différence = ''ratio_not_equal'' ; | ||
+ | * infériorité = ''ratio_less'' ; | ||
+ | * infériorité ou égalité = ''ratio_less_equal'' ; | ||
+ | * supériorité = ''ratio_greater'' ; | ||
+ | * supériorité ou égalité = ''ratio_greater_equal''. | ||
+ | |||
+ | Le résultat booléen est obtenu en utilisant ''value'' : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <iostream> | ||
+ | #include <ratio> | ||
+ | |||
+ | int main() { | ||
+ | std::cout << "2/3 == 3/4 ? " << std::boolalpha; | ||
+ | std::cout << std::ratio_equal<std::ratio<2, 3>, std::ratio<3, 4>>::value << std::endl; | ||
+ | |||
+ | std::cout << "2/3 == 4/6 ? " << std::boolalpha; | ||
+ | std::cout << std::ratio_equal<std::ratio<2, 3>, std::ratio<4, 6>>::value << std::endl; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | affiche : | ||
+ | |||
+ | <code> | ||
+ | 2/3 == 3/4 ? false | ||
+ | 2/3 == 4/6 ? true | ||
+ | </code> | ||
- | Opération arithmétiques : ratio_add, ratio_subtract, ratio_multiply, ratio_divide | + | **Exercices** |
- | Opérateur comparaison : ratio_equal, ratio_not_equal, ratio_less, ratio_less_equal, ratio_greater, ratio_greater_equal | + | * écrivez les opérations suivantes : 2/3+3/4, 2/3-3/4, 2/3*3/4, (2/3) / (3/4). |
- | Ratios communs : micro, milli, kilo, mega, etc. | ||
- | ^ [[complex|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[bitset|Chapitre suivant]] ^ | + | ^ [[complex|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[rvalue_et_lvalue|Chapitre suivant]] ^ |