programmez-en-d/arithmetique.whata

620 lines
26 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[set
title = "Nombres entiers et opérations arithmétiques"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
Nous avons vu que les instructions [c if] et [c while] permettent aux programmes de prendre des décisions en utilisant le type [c bool] sous forme d'expressions logiques. Dans ce chapitre, nous allons voir les opérations arithmétiques sur les types entiers du D. Ces fonctionnalités nous permettrons d'écrire des programmes beaucoup plus utiles.
Même si les opérations arithmétiques font partie de notre vie quotidienne et sont en fait simples, il y a des concepts très importants dont un programmeur doit être conscient pour produire des programmes corrects~ : taille en bits d'un type, débordements, [[wp:fr Soupassement arithmétique | soupassements]], et troncages.
Avant d'aller plus loin, je voudrais résumer les opérations arithmétiques dans le tableau suivant comme référence~ :
|= Opérateur |= Effet |= Exemple
| [c ++] | incrémente | [c ++variable]
| [c -~-] | décrémente | [c -~-variable]
| [c +] | fait la somme de deux valeurs | [c premier + second]
| [c -] | soustrait [c second] à [c premier] | [c premier - second]
| [c *] | fait le produit de deux valeurs | [c premier * second]
| [c /] | divise [c premier]par [c second] | [c premier / second]
| [c %] | donne le reste de la division de [c premier] par [c second] | [c premier % second]
| [c ^^] | élève [c premier] à la puissance [c second] (multiplie [c premier] par lui-même [c second] fois) | [c premier ^^ second]
La plupart de ces opérateurs ont leurs homologues qui ont un signe [c > =] collé à eux~ : [c +=], [c -=], [c *=], [c %=], [c ^^=]. La différence avec ces opérateurs est qu'ils affectent le résultat à la variable à gauche de l'opérateur~ :
[code=d variable += 10;]
Cette expression ajoute la valeur de [c variable] et 10 et enregistre le résultat dans [c variable]. À la fin, la valeur de [c variable] est augmentée de 10. C'est l'équivalent de l'expression~ :
[code=d variable = variable + 10;]
Je souhaiterai aussi résumer ici trois concepts importants avant de les développer plus.
- [** Dépassement]~ : toutes les valeurs ne peuvent pas entrer dans un type, dans certain cas il y a dépassement (ou débordement). Par exemple, une variable de type [c ubyte] ne peut contenir que des valeurs entre 0 et 255~ ; quand on lui affecte 260, la valeur déborde et la variable contient 4.
- [** Soupassement]~ : de façon similaire, des valeurs ne peuvent pas être plus petites que la valeur minimum qu'un type peut contenir.
- [** Troncage]~ : les types entiers ne peuvent pas avoir de valeurs avec parties fractionnaires. Par exemple, la valeurs de l'expression entière 1/2 n'est pas 1,5 mais 1.
[ = Information supplémentaire
Nous rencontrons des opérations arithmétiques quotidiennement sans trop de surprises~ : si une baguette est à 1€, deux baguettes sont à 2€~ ; si quatre sandwichs sont à 15€, un sandwich est à 3,75€, etc.
Malheureusement, les choses ne sont pas aussi simple avec les opérations arithmétiques des ordinateurs. Si on ne comprend pas comment les valeurs sont stockées dans un ordinateur, on peut être supris de voir que la dette d'une companie est réduite à 1,7 milliards de dollars quand elle emprunte 3 nouveaux milliards en plus de sa dette existante de 3 milliards de dollars~ !
Ou quand, alors qu'une boîte de glace satisfait 4 enfants, une opération arithmérique peut prétendre que deux boîtes de glace suffiraient à 11 enfants~ !
Les programmeurs doivent comprendre comment les entiers sont stockés dans l'ordinateur.
[ = Types entiers
Les types entier sont les types qui ne peuvent garder que des valeurs entières comme -2, 0, 10, etc. Ces types ne peuvent pas avoir de parties fractionnaires comme dans le nombre 2,5. Les types entiers que nous avons vu dans le [[part:types | chapitre sur les Types Fondamentaux]] sont :
|= Type |= Nombre de bits |= Valeur initiale
|= ``byte`` | 8 | ``0``
|= ``ubyte`` | 8 | ``0``
|= ``short`` | 16 | ``0``
|= ``ushort``| 16 | ``0``
|= ``int`` | 32 | ``0``
|= ``uint`` | 32 | ``0``
|= ``long`` | 64 | ``0L``
|= ``ulong`` | 64 | ``0L``
Le [c u] au début du type veut dire «~ unsigned~ » (non signé) et indique que de tels types ne peuvent pas avoir de valeurs en dessous de zéro.
]
[ = Nombre de bits d'un type
Dans les systèmes informatiques d'aujourd'hui, la plus petite unité d'information est appelée un [** bit]. Au niveau physique, un bit est représenté par des signaux électriques à certains endroits des circuits d'un ordinateur. Un bit peut être dans un ou deux états qui correspondent à la présence et à l'absence de signaux électriques au point qui définit un bit particulier. Ces deux états sont définis arbitrairement aux valeurs 0 et 1. De ce fait, un bit peut avoir une de ces deux valeurs.
Comme il n'y a pas 36 idées pouvant être représenté par seulement deux états, [c bit] n'est pas un type très utile. Il ne peut être utile que pour des idées avec deux états comme «~ pile ou face~ »ou une lumière qui peut être allumée ou éteinte.
Si on considère deux bits à la fois, le nombre total d'informations qui peuvent être représentées est multiplié. Comme chaque bit vaut 0 ou 1, il y a un nombre total de 4 états possibles. En supposant que le chiffre de gauche représente le premier bit et que le chiffre de droite représente le deuxième bit, ces états sont 00, 01, 10 et 11. Ajoutons encore un bit pour voir mieux cet effet~ : 3 bits peuvent être dans 8 états différents : 000, 001, 010, 011, 100, 101, 110, 111. Comme on peut le voir, chaque bit ajouté double le nombre total d'états qui peuvent être représentés.
Les valeurs auxquelles ces 8 états correspondent sont définies par conventions. Le tableau suivant montre ces valeurs pour les représentations signées et non signées de 3 bits~ :
|=État binaire |= Valeur non signée |= Valeur signée
|000 | 0 | 0
|001 | 1 | 1
|010 | 2 | 2
|011 | 3 | 3
|100 | 4 | -4
|101 | 5 | -3
|110 | 6 | -2
|111 | 7 | -1
On peut écrire le tableau suivant en ajoutant plus de bits~ :
|=Bits |= nombre de Valeur distinctes |= Type D|= Valeur minimale |= Valeur maximale |
|1 | 2 | | | |
|2 | 4 | | | |
|3 | 8 | | | |
|4 | 16 | | | |
|5 | 32 | | | |
|6 | 64 | | | |
|7 | 128 | | | |
|_ 8 |_ 256 | byte | -128 | 127|
| ubyte | 0 | 255|
||||| ...
|_ 16 |_ 65 536 | short | -32768 | 32767 |
| ushort | 0 | 65535 |
||||| ...
|_ 32 |_ 4 294 967 296 | int | -2147483648 | 2147483647
| uint | 0 | 4294967295
||||| ...
|_ 64 |_ 18 446 744 073 709 551 616 | long | -9223372036854775808 | 9223372036854775807
| ulong | 0 | 18446744073709551615
||||| ...
J'ai sauté beaucoup de lignes dans le tableau, et indiqué les versions signées et non signées des type D qui ont le même nombre de bits sur la même ligne (par exemple [c int] et [c uint] sont tous les deux sur la ligne des 32 bits)
]
[ = Choisir un type
Comme un type 3~ bits ne peut avoir que 8 valeurs distinctes, il ne peut représenter que des idées comme une face d'un dé ou le nombre de jours dans une semaine. (Ce n'est qu'un exemple, il n'y a pas de type 3~ bits en D).
D'un autre côté, même si [c uint] est un type très large, il ne peut pas représenter l'idée d'un numéro qui identifierait chaque personne vivante puisque sa valeur maximum est inférieure à la population mondiale de 7 milliards. [c long] et [c ulong] seraient plus qu'assez pour représenter beaucoup d'idées.
En règle générale, tant qu'il n'y a aucune raison spécifique de ne pas le faire, vous pouvez utiliser [c int] pour les valeurs entières.
]
[ = Dépassement
Le fait que les types ne peuvent avoir qu'un nombre limité de valeurs peut causer des effets inattendus. Par exemple, même si ajouter deux [c uint] valant 3 milliards chacun devrait donner 6 milliards, comme la somme est plus grande que la valeur maximum qu'une variable [c uint] peut stocker (environ 4 milliards), cette somme déborde. Sans aucun avertissement, seule la différence entre 6 et 4 milliards est stockée (un peu plus précisément, 6 moins 4,3 milliards).
]
[ = Troncage
Comme les entiers ne peuvent pas avoir de valeur avec des parties fractionnaires, ils perdent la partie après la virgule. Par exemple, en supposant qu'une boîte de glace satisfait 4 enfants, même si 11 enfants auraient besoin de 2,75 boîtes, 2,75 ne peut être que stocké comme 2 dans un type entier.
Nous verrons des techniques basiques pour aider à réduire les effets de débordements, soupassements et troncages plus loin dans le chapitre.
]
[ = [c .min] et [c .max]
Nous utiliserons les propriétés [c min] et [c max] plus bas, que nous avons vues dans le [[part:types | chapitre sur les Types Fondamentaux]]. Ces propriétés donnent les valeurs minimale et maximale qu'un type entier peut avoir.
]
[ = Incrémentation: [c ++]
Cet opérateur est utilisé avec une seule variable (plus généralement, avec une seule expression) et est écrite avant le nom de la variable.
Il incrémente la valeur de cette variable de 1~ :
[code=d <<<
import std.stdio;
void main()
{
int nombre = 10;
++nombre;
writeln("Nouvelle valeur : ", nombre);
}
>>>]
[output Nouvelle valeur : 11]
L'opérateur d'incrémentation est équivalent à l'utilisation de l'opérateur ajouter-et-affecter avec la valeur 1~ :
[code=d nombre += 1; // pareil que ++nombre]
Si le résultat de l'opérateur d'incrémentation est plus grand que la valeur maximum du type de la variable, le résultat déborde et devient la valeur minimale. On peut voir cet effet en incrémentant une variable qui a la valeur [c int.max]~ :
[code=d <<<
import std.stdio;
void main()
{
writeln("valeur int maximum : ", int.min);
writeln("valeur int maximum : ", int.max);
int nombre = int.max;
writeln("avant l'incrémentation : ", nombre);
++nombre;
writeln("après l'incrémentation : ", nombre);
}
>>>]
La valeur devient [c int.min] après l'incrémentation :
[output <<<
valeur int minimum : -2147483648
valeur int maximum : 2147483647
avant l'incrémentation : 2147483647
après l'incrémentation : -2147483648
>>>]
C'est une observation très importante, parce que la valeur change du maximum au minimum après une incrémentation et sans aucun avertissement~ ! Ce phénomène est appelé [* débordement] (''overflow''). Nous verrons des effets similaires avec d'autres opérations.
]
[ = Décrémentation : [c -~-]
Cet opérateur est similaire à l'opérateur d'incrémentation~ ; la différence est que la valeur est diminuée de 1~ :
[code=d <<< --nombre; // La valeur est diminuée de 1>>>]
L'opération de décrémentation est équivalente à l'utilisation de l'opérateur soustraire-puis-affecter avec la valeur 1~ :
[code=d <<< nombre -= 1; // pareil que --nombre >>>]
De manière similaire à l'opérateur ++, si la valeur de la variable est la valeur minimum, elle devient la valeur maximum. Ce phénomène est appelé [* soupassement] (''underflow'').
]
[ = Addition : +
Cet opérateur est utilisé avec deux expressions et ajoute leurs valeurs~ :
[code=d <<<
import std.stdio;
void main()
{
int premier = 12;
int second = 100;
writeln("Résultat : ", premier + second);
writeln("Avec une expression constante : ", 1000 + second);
}
>>>]
[output <<<
Résultat : 112
Avec une expression constante : 1100
>>>]
De même, si le résultat est plus grand que la somme des deux expressions, il déborde et devient inférieur aux deux expressions :
[code=d <<<
import std.stdio;
void main()
{
// 3 milliards chacun
uint premier = 3000000000;
uint second = 3000000000;
writeln("valeur minimum de uint : ", uint.max);
writeln(" premier : ", premier);
writeln(" second : ", second);
writeln(" somme : ", premier + second);
writeln("DÉBORDEMENT ! Le résultat n'est pas 6 milliards !");
}
>>>]
[output <<<
valeur minimum de uint : 4294967295
premier : 3000000000
second : 3000000000
somme : 1705032704
DÉBORDEMENT ! Le résultat n'est pas 6 milliards !
>>>]
]
[ = Soustraction : [c -]
Cet opérateur est utilisé avec deux expressions et donne la différence entre la première et la seconde~ :
[code=d <<<
import std.stdio;
void main()
{
int nombre_1 = 10;
int nombre_2 = 20;
writeln(nombre_1 - nombre_2);
writeln(nombre_2 - nombre_1);
}
>>>]
[output <<<
-10
10
>>>]
Si le résultat d'une soustraction est inférieur à zéro, ce qu'on obtient en stockant ce résultat dans un type non signé est encore une fois surprenant. Réécrivons le programme en utilisant le type [c uint]~ :
[code=d <<<
import std.stdio;
void main()
{
uint nombre_1 = 10;
uint nombre_2 = 20;
writeln("PROBLÈME ! uint ne peut pas stocker de valeur négatives :");
writeln(nombre_1 - nombre_2);
writeln(nombre_2 - nombre_1);
}
>>>]
[output <<<
PROBLÈME ! uint ne peut pas stocker de valeur négatives :
4294967286
10
>>>]
On peut recommander d'utiliser des types signés pour représenter des choses qui pourraient être soustraites. Tant qu'il n'y a pas de raison spécifique de ne pas le faire, vous pouvez choisir [c int].
]
[ = Multiplication : [c *]
Cet opérateur multiplie les valeurs de deux expressions~ :
[code=d <<<
import std.stdio;
void main()
{
uint nombre_1 = 6;
uint nombre_2 = 7;
writeln(nombre_1 * nombre_2);
}
>>>]
[output 42]
Le résultat est encore une fois sujet au débordement.
]
[ = Division : [c /]
Cet opérateur divise la première expression par la seconde. Comme les types entiers ne peuvent pas avoir de partie fractionnaire, la partie fractionnaire est abandonnée. Cet effet est appelé troncage. De ce fait, le programme suivant affiche 3 et non 3.5.
[code=d <<<
import std.stdio;
void main()
{
writeln(7 / 2);
}
>>>]
[output 3]
Pour les calculs où les parties fractionnaires importent, les types à virgule flottante doivent être utilisés à la place des entiers. Nous verrons les types flottants dans le [[part:virgule_flottante | chapitre suivant]].
]
[ = Modulo : %
Cet opérateur divise la première expression par la seconde et donne le reste de cette division~ :
[code=d <<<
import std.stdio;
void main()
{
writeln(10 % 6);
}
>>>]
[output 4]
Une utilisation fréquente de cet opérateur est de déterminer si un nombre est pair ou impair. Comme le reste de la division d'un nombre par 2 est toujours 0 et le reste de la division d'un nombre impair par 2 est toujours 1, comparer la valeur à 0 est suffisant pour déterminer la parité d'un nombre~ :
[code=d <<<
if ((nombre % 2) == 0) {
writeln("nombre pair");
} else {
writeln("nombre impair");
}
>>>]
]
[ = Puissance : [c ^^]
Cet opérateur élève la première expression à la puissance de la seconde. Par exemple, pour élever 3 à la puissance 4 :
[code=d <<<
import std.stdio;
void main()
{
writeln(3 ^^ 4); // équivalent à 3*3*3*3
}
>>>]
[output 81]
]
[ = Opérations arithmétiques avec affectation
Tous les opérateurs qui prennent deux expressions ont un équivalent d'affectation. Ces opérateurs affectent le résultat à l'expression qui est à sa gauche :
[code=d <<<
import std.stdio;
void main()
{
int nombre = 10;
nombre += 20; // pareil que nombre = nombre + 20 ; maintenant 30
nombre -= 5; // pareil que nombre = nombre - 5 ; maintenant 25
nombre *= 2; // pareil que nombre = nombre * 2 ; maintenant 50
nombre /= 3; // pareil que nombre = nombre / 3 ; maintenant 16
nombre %= 7; // pareil que nombre = nombre % 7 ; maintenant 2
nombre ^^= 6; // pareil que nombre = nombre ^^ 6 ; maintenant 64
writeln(nombre);
}
>>>]
[output 64]
]
[ = Négation : [c -]
Cet opérateur change le signe de la valeur de l'expression (négatif devient positif et inversement)~ :
[code=d <<<
import std.stdio;
void main()
{
int nombre_1 = 1;
int nombre_2 = -2;
writeln(-nombre_1);
writeln(-nombre_2);
}
>>>]
[output <<<
-1
2
>>>]
Le type du résultat de cette opération est le même que le type de l'expression. Comme les types non signés ne peuvent pas stocker de valeurs négatives, utiliser cet opérateur sur des types non signés peut mener à des résultats surprenants~ :
[code=d <<<
uint nombre = 1;
writeln("négation : ", -nombre);
>>>]
le type de [c=d -nombre] est [c uint] également, et celui-ci ne peut pas représenter des valeurs négatives~ :
[output negation: 4294967295]
]
[ = Signe plus : [c +]
Cet opérateur n'a pas d'effet et n'existe que pour avoir une certaine symétrie avec l'opérateur de négation. Les valeurs positives restent positives et les valeurs négatives restent négatives~ :
[code=d <<<
import std.stdio;
void main()
{
int nombre_1 = 1;
int nombre_2 = -2;
writeln(+nombre_1);
writeln(+nombre_2);
}
>>>]
[output <<<
1
-2
>>>]
]
[ = Post-incrémentation : [c ++]
[p Note~ : | Sauf s'il y a une très bonne raison de le faire, utilisez toujours l'opérateur d'incrémentation usuel (qui est également appelé opérateur de pré-incrémentation).]
Contrairement à l'opérateur de pré-incrémentation, il est écrit après l'expression et incrémente également la valeur de l'expression de 1. La différence est que l'opérateur de post-incrémentation donne l'ancienne valeur de l'expression, et non la nouvelle. Pour voir cette différence, comparons-la avec l'opérateur de préincrémentation~ :
[code=d <<<
import std.stdio;
void main()
{
int incrémenté_normalement = 1;
writeln(++incrémenté_normalement); // affiche 2
writeln(incrémenté_normalement); // affiche 2
int post_incrémenté = 1;
// incrémenté, mais l'ancienne valeur est utilisée :
writeln(post_incrémenté++); // affiche 1
writeln(post_incrémenté); // affiche 2
}
>>>]
[output <<<
2
2
1
2
>>>]
L'instruction [c writeln(post_incrémenté++);] ci-dessus est équivalente à ce code~ :
[code=d <<<
int ancienne_valeur = post_incrémenté;
++post_incrémenté;
writeln(ancienne_valeur); // affiche 1
>>>]
]
[ = Post-décrémentation : [c -~-]
Note~ : sauf s'il y a une bonne raison, utilisez toujours l'opérateur de décrémentation usuel (aussi appelé opérateur de pré-décrémentation).
Se comporte de la même manière que l'opérateur de post-incrémentation mais décrémente.
]
[ = Priorité opératoire
Les opérateurs que nous avons vus jusqu'alors ont toujours été utilisés tout seuls, avec seulement une ou deux expressions. Cependant, comme les expressions logiques, il est fréquent de combiner ces opérateurs pour former des expressions arithmétiques plus complexes~ :
[code=d <<<
int valeur = 77;
int résultat = (((valeur + 8) * 3) / (valeur - 1)) % 5;
>>>]
Comme pour les opérateurs logiques, les opérateurs arithmétiques obéissent aussi à des règles de priorité. Par exemple, l'opérateur [c *] est prioritaire sur l'opérateur [c +]. Pour cette raison, quand il n'y a pas de parenthèses (par exemple dans l'expression [c valeur + 8 * 3]), l'opérateur [c *] est évalué avant l'opérateur [c +]. De ce fait, cette expression vaut [c valeur + 24], ce qui est différent de [c (valeur + 8) * 3].
Utiliser des parenthèses sert à la fois à assurer des résultats corrects et à communiquer le but d'un code aux programmeurs qui pourraient à l'avenir travailler sur le code.
]
[ = Solution potentielle contre les débordements
Si le résultat d'une opération ne peut pas être contenu dans le type du résultat, alors il n'y a rien qui puisse être fait. Parfois, alors que le résultat final pourrait être contenu dans un certain type, les résultats intermédiaires pourraient déborder et mener à des résultats incorrects.
Par exemple, supposons que nous avons besoin de planter un pommier par 1000 m² d'une aire de 40 par 60 km. De combien de pommiers avons-nous besoin~ ?
Quand on résout ce problème sur papier, on voit que le résultat est [m \frac{40000\times 60000}{1000}], ce qui vaut 2,4 millions de pommiers. Écrivons un programme qui effectue ce calcul :
[code=d <<<
import std.stdio;
void main()
{
int largeur = 40000;
int longueur = 60000;
int airParArbre = 1000;
int arbresNecessaires = largeur * longueur / airParArbre;
writeln("Nombre d'arbres nécessaires : ", arbresNecessaires);
}
>>>]
[output Nombre d'arbres nécessaires : -1894967]
Pour ne pas mentionner le fait que ce n'est même pas proche du résultat, on obtient un nombre négatif~ ! Dans ce cas, le calcul intermédiaire [c largeur~ *~ longueur] déborde et le calcul suivant [c /~ airParArbre] donne un résultat incorrect.
Une façon de d'éviter le débordement dans cet exemple est de changer l'ordre des opérations~ :
[code=d int arbresNecessaires = largeur / airParArbre * longueur ;]
Le résultat est maintenant correct :
[output Nombre d'arbres nécessaires : 2400000]
La raison pour laquelle cette méthode fonctionne est le fait que chaque étape du calcul est contenu dans le type [c int].
Notez que ce n'est pas une solution complète parce que cette fois la valeur intermédiaire est sujette au troncage, qui pourrait affecter le résultat de façon significative dans certains ordres de calcul.
Une autre solution serait d'utiliser un type flottant à la place d'un type entier~ : [c float], [c double] ou [c real].
]
[ = Solution potentielle contre le troncage
Changer l'ordre des opérateurs peut aussi être une solution contre le troncage. Un exemple intéressant peut être vu en divisant et en multipliant une valeur avec le même nombre. On s'attendrait à ce que [c 10/9*9] donne 10, mais on obtient 9~ :
[code=d <<<
import std.stdio;
void main()
{
writeln(10 / 9 * 9);
}
>>>]
[output 9]
Le résultat est correct quand le troncage est évité en changeant l'ordre des opérations~ :
[code=d writeln(10 * 9 / 9);]
[output 10]
Encore une fois, ce n'est pas une solution complète~: cette fois, le calcul intermédiaire pourrait être sujet au débordement. Utiliser un type flottant peut être une autre solution au troncage dans certains calculs.
]
]
[ = Exercices
# [ Écrire un programme qui demande deux entiers à l'utilisateur, affiche le quotient entier d'une division du premier et du second, et affiche aussi le reste. Par exemple, quand 7 et 3 sont entrés, le programme affiche l'équation suivante~ :
[output 7 = 3 * 2 + 1]
]
# [ Modifier le programme pour donner une sortie plus courte quand le reste est 0. Par exemple, quand 10 et 5 sont entrés, il ne devrait pas afficher "10 = 5 * 2 + 0" mais ceci~ :
[output 10 = 5 * 2]
]
# [ Écrire une calculette simple qui supporte les 4 opérations arithmétiques basiques. L'utilisateur choisit l'opération à effectuer depuis un menu et le programme effectue l'opération sur les deux valeurs qui ont été entrées. Vous pouvez ignorer les débordements et les troncages dans ce programme.
]
# [ Écrire un programme qui affiche les valeurs de 1 à 10, chacune sur une ligne propre, à l'exception de la valeur 7. N'utilisez pas des lignes répétés comme cela~ :
[code=d <<<
import std.stdio;
void main()
{
// Ne faîtes pas cela !
writeln(1);
writeln(2);
writeln(3);
writeln(4);
writeln(5);
writeln(6);
writeln(8);
writeln(9);
writeln(10);
}
>>>]
Imaginez plutôt une variable dont la valeur est incrémentée dans une boucle. Vous pouriez avoir besoin de l'opérateur [c !=] ici.
]
[[part:corrections/arithmetique | … Les solutions]]
]