CentraleSupélecDépartement informatique
Plateau de Moulon
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
3IF1020 - Concepts des langages de programmation, mise en œuvre en C/C++ - TD n°1
  • 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 avec 1 puis avec -4.5 comme argument, et qui affiche les résultats retournés.

    Appeler cette fonction dans main(), 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 fonction sin_x_plus_cos_sqrt2_times_x() avec la valeur fournie par l'utilisateur comme argument et affiche le résultat retourné.

    Appeler cette fonction dans main(), 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 fonction sin_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 boucle for avec une variable de boucle commençant à 0.

    Appeler cette fonction dans main(), 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 entre begin et end avec un pas de step.

Vous utiliserez une boucle for avec une variable de boucle commençant à begin.
  • Définir une fonction test_22() qui appelle la précédente avec -10, 10 et 2 comme arguments. Appeler cette fonction dans main(), 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 fonction print_sin_x_plus_cos_sqrt2_times_x() pour afficher les valeurs demandées.

    Vous utiliserez une instruction if pour vérifier que la borne haute donnée par l'utilisateur est strictement supérieure à la borne basse, et une instruction while 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 dans main(), 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 en x.

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 de sin_x_plus_cos_sqrt2_times_x() dans l'intervalle [-4.6, -4.5] par pas de 0.01 avec une valeur d'epsilon égale à 10-5.

    Appeler cette fonction dans main(), 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 de func sur ce nombre est inférieur en valeur absolue à precision.
    Cette fonction a pour précondition que func( begin ) et func( 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 fonction sin_x_plus_cos_sqrt2_times_x() dans l'intervalle [-2, 0] avec une précision de 10-5.

    Appeler cette fonction dans main(), 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 largeur width (de la forme [begin + n * width, begin + (n + 1) * width]n est un naturel quelconque), sans dépasser end comme borne haute, un nombre pour lequel le résultat de l'appel de func est inférieur en valeur absolue à precision ; au maximum, max_number_of_results seront retournés dans le tableau results ; la fonction a pour résultat le nombre exact de valeurs présentes dans results.
  • Définir une fonction test_41() qui cherche les zéros de la fonction sin_x_plus_cos_sqrt2_times_x() dans l'intervalle [-10, 10], avec une largeur de 0.5, une précision de 10-5 et un maximum de 10 zéros retournés.

    Appeler cette fonction dans main(), 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 largeur width (de la forme [begin + n * width, begin + (n + 1) * width]n est un naturel quelconque), sans dépasser end comme borne haute, un extrema de func, un extrema étant défini comme un nombre pour lequel la valeur estimée (calculée en utilisant epsilon) de la dérivée de func en ce nombre est inférieur en valeur absolue à precision ; au maximum, max_number_of_results seront retournés dans le tableau results ; la fonction a pour résultat le nombre exact de valeurs présentes dans results.

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 arguments func et espilon reçus comme arguments de find_all_zeros(), prendre un flottant en argument et appeler compute_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 fonction sin_x_plus_cos_sqrt2_times_x() dans l'intervalle [-10, 10], avec une largeur de 0.5, une précision de 10-5, une dérivée estimée en utilisant 10-5 pour epsilon et un maximum de 10 extrema retournés.

    Appeler cette fonction dans main(), 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";
    }
}