18 octobre 2023

Opérateurs de base, mathématiques

De nombreux opérateurs nous sont connus de l’école. Ce sont les additions +, multiplications *, soustractions - et ainsi de suite.

Dans ce chapitre, nous nous concentrons sur les aspects qui ne sont pas couverts par l’arithmétique scolaire.

Termes: “unaire”, “binaire”, “opérande”

Avant de continuer, saisissons la terminologie commune.

  • Un opérande est ce à quoi les opérateurs sont appliqués. Par exemple, dans la multiplication 5 * 2, il y a deux opérandes : l’opérande gauche est 5 et l’opérande droit est 2. Parfois, les gens disent “arguments” au lieu de “opérandes”.

  • Un opérateur est unaire s’il a un seul opérande. Par exemple, la négation unaire - inverse le signe du nombre :

    let x = 1;
    
    x = -x;
    alert( x ); // -1, le moins unaire a été appliqué
  • Un opérateur est binaire s’il a deux opérandes. La même négation existe également dans la forme binaire :

    let x = 1, y = 3;
    alert( y - x ); // 2, le moins binaire soustrait des valeurs

    D’un point de vue formel, dans les exemples ci-dessus, nous avons deux opérateurs différents qui partagent le même symbole : l’opérateur de négation, un opérateur unaire qui inverse le signe, et l’opérateur de soustraction, un opérateur binaire qui soustrait un nombre d’un autre.

Opérations mathématiques

Les opérations mathématiques suivantes sont supportées :

  • Addition +,
  • Soustraction -,
  • Multiplication *,
  • Division /,
  • Reste %,
  • Exponentiation **.

Les quatre premières sont assez simples, tandis que % et ** nécessitent quelques explications.

Reste % (Modulo)

L’opérateur reste %, malgré son apparence, n’est pas lié aux pourcentages.

Le résultat de a % b est le reste de la division entière de a par b.

Par exemple :

alert( 5 % 2 ); // 1, le reste de 5 divisé par 2
alert( 8 % 3 ); // 2, le reste de 8 divisé par 3
alert( 8 % 4 ); // 0, le reste de 8 divisé par 4

Exponentiation **

L’opérateur d’exponentiation a ** b multiplie a par lui-même b fois. En mathématiques à l’école, nous écrivons cela ab.

Par exemple :

alert( 2 ** 2 ); // 2² = 4
alert( 2 ** 3 ); // 2³ = 8
alert( 2 ** 4 ); // 2⁴ = 16

Tout comme en mathématiques, l’opérateur d’exponentiation est également défini pour les nombres non entiers.

Par exemple, une racine carrée est une exponentiation de ½ :

alert( 4 ** (1/2) ); // 2 (la puissance de 1/2 équivaut à une racine carrée)
alert( 8 ** (1/3) ); // 2 (la puissance de 1/3 équivaut à une racine cubique)

Concaténation de chaînes de caractères, binaire +

Découvrons les fonctionnalités des opérateurs JavaScript qui vont au-delà de l’arithmétique scolaire.

Habituellement, l’opérateur + additionne des chiffres.

Mais si le binaire + est appliqué aux chaînes de caractères, il les fusionne (concatène) :

let s = "my" + "string";
alert(s); // mystring

Notez que si l’un des opérandes est une chaîne de caractères, l’autre est automatiquement converti en chaîne de caractères.

Par exemple :

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"

Peu importe que le premier opérande soit une chaîne de caractères ou le second. La règle est simple : si l’un des opérandes est une chaîne de caractères, convertissez l’autre également en une chaîne de caractères.

Cependant, notez que les opérations se déroulent de gauche à droite. S’il y a deux nombres suivis d’une chaîne, les nombres seront ajoutés avant d’être convertis en chaîne :

alert(2 + 2 + '1' ); // "41" et non "221"

Ici, les opérateurs travaillent les uns après les autres. Le premier + additionne deux nombres, donc il renvoie 4, puis le + suivant ajoute la chaîne de caractères 1, donc c’est comme 4 + '1' = 41.

alert('1' + 2 + 2); // "122" and not "14"

Ici, le premier opérande est une chaîne de caractères, le compilateur traite également les deux autres opérandes comme des chaînes de caractères. Le 2 est concaténé à '1', donc c’est comme '1'+ 2 = "12" et "12" + 2 = "122".

Le binaire + est le seul opérateur qui prend en charge les chaînes de caractères de cette manière. D’autres opérateurs arithmétiques ne fonctionnent qu’avec des nombres et convertissent toujours leurs opérandes en nombres.

Voici l’exemple pour la soustraction et la division :

alert( 6 - '2' ); // 4, convertit '2' en nombre
alert( '6' / '2' ); // 3, convertit les deux opérandes en nombres

Conversion numérique, unaire +

Le plus + existe sous deux formes. La forme binaire que nous avons utilisée ci-dessus et la forme unaire.

L’unaire plus ou, en d’autres termes, l’opérateur plus + appliqué à une seule valeur, ne fait rien avec les nombres, mais si l’opérande n’est pas un nombre, alors il est converti en nombre.

Par exemple :

// Aucun effet sur les nombres
let x = 1;
alert( +x ); // 1

let y = -2;
alert( +y ); // -2

// Convertit les non-nombres
alert( +true ); // 1
alert( +"" );   // 0

En fait, il fait la même chose que Number(...), mais il est plus court.

La nécessité de convertir des chaînes de caractères en nombres est très fréquente. Par exemple, si nous obtenons des valeurs à partir de champs de formulaire HTML, il s’agit généralement de chaînes de caractères. Et si on veut les additionner ?

Le binaire plus les ajouterait comme des chaînes de caractères :

let apples = "2";
let oranges = "3";

alert( apples + oranges ); // "23", le binaire plus concatène les chaînes de caractères

Si nous voulons les traiter comme des nombres, nous devons d’abord les convertir et ensuite seulement nous pouvons les additionner :

let apples = "2";
let oranges = "3";

// les deux valeurs converties en nombres avant le binaire plus
alert( +apples + +oranges ); // 5

// c'est équivalent à cette variante plus longue
// alert( Number(apples) + Number(oranges) ); // 5

Du point de vue du mathématicien, l’abondance des + peut sembler étrange. Mais du point de vue du programmeur, il n’y a rien de spécial : les plus unaires sont appliqués en premier, ils convertissent les chaînes de caractères en nombres, puis le plus binaire les additionne.

Pourquoi les plus unaires sont-ils appliqués aux valeurs avant les binaires ? Comme nous allons le voir, c’est à cause de leur précédence supérieure.

Précédence des opérateurs

Si une expression à plusieurs opérateurs, l’ordre d’exécution est défini par leur priorité ou, en d’autres termes, il existe un ordre de priorité implicite entre les opérateurs.

De l’école, nous savons tous que la multiplication dans l’expression 1 + 2 * 2 devrait être calculée avant l’addition. C’est exactement cela la précédence. La multiplication est dite avoir une précédence supérieure à l’addition.

Les parenthèses outrepassent toute priorité, donc si nous ne sommes pas satisfaits de l’ordre par défaut, nous pouvons les utiliser, comme : (1 + 2) * 2.

Il y a beaucoup d’opérateurs en JavaScript. Chaque opérateur a un numéro correspondant à sa priorité de précédence. Celui qui est plus haut sur le tableau s’exécute en premier. Si la priorité est la même, l’ordre d’exécution est de gauche à droite.

Un extrait du tableau de précédence (vous n’avez pas besoin de vous en souvenir, mais notez que les opérateurs unaires ont une priorité plus élevée que les binaires correspondants) :

Précédence Nom Symbole
14 plus unaire +
14 négation unaire -
13 exponentiation **
12 multiplication *
12 division /
11 addition +
11 soustraction -
2 affectation =

Comme on peut le voir, le “plus unaire” a une priorité de 14, ce qui est supérieur à 11 pour “l’addition” (plus binaire). C’est pourquoi, dans l’expression "+apples + +oranges", les plus unaires fonctionnent avant l’addition.

Affectation

Notons qu’une affectation = est aussi un opérateur. Il est répertorié dans le tableau des précédences avec la très faible priorité de 2.

C’est pourquoi lorsque nous assignons une variable, comme x = 2 * 2 + 1, les calculs sont effectués en premier, puis le = est évalué, stockant le résultat dans x.

let x = 2 * 2 + 1;

alert( x ); // 5

Assignment = retourne une valeur

Le fait que = soit un opérateur, pas une construction de langage “magique” a une implication intéressante.

Tous les opérateurs en JavaScript renvoient une valeur. C’est évident pour + et -, mais aussi vrai pour =.

L’appel x = valeur écrit la valeur dans x puis la renvoie.

Voici un exemple qui utilise une affectation dans le cadre d’une expression plus complexe :

let a = 1;
let b = 2;

let c = 3 - (a = b + 1);

alert( a ); // 3
alert( c ); // 0

Dans l’exemple ci-dessus, le résultat de l’expression(a = b + 1) est la valeur qui a été affectée à a (c’est-à-dire 3). Il est ensuite utilisé pour d’autres évaluations.

Drôle de code, n’est-ce pas? Nous devons comprendre comment cela fonctionne, car parfois nous le voyons dans les bibliothèques JavaScript.

Cependant, n’écrivez pas le code comme ça. De telles astuces ne rendent certainement pas le code plus clair ou lisible.

Affectations chaînées

Une autre caractéristique intéressante est la possibilité de chaîner des affectations :

let a, b, c;

a = b = c = 2 + 2;

alert( a ); // 4
alert( b ); // 4
alert( c ); // 4

Les affectations en chaîne sont évaluées de droite à gauche. D’abord, l’expression la plus à droite 2 + 2 est évaluée puis assignée aux variables de gauche : c, b et a. À la fin, toutes les variables partagent une seule valeur.

Encore une fois, pour des raisons de lisibilité, il est préférable de diviser ce code en quelques lignes :

c = 2 + 2;
b = c;
a = c;

C’est plus facile à lire, en particulier lors de la numérisation rapide du code.

Modification sur place

Nous avons souvent besoin d’appliquer un opérateur à une variable et d’y stocker le nouveau résultat.

Par exemple :

let n = 2;
n = n + 5;
n = n * 2;

Cette notation peut être raccourcie en utilisant les opérateurs += et *= :

let n = 2;
n += 5; // maintenant n = 7 (identique à n = n + 5)
n *= 2; // maintenant n = 14 (identique à n = n * 2)

alert( n ); // 14

Il existe des opérateurs de “modification et assignation” courts pour tous les opérateurs arithmétiques et binaires : /=, -= etc.

Ces opérateurs ont la même précédence qu’une affectation normale. Ils s’exécutent donc après la plupart des autres calculs :

let n = 2;

n *= 3 + 5; // la partie de droite est évaluée en premier (identique à n *= 8)

alert( n ); // 16

Incrémentation / décrémentation

L’augmentation ou la diminution d’un nombre par 1 compte parmi les opérations numériques les plus courantes.

Il y a donc des opérateurs spéciaux pour cela :

  • Incrémentation ++ augmente une variable de 1 :

    let counter = 2;
    counter++;        // fonctionne de la même manière que counter = counter + 1, mais c'est plus court
    alert( counter ); // 3
  • Décrémentation -- diminue une variable de 1 :

    let counter = 2;
    counter--;        // fonctionne de la même manière que counter = counter - 1, mais c'est plus court
    alert( counter ); // 1
Important :

L’incrémentation / décrémentation ne peut être appliquée qu’à une variable. Une tentative pour l’utiliser sur une valeur comme 5++ donnera une erreur.

Les opérateurs ++ et -- peuvent être placés à la fois après et avant la variable.

  • Lorsque l’opérateur va après la variable, cela s’appelle une “forme postfixe” : counter++.
  • La “forme préfixe” est celle où l’opérateur se place devant la variable : ++counter.

Ces deux opérateurs font la même chose : augmenter le counter de 1.

Y a-t-il une différence ? Oui, mais nous ne pouvons le voir que si nous utilisons la valeur renvoyée de ++/--.

Soyons clairs. Comme nous le savons, tous les opérateurs renvoient une valeur. L’incrémentation / décrémentation n’est pas une exception ici. La forme préfixe renvoie la nouvelle valeur, tandis que la forme postfixe renvoie l’ancienne valeur (avant l’incrémentation / décrémentation).

Pour voir la différence, voici un exemple :

let counter = 1;
let a = ++counter; // (*)

alert(a); // 2

Ici, dans la ligne (*), l’appel du préfixe ++counter incrémente le compteur et retourne la nouvelle valeur qui est 2. Ainsi, l’alert affiche 2.

Maintenant, utilisons la forme postfixe :

let counter = 1;
let a = counter++; // (*) changé ++counter pour counter++

alert(a); // 1

Dans la ligne (*), la forme postfixe counter++ incrémente également counter, mais renvoie l’ancienne valeur (avant l’incrémentation). Donc, l’alert montre 1.

Pour résumer :

  • Si le résultat de l’incrémentation/décrémentation n’est pas utilisé, alors il n’y a pas de différence dans la forme à utiliser :

    let counter = 0;
    counter++;
    ++counter;
    alert( counter ); // 2, les lignes ci-dessus ont fait la même chose
  • Si nous souhaitons augmenter la valeur et utiliser le résultat de l’opérateur immédiatement, nous avons besoin de la forme préfixe :

    let counter = 0;
    alert( ++counter ); // 1
  • Si nous souhaitons incrémenter, mais utiliser la valeur précédente, alors nous avons besoin de la forme postfixe :

    let counter = 0;
    alert( counter++ ); // 0
Incrémentation / décrémentation parmi d’autres opérateurs

Les opérateurs ++/-- peuvent également être utilisés dans une expression. Leur précédence est plus élevée que la plupart des autres opérations arithmétiques.

Par exemple :

let counter = 1;
alert( 2 * ++counter ); // 4

A comparer avec :

let counter = 1;
alert( 2 * counter++ ); // 2, counter++ renvoie "l'ancienne" valeur

Bien que techniquement acceptable, une telle notation rend le code moins lisible. Une ligne fait plusieurs choses – pas bien.

Lors de la lecture du code, un scan oculaire “vertical” rapide peut facilement manquer un tel counter++, et il n’est pas évident que la variable augmente.

Le style “une ligne – une action” est conseillé :

let counter = 1;
alert( 2 * counter );
counter++;

Opérateurs binaires

Les opérateurs binaires traitent les arguments comme des nombres entiers de 32 bits et travaillent au niveau de leur représentation binaire.

Ces opérateurs ne sont pas spécifiques à JavaScript. Ils sont pris en charge dans la plupart des langages de programmation.

La liste des opérateurs :

  • AND ( & )
  • OR ( | )
  • XOR ( ^ )
  • NOT ( ~ )
  • LEFT SHIFT ( << )
  • RIGHT SHIFT ( >> )
  • ZERO-FILL RIGHT SHIFT ( >>> )

Ces opérateurs sont très rarement utilisés, lorsque nous devons jouer avec des nombres au niveau le plus bas (bit à bit). Nous n’aurons pas besoin de ces opérateurs de si tôt, car le développement Web les utilise peu, mais dans certains domaines particuliers, comme la cryptographie, ils sont utiles. Vous pouvez lire le chapitre Opérateurs binaires sur MDN en cas de besoin.

Virgule

L’opérateur virgule , est l’un des opérateurs les plus rares et les plus inhabituels. Parfois, il faut écrire un code plus court, il faut donc le connaître pour comprendre ce qui se passe.

L’opérateur virgule nous permet d’évaluer plusieurs expressions en les divisant par une virgule ,. Chacun d’eux est évalué, mais seulement le résultat de la dernière est renvoyé.

Par exemple :

let a = (1 + 2, 3 + 4);

alert( a ); // 7 (le résultat de 3 + 4)

Ici, la première expression 1 + 2 est évaluée mais son résultat n’est pas utilisé, puis 3 + 4 est évalué et renvoyé comme résultat.

La virgule a une très faible précédence

Veuillez noter que l’opérateur virgule a une précédence très basse, inférieure à =, donc les parenthèses sont importantes dans l’exemple ci-dessus.

Sans eux : a = 1 + 2, 3 + 4 évalue d’abord +, additionnant les nombres dans a = 3, 7, ensuite l’opérateur d’affectation = assigne a = 3, et le reste est ignoré. C’est comme (a = 1 + 2), 3 + 4.

Pourquoi avons-nous besoin d’un tel opérateur qui jette tout sauf la dernière partie ?

Parfois, les gens l’utilisent dans des constructions plus complexes pour placer plusieurs actions sur une seule ligne.

Par exemple :

// trois opérations en une seule ligne
for (a = 1, b = 3, c = a * b; a < 10; a++) {
 ...
}

Ces astuces sont utilisées dans de nombreux frameworks JavaScript, c’est pourquoi nous les mentionnons. Mais généralement, ils n’améliorent pas la lisibilité du code, nous devrions bien réfléchir avant de les utiliser.

Exercices

importance: 5

Quelles sont les valeurs finales de toutes les variables a, b, c et d après le code ci-dessous ?

let a = 1, b = 1;

let c = ++a; // ?
let d = b++; // ?

La réponse est :

  • a = 2
  • b = 2
  • c = 2
  • d = 1
let a = 1, b = 1;

alert( ++a ); // 2, la forme préfixe renvoie la nouvelle valeur
alert( b++ ); // 1, la forme postfixe renvoie l'ancienne valeur

alert( a ); // 2, incrémenté une fois
alert( b ); // 2, incrémenté une fois
importance: 3

Quelles sont les valeurs de a et x après le code ci-dessous ?

let a = 2;

let x = 1 + (a *= 2);

La réponse est :

  • a = 4 (multiplié par 2)
  • x = 5 (calculé comme 1 + 4)
importance: 5

Quels sont les résultats de ces expressions ?

"" + 1 + 0
"" - 1 + 0
true + false
6 / "3"
"2" * "3"
4 + 5 + "px"
"$" + 4 + 5
"4" - 2
"4px" - 2
"  -9  " + 5
"  -9  " - 5
null + 1
undefined + 1
" \t \n" - 2

Réfléchissez bien, notez et comparez avec la réponse.

"" + 1 + 0 = "10" // (1)
"" - 1 + 0 = -1 // (2)
true + false = 1
6 / "3" = 2
"2" * "3" = 6
4 + 5 + "px" = "9px"
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
"  -9  " + 5 = "  -9  5" // (3)
"  -9  " - 5 = -14 // (4)
null + 1 = 1 // (5)
undefined + 1 = NaN // (6)
" \t \n" - 2 = -2 // (7)
  1. L’addition avec une chaîne de caractères "" + 1 converti 1 vers une chaîne de caractères : "" + 1 = "1", ensuite nous avons "1" + 0, la même règle est appliquée.
  2. La soustraction - (comme la plupart des opérations mathématiques) ne fonctionne qu’avec des nombres, il convertit une chaîne de caractères vide "" vers 0.
  3. L’addition avec un string ajoute le number 5 au string.
  4. La soustraction est toujours convertie en nombres, donc elle fait de " -9 " un number -9 (en ignorant les espaces qui l’entourent).
  5. null devient 0 après la conversion numérique.
  6. undefined devient NaN après la conversion numérique.
  7. Les caractères d’espacement sont coupés au début et à la fin de la chaîne de caractères lorsque celle-ci est convertie en nombre. Ici toute la chaîne se compose d’espaces, tels que \t, \n et d’un espace “normal” entre eux. Ainsi, de manière similaire à une chaîne de caractères vide, elle devient 0.
importance: 5

Voici un code qui demande à l’utilisateur deux nombres et affiche leur somme.

Cela ne fonctionne pas correctement. La sortie dans l’exemple ci-dessous est 12 (pour les valeurs d’invite par défaut).

Pourquoi ? Réparez-le. Le résultat doit être 3.

let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);

alert(a + b); // 12

La raison en est que le prompt renvoie l’entrée utilisateur sous forme de chaîne de caractères.

Les variables ont donc respectivement les valeurs "1" et "2".

let a = "1"; // prompt("First number?", 1);
let b = "2"; // prompt("Second number?", 2);

alert(a + b); // 12

Ce que nous devons faire est de convertir les chaînes de caractères en nombres avant +. Par exemple, en utilisant Number() ou en les préfixant avec +.

Par exemple, juste avant prompt :

let a = +prompt("First number?", 1);
let b = +prompt("Second number?", 2);

alert(a + b); // 3

Ou dans l’alert:

let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);

alert(+a + +b); // 3

Nous utilisons à la fois unaire et binaire + dans le dernier code. Ça a l’air drôle, non ?

Carte du tutoriel