Compteurs et longueurs

\setcounter et \setlength se ressemblent et se comportent à l’inverse l’une de l’autre. Exécutez-les toutes deux dans un groupe : à la fermeture, le compteur garde sa nouvelle valeur tandis que la longueur revient en arrière sans un mot — car une seule des deux définitions du noyau porte le mot \global. Cette page traite des deux registres numériques de LaTeX : les compteurs (\newcounter), qui contiennent un entier et pilotent tous les numéros de chapitre, de figure et d’équation, et les longueurs (\newlength), qui contiennent une dimension et se révèlent être des registres de ressort plutôt que de simples dimensions. En chemin : pourquoi un \label placé après \stepcounter référence discrètement le numéro d’un autre, ce en quoi \value s’expanse réellement, et comment calculer avec \dimexpr, calc et \fpeval — conversions comme 1in = 72,27pt et 1pt = 65536sp comprises.

Créer et faire avancer un compteur avec \newcounter

Un nouveau compteur naît de \newcounter{foo}. Le nom est une simple chaîne sans antislash, et la valeur juste après la création est 0. Dessous se cache un registre \count de TeX — interrogez \meaning et il répond quelque chose comme \count196. Trois commandes changent la valeur : \setcounter{foo}{3} affecte, \addtocounter{foo}{2} ajoute (un nombre négatif soustrait), et \stepcounter{foo} ajoute exactement 1. Le second argument optionnel, \newcounter{foo}[⟨parent⟩], subordonne foo à parent, si bien que chaque avancée de parent ramène foo à 0. C’est exactement le mécanisme derrière la remise à zéro des numéros de sous-section au changement de section.

latex
\newcounter{trial}            % created with value 0
\setcounter{trial}{5}         % trial = 5
\addtocounter{trial}{-2}      % trial = 3
\stepcounter{trial}           % trial = 4, and every child counter resets

\newcounter{item}[section]    % "item" restarts at 0 whenever section steps

% errors you will actually see
\setcounter{nosuch}{3}        % ! LaTeX Error: No counter 'nosuch' defined.
\newcounter{trial}            % ! LaTeX Error: Command \c@trial already defined.
\setcounter{trial}{}          % ! Missing number, treated as zero.

La formulation exacte des erreurs mérite d’être retenue. Toucher un compteur inexistant donne ! LaTeX Error: No counter 'nosuch' defined. ; exécuter \newcounter deux fois sur le même nom donne ! LaTeX Error: Command \c@trial already defined. Ce \c@trial désigné est le registre interne qui détient réellement trial — et comme le nom contient un @, l’atteindre depuis un préambule exige \makeatletter. La remise à zéro est confiée à une macro interne, \@stpelt, dont le corps met l’enfant à −1 puis lui applique \stepcounter. Avancer plutôt qu’affecter simplement 0, c’est précisément ce qui fait cascader la remise à zéro jusqu’aux compteurs petits-enfants.

\stepcounter contre \refstepcounter : quand \label désigne le numéro d’un autre

\refstepcounter{foo} effectue le même avancement et la même remise à zéro que \stepcounter, puis fait de foo le numéro auquel on peut actuellement se référer. Sans ce geste supplémentaire, \label n’a rien à quoi se raccrocher. Mesuré, le symptôme est plus retors qu’on ne l’imagine. Dans un document comportant trois sections, \stepcounter{demo}\label{bad} laisse demo à 1 — mais ce qui atterrit dans le fichier .aux est \newlabel{bad}{{3}{1}{}{}{}}, si bien que \ref{bad} imprime 3, le numéro de la section précédente. L’étiquette ne se rabat pas sur la valeur antérieure du compteur : elle s’accroche au compteur que \refstepcounter a touché en dernier, généralement un tout autre. Et il n’y a ni erreur ni avertissement. La même chose tout en tête d’un document donne \newlabel{step}{{}{1}{}{}{}}, et \ref n’imprime rien du tout.

document.tex
\newcounter{demo}
\begin{document}
\section{First}\section{Second}\section{Third}

Here: \stepcounter{demo}\label{bad} (demo is now \thedemo)

Reference: \ref{bad}
\end{document}

% .aux    -> \newlabel{bad}{{3}{1}{}{}{}}
% output  -> "Here: (demo is now 1)"  /  "Reference: 3"
% log     -> 0 errors, 0 warnings
% the fix -> \refstepcounter{demo}\label{good}   gives \ref = 1

Le mécanisme saute aux yeux dans latex.ltx. \refstepcounter appelle \stepcounter puis écrit la valeur de \thefoo dans \@currentlabel ; \label ne fait rien d’autre que recopier \@currentlabel dans le fichier .aux. \label n’enregistre donc jamais « le compteur qui le précède » mais la valeur courante d’une variable globale. LaTeX lui-même emploie \refstepcounter lorsqu’il avance section, equation ou figure : voilà pourquoi un \label posé sur un titre ou une équation donne le bon numéro. La règle pratique suit aussitôt : \stepcounter quand un nombre ne doit avancer qu’en interne, \refstepcounter quand on crée une chose numérotée que \label doit pouvoir désigner. Si un environnement de théorème maison ou un numéro d’exercice refuse de se référencer correctement, c’est ici qu’il faut regarder d’abord.

\value : extraire un compteur comme nombre calculable

\value{foo} s’expanse exactement en le registre \count qui détient le compteur — la définition du noyau tient littéralement dans la ligne \csname c@#1\endcsname. Voilà pourquoi il fonctionne dans la position de valeur de \setcounter et \addtocounter, dans une comparaison \ifnum, à l’intérieur d’une expression \numexprpartout où LaTeX attend un entier. Retournez la proposition et le corollaire est que \value n’est pas une commande d’affichage. Poser un numéro dans le texte relève de \arabic ou \thefoo, section suivante ; \value ne rend jamais que le nombre brut destiné au calcul. Gardez les deux rôles séparés et les confusions « mon numéro n’apparaît pas » ou « le chiffre est sorti deux fois » cessent.

latex
\setcounter{bar}{\value{foo}}            % copy foo into bar
\addtocounter{bar}{\value{foo}}          % bar = bar + foo

\ifnum\value{page}>10 \dots\fi           % compare
\setcounter{bar}{\numexpr\value{foo}*2+1\relax}   % arithmetic, no package

% \value is NOT for printing:
\value{foo}          % wrong -- prints nothing useful
\arabic{foo}         % right
\thefoo              % right, and honours any format you defined

Changer l’apparence d’un numéro : de \arabic à \thefoo

Six commandes transforment la valeur d’un compteur en caractères imprimés, chacune prenant un nom de compteur en argument. Mais le numéro que l’on voit réellement est produit par \thefoo, une macro dédiée que chaque compteur reçoit automatiquement : section a \thesection, figure a \thefigure, et la redéfinir avec \renewcommand change d’un coup tout le format du numéro. \alph et \Alph se projettent sur les 26 lettres. La frontière est plus fine qu’il n’y paraît : 0 n’imprime rien du tout et ne déclenche aucune erreur, tandis que les valeurs négatives et tout ce qui dépasse 26 donnent ! LaTeX Error: Counter too large. \fnsymbol a lui aussi pour plage valide 1–9, et 10 déclenche le même ! LaTeX Error: Counter too large. Une correction s’impose face à des conseils plus anciens : dans le LaTeX2e livré avec TeX Live 2024, \@fnsymbol est défini avec \TextOrMath dans chaque cas, de sorte que \fnsymbol fonctionne directement dans le texte courant — l’enveloppe $\fnsymbol{footnote}$ n’est plus nécessaire (pour les notes, \thefootnote reste la voie habituelle).

CommandeSortiePlage / précautions
\arabic1, 2, 3 …Par défaut ; les valeurs négatives s’impriment aussi
\romani, ii, iii …En dessous de 1, rien ne s’imprime
\RomanI, II, III …En dessous de 1, rien ne s’imprime
\alpha, b, c …1–26. 0 n’imprime rien ; négatifs et 27 et plus, Counter too large
\AlphA, B, C …1–26. 0 n’imprime rien ; négatifs et 27 et plus, Counter too large
\fnsymbol∗ † ‡ § ¶ ‖ ∗∗ †† ‡‡1–9 ; 10 donne Counter too large. Fonctionne dans le texte, sans enveloppe mathématique
\thefooLe format officiel du compteurC’est lui qui s’imprime ; à modifier par \renewcommand
latex
\renewcommand{\thesection}{\Roman{section}}            % 1, 2, 3 -> I, II, III
\renewcommand{\thefigure}{\thesection.\arabic{figure}}  % figure 2.3
\renewcommand{\thefootnote}{\fnsymbol{footnote}}        % footnotes as * dagger ...

% appendix figures as A.1, A.2, ...
\appendix
\renewcommand{\thefigure}{\Alph{section}.\arabic{figure}}

Comme dans le second exemple, insérer l’affichage d’un autre compteur dans une définition \the… produit des numéros composés du type « 2.3 ». Cela révèle le principe de conception : l’apparence d’un numéro (la définition de \thefigure) et le lien qui décide de sa remise à zéro (l’argument parent de \newcounter) se règlent tout à fait séparément. Écrire seulement \renewcommand{\thefigure}{\thesection.\arabic{figure}} ne fait donc pas repartir les numéros de figure à chaque section : il faut aussi établir la relation parent-enfant, par exemple avec \counterwithin{figure}{section}.

Les compteurs standard, et secnumdepth / tocdepth comme valeurs de configuration

LaTeX prédéfinit toute une famille de compteurs et les pilote automatiquement à mesure qu’il lit le document. On peut les toucher aussi avec \setcounter : c’est ainsi qu’on renumérote des chapitres en cours de route ou qu’on fait afficher A.1 pour les figures d’une annexe. Créez un seul compteur dans un article vide et son registre interne atterrit vers \count196 — plusieurs dizaines de compteurs standard tournent donc déjà. TeX classique n’autorisait que 256 registres de chaque sorte ; e-TeX a relevé ce plafond, si bien qu’allouer 400 compteurs d’affilée ne suscite plus la moindre protestation (le dernier s’est posé sur \count662).

  • Sectionnement : part, chapter, section, subsection, subsubsection, paragraph, subparagraph (ceux qui existent dépendent de la classe)
  • Flottants et équations : figure, table, equation
  • Notes de bas de page : footnote, mpfootnote (notes à l’intérieur d’une minipage)
  • Listes : enumi, enumii, enumiii, enumiv (les quatre niveaux d’imbrication d’enumerate)
  • Page : page
  • Valeurs de configuration : secnumdepth (jusqu’à quelle profondeur on numérote) et tocdepth (jusqu’à quelle profondeur les entrées entrent dans la table des matières)

Les deux derniers sont d’une autre espèce : ce sont des molettes entières qui gouvernent la « profondeur » de la numérotation et de la table des matières, et non des décomptes. Chaque commande de sectionnement possède un numéro de niveau (section vaut 1, subsection vaut 2, …) et un niveau n’est numéroté que si ce numéro est inférieur ou égal à secnumdepth. La valeur par défaut tourne autour de 2, si bien que \setcounter{secnumdepth}{1} supprime les numéros sous le niveau section et \setcounter{tocdepth}{1} réduit la table des matières aux sections. Ces deux-là le montrent bien : un compteur n’est pas qu’un décompte, c’est aussi une valeur de configuration qui pilote la composition.

\newlength alloue un registre skip, pas un dimen

Sous \newlength{\mylen} se cache \newskip. La définition du noyau tient dans la seule ligne \@ifdefinable#1{\newskip#1}, et \meaning le confirme en répondant quelque chose comme \skip50. Cela compte, car un registre skip peut porter de l’étirement et du rétrécissement : voilà pourquoi \setlength{\parskip}{1em plus 2pt minus 1pt} est une affectation parfaitement légale. Mesuré, \the\parskip renvoie alors 10.00002pt plus 2.0pt minus 1.0pt, les trois composantes étant conservées. Contrairement à un compteur, l’argument est un nom de commande avec antislash (\mylen), et la valeur initiale est 0pt. Déclarer deux fois le même nom donne ! LaTeX Error: Command \mylen already defined. Et comme \a, \b, \c, \d et \t existent déjà comme commandes d’accent, c’est exactement pourquoi \newlength{\a} ne passe pas.

document.tex
\newlength{\gap}
\setlength{\gap}{20pt plus 10pt minus 5pt}   % a rubber length in a register

% watch the stretch and shrink actually being used
\showboxbreadth=5 \showboxdepth=5
\setbox0=\hbox to 70pt{\rule{20pt}{2pt}\hspace{\gap}\rule{20pt}{2pt}}\showbox0
\setbox1=\hbox to 55pt{\rule{20pt}{2pt}\hspace{\gap}\rule{20pt}{2pt}}\showbox1

% log:  \hbox(2.0+0.0)x70.0, glue set 1.0      <- all 10pt of "plus" used
%       \hbox(2.0+0.0)x55.0, glue set - 1.0    <- all 5pt of "minus" used

\showbox permet de lire l’étirement et le rétrécissement directement dans le journal. Forcez une boîte de largeur naturelle 60pt à 70pt et vous obtenez glue set 1.0 — tout le plus 10pt a été consommé. Comprimez-la à 55pt et vous obtenez glue set - 1.0, la totalité du minus 5pt étant épuisée. Ce chiffre glue set est un allié précieux pour enquêter sur une mise en page : une valeur de 2 ou 3 signale une boîte étirée bien au-delà du confortable. La façon dont plus et minus agissent à l’échelle d’une page entière — l’histoire de \fill et de \flushbottom — est traitée en détail sur la page consacrée aux longueurs et aux espaces.

Pourquoi \setcounter est global et \setlength local

La raison tient à un seul mot dans les définitions de latex.ltx. \setcounter et \addtocounter portent \global ; \setlength et \addtolength non. Cette unique différence engendre des comportements entièrement distincts. Modifiez un compteur et une longueur dans le même groupe : à la fermeture, le compteur garde sa nouvelle valeur tandis que la longueur est revenue à l’ancienne — mesuré, une longueur portée de 1cm à 9cm est revenue à 28.45274pt (soit 1cm) à l’instant même où le groupe s’est refermé. L’asymétrie se justifie : un numéro de chapitre est un état du document qui ne doit pas s’oublier à la fin d’un environnement, alors qu’un ajustement temporaire de marge n’est censé valoir que sur sa propre portée.

document.tex
\newcounter{cnt}\newlength{\len}
\setcounter{cnt}{1}\setlength{\len}{1cm}

{\setcounter{cnt}{99}\setlength{\len}{9cm}%
  inside the group: cnt=\thecnt\ len=\the\len}      % 99 / 256.0748pt

after the group:  cnt=\thecnt\ len=\the\len         % 99 / 28.45274pt

% latex.ltx:
%   \def\setcounter#1#2{... {\global\csname c@#1\endcsname#2\relax}}
%   \def\setlength#1#2{#1 #2\relax}          <- no \global
% to make a length assignment survive a group:
\global\setlength{\len}{9cm}

Cette différence prend de l’importance dès qu’on écrit ses propres outils avec \newcommand ou \newenvironment. Un \setlength{\parindent}{0pt} placé dans un environnement se rétablit tout seul au \end — aucun nettoyage nécessaire. À l’inverse, si un décompte effectué dans l’environnement doit survivre à l’extérieur, il faut \setcounter (ou un \global explicite). Les deux bogues classiques — « mon réglage a disparu à la fin de l’environnement » et « ma modification de marge a fui hors de l’environnement » — s’expliquent tous deux par la présence ou l’absence de ce seul mot. Le fait qu’un environnement forme un groupe est traité sur la page consacrée à l’écriture d’environnements.

Calculer avec des longueurs : \dimexpr, calc, \fpeval et les conversions d’unités

De l’addition, de la multiplication et de la division, celles qui s’écrivent sans aucun package sont les primitives e-TeX \numexpr et \dimexpr. Dans un article par défaut, \the\dimexpr\textwidth/3\relax renvoie 115.0pt et \the\dimexpr 2cm+4pt\relax donne 60.9055pt. Une réserve : la division de \numexpr arrondit, elle ne tronque pas — \the\numexpr 7/2\relax vaut 4 et -7/2 vaut −4. Pour une syntaxe plus proche de LaTeX, chargez le package calc, qui autorise une expression comme \setlength{\x}{(\textwidth - 2em)/2} directement dans \setlength (mesuré : 162.49998pt). Seule la mise à l’échelle par un facteur réel s’écrit autrement : on intercale \real{}, comme dans \widthof{Total:} * \real{0.5}.

ÉcritureCe qu’il fautRésultat mesuré sous TeX Live 2024
\dimexprrien (primitive e-TeX)\the\dimexpr\textwidth/3\relax donne 115.0pt
\numexprrien (primitive e-TeX)7/2 donne 4, -7/2 donne -4 — arrondi, pas troncature
calc\usepackage{calc}(\textwidth - 2em)/2 donne 162.49998pt
\real\usepackage{calc}\widthof{Total:} * \real{0.5} donne 12.91669pt
\fpevalle noyau LaTeX (xfp inutile)\fpeval{345/7}pt donne 49.28572pt ; \fpeval{sqrt(2)} donne 1.414213562373095

Savoir ce que sont réellement les unités évite les accidents quand on calcule. La référence de TeX est pt, le point TeX, avec 1in = 72,27pt. \the\dimexpr 1in\relax répond 72.26999pt — pas exactement 72,27, car TeX conserve toute dimension comme un nombre entier de sp, le point mis à l’échelle, avec 1pt = 65536sp ; \the\dimexpr 65536sp\relax vaut exactement 1.0pt. Le bp de la famille PostScript vaut 1/72 in, donc 1bp fait 1.00374pt et 72bp revient à 72.26999pt, soit précisément 1in. Autre point : em est évalué au moment de l’affectation puis figé. Écrivez \setlength{\x}{1em} dans le préambule et 10.00002pt s’y loge ; entrez ensuite dans un groupe \Large et \x vaut toujours 10.00002pt, alors qu’un 1em mesuré à cet endroit donne 14.09984pt. Le catalogue des unités lui-même, ainsi que \hspace/\vspace, relèvent de la page sur les longueurs et les espaces.

Mesurer ce que l’on compose : \settowidth, \settoheight, \settodepth

L’opération de longueur la plus puissante consiste à composer quelque chose, en mesurer le résultat et le ranger dans une longueur. \settowidth{\mylen}{text} compose en interne le texte donné et affecte sa largeur à \mylen. De même, \settoheight mesure la hauteur au-dessus de la ligne de base et \settodepth la profondeur en dessous. Les valeurs mesurées rendent la distinction éclatante : Total: fait 25.83339pt de large, 6.94444pt de haut et 0.0pt de profondeur ; xyz fait 4.30554pt de haut (la hauteur d’x) et 1.94444pt de profondeur ; gjpqy fait 6.67859pt de haut et 1.94444pt de profondeur. On voit la profondeur apparaître à l’instant même où un jambage descendant entre dans le texte.

document.tex
\usepackage{calc}
\newlength{\labelw}

\settowidth{\labelw}{Total:}      % 25.83339pt -- the real typeset width
\noindent\makebox[\labelw][l]{Total:}42\par
\rule{\labelw}{0.4pt}             % a rule exactly as wide as the label

% the same idea as a value inside an expression (needs calc):
\setlength{\labelw}{\widthof{Total:} + 1em}
\the\labelw                       % print the value to check it

En chargeant calc, la même idée devient disponible dans les expressions : \widthof{text}, \heightof et \depthof s’écrivent directement comme valeurs. La différence tient au rôle : \settowidth est une commande qui affecte le résultat à une longueur, tandis que \widthof est une valeur que l’on glisse dans une expression. Et retenez le symptôme d’un calc oublié : \setlength{\mylen}{\textwidth/3} ne déclenche pas la moindre erreur. \mylen reçoit simplement la valeur de \textwidth (345.0pt dans un article par défaut) et le /3 restant est composé dans le document comme du texte. On s’en aperçoit quand « /3 » apparaît dans le PDF — donc pour diviser, chargez calc ou employez \dimexpr, qui ne demande aucun package.

Tout assembler : un numéro d’exercice référencable fait maison

Pour finir, voici toutes les pièces réunies. Créer un compteur question, fixer sa forme imprimée en « Q1. » via \thequestion, mesurer la largeur de l’étiquette avec \settowidth pour que les numéros s’alignent, et faire avancer le compteur avec \refstepcounter afin que \label et \ref fonctionnent — avec ces quatre points, on obtient un dispositif numéroté utilisable pour de bon. Comme le format tient en un seul endroit, la définition de \thequestion, remplacer plus tard « Q » par autre chose ne coûte qu’une ligne.

document.tex
\documentclass{article}
\newcounter{question}
\newlength{\qlabel}
\renewcommand{\thequestion}{Q\arabic{question}}

\newcommand{\question}{%
  \refstepcounter{question}%        <- \refstepcounter, so \label works
  \settowidth{\qlabel}{\textbf{\thequestion.}}%
  \par\noindent\makebox[\qlabel][l]{\textbf{\thequestion.}}\hspace{0.5em}}

\begin{document}
\question The first question.\label{q:first}
\question The second question.

See question~\ref{q:first}.        % -> "See question Q1."
\end{document}

Ce petit exemple condense l’art d’employer compteurs et longueurs comme outils de programmation : n’écrivez pas les valeurs en dur ; nommez-les et gérez-les en un seul endroit. Rangez les formats de numéro dans \the… et les dimensions dans un nom déclaré par \newlength ; quand la spécification change, vous n’éditez qu’un point. Construisez les dimensions à partir de longueurs existantes chaque fois que possible — 0.8\textwidth ou 2\baselineskip survivent à un changement de format de papier ou de marges. Écrivez 8cm en dur et, dès cet instant, la valeur a rendez-vous avec la casse au prochain remaniement de mise en page.