Licence CC 0

TP : une calculatrice basique

Dernière mise à jour :

Après tout ce que vous venez de découvrir, il est temps de faire une petit pause et de mettre en pratique vos nouveaux acquis. Pour ce faire, rien de tel qu’un exercice récapitulatif : réaliser une calculatrice basique.

Objectif

Votre objectif sera de réaliser une calculatrice basique pouvant calculer une somme, une soustraction, une multiplication, une division, le reste d’une division entière, une puissance, une factorielle, le PGCD et le PPCD.

Celle-ci attendra une entrée formatée suivant la notation polonaise inverse. Autrement dit, les opérandes d’une opération seront entrés avant l’opérateur, par exemple comme ceci pour la somme de quatre et cinq : 4 5 +.

Elle devra également retenir le résultat de l’opération précédente et déduire l’utilisation de celui-ci en cas d’omission d’un opérande. Plus précisément, si l’utilisateur entre par exemple 5 +, vous devrez déduire que le premier opérande de la somme est le résultat de l’opération précédente (ou zéro s’il n’y en a pas encore eu).

Chaque opération se verra attribuer un symbole ou une lettre, comme suit :

  • addition : + ;
  • soustraction : - ;
  • multiplication : * ;
  • division : / ;
  • reste de la division entière : % ;
  • puissance : ^ ;
  • factorielle : ! ;
  • PGCD : g ;
  • PPCD : p.

Le programme doit s’arrêter lorsque la lettre « q » est spécifiée comme opération (avec ou sans opérande).

Préparation

Précisions concernant scanf

Pourquoi utiliser la notation polonaise inverse et non l’écriture habituelle ?

Parce qu’elle va vous permettre de bénéficier d’une caractéristique intéressante de la fonction scanf() : sa valeur de retour. Nous anticipons un peu sur les chapitres suivants, mais sachez que la fonction scanf() retourne une valeur entière qui correspond au nombre de conversions réussies. Une conversion est réussie si ce qu’entre l’utilisateur correspond à l’indicateur de conversion.

Ainsi, si nous souhaitons récupérer un entier à l’aide de l’indicateur d, la conversion sera réussie si l’utilisateur entre un nombre (par exemple 2) alors qu’elle échouera s’il entre une lettre ou un signe de ponctuation.

Grâce à cela, vous pourrez détecter facilement s’il manque ou non un opérande pour une opération.

Lorsqu’une conversion échoue, la fonction scanf() arrête son exécution. Aussi, s’il y avait d’autres conversions à effectuer après celle qui a avorté, elles ne seront pas réalisée.

1
2
3
4
5
double a;
double b;
char op;

scanf("%lf %lf %c", &a, &b, &op);

Dans le code ci-dessus, si l’utilisateur entre 7 *, la fonction scanf() retournera 1 et n’aura lu que le nombre 7. Il sera nécessaire de l’appeler une seconde fois pour que le symbole * soit récupéré.

Petit bémol tout de même : les symboles + et - sont considérés comme des débuts de nombre valables (puisque vous pouvez par exemple entrer -2). Dès lors, si vous souhaitez additionner ou soustraire un nombre au résultat de l’opération précédente, vous devrez doubler ce symbole. Pour ajouter cinq cela donnera donc : 5 ++.

Les puissances

Pour élever une nombre à une puissance donnée (autrement dit, pour calculer $x^y$), nous allons avoir besoin d’une nouvelle partie de la bibliothèque standard dédiée aux fonctions mathématiques de base. Le fichier d’en-tête de la bibliothèque mathématique se nomme <math.h> et contient, entre autre, la déclaration de la fonction pow().

1
double pow(double x, double y);

Cette dernière prends deux arguments : la base et l’exposant.

L’utilisation de la bibliothèque mathématique requiert d’ajouter l’option -lm lors de la compilation, par exemple comme ceci : zcc -lm main.c

La factorielle

La factorielle d’un nombre est égal au produit des nombres entiers positifs et non nuls inférieurs ou égaux à ce nombre. La factorielle de quatre équivaut donc à 1 * 2 * 3 * 4, donc vingt-quatre. Cette fonction n’est pas fournie par la bibliothèque standard, il vous faudra donc la programmer par vous-même (pareil pour le PGCD et le PPCD que nous avons vus dans les chapitres précédents).

Par convention, la factorielle de zéro est égale à un.

Exemple d’utilisation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
> 5 6 +
11.000000
> 4 *
44.000000
> 2 /
22.000000
> 5 2 %
1.000000
> 2 5 ^
32.000000
> 1 ++
33.000000
> 5 !
120.000000

Derniers conseils

Nous vous conseillons de récupérer les nombres sous forme de double. Cependant, gardez bien à l’esprit que certaines opérations ne peuvent s’appliquer qu’à des entiers : le reste de la division entière, la factorielle, le PGCD et le PPCD. Il vous sera donc nécessaire d’effectuer des conversions.

Également, notez bien que la factorielle ne s’applique qu’à un seul opérande à l’inverse de toutes les autres opérations.

Bien, vous avez à présent toutes les cartes en main : au travail ! :)

Correction

Alors ? Pas trop secoué ? :D
Bien, voyons à présent la correction.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

unsigned long pgcd(unsigned long, unsigned long);
unsigned long ppcd(unsigned long, unsigned long);
unsigned long factorielle(unsigned long);


unsigned long pgcd(unsigned long a, unsigned long b)
{
    unsigned long r = a % b;

    while (r != 0)
    {
            a = b;
            b = r;
            r = a % b;
    }

    return b;
}


unsigned long ppcd(unsigned long a, unsigned long b)
{
    unsigned long i;
    unsigned long min = (a < b) ? a : b;

    for (i = 2; i <= min; ++i)
        if (a % i == 0 && b % i == 0)
            return i;

    return 0;
}


unsigned long factorielle(unsigned long a)
{
    unsigned long i;
    unsigned long r = 1;

    for (i = 2; i <= a; ++i)
        r *= i;

    return r;
}


int
main(void)
{
    double a;
    double b;
    double res = 0;
    int n;
    char op;

    while (1)
    {
        printf("> ");
        n = scanf("%lf %lf %c", &a, &b, &op);

        if (n <= 1)
        {
            scanf("%c", &op);
            b = a;
            a = res;
        }
        if (op == 'q')
            break;

        switch (op)
        {
        case '+':
            res = a + b;
            break;

        case '-':
            res = a - b;
            break;

        case '*':
            res = a * b;
            break;

        case '/':
            res = a / b;
            break;

        case '%':
            res = (unsigned long)a % (unsigned long)b;
            break;

        case '^':
            res = pow(a, b);
            break;

        case '!':
            res = factorielle((n == 0) ? a : b);
            break;

        case 'g':
            res = pgcd(a, b);
            break;

        case 'p':
            res = ppcd(a, b);
            break;
        }

        printf("%lf\n", res);
    }
    return 0;
}

Commençons par la fonction main(). Nous définissons plusieurs variables :

  • a et b, qui représentent les éventuels opérandes fournis ;
  • res, qui correspond au résultat de la dernière opération réalisée (ou zéro s’il n’y en a pas encore eu) ;
  • n, qui est utilisée pour retenir le retour de la fonction scanf() ; et
  • op, qui retient l’opération demandée.

Ensuite, nous entrons dans une boucle infinie (la condition étant toujours vraie puisque valant un) où nous demandons à l’utilisateur d’entrer l’opération à réaliser et les éventuels opérandes. Nous vérifions ensuite si un seul opérande est fourni ou aucune (ce qui ce déduit, respectivement, d’un retour de la fonction scanf() valant un ou zéro). Si c’est le cas, nous appelons une seconde fois scanf() pour récupérer l’opérateur. Puis, la valeur de a est attribuée à b et la valeur de res à a.

Si l’opérateur utilisé est q, alors nous quittons la boucle et par la même occasion le programme. Notez que nous n’avons pas pu effectuer cette vérification dans le corps de l’instruction switch qui suit puisque l’instruction break nous aurait fait quitter celui-ci et non la boucle.

Enfin, nous réalisons l’opération demandée au sein de l’instruction switch, nous stockons le résultat dans la variable res et l’affichons. Remarquez que l’utilisation de conversions explicites n’a été nécessaire que pour le calcul du reste de la division entière. En effet, dans les autres cas (par exemple lors de l’affectation à la variable res), il y a des conversions implicites.

Nous avons utilisé le type unsigned long lors des calculs nécessitant des nombres entiers afin de disposer de la plus grande capacité possible et parce que l’usage de nombres négatifs n’a pas beaucoup d’intérêt dans ce cadre. Cependant, l’usage de nombres non signés n’est obligatoire que pour la fonction factorielle (puisque celle-ci n’opère que sur des nombres strictement positifs).


Ce chapitre nous aura permit de revoir la plupart des notions des chapitres précédents. Dans le chapitre suivant, nous verrons comment découper nos projets en plusieurs fichiers.