Tester une inégalité dans LaTeX

Bonjour,
Je voudrais, dans une commande LaTeX que je crée, faire une distinction de cas suivant la valeur d'un nombre. Voici un exemple représentatif de ce que je voudrais faire et de ce que j'ai essayé.
\documentclass{article}
\usepackage{ifthen}

\newcommand{\essaiA}[1]{
    \ifthenelse{#1 < 1}{petit}{grand}}

\newcommand{\essaiB}[1]{
    \pgfmathparse{#1}
    \ifthenelse{\pgfmathresult < 1}{petit}{grand}}

\begin{document}

\essaiA{2.5} \\
\essaiA{4/12} \\
\essaiB{2.5} \\
\essaiB{4/12} \\

\end{document}

Mais ça ne fonctionne pas puisque j'obtiens dans mon pdf :
grand
grand
2.5 petit
4/12 petit

Savez-vous comment faire ?
Merci d'avance

Réponses

  • Bonjour,
    Calli a écrit:
    Savez-vous comment faire ?
    Non, pas vraiment. Cependant, en attendant mieux, le code suivant affiche bien "grand petit" :
    \makeatletter
    
    \def\@gobble#1/{#1}
    
    \def\essaiC#1{\@essaiC#1/\@nil}
    
    \def\@essaiC#1/#2\@nil{%
        \edef\@tempa{%
            \ifx\relax#2\relax
                \dimexpr #1pt < 1pt\relax
            \else
                \dimexpr#1pt / \@gobble#2 < 1pt\relax
        \fi}%
        \ifdim\@tempa
            petit
        \else
            grand
        \fi}
    
    \makeatother
    
    \essaiC{2.5} \essaiC{4/12}
    
  • Merci bien. :)
    Mais comme je ne comprends rien au code, je suis preneur pour un code plus simple et facilement adaptable si quelqu'un a ça en stock ^^.
    Et j'aimerais bien que ça fonctionne pour des nombres du type 2+4/5. Pour l'instant \essaiC{2+1/5} donne le bon résultat, mais provoque une erreur.
  • \def\essaiD#1#2#3{%
        \ifdim\dimexpr#1pt + #2pt / #3 < 1pt
            petit
        \else
            grand
        \fi}
    
    \essaiD{2}{1}{5} % affiche "grand" car 2+1/5>=1
    

    Ajout : Sinon fp pourrait peut-être vous être utile.
  • La solution avec \ifdim n'est pas mauvaise, mais :
    • la syntaxe supportée pour les expressions de \dimexpr n'est pas hyper puissante ;
    • la précision est assez limitée (dimen registers de TeX) ;
    • les primitives TeX sont piégeuses si on ne connaît pas : il faut effectivement ne pas mettre de % après le 1pt, ou bien mettre un \relax juste après ce 1pt, sinon le \ifdim développerait systématiquement le début de la branche « vrai » après avoir lu 1pt, ce qui pourrait bouffer par exemple des arguments de macro (ex. : s'il y avait une macro à la place de "petit").
    Le package ifthen a été écrit par quelqu'un d'illustre et archi-compétent, mais il est très vieux (le package :-)) et son auteur aurait fait les choses différemment aujourd'hui (il l'a écrit). Il est encore utile à l'occasion, mais je lui préfère des solutions plus modernes comme expl3. Ce qui ne marche pas ici avec ifthen, c'est que <number> dans sa documentation est à interpréter selon la grammaire formelle présentée dans le TeXbook. Ceci implique qu'un <number> est forcément un nombre entier (cela peut être un compteur TeX comme obtenu en développant \value{compteur LaTeX}, un \numexpr ...\relax, un paramètre interne entier de TeX comme \prevgraf... mais au final, c'est toujours un entier).

    fp est un peu vieux aussi, mais pas forcément mauvais. Le calcul avec fp n'est pas développable, contrairement à l3fp qui fait partie du noyau LaTeX aujourd'hui (même pas besoin de \usepackage{xparse} ci-dessous si le LaTeX est 2020-10-01 ou supérieur). Ce n'est pas dramatique, mais les choses sont plus faciles à utiliser quand elles sont développables.

    Bref, ma recommandation, c'est ça :
    \documentclass{article}
    \usepackage{xparse}
    
    \ExplSyntaxOn
    \NewExpandableDocumentCommand \essai { m }
      {
        \fp_compare:nNnTF {#1} < { 1 } { petit } { grand }
      }
    \ExplSyntaxOff
    
    \begin{document}
    
    \essai{2}    \par
    \essai{4/12} % Pas de \\ en fin de paragraphe !
    
    \medskip
    \essai{0.1+4/5} \par
    \essai{0.2+4/5} \par
    \essai{2 + 4/5}
    
    \end{document}
    
    On peut enlever les \ExplSyntaxOn et \ExplSyntaxOff, mais dans ce cas, les espaces (et fins de lignes, ':' et '_', suivent les règles usuelles de LaTeX2e : il faut alors mettre des % à divers endroits pour éviter les espaces non voulus dans le document). En clair, on peut faire ça :
    \documentclass{article}
    \usepackage{xparse}
    
    \ExplSyntaxOn
    % Mieux que \let
    \cs_new_eq:NN \monfpcompare \fp_compare:nNnTF
    \ExplSyntaxOff
    
    \NewExpandableDocumentCommand \essai { m } % ces espaces-là ne feront rien
      {% <---
        \monfpcompare{#1}<{1}{petit}{grand}% <---
      } % Ici, OK (comme la ligne blanche suivante, évidemment), car la définition
        % de la macro est terminée et on est *avant* \begin{document}.
    
    \begin{document}
    ...
    \end{document}
    
    On pourrait aussi utiliser \fpeval du package xfp. C'est le même moteur sous le capot : l3fp. Les calculs avec l3fp sont développables (“expandable”, “fully expandable”). Ils marchent donc à plein, plein d'endroits sans avoir rien de spécial à faire (dans l'argument de \num{...} du package siunitx ; à l'intérieur d'une expression \pgfmathparse{...}, \numexpr ... \relax, \dimexpr ... \relax, \glueexpr ... \relax, etc.). C'est parce que \fp_compare:nNnTF est développable (et qu'il n'y a donc rien de non-développable dans ma macro \essai) que j'utilise \NewExpandableDocumentCommand(*). \essai est alors elle-même développable. Comment sait-on que \fp_compare:nNnTF est développable ? Parce qu'il y a une étoile à gauche de sa description dans interface3.pdf (pour savoir ce qu'on peut mettre comme floating point expressions dans \fpeval{...}, il faut regarder 'texdoc xfp', mais la vraie référence est celle du moteur de calcul l3fp dans interface3.pdf).

    En plus de fournir des commandes développables et de supporter une syntaxe de floating point expressions bien plus puissante et intuitive que \dimexpr, l3fp est extrêmement précis (l3fp utilise une représentation IEEE-754-2008 avec une mantisse constituée de 16 chiffres décimaux et un exposant compris entre -10000 et 10000). Dans cet exemple, il a bien vu que $0.2+4/5$ n'est pas strictement inférieur à 1.

    En revanche, pgfmath utilise les dimen registers de TeX pour faire ses calculs et se plante pour le test $0.2 + 4/5 < 1$ à cause de la faible précision de cette technique. Il supporte une syntaxe de floating point expressions comparable à celle de l3fp, mais :
    • \pgfmathparse n'est pas développable (heureusement, \pgfmathresult l'est car contient directement le résultat. Pour utiliser le résultat dans un expansion-only context, il suffit donc de faire le \pgfmathparse{...} un peu avant l'expansion-only context, à un endroit où l'exécution dans l'estomac de TeX est permise, puis d'utiliser \pgfmathresult là où l'on en a besoin... à condition que rien ne l'ait modifié entre temps — s'il y a un risque, sauver le résultat dans une autre macro que \pgfmathresult en utilisant p. ex. \let\machin\pgfmathresult ou \pgfmathsetmacro{\machin}{...} au lieu de \pgfmathparse{...}) ;
    • comme il utilise les dimen registers de TeX pour faire les calculs, pgfmath est a priori assez rapide mais sans aucun doute peu précis (pas plus de 5 décimales significatives avec le moteur de calcul PGF standard ou la bibliothèque PGF 'fpu' et évidemmment, les erreurs s'accumulent lorsqu'on enchaîne les calculs).
    Voici quand même une solution avec ledit pgfmath :
    \documentclass{article}
    \usepackage{pgfmath}
    
    \newcommand*{\essai}[1]{%
      \pgfmathparse{#1 < 1 ? "petit" : "grand"}%
      \pgfmathresult
    }
    
    \newcommand*{\essaii}[1]{%
      \pgfmathparse{ifthenelse(#1 < 1, "petit", "grand")}%
      \pgfmathresult
    }
    
    \begin{document}
    
    \essai{2}    \essaii{2}    \par
    \essai{4/12} \essaii{4/12}
    
    \medskip
    \essai{0.1+4/5} \par
    \essai{0.2+4/5} \par
    \essai{2+4/5}
    
    \end{document}
    
    (*) Sinon, il faudrait utiliser \NewDocumentCommand et le résultat "petit" ou "grand" ne pourrait alors pas être extrait dans un expansion-only context (ce n'est pas par pédanterie que j'écris ça : cette expression donnera sans doute de bons résultats de recherche pour qui veut en savoir plus : TeXbook ou moteur de recherche).112276
    112274
  • Addendum concernant les règles de syntaxe à l'intérieur de \ExplSyntaxOn ... \ExplSyntaxOff.

    Dans mes exemples avec l3fp, tout ce qui se trouve entre \ExplSyntaxOn et \ExplSyntaxOff suit les règles de syntaxe expl3 :
    • les caractères « espace » et « fin de ligne » sont ignorés, sauf pour terminer le nom d'un control sequence token (une « commande »), ce qui permet d'aérer le code sans avoir à utiliser des % un peu partout ;
    • '~' se comporte (à peu près) comme un caractère « espace » usuel pour TeX (catcode 10) ;
    • ':' et '_' se comportent comme des lettres pour TeX (catcode 11 au lieu de 12 habituellement[1]).
    Ces changements sont faits pour permettre de programmer de manière confortable et lisible. \usepackage[french]{babel} ou pas, après \ExplSyntaxOn, ':' et '_' se comportent toujours comme des lettres. Pour avoir le ':' spécial de babel-french après \ExplSyntaxOn, il faut donc faire un truc spécial (voir ci-dessous).
    1. Pour mettre « petit malin » à la place de « petit » dans la macro \essai, on pourrait donc écrire « petit~malin ».

      [small]Note 1 : le ~ ne serait pas un espace insécable, car on est entre \ExplSyntaxOn et \ExplSyntaxOff ; l'espace insécable peut dans tous les cas être obtenu avec \nobreakspace.

      Note 2 : pour un espace « normal », il y a d'autres possibilités : \space (macro de plain TeX et de LaTeX2e), \c_space_tl (macro expl3 identique à \space), \c_space_token (pas une macro mais un implicit space token — pas grave si ça ne vous dit rien)...[/small]
    2. Si l'on veut le ':' « magique » de babel-french[2] après \ExplSyntaxOn, on peut faire comme suit (commande \deuxpointsfrench).
    \documentclass{article}
    \usepackage{lmodern}
    \usepackage[T1]{fontenc}
    \usepackage[utf8]{inputenc}
    \usepackage[french]{babel}
    \usepackage{xparse}
    
    \ExplSyntaxOn
    \NewDocumentCommand \deuxpointsfrench { }
      {
        \use:c { FDP@colonspace } \tl_to_str:n { : }
      }
    
    \NewExpandableDocumentCommand \essai { m }
      {
        \fp_compare:nNnTF {#1} < { 1 }
          { petit \deuxpointsfrench \c_space_token malin } % ou \c_space_tl, ou \space
          { grand }
      }
    \ExplSyntaxOff
    
    \begin{document}
    
    % \usepackage[french]{babel} insère automatiquement une espace avant le ':' car
    % on n'a pas utilisé \frenchsetup{AutoSpacePunctuation=false}.
    petit: malin
    
    \essai{0.9999999999999999}
    
    \end{document}
    
    Edit : précisions autour du '~'.

    [1] Notez que l'on n'est pas dans ce « habituellement » après \begin{document} lorsqu'on utilise \usepackage[french]{babel} avec pdfTeX : dans ce cas, en dehors de \ExplSyntaxOn et \ExplSyntaxOff, ':' est un caractère actif, i.e. son catcode est 13. Cela signifie qu'il se comporte comme une macro : on peut le définir avec \def ; TeX peut alors le développer, c'est-à-dire le remplacer par d'autres tokens (le « code » de la macro). C'est cela qui permet au ':' de babel-french d'être « magique »... et qui peut parfois causer des problèmes lorsqu'il est utilisé dans un contexte où l'auteur n'a pas prévu que le ':' pouvait être actif (macros écrites par des non-francophones...).
    [2] Qui insère automatiquement une espace s'il n'y en a pas déjà avant, sauf si ce comportement a été désactivé avec \frenchsetup{AutoSpacePunctuation=false}.112280
  • Pour terminer, voici ce que permet de faire l'aspect « développable » d'une commande telle que ma macro \essai implémentée avec l3fp. Si vous la remplacez par une macro qui fait le calcul de manière non développable (par exemple, \FPeval ou \pgfmathparse), il y aura des erreurs à peu près partout.
    \documentclass{article}
    \usepackage{xfp}
    \usepackage{pgf}
    \usepackage{pgfmath}
    \usepgflibrary{fpu}
    \usepackage{siunitx}
    
    \ExplSyntaxOn
    \cs_new_eq:NN \clistmapinline \clist_map_inline:nn
    
    \NewExpandableDocumentCommand \essai { m }
      {
        \fp_compare:nNnTF {#1} < { 1 } { 123456 } { 5200000 }
      }
    \ExplSyntaxOff
    
    \begin{document}
    
    \clistmapinline{0.5, 2}
      {% #1 est successivement remplacé par 0.5 et par 2.
        \num{\essai{#1}},
        \SI{\essai{#1}}{\meter},
        \the\numexpr 10*\essai{#1}\relax,
        \fpeval{(\essai{#1}) - 1},
        \fpeval{ln(\essai{#1})},
        %
        \begingroup % Il faut souffrir pour éviter le “Dimension too large” avec pgfmath
          \pgfset{fpu=true}%
          \pgfmathparse{int((\essai{#1}) - 1)}%
          \pgfmathfloattoint{\pgfmathresult}%
          \pgfmathresult,
          \pgfmathparse{ln(\essai{#1})}%
          \pgfmathfloattofixed{\pgfmathresult}%
          \pgfmathresult
        \endgroup
        \par\medskip
      }
    
    $123456\times 12 = \num{\fpeval{123456 * 12}}$
    
    $123456\times 12 = \num{\fpeval{\essai{0.5} * 12}}$
    
    \edef\zzz{Bla : \essai{0.5}. Pouet}
    \show\zzz %  \zzz=macro:->Bla : 123456. Pouet.
    \edef\zzz{Bla : \essai{1.1}. Pouet}
    \show\zzz % \zzz=macro:->Bla : 5200000. Pouet.
    
    \end{document}
    
    112286
  • Merci beaucoup brian ! Tu as résolu mon problème. (:D
  • Ravi de l'apprendre, Calli. ;-)
Connectez-vous ou Inscrivez-vous pour répondre.