- Date de la séance de TD : vendredi 27 septembre 2024 - 10h00
1. Définition de fonctions, lecture de données et affichage
1.1. Définition d'une fonction simple, affichage
Nous allons nous intéresser à la fonction {$f(x) = sin(x)+cos(\sqrt{2}*x)$} ; nous souhaitons plus particulièrement trouver ses extrema. La voici sous forme graphique sur l'intervalle {$ [-10,10] $} :
- Définir une fonction de signature
double sin_x_plus_cos_sqrt2_times_x( double x );
qui calcule et retourne la valeur de {$f$} en {$x$}.
À partir de C++20, {$\sqrt{2}$} peut être obtenu avec std::numbers::sqrt2
(avec le fichier d'entête <numbers>
).
Sinon, {$\sqrt{2}$} est définie comme constante nommée M_SQRT2
dans <cmath>
.
- Définir une fonction
test_11()
qui appelle la précédente avec1
puis avec-4.5
comme argument, et qui affiche les résultats retournés.
Appeler cette fonction dansmain()
, le résultat attendu est :
sin_x_plus_cos_sqrt2_times_x( 1 ) = 0.997415
sin_x_plus_cos_sqrt2_times_x( -4.5 ) = 1.97427
Solution possible
#include <iostream> #include <numbers> double sin_x_plus_cos_sqrt2_times_x( double x ) { //return std::sin( x ) + std::cos( M_SQRT2 * x ); return std::sin( x ) + std::cos( std::numbers::sqrt2 * x ); } void test_11() { std::cout << "sin_x_plus_cos_sqrt2_times_x( 1 ) = " << sin_x_plus_cos_sqrt2_times_x( 1 ) << "\n"; std::cout << "sin_x_plus_cos_sqrt2_times_x( -4.5 ) = " << sin_x_plus_cos_sqrt2_times_x( -4.5 ) << "\n"; }
1.2. Lecture
- Définir une fonction
test_12()
qui demande une valeur à l'utilisateur, appelle la fonctionsin_x_plus_cos_sqrt2_times_x()
avec la valeur fournie par l'utilisateur comme argument et affiche le résultat retourné.
Appeler cette fonction dansmain()
, voici un exemple du résultat attendu (en vert, ce qui est fourni par l'utilisateur) :
Valeur : -4.53
sin_x_plus_cos_sqrt2_times_x( -4.53 ) = 1.97583
Solution possible
void test_12() { std::cout << "Valeur : "; double value{ 0 }; std::cin >> value; std::cout << "sin_x_plus_cos_sqrt2_times_x( " << value << " ) = " << sin_x_plus_cos_sqrt2_times_x( value ) << "\n"; }
2. Structures de contrôle
2.1. Boucle for
version 1
- Définir une fonction
test_21()
qui demande une valeur à l'utilisateur, et affiche le résultat de l'appel de la fonctionsin_x_plus_cos_sqrt2_times_x()
pour 10 valeurs espacées de 1 en commençant avec la valeur fournie par l'utilisateur.
Vous utiliserez une bouclefor
avec une variable de boucle commençant à0
.
Appeler cette fonction dansmain()
, voici un exemple du résultat attendu (en vert, ce qui est fourni par l'utilisateur) :
Valeur initiale : -5
sin_x_plus_cos_sqrt2_times_x( -5 ) = 1.66427
sin_x_plus_cos_sqrt2_times_x( -4 ) = 1.56699
sin_x_plus_cos_sqrt2_times_x( -3 ) = -0.593782
sin_x_plus_cos_sqrt2_times_x( -2 ) = -1.86066
sin_x_plus_cos_sqrt2_times_x( -1 ) = -0.685527
sin_x_plus_cos_sqrt2_times_x( 0 ) = 1
sin_x_plus_cos_sqrt2_times_x( 1 ) = 0.997415
sin_x_plus_cos_sqrt2_times_x( 2 ) = -0.0420657
sin_x_plus_cos_sqrt2_times_x( 3 ) = -0.311542
sin_x_plus_cos_sqrt2_times_x( 4 ) = 0.0533811
Solution possible
void test_21() { std::cout << "Valeur initiale : "; double value{ 0 }; for( int i{ 0 }; i < 10; ++i ) { std::cout << "sin_x_plus_cos_sqrt2_times_x( " << value + i << " ) = " << sin_x_plus_cos_sqrt2_times_x( value + i ) << "\n"; } }
2.2. Boucle for
version 2
- Définir une fonction de signature
void print_sin_x_plus_cos_sqrt2_times_x( double begin, double end, double step );
- qui affiche le résultat de la fonction
sin_x_plus_cos_sqrt2_times_x()
pour toutes les valeurs entrebegin
etend
avec un pas destep
.
Vous utiliserez une bouclefor
avec une variable de boucle commençant àbegin
.
- Définir une fonction
test_22()
qui appelle la précédente avec-10
,10
et2
comme arguments. Appeler cette fonction dansmain()
, le résultat attendu est :
sin_x_plus_cos_sqrt2_times_x( -10 ) = 0.539052
sin_x_plus_cos_sqrt2_times_x( -8 ) = -0.676563
sin_x_plus_cos_sqrt2_times_x( -6 ) = -0.310779
sin_x_plus_cos_sqrt2_times_x( -4 ) = 1.56699
sin_x_plus_cos_sqrt2_times_x( -2 ) = -1.86066
sin_x_plus_cos_sqrt2_times_x( 0 ) = 1
sin_x_plus_cos_sqrt2_times_x( 2 ) = -0.0420657
sin_x_plus_cos_sqrt2_times_x( 4 ) = 0.0533811
sin_x_plus_cos_sqrt2_times_x( 6 ) = -0.86961
sin_x_plus_cos_sqrt2_times_x( 8 ) = 1.30215
sin_x_plus_cos_sqrt2_times_x( 10 ) = -0.54899
Solution possible
void print_sin_x_plus_cos_sqrt2_times_x( double begin, double end, double step ) { for( double x{ begin }; x <= end; x += step ) { std::cout << "sin_x_plus_cos_sqrt2_times_x( " << x << " ) = " << sin_x_plus_cos_sqrt2_times_x( x ) << "\n"; } } void test_22() { print_sin_x_plus_cos_sqrt2_times_x( -10, 10, 2 ); }
2.3. Conditionnelle, boucle while
- Définir une fonction
test_23()
qui demande à l'utilisateur une borne basse, une borne haute (qui devra être supérieure à la borne basse) et un nombre de valeurs à afficher, puis appelle la fonctionprint_sin_x_plus_cos_sqrt2_times_x()
pour afficher les valeurs demandées.
Vous utiliserez une instructionif
pour vérifier que la borne haute donnée par l'utilisateur est strictement supérieure à la borne basse, et une instructionwhile
pour refaire une demande en cas de test négatif ; vous ferez de même pour vérifier qu'au moins 2 valeurs sont demandées.
Appeler cette fonction dansmain()
, voici un exemple du résultat attendu (en vert, ce qui est fourni par l'utilisateur) :
Borne basse : -2
Borne haute : -4
Erreur : la borne haute doit être plus grande que la borne basse
Borne haute : 2
Nombre de valeurs : 1
Erreur : le nombre de valeurs doit être au minimum 2
Nombre de valeurs : 11
sin_x_plus_cos_sqrt2_times_x( -2 ) = -1.86066
sin_x_plus_cos_sqrt2_times_x( -1.6 ) = -1.63761
sin_x_plus_cos_sqrt2_times_x( -1.2 ) = -1.05796
sin_x_plus_cos_sqrt2_times_x( -0.8 ) = -0.291936
sin_x_plus_cos_sqrt2_times_x( -0.4 ) = 0.454803
sin_x_plus_cos_sqrt2_times_x( -1.11022e-16 ) = 1
sin_x_plus_cos_sqrt2_times_x( 0.4 ) = 1.23364
sin_x_plus_cos_sqrt2_times_x( 0.8 ) = 1.14278
sin_x_plus_cos_sqrt2_times_x( 1.2 ) = 0.806114
sin_x_plus_cos_sqrt2_times_x( 1.6 ) = 0.361537
sin_x_plus_cos_sqrt2_times_x( 2 ) = -0.0420657
Si vous entrez autre chose que des nombres lors de la lecture, votre programme risque de boucler de manière infinie et il faudra l'arrêter avec CTRL-C
.
Le corrigé vous montrera comment faire en utilisant ignore()
, mais il ne vous est pas demandé d'écrire ce traitement d'erreur.
Solution possible
#include <limits> bool check_input_is_number() { if( std::cin.eof() || std::cin.bad() ) std::exit( -1 ); if( std::cin.fail() ) { std::cin.clear(); std::cin.ignore( std::numeric_limits< std::streamsize >::max(), '\n' ); std::cout << "Erreur : il faut entrer un nombre\n"; return false; } return true; } void test_23() { double begin{ 0 }; do { std::cout << "Borne basse : "; std::cin >> begin; } while( ! check_input_is_number() ); double end{ begin }; while( end <= begin ) { do { std::cout << "Borne haute : "; std::cin >> end; } while( ! check_input_is_number() ); if( end <= begin ) std::cout << "Erreur : la borne haute doit être plus grande que la borne basse\n"; } unsigned int nb_values{ 0 }; while( nb_values < 2 ) { do { std::cout << "Nombre de valeurs : "; std::cin >> nb_values; } while( ! check_input_is_number() ); if( nb_values < 2 ) std::cout << "Erreur : le nombre de valeurs doit être au minimum 2\n"; } print_sin_x_plus_cos_sqrt2_times_x( begin, end, ( end - begin ) / ( nb_values - 1 )); }
3. Fonction comme argument
3.1. Calcul de dérivée
Pour trouver les extrema de notre fonction, nous allons chercher les points où sa dérivée s'annule. Nous souhaitons que notre recherche d'extrema soit générique et indépendante de la fonction étudiée, ce qui signifie que nous ne connaissons pas sa dérivée formelle. Nous allons donc utiliser un calcul approché de cette dérivée en utilisant la formule {$f'(x) ≃ \frac{f(x + ε) - f(x)}{ε}$}.
Une signature possible de notre fonction, calculant la valeur estimée de la dérivée de la fonction func
en x
, pourrait être :
double compute_derivative( double func( double ), double x, double epsilon );
Et nous pourrions alors l'utiliser ainsi :
double estimate = compute_derivative( sin_x_plus_cos_sqrt2_times_x, 1, 1e-5 );
Pour recevoir la fonction avec l'argument func
, nous allons plutôt utiliser le type std::function
de la bibliothèque standard qui est beaucoup plus général, ce qui sera nécessaire pour la suite ; cela ne changera rien à son utilisation.
- Définir une fonction de signature
double compute_derivative( std::function< double( double ) > func, double x, double epsilon );
- qui retourne la valeur estimée de la dérivée de la fonction
func
enx
.
Les caractères <
et >
indiquent que le type std::function
est générique ; entre ces deux symboles figure le paramètre de généricité : une fonction qui reçoit un double
en argument et retourne un double
. La généricité sera vue lors d'un cours ultérieur.
- Définir une fonction
test_31()
qui affiche l'estimation de la dérivée desin_x_plus_cos_sqrt2_times_x()
dans l'intervalle[-4.6, -4.5]
par pas de0.01
avec une valeur d'epsilon
égale à10-5
.
Appeler cette fonction dansmain()
, le résultat attendu est :
derivative of sin_x_plus_cos_sqrt2_times_x( -4.6 ) = 0.199488
derivative of sin_x_plus_cos_sqrt2_times_x( -4.59 ) = 0.170018
derivative of sin_x_plus_cos_sqrt2_times_x( -4.58 ) = 0.140501
derivative of sin_x_plus_cos_sqrt2_times_x( -4.57 ) = 0.110944
derivative of sin_x_plus_cos_sqrt2_times_x( -4.56 ) = 0.08135
derivative of sin_x_plus_cos_sqrt2_times_x( -4.55 ) = 0.0517246
derivative of sin_x_plus_cos_sqrt2_times_x( -4.54 ) = 0.0220728
derivative of sin_x_plus_cos_sqrt2_times_x( -4.53 ) = -0.0076006
derivative of sin_x_plus_cos_sqrt2_times_x( -4.52 ) = -0.0372906
derivative of sin_x_plus_cos_sqrt2_times_x( -4.51 ) = -0.0669923
derivative of sin_x_plus_cos_sqrt2_times_x( -4.5 ) = -0.0967007
Solution possible
#include <functional> double compute_derivative( std::function< double( double ) > func, double x, double epsilon ) { return (func( x + epsilon ) - func( x )) / epsilon; } void test_31() { for( double x{-4.6}; x <= -4.5; x += 0.01 ) { std::cout << "derivative of sin_x_plus_cos_sqrt2_times_x( " << x << " ) = " << compute_derivative( sin_x_plus_cos_sqrt2_times_x, x, 1e-5 ) << "\n"; } }
3.2. Recherche d'un zéro
- Définir une fonction de signature
double find_zero( std::function< double( double ) > func, double begin, double end, double precision );
qui retourne un nombre compris dans l'intervalle[begin, end]
pour lequel le résultat de l'appel defunc
sur ce nombre est inférieur en valeur absolue àprecision
.
Cette fonction a pour précondition quefunc( begin )
etfunc( end )
sont de signes opposés ; si cette précondition n'est pas vérifiée, la fonction retournera NAN.
Vous procéderez par dichotomie.
Attention : le calcul sur les flottants ne donne pas toujours de résultat exact puisque tout réel ne peut pas être représenté. Ainsi, si begin
et end
sont des double
, il est possible que :
( begin + end ) / 2 == begin
ou ( begin + end ) / 2 == end
Il faudra dans ce cas arrêter la dichotomie même si la précision demandée n'est pas obtenue.
- Définir une fonction
test_32()
qui cherche un zéro de la fonctionsin_x_plus_cos_sqrt2_times_x()
dans l'intervalle[-2, 0]
avec une précision de10-5
.
Appeler cette fonction dansmain()
, le résultat attendu est :
sin_x_plus_cos_sqrt2_times_x( -0.65065 ) = -9.37845e-06
La fonction std::abs()
, déclarée dans <cmath>
, retourne la valeur absolue de son argument.
Solution possible
#include <cmath> double find_zero( std::function< double( double ) > func, double begin, double end, double precision ) { double val_begin{ func( begin ) }; double val_end { func( end ) }; if( val_begin * val_end > 0 ) return NAN; while(( std::abs( val_begin ) > precision ) && ( std::abs( val_end ) > precision )) { double middle{ ( begin + end ) / 2 }; if( middle == begin ) return middle; if( middle == end ) return middle; double val_middle{ func( middle ) }; if( val_begin * val_middle > 0 ) { begin = middle; val_begin = val_middle; } else { end = middle; val_end = val_middle; } } return ( val_begin <= precision ) ? begin : end; } void test_32() { double zero{ find_zero( sin_x_plus_cos_sqrt2_times_x, -2, 0, 1e-5 ) }; std::cout << "sin_x_plus_cos_sqrt2_times_x( " << zero << " ) = " << sin_x_plus_cos_sqrt2_times_x( zero ) << "\n"; }
4. Recherche des extrema
4.1. Recherche des zéros
- Définir une fonction de signature
int find_all_zeros( std::function< double( double ) > func, double begin, double end, double width, double precision, double results[], int max_number_of_results );
qui cherche dans chaque intervalle de largeurwidth
(de la forme[begin + n * width, begin + (n + 1) * width]
oùn
est un naturel quelconque), sans dépasserend
comme borne haute, un nombre pour lequel le résultat de l'appel defunc
est inférieur en valeur absolue àprecision
; au maximum,max_number_of_results
seront retournés dans le tableauresults
; la fonction a pour résultat le nombre exact de valeurs présentes dansresults
. - Définir une fonction
test_41()
qui cherche les zéros de la fonctionsin_x_plus_cos_sqrt2_times_x()
dans l'intervalle[-10, 10]
, avec une largeur de0.5
, une précision de10-5
et un maximum de 10 zéros retournés.
Appeler cette fonction dansmain()
, le résultat attendu est :
sin_x_plus_cos_sqrt2_times_x( -8.45839 ) = 3.33726e-06
sin_x_plus_cos_sqrt2_times_x( -5.85583 ) = -6.30126e-05
sin_x_plus_cos_sqrt2_times_x( -3.25323 ) = 3.65268e-06
sin_x_plus_cos_sqrt2_times_x( -0.65065 ) = -9.37845e-06
sin_x_plus_cos_sqrt2_times_x( 1.95193 ) = 5.49956e-07
sin_x_plus_cos_sqrt2_times_x( 3.79224 ) = -4.83752e-07
sin_x_plus_cos_sqrt2_times_x( 4.5545 ) = 4.40336e-06
sin_x_plus_cos_sqrt2_times_x( 7.15709 ) = -1.13606e-05
sin_x_plus_cos_sqrt2_times_x( 9.75967 ) = 6.98163e-06
Solution possible
int find_all_zeros( std::function< double( double ) > func, double begin, double end, double width, double precision, double results[], int max_number_of_results ) { if( max_number_of_results <= 0 ) return 0; int current_result_index{ 0 }; for( double x{ begin }; x + width <= end; x += width ) { double val_x { func( x ) }; double val_x_width{ func( x + width ) }; if( val_x * val_x_width > 0 ) continue; double zero{ find_zero( func, x, x + width, precision ) }; results[current_result_index] = zero; if( ++current_result_index >= max_number_of_results ) return current_result_index; } return current_result_index; } void test_41() { double results[10]; int nb{ find_all_zeros( sin_x_plus_cos_sqrt2_times_x, -10, 10, 0.5, 1e-5, results, 10 ) }; for( int i{ 0 }; i < nb; ++i ) { std::cout << "sin_x_plus_cos_sqrt2_times_x( " << results[i] << " ) = " << sin_x_plus_cos_sqrt2_times_x( results[i] ) << "\n"; } }
4.2. Recherche des extrema
- Définir une fonction de signature
int find_all_extrema( std::function< double( double ) > func, double begin, double end, double width, double precision, double epsilon, double results[], int max_number_of_results );
qui cherche dans chaque intervalle de largeurwidth
(de la forme[begin + n * width, begin + (n + 1) * width]
oùn
est un naturel quelconque), sans dépasserend
comme borne haute, un extrema defunc
, un extrema étant défini comme un nombre pour lequel la valeur estimée (calculée en utilisantepsilon
) de la dérivée defunc
en ce nombre est inférieur en valeur absolue àprecision
; au maximum,max_number_of_results
seront retournés dans le tableauresults
; la fonction a pour résultat le nombre exact de valeurs présentes dansresults
.
La difficulté est d'appeler la fonction find_all_zeros()
avec pour argument func
non pas la fonction reçue en argument, mais celle qui estime sa dérivée en un point.
Voici une solution possible pour résoudre cette difficulté :
- Définir une fonction lambda qui appelle
compute_derivative()
: cette lambda doit capturer les argumentsfunc
etespilon
reçus comme arguments defind_all_zeros()
, prendre un flottant en argument et appelercompute_derivative()
avec ces trois éléments.
Une autre solution, si le compilateur que vous utilisez ne supporte pas les fonctions lambda est d'utiliser std::bind()
de la bibliothèque standard : cette fonction vous permettrra d'obtenir une fonction basée sur compute_derivative()
mais avec le premier et le troisième arguments fixés à func
et espilon
(reçus comme arguments de find_all_zeros()
), alors que le deuxième argument sera std::placeholders::_1
(la fonction obtenue sera donc une fonction à un argument).
- Définir une fonction
test_42()
qui cherche les extrema de la fonctionsin_x_plus_cos_sqrt2_times_x()
dans l'intervalle[-10, 10]
, avec une largeur de0.5
, une précision de10-5
, une dérivée estimée en utilisant10-5
pourepsilon
et un maximum de 10 extrema retournés.
Appeler cette fonction dansmain()
, le résultat attendu est :
sin_x_plus_cos_sqrt2_times_x( -9.44104 ) = 0.723454
sin_x_plus_cos_sqrt2_times_x( -7.04443 ) = -1.54879
sin_x_plus_cos_sqrt2_times_x( -4.53256 ) = 1.97584
sin_x_plus_cos_sqrt2_times_x( -2.00695 ) = -1.86073
sin_x_plus_cos_sqrt2_times_x( 0.479523 ) = 1.24009
sin_x_plus_cos_sqrt2_times_x( 2.71632 ) = -0.352361
sin_x_plus_cos_sqrt2_times_x( 4.18613 ) = 0.0701068
sin_x_plus_cos_sqrt2_times_x( 6.11841 ) = -0.88052
sin_x_plus_cos_sqrt2_times_x( 8.55212 ) = 1.65677
Solution possible
int find_all_extrema( std::function< double( double ) > func, double begin, double end, double width, double precision, double epsilon, double results[], int max_number_of_results ) { // Solution avec une fonction lambda auto derivative = [func, epsilon]( double x ) { return compute_derivative( func, x, epsilon ); }; // Solution avec std::bind //using namespace std::placeholders; //auto derivative{ std::bind( compute_derivative, func, _1, epsilon ) }; return find_all_zeros( derivative, begin, end, width, precision, results, max_number_of_results ); } void test_42() { double results[10]; int nb{ find_all_extrema( sin_x_plus_cos_sqrt2_times_x, -10, 10, 0.5, 1e-5, 1e-5, results, 10 ) }; for( int i{ 0 }; i < nb; ++i ) { std::cout << "sin_x_plus_cos_sqrt2_times_x( " << results[i] << " ) = " << sin_x_plus_cos_sqrt2_times_x( results[i] ) << "\n"; } }