Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.
complex [2015/09/11 02:52] gbdivers |
complex [2020/10/06 19:10] (Version actuelle) gbdivers |
||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
- | ^ [[boole_et_morgan|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[virgule_fixe|Chapitre suivant]] ^ | ||
- | __ manque explications sur using namespace std::literals; __ | + | <note warning>Ce cours n'est plus à jour, il est préférable de ne pas le suivre. Je vous recommande le cours sur Zeste de Savoir : https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/.</note> |
- | ====== Les nombres complexes ====== | ||
- | L'un des utilisations majeurs du C++ est le calcul numérique intensif. Pour cela, il est possible d'utiliser des fonctionnalités du langage (comme par exemple les nombres à virgule flottante que vous avez vu précédemment) ou des fonctionnalités apportées par des bibliothèques spécialisées ou non. Vous en découvrirez certaines dans les projets d'exemple. | ||
- | |||
- | La bibliothèque standard fournit également quelques outils mathématiques. Vous allez voir dans ce chapitre un exemple permettant de manipuler des nombres complexes. Le but ici n'est pas de présenter mathématiquement les nombres complexes, mais de donner un aperçu de ce qu'une bibliothèque peut fournir comme outil. | ||
- | |||
- | ===== Rappels mathématiques ===== | ||
- | |||
- | __ explications bof bof __ | ||
- | |||
- | Pour commencer, un petit rappel sur les nombres complexes. Pour ceux intéressé par les détails, vous pouvez consulter la page de Wikipédia correspondante ([[https://fr.wikipedia.org/wiki/Nombre_complexe|Nombre complexe]]) ou consulter un cours de mathématique. | ||
- | |||
- | Les équations du second degré peuvent s'écrire de la façon suivante : | ||
- | |||
- | $$ ax^2 + bx + c = 0 $$ | ||
- | |||
- | Si vous vous souvenez de vos cours de lycée, pour résoudre cette équitation, on calcule le déterminé donné par cette formule : | ||
- | |||
- | $$ \Delta = b^2 - 4ac $$ | ||
- | |||
- | Si ce déterminant est positif, l'équation admet deux solutions réelles. S'il est nul, elle admet une seule solution réelle. Le dernier cas, qui nous intéresse plus particulièrement ici, est que si le déterminant est négatif, cette équation n'admet pas de solution réelle. | ||
- | |||
- | Cependant, on peut définir les nombres complexes de la façon suivante : | ||
- | |||
- | $$ z = x + i y $$ | ||
- | |||
- | avec //x// et //y// réels et : | ||
- | |||
- | $$ i^2 = -1 $$ | ||
- | |||
- | Dans ce cas, l'équation admet deux solutions complexes. | ||
- | |||
- | //x// est la partie réelle d'un nombre complexe et //y// est la partie imaginaire. | ||
- | |||
- | Il est classique de représenter les nombres complexes sur un plan, de la façon suivante : | ||
- | |||
- | {{ :complex.png }} | ||
- | |||
- | On peut également définir un nombre complexe par l'angle entre l'abscisse et la diagonale, que l'on appelle "argument" d'un nombre complexe, et la distance entre le centre et le point, que l'on appelle "module". | ||
- | |||
- | ===== Nombres complexes en C++ ===== | ||
- | |||
- | Les nombres complexes sont fournit par la classe ''std::complex'' de la bibliothèque standard. Comme toujours, la documentation de cette classe se trouve sur le site [[http://en.cppreference.com/w/cpp/numeric/complex|cppreference]]. En consultant cette page, vous pouvez trouver au début le fichier d'en-tête à inclure : ''<complex>''. Vous avez également des codes d'exemple à la fin. | ||
- | |||
- | ===== Ecrire une littérale d'un nombre complexe ===== | ||
- | |||
- | Pour rappel, une littérale est une valeur écrite directement dans un code. Pour écrire un nombre complexe en C++, il a fallut commencer par définir une syntaxe pour cela. Une première approche peut être d'écrire directement un nombre complexe en suivant sa définition. Par exemple, pour le nombre : | ||
- | |||
- | $$ z = 2 + 3 i $$ | ||
- | |||
- | On pourrait écrire : | ||
- | |||
- | <code cpp> | ||
- | 2 + 3 * i; | ||
- | </code> | ||
- | |||
- | Cette écriture est tout à fait valable. Cependant, sous cette forme, ''i'' correspond à l'écriture d'une variable (vous verrez cela par la suite), ce qui peut être limitant. Surtout que l'on a l'habitude en C++ d'utiliser la variable ''i'' comme indice (dans un tableau par exemple), d'où le risque de confusion. (Mais rien ne vous interdit par le suite, quand vous saurez créer une variable, de créer cette variable ''i'' avec le nombre complexe ''z = 0 + 1 i''). | ||
- | |||
- | Pour éviter cette ambiguïté, une autre écriture a été choisie : un nombre imaginaire pur s'écrit sous forme d'une littérale réelle, suivie du suffixe ''i''. Par exemple : | ||
- | |||
- | <code cpp> | ||
- | 2.0 + 3.0i; | ||
- | </code> | ||
- | |||
- | Dans ce code, la littérale ''2.0'' est correspond à un littérale réelle et la littérale ''3.0i'' à un nombre imaginaire pur (en pratique, à une littérale réelle avec le suffixe ''i''). Il est possible d'afficher directement un nombre complexe avec ''std::cout'' : | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | std::cout << "2+3i = " << (2.0 + 3.0i) << std::endl; | ||
- | } | ||
- | </code> | ||
- | |||
- | Ce code affiche : | ||
- | |||
- | affiche : | ||
- | |||
- | <code> | ||
- | 2+3i = (2,3) | ||
- | </code> | ||
- | |||
- | Vous voyez ici qu'un nombre complexe est affiché sous la forme ''(partie réelle,partie imaginaire)''. On peut en particulier afficher ''i'' et vérifier que le carré de ''i'' vaut -1. | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | std::cout << "i = " << 1.0i << std::endl; | ||
- | std::cout << "i² = " << (1.0i * 1.0i) << std::endl; | ||
- | } | ||
- | </code> | ||
- | |||
- | affiche : | ||
- | |||
- | <code> | ||
- | i = (0,1) | ||
- | i² = (-1,0) | ||
- | </code> | ||
- | |||
- | Le résultat affiché correspond bien aux valeurs attendues. | ||
- | |||
- | Notez bien que le nombre imaginaire ''i'' ne peut pas s'écrire directement ''i'' dans un code C++, puisque cela correspondrait à l'écriture d'une variable et non d'une littérale. Le ''i'' d'une littérale représentant un nombre imaginaire est un suffixe, il doit toujours suivre une littérale numérique. | ||
- | |||
- | ===== Comparer des nombres complexes ===== | ||
- | |||
- | Il est possible de comparer l'égalité (ou l'inégalité) des nombres complexes entre eux en utilisant les opérateurs ''=='' et ''!='', comme vous l'avez fait avec les nombres entiers et réels. | ||
- | |||
- | Par exemple : | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | std::cout << std::boolalpha << ((2.0 + 3.0i) == (2.0 + 3.0i)) << std::endl; | ||
- | std::cout << std::boolalpha << ((2.0 + 3.0i) == (3.0 + 2.0i)) << std::endl; | ||
- | } | ||
- | </code> | ||
- | |||
- | affiche : | ||
- | |||
- | <code> | ||
- | true | ||
- | false | ||
- | </code> | ||
- | |||
- | Même si le calcul de i au carré donne 1, le résultat affiché correspond au nombre complexe ''(-1,0)''. Mathématiquement, cela est correct : | ||
- | |||
- | $$ -1 + 0 i = -1 $$ | ||
- | |||
- | Il n'est possible de comparer les nombres complexes que par égalité ou inégalité. Les comparaisons d'ordre (plus petit, plus grand, etc.) n'ont pas de sens pour les complexes. | ||
- | |||
- | Cependant, n'oubliez pas que même si deux valeurs sont mathématiquement identiques, le C++ est basé sur un typage fort et différencie les valeurs en fonction de leur type. Ainsi, même si "-1" (nombre entier) est égale à "-1.0" (nombre à virgule flottante) et à "(-1,0)" (nombre complexe), ce sont des valeurs différentes en C++. | ||
- | |||
- | Vous pouvez tester ces égalités, en utilisant l'opérateur d'égalité ''==''. Commençons par la comparaison d'un nombre complexe et d'un nombre à virgule flottante : | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | std::cout << std::boolalpha << ((1.0i * 1.0i) == -1.0) << std::endl; | ||
- | } | ||
- | </code> | ||
- | |||
- | affiche : | ||
- | |||
- | <code> | ||
- | true | ||
- | </code> | ||
- | |||
- | Dans ce cas, pas de problème, le résultat affiché est celui attendu. Si maintenant, vous testez avec un nombre entier : | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | std::cout << std::boolalpha << ((1.0i * 1.0i) == -1) << std::endl; | ||
- | } | ||
- | </code> | ||
- | |||
- | affiche : | ||
- | |||
- | <code> | ||
- | main.cpp:6:51: error: invalid operands to binary expression ('complex<double>' and 'int') | ||
- | std::cout << std::boolalpha << ((1.0i * 1.0i) == -1) << std::endl; | ||
- | ~~~~~~~~~~~~~ ^ ~~ | ||
- | 1 error generated. | ||
- | </code> | ||
- | |||
- | Dans ce cas, le compilateur produit une erreur, indiquant qu'il ne sait pas comparer un nombre complexe et un nombre entier. Ce sont deux types différents pour lui. | ||
- | |||
- | Pour être plus précis, cela signifie que le compilateur connait l'opérateur d'égalité ''=='' entre un complexe et un réel, mais qu'il n'en connait pas entre un complexe et un entier. | ||
- | |||
- | ===== La classe std::complex ===== | ||
- | |||
- | Dans le message d'erreur précédent, vous pouvez remarquer que le compilateur interprète le calcul ''1.0i * 1.0i'' sous forme d'un type qui s'appelle ''std::complex<double>''. Voyant plus en détail cela. | ||
- | |||
- | Une classe est un type, mais définie par un code C++ et non par le langage. Vous ne trouverez nul part un fichier C++ qui définit ''int'' ou ''float'', par contre le type ''std::complex'' est définie dans le fichier d'en-tête "<complex>". | ||
- | |||
- | Vous pourrez de la même façon créer vos propres types en créant des classes, mais plus tard dans ce cours. (La création de classes a une importance particulière en programmation, on parle de "programmation orientée objet"). | ||
- | |||
- | Cette classe est une **abstraction** représentant un nombre complexe : | ||
- | |||
- | * cela représente une version "manipulable par l'ordinateur" d'un concept mathématique et pas exactement ce concept mathématique (par exemple, comme vous l'avez vu, il n'est pas possible de comparer un entier avec un nombre complexe en C++, alors que cela peut avoir un sens en mathématique). | ||
- | * vous n'avez pas besoin de savoir comment est écrit le code C++ définissant cette classe ou même comment l'ordinateur réalise les calculs. Tout ce qu'il vous faut connaître est l'interface publique, c'est à dire la partie de la classe accessible en dehors de la classe. | ||
- | |||
- | Cette notion d'abstraction est très importante à comprendre, puisque cela définit comment vous allez utiliser cette classe (interface publique) et ses limites (ce qui la différencie du modèle mathématique). En particulier pour les calculs numériques, n'oubliez pas que les nombres sur un ordinateur ont des limites (valeur minimale, valeur maximale, nombre maximal de chiffres après la virgule, etc.) | ||
- | |||
- | Pour terminer avec la notion ''std::complex<double>'' : vous avez vu que ''std::complex'' correspond donc au nom de la classe représentant un nombre complexe. Les nombres complexes sont représentés par deux nombres réels "x + y i", donc la classe ''std::complex'' manipule également des nombres réels en interne. Pour le moment, vous n'avez pas vu à quel type correspond les nombres réels que vous avez écrit dans vos codes C++, mais sachez en fait que le C++ peut utiliser plusieurs types différents pour représenter des nombres réels. | ||
- | |||
- | Le type ''double'' est un de ces types, mais il en existe d'autres (''float'', ''long double'', etc.). Les chevrons dans la définition de la classe ''std::complex'' permettent de préciser le type qui sera manipuler en interne par cette classe. Dit autrement, cela signifie que ''std::complex'' utilise le type ''double'' en interne lorsque vous écrivez ''std::complex<double>'', elle utilise ''float'' lorsque vous écrivez ''std::complex<float>'', ''MonType'' lorsque vous écrivez ''std::complex<MonType>'', et ainsi de suite. | ||
- | |||
- | (Notez bien que c'est toujours un type que vous devez mettre entre les chevrons et pas une valeur). | ||
- | |||
- | Pour créer une valeur de type ''std::complex<double>'', vous avez vu que le plus simple est donc d'écrire une littérale numérique utilisant le suffixe ''i''. Cependant, vous aurez besoin dans certains cas de créer un nombre complexe sans écrire de littérale. Par exemple, si vous souhaiter utiliser le résultat d'une expression pour calculer les parties réelle et imaginaire d'un nombre complexe : | ||
- | |||
- | $$ (2 * 3) + (4 * 5) i $$ | ||
- | |||
- | Une première solution est de multiplier le résultat de l'expression de droite par le nombre imaginaire //i// (qui s'écrit donc ''1.0i'' en C++) : | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | std::cout << ((2.0 * 3.0) + (4.0 * 5.0) * 1.0i) << std::endl; | ||
- | } | ||
- | </code> | ||
- | |||
- | affiche : | ||
- | |||
- | <code> | ||
- | (6,20) | ||
- | </code> | ||
- | |||
- | Une autre solution est d'appeler spécifiquement la classe ''std::complex<double>'' en passant les expressions entre parenthèses, sous la forme : ''std::complex<double>(partie réelle, partie imaginaire)''. Concrètement, cela donne le code suivant : | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | std::cout << std::complex<double>(2.0 * 3.0, 4.0 * 5.0) << std::endl; | ||
- | } | ||
- | </code> | ||
- | |||
- | Ce qui affiche la même chose que précédemment. | ||
- | |||
- | Notez bien qu'il ne faut pas mettre dans cette écriture l'opérateur ''+'', ni le nombre imaginaire //i//. | ||
- | |||
- | <note> | ||
- | Cette syntaxe est nouvelle, donc pas forcement claire pour le moment. Mais pas d'inquiétude, vous verrez cela régulièrement, dans de nombreux codes. Retenez simplement l'idée générale : | ||
- | |||
- | Le code ''std::complex<double>(2.0, 3.0)'' signifie que ''std::complex'' manipule en interne des nombres réels de type ''double'' et représente le nombre complexe //2 + 3i//. | ||
- | </note> | ||
- | |||
- | ===== Les opérations et fonctions sur std::complex ===== | ||
- | |||
- | Pour terminer ce chapitre sur les nombres complexes, vous avez vu dans le chapitre sur les nombres réels que le C++ propose de nombreuses fonctions mathématiques pour les réels. C'est également le cas pour les nombres complexes. Cependant, toutes les fonctions sur les nombres réels ne sont pas forcement définies pour les nombres complexes. | ||
- | |||
- | Pour commencer, les nombres complexes définissent les opérations arithmétiques de base, comme l'addition et la soustraction entre complexes, ainsi que l'addition, la soustraction, la multiplication et la division entre un complexe et une nombre réel. | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | |||
- | std::cout << ((2.0 + 3.0i) + (4.0 + 5.0i)) << std::endl; // addition | ||
- | std::cout << ((2.0 + 3.0i) - (4.0 + 5.0i)) << std::endl; // soustraction | ||
- | |||
- | std::cout << ((2.0 + 3.0i) + 4.0) << std::endl; // addition | ||
- | std::cout << ((2.0 + 3.0i) - 4.0) << std::endl; // soustraction | ||
- | std::cout << ((2.0 + 3.0i) * 4.0) << std::endl; // multiplication | ||
- | std::cout << ((2.0 + 3.0i) / 4.0) << std::endl; // division | ||
- | } | ||
- | </code> | ||
- | |||
- | affiche : | ||
- | |||
- | <code> | ||
- | (6,8) | ||
- | (-2,-2) | ||
- | (6,3) | ||
- | (-2,3) | ||
- | (8,12) | ||
- | (0.5,0.75) | ||
- | </code> | ||
- | |||
- | En complément de ces opérations de base, les nombres complexes peuvent être utilisés avec différentes fonctions mathématiques. La syntaxe a utiliser est similaire à celle que vous avez vu pour les nombres réels : | ||
- | |||
- | <code cpp main.cpp> | ||
- | #include <iostream> | ||
- | #include <complex> | ||
- | |||
- | int main() { | ||
- | using namespace std::literals; | ||
- | |||
- | std::cout << real(2.0 + 3.0i) << std::endl; // partie réelle | ||
- | std::cout << imag(2.0 + 3.0i) << std::endl; // partie imaginaire | ||
- | std::cout << abs(2.0 + 3.0i) << std::endl; // module (valeur absolue en anglais) | ||
- | std::cout << arg(2.0 + 3.0i) << std::endl; // argument | ||
- | std::cout << norm(2.0 + 3.0i) << std::endl; // norme | ||
- | std::cout << conj(2.0 + 3.0i) << std::endl; // conjugué | ||
- | std::cout << proj(2.0 + 3.0i) << std::endl; // projection | ||
- | std::cout << polar(2.0 + 3.0i) << std::endl; // coordonnées polaires | ||
- | } | ||
- | </code> | ||
- | |||
- | affiche : | ||
- | |||
- | <code> | ||
- | 2 | ||
- | 3 | ||
- | 3.60555 | ||
- | 0.982794 | ||
- | 13 | ||
- | (2,-3) | ||
- | (2,3) | ||
- | ((2,3),(0,0)) | ||
- | </code> | ||
- | |||
- | Il existe d'autres fonctions mathématiques sur les nombres complexes, qui s'utilisent de la même façon (voir la documentation pour la liste des fonctions : [[http://en.cppreference.com/w/cpp/numeric/complex|Documentation de std::complex]]) : fonctions exponentielles, puissances, trigonométriques et hyperboliques (voir la page de Wikipédia pour les explications sur ces fonctions mathématiques : [[https://fr.wikipedia.org/wiki/Nombre_complexe|Nombre complexe]]). | ||
- | |||
- | ^ [[boole_et_morgan|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[virgule_fixe|Chapitre suivant]] ^ |