TP : une calculatrice basique

Après tout ce que vous venez de découvrir, il est temps de faire une petite 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 sera identifiée par 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ées.

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 un nombre à une puissance donnée (autrement dit, pour calculer xyx^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 autres, la déclaration de la fonction pow().

double pow(double x, double y);

Cette dernière prend 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, comme ceci : gcc -Wall -Wextra -pedantic -std=c11 -fno-common -fno-builtin main.c -lm (faites bien en sorte de placer -lm après le ou les fichiers sources).

La factorielle

La factorielle d’un nombre est égale 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 (pareil pour le PGCD et le PPCD).

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

Le PGCD

Le plus grand commun diviseur de deux entiers (abrégé PGCD) est, parmi les diviseurs communs à ces entiers, le plus grand d’entre eux. Par exemple, le PGCD de 60 et 18 est 6.

Par convention, le PGCD de 0 et 0 est 0 et le PGCD d’un entier non nul et de zéro est cet entier non nul.

Le PPCD

Le plus petit commun dénominateur (ou le plus petit commun multiple), abrégé PPCD, de deux entiers est le plus petit entier strictement positif qui soit multiple de ces deux nombres. Par exemple, le PPCD de 2 et 3 est 6.

Par convention, si l’un des deux entiers (ou les deux) sont nuls, le résultat est zéro.

Exemple d’utilisation
> 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.

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

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


long long pgcd(long long a, long long b)
{
    if (b == 0)
        return a;

    long long r = a % b;

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

    return b;
}


long long ppcd(long long a, long long b)
{
    if (a == 0 || b == 0)
        return 0;

    long long max = (a > b) ? a : b;
    long long i = max;

    while (i % a != 0 || i % b != 0)
        ++i;

    return i;
}


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

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

    return r;
}


int
main(void)
{
    double res = 0.;

    while (1)
    {
        double a;
        double b;
        char op;

        printf("> ");
        int 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 = (long long)a % (long 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 :

  • 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) ;
  • a et b, qui représentent les éventuels opérandes fournis ;
  • op, qui retient l’opération demandée ; et
  • n, qui est utilisée pour retenir le retour de la fonction scanf().

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 aucun (ce qui se 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 long long lors des calculs nécessitants des nombres entiers afin de disposer de la plus grande capacité possible. Par ailleurs, nous avons employé le type unsigned long long pour la fonction factorielle puisque celle-ci n’opère que sur des nombres strictement positifs.


Ce chapitre nous aura permis 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.