Comment déterminer qu’un argument est un nombre entier ?#

L’analyse lexicale propre à n’offre pas beaucoup d’aide au programmeur de commandes : alors que les codes de catégorie, ou catcodes, distinguent les lettres (ou, pour être exact, ce que considère comme des lettres) de tout le reste, il n’y a pas d’aide pour l’analyse des nombres.

1.  Avec l’extension xstring#

L’extension xstring, de Christian Tellechea, est dédiée à des tests et opérations sur des chaînes de tokens. Sa commande \IfInteger permet de traiter preque intégralement le problème. La gestion des espaces pouvant poser difficultés, Peter Grill propose une commande qui corrige ce point. En voici un exemple :`

\documentclass{article}
  \usepackage{lmodern} % Caractères plus lisibles
  \pagestyle{empty}    % N'affiche pas de numéro de page
  \usepackage{xstring}

  \newcommand*{\EstEntier}[3]{%
    \IfStrEq{#1}{ }{%
        #3% est une chaîne vide.
    }{%
        \IfInteger{#1}{#2}{#3}%
    }%
  }%

\begin{document}
$2$ est \EstEntier{2}{un entier}{autre chose qu'un entier}.\par
$2.0$ est \EstEntier{2.0}{un entier}{autre chose qu'un entier}. \par
$-7$ est \EstEntier{-7}{un entier}{autre chose qu'un entier}.\par
$-7.0$ est \EstEntier{-7.0}{un entier}{autre chose qu'un entier}. \par
$2.1$ est \EstEntier{2.1}{un entier}{autre chose qu'un entier}.\par
$-7.1$ est \EstEntier{-7.1}{un entier}{autre chose qu'un entier}. \par
a est \EstEntier{a}{un entier}{autre chose qu'un entier}. \par
La chaîne vide est \EstEntier{}{un entier}{autre chose qu'un entier}.\par
Une chaîne d'espace est \EstEntier{       }{un entier}{autre chose qu'un entier}.
\end{document}

Plus largement, l’extension xstring propose aussi une commande pour tester qu’un nombre est décimal avec la commande \IfDecimal.

2.  Avec les commandes de base#

Avertissement

Les solutions proposées ici sont plus anciennes et demandent une connaissance un peu plus poussée de

La solution la plus simple consiste à comparer les différents caractères numériques avec les caractères de l’argument, un par un, et à déclarer que l’argument « n’est pas un nombre » si un caractère échoue à toutes les comparaisons :

\ifx1#1
\else\ifx2#1
...
\else\ifx9#1
\else\isanumfalse
\fi\fi...\fi

que l’on utiliserait ensuite dans une commande qui teste ensuite, par récurrence, le reste de la chaîne de l’argument. On pourrait faire un peu mieux en supposant (de manière assez sereine) que les codes de caractères des chiffres sont consécutifs :

\ifnum''#1<''0 \isanumfalse
\else\ifnum''#1>''9 \isanumfalse
     \fi
\fi

Une nouvelle fois, ce test s’imbriquerait dans un test par récurrence pour tester toute la chaine de caractères de l’argument. Toutefois, ces formes ne sont pas très satisfaisantes :

  • il est difficile d’obtenir une forme de récurrence « correcte » (elle a tendance à engloutir les espaces dans l’argument) ;

  • même dispose de mécanismes pour lire les nombres, et il serait bon de les utiliser.

L’extension cite, de Donald Arseneau, propose le test suivant pour vérifier qu’un argument est un entier strictement positif :

\def\EstPositif#1{%
  TT\fi
  \ifcat//\ifnum0<0#1 //\else A\fi
}

Ce test peut être adapté en un test pour un entier non négatif :

\def\EstNonNegatif{%
  \ifcat//\ifnum9<1#1 //\else A\fi
}

Plus généralement, il peut être adapté pour un test pour tout entier (mais cela développe la technique au-delà de ce qui est raisonnable) :

\def\SupprimeMoins#1{\ifx-#1\else#1\fi}
\def\EstEntier#1{%
  TT\fi
  \ifcat//\ifnum9<1\SupprimeMoins#1 //\else A\fi
}

Si nous ne nous soucions pas du signe, nous pouvons utiliser pour supprimer le nombre entier (signe et tout) du flux d’entrée, puis regarder ce qui reste. Le code suivant a été proposé par David Kastrup (avec une astuce suggérée par Andreas Matthias) :

\def\testnum#1{\afterassignment\testresultat\count255=0#1 \end}
\def\testresultat#1\end{\ifx\end#1\end\isanumtrue\else\isanumfalse\fi}

Dans un fil de discussion sur le même sujet, Michael Downes a proposé le code suivant. Il est basé sur le fait que \romannumeral produit un résultat vide si son argument est zéro. Malheureusement, cette technique a la fâcheuse propriété d’accepter des expressions simples comme 1+2-3 ; ceci pourrait être résolu par une construction initiale similaire à \SupprimeMoins.

\def\EstEntier#1{%
  TT\fi
  \begingroup \lccode''\-=''\0 \lccod+=''\0
    \lccode''\1=''\0 \lccode''\2=''\0 \lccode''\3=''\0
    \lccode''\4=''\0 \lccode''\5=''\0 \lccode''\6=''\0
    \lccode''\7=''\0 \lccode''\8=''\0 \lccode''\9=''\0
  \lowercase{\endgroup
    \expandafter\ifx\expandafter\delimiter
    \romannumeral0\string#1}\delimiter
}

Toutes les fonctions ci-dessus sont conçues pour être utilisées dans des tests conditionnels écrits « naturellement », par exemple :

\if\EstEntier{⟨sujet ou test⟩}%
  ⟨traitement de l'entier⟩%
\else
  ⟨traitement de ce qui n'est pas un entier⟩%
\fi

La classe memoir possède sa propre commande interne, \checkifinteger{num}, qui active la commande conditionnelle \ifinteger selon que l’argument est un entier ou pas.

Bien sûr, toutes ses suggestions seraient peu intéressantes s’il existait un moyen simple pour capter les erreurs de En imaginant une primitive de capture d’erreur \ifnoerror, on pourrait écrire :

\def\EstEntier#1{%
  TT%
  \ifnoerror
    \tempcount=#1\relax
% gestion s'il n'y a pas d'erreur
    \expandafter\iftrue
  \else
% gestion s'il y a une erreur
    \expandafter\iffalse
  \fi
}

On utiliserait ainsi le code d’analyse des nombres entiers propre à pour effectuer la vérification. Il est dommage qu’un tel mécanisme n’ait jamais été défini (et peut-être est-il impossible de programmer dans ).