Léa Saviot

Léa Saviot — 15 février 2019

Développer, c'est renoncer !

Pour la mise à jour 9.2.0, nous avons répondu à une demande de nombreux utilisateurs : pouvoir utiliser dans l’application Calculs les fonctions définies dans l’application Fonctions. Nous en avons profité pour permettre de renommer les fonctions et les variables sans (trop de) contraintes, et pour rendre accessibles les fonctions depuis toutes les applications.

Nous nous sommes posé des questions en développant ces fonctionnalités, voici ce que nous avons décidé pour deux d’entre elles.

Variable ou fonction ?

Quand vous appuyez sur exe après avoir écrit un calcul dans l’application Calculs, une des premières tâches de la calculatrice est de comprendre ce que vous avez écrit. Si vous écrivez 13+2, la calculatrice repère d’abord les différents “mots” du calcul, qui sont le nombre 13, le symbole + et le nombre 2. Elle applique ensuite des règles de grammaire, comme vous le faites en ce moment même pour comprendre ce que vous lisez : un nombre, suivi du symbole +, suivi d’un nombre, représente l’addition du premier nombre avec le second.

En ajoutant à cela des fonctions et des variables que vous pouvez nommer comme vous le voulez, l’affaire se corse. Prenons un exemple : si vous entrez abba(1+2), vous-même ne pouvez pas être certain de ce qui est écrit, parce que la grammaire est ambiguë.

  • Est-ce la fonction abba appliquée à 1+2 ?
  • Est-ce la variable abba multipliée par 1+2 ?
  • Est-ce la variable a multipliée par la variable b, multipliée par la variable b, multipliée par la variable a, multipliée par 1+2 ?
  • Est-ce la variable abb multipliée par la fonction a appliquée à 1+2 ?

L'interprétation d'une fonction

Nous avons pesé le pour et le contre de plusieurs méthodes pour distinguer les fonctions des multiplications implicites de variables :

  1. Toujours créer le plus gros nom de variable possible et, s’il est suivi de parenthèses, l’interpréter comme une fonction. Cette règle est simple, mais nécessite d’écrire les multiplications là où elles sont nécessaires.
  2. Vérifier s’il n’y a pas des fonctions et variables définies par l’utilisateur dont les noms pourraient bien s’agencer pour former une expression. Dans l’exemple abba(1+2), si abba est une variable ou une fonction qui existe, il faut l’interpréter comme telle. Si abba n’existe pas mais que a et b sont deux variables définies, interpréter comme a*b*b*a(1+2). Cette méthode permet de mieux correspondre à l’intention de l’utilisateur, mais complexifie la tâche de la calculatrice et risque de la ralentir.
  3. Faire un mélange des deux : toujours créer le nom le plus long possible, puis vérifier s’il existe une fonction ou une variable qui lui correspond pour décider de quel sens lui donner.

Nous avons pour l’instant choisi la première solution, car elle est plus simple, prend moins de temps de calcul, et ne demande pas beaucoup d’efforts supplémentaires de la part de l’utilisateur pour qu’il se fasse comprendre.

Définitions circulaires

Vous pouvez définir des fonctions (ou variables) les unes en fonction des autres. Nous devons donc gérer les définitions circulaires, telles que la combinaison f(x)=g(x) et g(x)=f(x).
La façon initiale dont nous gérions l’évaluation de f(1) (par exemple), est de remplacer f par sa définition, g(x), dans laquelle l’inconnue x est remplacée par 1, puis d’évaluer cette définition. En faisant cela, la calculatrice garde dans une mémoire, qui s’appelle la stack, le fait qu’elle doit évaluer g(1), pour pouvoir évaluer f(1). En faisant une étape de plus, la calculatrice doit garder en mémoire le fait qu’elle évalue f(1) pour évaluer g(1), pour évaluer f(1). Ainsi de suite, la calculatrice finit par ne plus avoir assez de mémoire pour continuer à évaluer f et g, et elle plante.

Définition circulaire

Il y a encore une fois plusieurs moyens de résoudre ce problème :

  1. Empêcher l’utilisateur de créer une fonction à définition circulaire. Cette solution ajoute beaucoup de vérifications pour la calculatrice et limite les actions de l’utilisateur. Par exemple, l’utilisateur peut avoir défini f(x)=1 et g(x)=f(x)+1, mais finalement décidé que f(x)=g(x)+1 et g(x)=1. Il doit alors nécessairement modifier g(x) avant f(x), parce que l’étape intermédiaire f(x)=g(x)+1 et g(x)=f(x)+1 serait interdite.
  2. Limiter le nombre d’opérations que la calculatrice peut faire pour évaluer une expression
    • En mettant une limite de 1000 opérations, par exemple. Ce n’est pas difficile à mettre en place, mais choisir le nombre maximal d’opérations est compliqué, car toutes les évaluations n’utilisent pas la même quantité d’opérations, et nous ne voulons pas limiter des calculs qui auraient pu réussir, comme somme(n,0,10000).
    • En arrêtant d’évaluer lorsque la stack est trop pleine. C’est une solution intéressante, mais qui est compliquée à mettre en place sur l’émulateur Web de la calculatrice, et nous voulons régler le problème sur tous nos supports. De plus, la calculatrice calculerait pendant longtemps avant de s’arrêter.
  3. Arrêter d’évaluer une fonction, si elle a besoin de s’évaluer elle-même à un moment. Il faut pour cela se rappeler de toutes les fonctions évaluées avant, ce qui est faisable ! Nous ne pouvons cependant pas inspecter la stack de la calculatrice pour savoir cela car c’est compliqué dans la pratique.
  4. Remplacer les fonctions par leurs définitions jusqu’à qu’il n’y ait plus de fonction qui soit remplaçable, en s’arrêtant à un nombre limité de remplacements, au bout duquel on considère la fonction comme définie circulairement.

Les solutions 3. et 4. permettent d’arrêter rapidement un calcul circulaire, tout en ne limitant pas des calculs longs mais qui peuvent aboutir. Nous avons retenu la solution 4. car plus simple à développer et tout aussi satisfaisante dans la pratique que la solution 3.

Et beaucoup d’autres choix…

Voilà deux choix que nous avons faits pour vous permettre d’utiliser les fonctions et les variables dans toutes les applications, mais il y en a d’autres : que faut-il faire quand on veut créer une variable/fonction dont le nom est déjà pris ? Si a=x+1 et f(x)=a, que vaut f(3) ? Faut-il stocker f(x)=1+1+g(x) tel quel, en simplifiant f(x) en 2+g(x) ou en remplaçant g(x) par sa valeur ?

Explorez la calculatrice, observez nos choix et n’hésitez pas à proposer des améliorations !

Léa Saviot
Léa Saviot — Développeuse

Léa est diplômée de l'École Polytechnique et elle a rejoint NumWorks en septembre 2017 comme développeuse. Léa est une de nos génies du C++ qui s'efforce mise à jour après mise à jour de rendre la calculatrice NumWorks encore meilleure ! Mais le code est loin d'être son seul talent et vous aurez peut-être un jour la chance participer à un de ses blind tests a capella !