Les opérations mathématiques

Nous savons désormais déclarer, affecter et initialiser une variable, mais que diriez-vous d’apprendre à réaliser des opérations dessus ? Il est en effet possible de réaliser des calculs sur nos variables, comme les additionner, les diviser voire des opérations plus complexes. C’est le but de cette sous-partie. Nous allons donc enfin transformer notre ordinateur en grosse calculette programmable !

Les opérations mathématiques de base

Jusqu’à présent, nous nous sommes contentés d’afficher du texte et de manipuler très légèrement les variables. Voyons à présent comment nous pouvons réaliser quelques opérations de base. Le langage C nous permet d’en réaliser cinq :

  • l’addition (opérateur +) ;
  • la soustraction (opérateur -) ;
  • la multiplication (opérateur *) ;
  • la division (opérateur /) ;
  • le modulo (opérateur %).

Le langage C fournit bien entendu d’autres fonctions mathématiques et d’autres opérateurs, mais il est encore trop tôt pour vous les présenter.

Division et modulo

Les quatre premières opérations vous sont connues depuis l’école primaire. Cependant, une chose importante doit être précisée concernant la division : quand les deux nombres manipulés sont des entiers, il s’agit d’une division entière. Autrement dit, le quotient sera un entier et il peut y avoir un reste. Par exemple, 15 ÷ 6, ne donnera pas 2,5 (division réelle), mais un quotient de 2, avec un reste de 3.

printf("15 / 6 = %d\n", 15 / 6);
Résultat
15 / 6 = 2

Le modulo est un peu le complément de la division entière : au lieu de donner le quotient, il renvoie le reste d’une division euclidienne. Par exemple, le modulo de 15 par 6 est 3, car 15 = 2 × 6 + 3.

printf("15 %% 6 = %d\n", 15 % 6);
Résultat
15 % 6 = 3

Notez que le symbole % doit être doublé afin de pouvoir être utilisé littéralement.

Avec des flottants, la division se comporte autrement et n’est pas une division avec reste. La division de deux flottants donnera un résultat « exact », avec potentiellement plusieurs chiffres après la virgule.

printf("15 / 6 = %f\n", 15. / 6.);   /* En C, ce n’est pas la même chose que 15 / 6 */
Résultat
15 / 6 = 2.500000
Utilisation

Il est possible d’affecter le résultat d’une opération à une variable, comme lorsque nous affichons leur résultat avec printf().

#include <stdio.h>


int main(void)
{
    int somme = 5 + 3;

    printf("5 + 3 = %d\n", somme);
    return 0;
}
Résultat
5 + 3 = 8

Toute opération peut manipuler :

  • des constantes ;
  • des variables ;
  • les deux à la fois.

Voici un exemple avec des constantes.

#include <stdio.h>


int main(void)
{
    printf("2 + 3 = %d\n", 2 + 3);
    printf("8 - 12 = %d\n", 8 - 12);
    printf("6 x 7 = %d\n", 6 * 7);
    printf("11 %% 4 = %d\n", 11 % 4);
    return 0;
}
Résultat
2 + 3 = 5
8 - 12 = -4
6 x 7 = 42
11 % 4 = 3

Un autre avec des variables.

int a = 5;
int b = 3;
int somme = a + b;

printf("%d + %d = %d\n", a, b, somme);
Résultat
5 + 3 = 8

Et enfin, un exemple qui mélange variables et constantes.

int a = 5;
int b = 65;

printf("%d\n", b / a * 2 + 7 % 2);
Résultat
27
La priorité des opérateurs

Dans l’exemple précédent, nous avons utilisé plusieurs opérations dans une même ligne de code, une même expression. Dans ces cas-là, faites attention à la priorité des opérateurs ! Comme en mathématiques, certains opérateurs passent avant d’autres : les opérateurs * / % ont une priorité supérieure par rapport aux opérateurs + -.

Dans le code ci-dessous, c’est c * 4 qui sera exécuté d’abord, puis b sera ajouté au résultat. Faites donc attention sous peine d’avoir de mauvaises surprises. Dans le doute, ajoutez des parenthèses.

a = b + c * 4;

Raccourcis

Dans les exemples précédents, nous avons utilisé des affectations pour sauvegarder le résultat d’une opération dans une variable. Les expressions obtenues ainsi sont assez longues et on peut se demander s’il existe des moyens pour écrire moins de code. bien, le langage C fournit des écritures pour se simplifier la vie. Certains cas particuliers peuvent s’écrire avec des raccourcis, du « sucre syntaxique ».

Les opérateurs combinés

Comment vous y prendriez-vous pour multiplier une variable par trois ? La solution qui devrait vous venir à l’esprit serait d’affecter à la variable son ancienne valeur multipliée par trois.

int variable = 3;

variable = variable * 3;
printf("variable * 3 = %d\n", variable);
Résultat
variable * 3 = 9

Ce qui est parfaitement correct. Cependant, cela implique de devoir écrire deux fois le nom de la variable, ce qui est quelque peu pénible et source d’erreurs. Aussi, il existe des opérateurs combinés qui réalisent une affectation et une opération en même temps.

Opérateur combiné Équivalent à
variable += nombre variable = variable + nombre
variable -= nombre variable = variable - nombre
variable *= nombre variable = variable * nombre
variable /= nombre variable = variable / nombre
variable %= nombre variable = variable % nombre

Avec le code précédent, nous obtenons ceci.

int variable = 3;

variable *= 3;
printf("variable * 3 = %d\n", variable);
Résultat
variable * 3 = 9
L’incrémentation et la décrémentation

L’incrémentation et la décrémentation sont deux opérations qui, respectivement, ajoute ou enlève une unité à une variable. Avec les opérateurs vus précédemment, cela se traduit par le code ci-dessous.

variable += 1; /* Incrémentation */
variable -= 1; /* Décrémentation */

Cependant, ces deux opérations étant très souvent utilisées, elles ont droit chacune à un opérateur spécifique disponible sous deux formes :

  • une forme préfixée ;
  • une forme suffixée.

La forme préfixée s’écrit comme ceci.

++variable; /* Incrémentation */
--variable; /* Décrémentation */

La forme suffixée s’écrit comme cela.

variable++; /* Incrémentation */
variable--; /* Décrémentation */

Le résultat des deux paires d’opérateurs est le même : la variable variable est incrémentée ou décrémentée, à une différence près : le résultat de l’opération.

  1. Dans le cas de l’opérateur préfixé (--variable ou ++variable), le résultat sera la valeur de la variable augmentée ou diminuée d’une unité.
  2. Dans le cas de l’opérateur suffixé (variable-- ou variable++), le résultat sera la valeur de la variable.

Illustration !

#include <stdio.h>

int main(void)
{
    int x = 1;
    int y = 1;
    int a = x++;
    int b = ++y;

    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("x = %d\n", x);
    printf("y = %d\n", y);
    return 0;
}
Résultat
a = 1
b = 2
x = 2
y = 2

Comme vous pouvez le constater, le résultat de l’opération x++ est 1 alors que celui de ++y est 2. Cela étant, dans les deux cas, les variables x et y ont bien été incrémentées.

Le type d'une constante

Dans les sections précédentes, nous avons vu plusieurs opérations mathématiques et avons affiché leur résultat. Cependant, peut-être vous êtes-vous posé la question suivante : quel est le type du résultat ?

En effet, nous avons par exemple écrit printf("2 + 3 = %d\n", 2 + 3), ce qui laisse supposer que le résultat de 2 + 3 est de type int, sans finalement en savoir plus.

En fait, le type du résultat d’une opération dépend de celui de ses opérandes. Nous allons voir cela en détail un peu après, mais par exemple si les opérandes sont de type int, le résultat sera de type int.

Toutefois, cela amène une autre question : de quel type sont les constantes comme 2 ou 3 ?

Les constantes entières

Le type des constantes entières, par défaut, dépend de leur valeur et, de manière plus étonnante, de leur base.

Base

Type

10

int

long

long long

8 ou 16

int

unsigned

long

unsigned long

long long

unsigned long long

Ce tableau doit être compris comme suit :

  • Pour un nombre en base 10 (comme 2), le premier type pouvant stocker la valeur entre les types int, long et long long sera choisi ;
  • Pour un nombre en base 8 ou 16 (comme 0777 ou 0x16), le premier type pouvant stocker la valeur entre les types int, unsigned, long, unsigned long, long long et unsigned long long sera choisi.

Si aucun de ces types ne peut stocker la valeur indiquée, le comportement des compilateurs varie. En général, ils généreront une erreur de compilation.

Ces règles par défaut sont utiles, mais pas forcément pratiques. En effet, rappelez-vous : les valeurs minimale et maximale d’un type varient d’une machine à l’autre (revoyez le chapitre sur les variables si cela ne vous dit rien), ce qui signifie que le type d’une constante peut varier d’une machine à l’autre.

Il serait préférable de se baser sur les minimums garantis par la norme et de pouvoir fixer le type au regard de ceux-ci. Heureusement pour nous, cela est possible à l’aide de suffixes. Il en existe trois pour les constantes entières.

Suffixe

Type

u ou U

unsigned

unsigned long

unsigned long long

l ou L

long

long long

ul ou UL

unsigned long

unsigned long long

ll ou LL

long long

ull ou ULL

unsigned long long

Notez que suivant le cas, le suffixe ne fixe pas un type précis, mais une liste de types. Par exemple, le suffixe U précise que le type sera unsigned ou unsigned long ou unsigned long long. Dans un tel cas, le type effectivement utilisé sera le premier pouvant stocker la valeur de la constante.

Ainsi, la constante 1UL sera par exemple de type unsigned long, alors qu’elle aurait été de type int par défaut.

Nous vous conseillons d’opter pour les lettres majuscules qui ont l’avantage d’être plus lisibles.

Les constantes flottantes

Pour les constantes flottantes, la règle est simple : elles sont par défaut de type double.

Toutefois, comme pour les constantes entières, il est possible de fixer le type à l’aide de suffixes. Il en existe deux pour les constantes flottantes.

Suffixe Type
f ou F float
l ou L long double

Ainsi, la constante 1.L sera de type long double alors qu’elle aurait été de type double par défaut.

Si le type choisi ne peut stocker la valeur indiquée, le comportement des compilateurs varie. En général, ils généreront un avertissement.

N’oubliez pas le . sans quoi la constante sera considérée comme entière.

Comme pour les constantes entières, nous vous conseillons d’opter pour les lettres majuscules qui ont l’avantage d’être plus lisibles.

Le type d'une opération

Dans la section précédente, nous vous avons dit que le type d’une opération, dit autrement le type du résultat d’une opération, dépendait de celui de ses opérandes. Or, si c’est évident dans le cas où les opérandes ont le même type, cela ne l’est pas dans le cas où leurs types sont différents.

Par exemple, quel est le type de l’opération 2 + 1.2 ? Intuitivement on serait tenté de choisir un type flottant pour conserver la partie décimale, mais sans règles spécifiques, c’est impossible à dire.

Heureusement pour nous, la norme a prévu ces différents cas et a fixé des règles1 qui sont appliquées avant que l’opération n’ait lieu :

  1. si l’un des deux opérandes est de type long double, l’autre opérande est converti en long double ;
  2. si l’un des deux opérandes est de type double, l’autre opérande est converti en double ;
  3. si l’un des deux opérandes est de type float, l’autre opérande est converti en float ;
  4. si l’un des deux opérandes est de type unsigned long long, l’autre opérande est converti en unsigned long long ;
  5. si l’un des deux opérandes est de type long long, l’autre opérande est converti en long long ;
  6. si l’un des deux opérandes est de type unsigned long, l’autre opérande est converti en unsigned long ;
  7. si l’un des deux opérandes est de type long, l’autre opérande est converti en long ;
  8. si l’un des deux opérandes est de type unsigned int, l’autre opérande est converti en unsigned int.

Si vous êtes attentifs, vous remarquerez qu’il y a 3 types oubliés dans cette liste : short, char et _Bool. C’est parce que pour ces derniers, une règle supplémentaire s’applique avant celles exposées précédemment : si l’un des opérandes est de type short (signé ou non), char (signé ou non), ou _Bool, il est converti en int (ou en unsigned int si le type int n’a pas une capacité suffisante). Cette règle est appelée la promotion intégrale2 (ou promotion entière).

Dit autrement, si aucune des huit premières règles ne s’applique, les opérandes sont nécessairement de types int, via la règle de promotion intégrale ou non.

Notez que la promotion intégrale explique pourquoi l’indicateur de conversion de printf() reste d (ou u) pour les types short, char et _Bool.

Voyons un peu tout ça à l’aide d’un exemple.

#include <stdio.h>


int main(void)
{
    /* long double + int = long double */
    printf("78.56 + 5 = %Lf\n", 78.56L + 5);

    /* long + double = double */
    printf("5678 + 2.2 = %f\n", 5678L + 2.2);

    /* long + unsigned long = unsigned long */
    printf("2 + 5 = %lu\n", 2L + 5UL);

    /* long long + int = long long */
    printf("1 + 1 = %lld\n", 1LL + 1);

    /*
     * Promotion intégrale de n qui devient de type int.
     * Puis int + int = int
     */
    signed char n = 3;
    printf("n + 2 = %d\n", n + 2);
    return 0;
}
Résultat
78.56 + 5 = 83.560000
5678 + 2.2 = 5680.200000
2 + 5 = 7
1 + 1 = 2
n + 2 = 5

  1. ISO/IEC 9899:201x, doc. N1570, § 6.3.1.8, Usual arithmetic conversions, p. 52.
  2. ISO/IEC 9899:201x, doc. N1570, § 6.3.1.1, Boolean, characters, and integers, p. 50.

Les conversions

Nous venons de le voir, dans le cadre d’opérations, des conversions peuvent avoir lieu d’un type à l’autre. Ces conversions sont appelées des conversions implicites, car elles sont effectuées automatiquement. Il en existe beaucoup en langage C. Par exemple, lors d’une affectation, il y a également une conversion implicite vers le type de la variable assignée.

int a = 2.; /* Conversion implicite du type double vers le type int. */

Il est toutefois également possible de demander explicitement une conversion. Pour convertir un opérande vers un type donné, il suffit de le préfixer par le type désiré placé entre parenthèses. Le code ci-dessous convertit la constante 2 vers le type double et l’affiche ensuite.

#include <stdio.h>

int
main(void)
{
    printf("%f\n", (double)2);
    return 0;
}
Résultat
2.000000

Une conversion explicite permet également d’imposer le résultat d’une opération en fixant ou en induisant le type de ses opérandes. Ainsi, l’exemple qui suit utilise des conversions explicites pour effectuer une division avec des nombres flottants.

#include <stdio.h>

int
main(void)
{
    int a = 5;
    int b = 2;

    /*
     * b est converti en double.
     * Comme b est de type double, a est converti implicitement en double.
     */
    printf("%f\n", a / (double)b);

    /* a et b sont explicitement convertis en double. */
    printf("%f\n", (double)a / (double)b);
    return 0;
}
Résultat
2.500000
2.500000

Exercices

Vous êtes prêts pour un exercice ?

Essayez de réaliser une minicalculatrice qui :

  • dit « bonjour » ;
  • demande deux nombres entiers à l’utilisateur ;
  • les additionne, les soustrait, les multiplie et les divise (au millième près) ;
  • dit « au revoir ».

Un exemple d’utilisation pourrait être celui-ci.

Bonjour !
Veuillez saisir le premier nombre : 4
Veuillez saisir le deuxième nombre : 7
Calculs :
        4 + 7 = 11
        4 - 7 = -3
        4 * 7 = 28
        4 / 7 = 0.571
Au revoir !

Bien, vous avez maintenant toutes les cartes en main, donc : au boulot ! :)

#include <stdio.h>


int main(void)
{
   int a;
   int b;

   printf("Bonjour !\n");

   /* Nous demandons deux nombres à l'utilisateur */
   printf("Veuillez saisir le premier nombre : ");
   scanf("%d", &a);
   printf("Veuillez saisir le deuxième nombre : ");
   scanf("%d", &b);

   /* Puis nous effectuons les calculs */
   printf("Calculs :\n");
   printf("\t%d + %d = %d\n", a, b, a + b);
   printf("\t%d - %d = %d\n", a, b, a - b);
   printf("\t%d * %d = %d\n", a, b, a * b);
   printf("\t%d / %d = %.3f\n", a, b, a / (double)b);
   printf("Au revoir !\n");
   return 0; 
}

Vous y êtes arrivé sans problèmes ? Bravo ! Dans le cas contraire, ne vous inquiétez pas, ce n’est pas grave. Relisez bien tous les points qui ne vous semblent pas clairs et ça devrait aller mieux.


Dans le chapitre suivant, nous nous pencherons sur les conditions.

En résumé
  1. Le C fournit des opérateurs pour réaliser les opérations mathématiques de base ;
  2. Dans le cas où une division est effectuée entre des nombres entiers, le quotient sera également un entier ;
  3. La priorité des opérateurs est identique à celle décrite en mathématiques ;
  4. Le C fournit des opérateurs combinés et des opérateurs pour l’incrémentation et la décrémentation afin de gagner en concision ;
  5. Le type d’une constante peut-être modifié à l’aide de suffixes ;
  6. En cas de mélange des types lors d’une opération, le C prévoit des règles de conversions ;
  7. Il est possible de convertir un opérande d’un type vers un autre, certaines conversions sont implicites.