Saturday, February 2, 2013






S'agissant d'exercices « pour débutants », les programmes que nous développons ici et dans les séries suivantes sont toujours corrects, mais pas toujours en accord avec les règles de l'art (l'art de programmer). En particulier :
  • Au début, les variables sont toutes déclarées globales, ce n'est pas l'idéal. Nous verrons plus tard le mode d'emploi et les avantages des variables locales.
  • Nos programmes étant très courts et accompagnés d'explications, il n'y a pas dans leur code les commentaires qui nomalement seraient indispensables.

Exo 1

#include <stdio.h>

float pht, pttc;

main() {
    printf("Prix HT ? ");
    scanf("%f", &pht);
    pttc = pht * (1 + 19.6 / 100);
    printf("Prix TTC : %f\n", pttc);
}
[1] C'est un exercice tout à fait élémentaire. Notez l'absolue nécessité de mettre l'opérateur & devant la variable pht. Faites des essais et constatez que l'oubli de cet opérateur peut engendrer les erreurs les plus graves (« Erreur de segmentation », etc.).
[2] Si on avait déclaré pht de type double au lieu de float, l'instruction scanf aurait dû s'écrire
    scanf("%lf", &pht);
Faites des essais et constatez que ce programme ne marche pas bien si on oublie ce « l ».
La fonction scanf est donc assez délicate à utiliser ; quand les programmes deviendront plus complexes on ara tendance à l'oublier... A retenir, pour la suite : quand un programme qui utilise scanf ne donne pas le résultat attendu, commencez par valider les appels de scanf. Pour cela il suffit de faire suivre chaque appel de scanf par un printf de contrôle :
    ...
    scanf("%f", &pht);
    printf("pht : %f\n", pht);
    ...
[3] La fonction printf requiert des expressions qui n'ont pas à être nécessairement des variables (contrairement à scanf). On peut donc économiser la variable pttc, en remplaçant les quatre lignes de la fonction main ci-dessus par
    ...
    printf("Prix HT ? ");
    scanf("%f", &pht);
    printf("Prix TTC : %f\n", pht * (1 + 19.6 / 100));
    ...
En efficacité on n'y gagne rien, et le programme n'en devient pas plus facile à lire...
[4] Ceux qui utilisent Dev-C+ auront constaté que la fenêtre d'exécution se ferme à la fin du programme et on n'a pas le temps de lire le résultat. Indiquons une fois pour toutes qu'on corrige cela en écrivant, à la fin de la fonction main :
    ...
    system("pause");
}
system est une fonction standard C qui appelle une commande du système sous-jacent ; pause est une commande du système Windows qui arrête le programme, affiche un message (Appuyez sur une touche pour continuer...) et attend un geste de l'utilisateur : voyez l'image ci-dessous.
[5] Voici l'image d'une exécution de ce programme, par exemple sur Windows :
Vous pensez peut-être que 119.599998 n'est pas le résultat attendu (119.6). Eh bien, c'est décevant mais il faudra vous y faire ! Les nombres flottants utilisés dans les ordinateurs ne sont qu'une piètre imitation des nombres réels des mathématiques, et on ne doit pas en espérer une grande précision. Songez qu'avec le codage habituel des float et des double, la représentation d'un nombre aussi simple que 0.1 n'est pas exacte !
Ici l'erreur n'est pas énorme, si on se limite à deux chiffres après la virgule le résultat est exact. On peut améliorer notre programme en imposant le nombre de décimales voulues :
    ...
    printf("Prix TTC : %.2f\n", pttc);
    ...
Maintenant, le résultat s'affiche ainsi : « Prix TTC : 119.60 »

Exo 2

Première version du programme (on rappelle que les quatre lignes mises en commentaire ne sont pas prises en considération) :
#include <stdio.h>
#include <math.h>

float a, b, mArith, mGeo;

main() {
    a = 2;
    b = 3;

 /* printf("a ? ");
    scanf("%f", &a);
    printf("b ? ");
    scanf("%f", &b); */

    printf("a : %f\n", a);
    printf("b : %f\n", b);
    mArith = (a + b) / 2;
    mGeo = sqrt(a * b);
    printf("moyenne arithmétique : %f\n", mArith);
    printf("moyenne gémétrique   : %f\n", mGeo);
}
La deuxième version est la même, en déplaçant les commentaires :
#include <stdio.h>
#include <math.h>

float a, b, mArith, mGeo;

main() {
 /* a = 2;
    b = 3; */

    printf("a ? ");
    scanf("%f", &a);
    printf("b ? ");
    scanf("%f", &b);
 
    printf("a : %f\n", a);
    printf("b : %f\n", b);
    mArith = (a + b) / 2;
    mGeo = sqrt(a * b);
    printf("moyenne arithmétique : %f\n", mArith);
    printf("moyenne gémétrique   : %f\n", mGeo);
}
Notez l'importance des lignes #include, surtout la deuxième. Aussi longtemps qu'on utilise simplement printf et scanf, omettre la directive « #include <stdio.h> » est déconseillé mais n'empêche pas le programme de fonctionner.
En revanche (faites des essais), il n'y a pas moyen d'obtenir un résultat juste si on a oublié de mettre la directive « #include <math.h> ». En effet, en l'absence de toute déclaration à son propos, la fonction sqrt sera considérée comme rendant un int. Or, le codage interne des int et celui des float et double sont différents et la suite du travail ne peut pas être juste.

Exo 3

Notre principal problème, ici, est de calculer le « quotient par défaut » (appelé aussi quotient de la division euclidienne, le quotient de l'école primaire). Pour faire cela en C il n'y a pas besoin de chercher bien loin : si a et b sont de type entier (int, char, short, long), alors a / b représente ce quotient par défaut. Par exemple, si somme vaut 1234 alors somme / 500 vaut 2, non 2.468.
Donc quotient = somme / 500 est le nombre de billets de 500 qu'il faut donner ; la somme qui reste à rendre ensuite est le reste de la division :
somme = somme - quotient * 500;
ou, plus simplement
somme = somme % 500;  
Il y a plusieurs manières d'organiser ce programme. En voici deux : une très rustique et une assez sophistiquée.
#include <stdio.h>

int somme, quotient;

main() {
    printf("somme ? ");
    scanf("%d", &somme);
    printf("%d = ", somme);
    
    quotient = somme / 500;
    if (quotient != 0)
        printf("%d x 500 + ", quotient);
    somme = somme % 500;
    
    quotient = somme / 200;
    if (quotient != 0)
        printf("%d x 200 + ", quotient);
    somme = somme % 200;
    
    quotient = somme / 100;
    if (quotient != 0)
        printf("%d x 100 + ", quotient);
    somme = somme % 100;
    
    quotient = somme / 50;
    if (quotient != 0)
        printf("%d x 50 + ", quotient);
    somme = somme % 50;
    
    quotient = somme / 20;
    if (quotient != 0)
        printf("%d x 20 + ", quotient);
    somme = somme % 20;
    
    quotient = somme / 10;
    if (quotient != 0)
        printf("%d x 10 + ", quotient);
    somme = somme % 10;
    
    quotient = somme / 5;
    if (quotient != 0)
        printf("%d x 5 + ", quotient);
    somme = somme % 5;
    
    quotient = somme / 2;
    if (quotient != 0)
        printf("%d x 2 + ", quotient);
    somme = somme % 2;
    
    if (somme != 0)
        printf("%d", somme);

    printf("\n");
}
C'est un peu lourd, même si, grâce à la fonction copier-coller des éditeurs actuels, ce programme est vite écrit. La présentation du résultat est décevante car une fois sur deux le résultat affiché se termine par un + sans objet :
somme ? 1949
1949 = 3 x 500 + 2 x 200 + 2 x 20 + 1 x 5 + 2 x 2 +
Voici une version plus « pro » (si vous ne connaissez pas encore les tableaux et la boucle for ignorez cette solution ; vous y reviendrez plus tard) :
#include <stdio.h>

int billets[] = { 500, 200, 100, 50, 20, 10, 5, 2, 1 };
int nBillets = sizeof billets / sizeof billets[0];

main() {
    int somme, quotient, i;

    printf("somme ? ");
    scanf("%d", &somme);
    printf("%d = ", somme);
    
    for (i = 0; i < nBillets; i++) {
        quotient = somme / billets[i];
        somme = somme % billets[i];
        if (quotient != 0) {
            printf("%d x %d", quotient, billets[i]);
            if (somme != 0)
                printf(" + ");
        }
    }
    printf("\n");
}
A l'exécution les deux programmes proposés se valent ; le premier serait même un poil plus efficace. Le deuxième programme est évidemment plus facile à maintenir : si on veut ajouter ou enlever des coupures il suffit d'ajouter ou enlever des éléments dans la liste qui sert à initialiser le tableau billets.

Exo 4

La version la plus simple :
#include <stdio.h>

float prix, arrInf, arrProche, arrQuart;
int i;

main() {
    printf("prix à arrondir      ? ");
    scanf("%f", &prix);
    
    i = prix;
    arrInf = i;
    printf("euro inferieur       : %f\n", arrInf);
    
    i = prix + 0.5; 
    arrProche = i;
    printf("euro le plus proche  : %f\n", arrProche);
    
    i = 4 * prix + 0.5; 
    arrQuart = i / 4.0;
    printf("quart le plus proche : %f\n", arrQuart);
}
Exemple d'exécution :
prix à arrondir      ? 100.83
euro inferieur       : 100.000000
euro le plus proche  : 101.000000
quart le plus proche : 100.750000
[1] La première question est comment « tronquer » un flottant (c.-à-d. lui enlever les chiffres après la virgule) pour en faire l'entier immédiatement en-dessous. Or, c'est exactement l'opération qui est faite automatiquement lorsqu'un flottant est affecté à un entier, d'où le programme proposé. Les deux autres questions se ramènent chacune à sa précédente :
  • l'entier le plus proche de p est l'entier inférieur à p + 0.5 (remarque cryptée : cela marche ici, mais ce n'est pas la meilleure manière de trouver la borne téléphonique la plus proche quand on est en panne sur l'autoroute!)
  • le quart d'entier le plus proche serait l'entier le plus proche dans un monde où tous les nombres seraient quatre fois plus grands (bonjour la métaphore...)
La conversion qui est faite lors d'une affectation à un entier est la même que celle que fait l'opérateur (int). Une écriture plus légère de notre programme :
    ...
    arrInf = (int) prix;
    printf("euro inferieur       : %f\n", arrInf);
    
    arrProche = (int)(prix + 0.5);
    printf("euro le plus proche  : %f\n", arrProche);
    
    arrQuart = (int)(4 * prix + 0.5) / 4.0;
    printf("quart le plus proche : %f\n", arrQuart);
    ...
[2] Notez qu'il n'est pas possible (faites des essais) d'obtenir le travail souhaité en agissant au niveau de printf (en essayant de « tromper » printf on n'obtient jamais rien de sensé) :
printf("euro inferieur : %d\n", prix);       Résultat imprévisible
[3] Remarquez l'expression
arrQuart = i / 4.0;
Il est indispensable que le dénominateur soit un nombre flottant. Si on avait écrit cette expression sous la forme
arrQuart = i / 4;      Attention, erreur !
le membre droit de l'affectation aurait exprimé un quotient par défaut (cf. exercice 3), ce qui ne nous arrange pas car on aurait perdu les deux chiffres après la virgule (00, 25, 50 ou 75).

Exo 5

#include <stdio.h>
#include <math.h>   /* à cause de sqrt */

float a, b, c, delta, x1, x2;

main() {
    printf("a b c ?\n");
    scanf("%f%f%f", &a, &b, &c);
    
    if (a != 0) {
        delta = b * b - 4 * a * c;
        if (delta > 0) {
            x1 = ( - b + sqrt(delta) ) / (2 * a);
            x2 = - b / a - x1;
            printf("deux solutions : %f et %f\n", x1, x2);
        } 
        else
            if (delta == 0)
                printf("solution double : %f\n", - b / (2 * a));
            else
                printf("pas de solution réelle\n");
    }
    else
        if (b != 0)
            printf("solution unique : %f\n", -c / b);
        else
            if (c != 0)
                printf("equation impossible\n");
            else
                printf("equation vide\n");
}
Un grand classique des exercices pour débutant. Sans commentaire.

Exo 6

Si (j, m, a) n'est pas le dernier jour d'un mois alors son lendemain est (j+1, m, a), sinon c'est (1, m+1, a) sauf si m = 12, auquel cas le lendemain est (1, 1, a+1). Nous devons donc écrire la condition qui caractérise que (j, m, a) est le dernier jour d'un mois, ce qui équivaut, puisque la date est garantie correcte, à :
  • j = 31, ou bien
  • j = 30 et le mois est avril, juin, septembre ou novembre, ou bien
  • j = 29 et le mois est février, ou bien
  • j = 28, le mois est février et l'année n'est pas bissextile.
D'autre part, une année est bissextile si elle est divisible par 4 mais pas par 100. Cependant, les années multiples de 400 sont bissextiles. D'où le programme suivant, dans lequel on reconnaît sans peine la traduction des quatre cas ci-dessus.
#include <stdio.h>
#include <math.h>

int j, m, a, i;

main() {
    printf("j m a ?\n");
    scanf("%d%d%d", &j, &m, &a);

    if (j == 31 
            || (j == 30 && (m == 4 || m == 6 || m == 9 || m == 11))
            || (j == 29 && m == 2)
            || (j == 28 && m == 2 && ! (a % 4 == 0 && a % 100 != 0 || a % 400 == 0))) {
        j = 1;
        if (m == 12) {
            m = 1;
            a = a + 1;
        }
        else
            m = m + 1;
    }
    else
        j = j + 1;
    
    printf("%d %d %d\n", j, m, a);
}

Exo 7

Tout le travail à faire ici c'est programmer la formule donnée (nous l'acceptons sans explication!) sans y faire le moindre regroupement ou simplification car les barres de fraction indiquent des quotients entiers, donc impliquent des troncations qui sont nécessaires à la formule (par exemple, à cause de ces troncations, -2ns+ns/4 n'est pas toujours égal à -7ns/4).
Voici le programme, pour une fois il n'y a pas tellement de possibilités :
#include <stdio.h>
#include <math.h>

int j, m, a, as, ns, f;

main() {
    printf("j m a ? ");
    scanf("%d%d%d", &j, &m, &a);
    printf("le %d/%02d/%d est un ", j, m, a);
    
    if (m >= 3)
        m = m - 2;
    else {
        m = m + 10;
        a = a - 1;
    }
    ns = a / 100;
    as = a % 100;
    f = (j + as + as / 4 - 2 * ns + ns / 4 + (26 * m - 2) / 10) % 7;
    
    if (f < 0)
        f = f + 7;
    
    switch (f) {
        case 0:
            printf("dimanche\n");
            break;
        case 1:
            printf("lundi\n");
            break;
        case 2:
            printf("mardi\n");
            break;
        case 3:
            printf("mercredi\n");
            break;
        case 4:
            printf("jeudi\n");
            break;
        case 5:
            printf("vendredi\n");
            break;
        default:
            printf("samedi\n");
    }
}
Après avoir décalé le mois et l'année comme indiqué (puisque m, a et m1, a1 ne coexistent pas, il n'y a pas besoin de deux paires de variables distinctes), on sépare le numéro de l'année en deux morceaux : a = 2003 donne ns = 20 et as = 3 puis on calcule f.
Dans de nombreux cas f est négatif. Par exemple, pour le 1er mars 2002, f = -30. Cela pose un problème car là où un arithméticien dirait que -30 % 7 vaut 5 (en effet, -30 = -5 x 7 + 5) la bibliothèque C dira que -30 % 7 vaut -2 (car -30 = -4 x 7 - 2). Il se trouve que 5 et -2 sont deux réponses justes mais -2 nous gêne pour la suite du programme, d'où l'ajustement opéré [la bibliothèque garantit que x % n est compris, quel que soit x, entre - (n - 1) et (n - 1)].
Enfin, il n'y a plus qu'à traduire le résultat obtenu, un nombre compris entre 0 et 6, et un texte "lundi", "mardi", etc. La solution proposée est la plus simple. Une solution au mieux équivalente, au pire un poil moins efficace, consiste à remplacer switch par une cascade de if :
    ...
    if (f == 0) 
        printf("dimanche\n");
    else if (f == 1) 
        printf("lundi\n");
    else if (f == 2) 
        printf("mardi\n");
    else if (f == 3) 
        printf("mercredi\n");
    else if (f == 4) 
        printf("jeudi\n");
    else if (f == 5) 
        printf("vendredi\n");
    else 
        printf("samedi\n");
}
Si vous connaissez les tableaux de chaînes de caractères (c'est assez pointu, on y reviendra plus tard) voici une solution plus élégante :
#include <stdio.h>
#include <math.h>

int j, m, a, ns, f;

char *jour[] = { "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" };

main() {
    printf("j m a ? ");
    scanf("%d%d%d", &j, &m, &a);
    printf("le %d/%02d/%d est un ", j, m, a);
    
    if (m >= 3)
        m = m - 2;
    else {
        m = m + 10;
        a -= 1;
    }
    ns = a / 100;
    a = a % 100;
    f = (j + a + a / 4 - 2 * ns + ns / 4 + (26 * m - 2) / 10) % 7;   
    
    if (f < 0)
        f = f + 7;
    printf("%s\n", jour[f]);

0 comments:

Post a Comment