Ceci est une ancienne révision du document !
manque explications sur using namespace std::literals;
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.
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 (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 équation, 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 :
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”.
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 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.
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 :
2 + 3 * i;
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 :
2.0 + 3.0i;
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
:
#include <iostream> #include <complex> int main() { using namespace std::literals; std::cout << "2+3i = " << (2.0 + 3.0i) << std::endl; }
Ce code affiche :
affiche :
2+3i = (2,3)
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.
#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; }
affiche :
i = (0,1) i² = (-1,0)
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.
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 :
#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; }
affiche :
true false
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 :
#include <iostream> #include <complex> int main() { using namespace std::literals; std::cout << std::boolalpha << ((1.0i * 1.0i) == -1.0) << std::endl; }
affiche :
true
Dans ce cas, pas de problème, le résultat affiché est celui attendu. Si maintenant, vous testez avec un nombre entier :
#include <iostream> #include <complex> int main() { using namespace std::literals; std::cout << std::boolalpha << ((1.0i * 1.0i) == -1) << std::endl; }
affiche :
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.
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.
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 :
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++) :
#include <iostream> #include <complex> int main() { using namespace std::literals; std::cout << ((2.0 * 3.0) + (4.0 * 5.0) * 1.0i) << std::endl; }
affiche :
(6,20)
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 :
#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; }
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.
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.
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.
#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 }
affiche :
(6,8) (-2,-2) (6,3) (-2,3) (8,12) (0.5,0.75)
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 :
#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 }
affiche :
2 3 3.60555 0.982794 13 (2,-3) (2,3) ((2,3),(0,0))
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 : 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 : Nombre complexe).