Contadores y longitudes

\setcounter y \setlength se parecen y se comportan justo al revés. Ejecute ambos dentro de un grupo y, al cerrarse, el contador conserva su valor nuevo mientras la longitud vuelve atrás en silencio, porque solo una de las dos definiciones del núcleo lleva la palabra \global. Esta página trata los dos registros numéricos de LaTeX: los contadores (\newcounter), que guardan un entero y mueven todos los números de capítulo, figura y ecuación, y las longitudes (\newlength), que guardan una dimensión y resultan ser registros de goma más que dimensiones puras. Por el camino: por qué un \label colocado tras \stepcounter referencia calladamente el número de otro, en qué se expande realmente \value, y cómo calcular con \dimexpr, calc y \fpeval, conversiones como 1in = 72,27pt y 1pt = 65536sp incluidas.

Crear y mover un contador con \newcounter

Un contador nuevo nace de \newcounter{foo}. El nombre es una cadena sin barra invertida, y el valor justo tras crearlo es 0. Por debajo hay un registro \count de TeX: pregunte con \meaning y responderá algo como \count196. Tres órdenes cambian el valor: \setcounter{foo}{3} asigna, \addtocounter{foo}{2} suma (un número negativo resta) y \stepcounter{foo} suma exactamente 1. El segundo argumento opcional, \newcounter{foo}[⟨parent⟩], subordina foo a parent, de modo que cada vez que parent avanza, foo cae a 0. Ese es justamente el mecanismo tras el reinicio de los números de subsección al cambiar de sección.

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.

Vale la pena recordar el texto exacto de los errores. Tocar un contador inexistente da ! LaTeX Error: No counter 'nosuch' defined.; ejecutar \newcounter dos veces con el mismo nombre da ! LaTeX Error: Command \c@trial already defined. Ese \c@trial que se nombra es el registro interno que realmente guarda trial, y como el nombre contiene @, alcanzarlo desde un preámbulo exige \makeatletter. Del reinicio se encarga una macro interna, \@stpelt, cuyo cuerpo pone el hijo a −1 y luego le aplica \stepcounter. Avanzar en lugar de asignar simplemente 0 es justo lo que hace que el reinicio se propague en cascada hasta los contadores nietos.

\stepcounter frente a \refstepcounter: cuando \label apunta al número de otro

\refstepcounter{foo} hace el mismo avance y el mismo reinicio que \stepcounter, y además convierte foo en el número al que se puede hacer referencia en ese momento. Sin ese movimiento extra, \label no tiene a qué agarrarse. Medido, el síntoma es más retorcido de lo que uno supondría. En un documento con tres secciones, \stepcounter{demo}\label{bad} deja demo en 1, pero lo que aterriza en el archivo .aux es \newlabel{bad}{{3}{1}{}{}{}}, de modo que \ref{bad} imprime 3, el número de la sección anterior. La etiqueta no retrocede al valor previo del contador: se aferra al contador que \refstepcounter tocó por última vez, normalmente uno completamente distinto. Y no hay error ni aviso. Lo mismo al principio del documento produce \newlabel{step}{{}{1}{}{}{}}, y \ref no imprime nada.

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

El mecanismo salta a la vista en latex.ltx. \refstepcounter llama a \stepcounter y después escribe el valor de \thefoo en \@currentlabel; \label no hace más que copiar \@currentlabel al archivo .aux. Así que \label nunca guarda «el contador que tiene justo delante», sino el valor actual de una variable global. El propio LaTeX usa \refstepcounter al avanzar section, equation o figure, y por eso un \label sobre un encabezado o una ecuación da el número correcto. La regla práctica se sigue de inmediato: \stepcounter cuando solo hace falta que un número avance internamente, y \refstepcounter cuando se crea una cosa numerada a la que \label deba poder referirse. Si un entorno de teorema casero o un número de ejercicio se niega a referenciarse bien, mire aquí primero.

\value: extraer un contador como número con el que calcular

\value{foo} se expande exactamente al registro \count que guarda el contador: la definición del núcleo es literalmente la línea \csname c@#1\endcsname. Por eso funciona en el hueco de valor de \setcounter y \addtocounter, en una comparación \ifnum, dentro de una expresión \numexpr, en cualquier sitio donde LaTeX espere un entero. Dele la vuelta y el corolario es que \value no es una orden de impresión. Poner un número en el texto es tarea de \arabic o \thefoo, en la sección siguiente; \value solo devuelve el número en bruto para la aritmética. Mantenga separados los dos papeles y desaparecen las confusiones de «mi número no aparece» y «el dígito salió dos veces».

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

Cambiar el aspecto de un número: de \arabic a \thefoo

Seis órdenes convierten el valor de un contador en caracteres impresos, y cada una toma un nombre de contador como argumento. Pero el número que uno ve realmente lo emite \thefoo, una macro dedicada que cada contador recibe automáticamente: section tiene \thesection, figure tiene \thefigure, y redefinirla con \renewcommand cambia de un golpe todo el formato del número. \alph y \Alph se proyectan sobre las 26 letras. La frontera es más fina de lo que parece: el 0 no imprime nada y no produce error alguno, mientras que los negativos y cualquier valor por encima de 26 dan ! LaTeX Error: Counter too large. \fnsymbol también tiene un rango válido de 1–9, y un 10 produce el mismo ! LaTeX Error: Counter too large. Conviene corregir aquí un consejo antiguo: en el LaTeX2e que trae TeX Live 2024, \@fnsymbol está definido con \TextOrMath en todos los casos, de modo que \fnsymbol funciona directamente en el texto corriente; la envoltura $\fnsymbol{footnote}$ ya no hace falta (para las notas al pie, \thefootnote sigue siendo la vía habitual).

ComandoSalidaRango / advertencias
\arabic1, 2, 3 …Por defecto; también imprime valores negativos
\romani, ii, iii …Por debajo de 1 no imprime nada
\RomanI, II, III …Por debajo de 1 no imprime nada
\alpha, b, c …1–26. El 0 no imprime nada; negativos y 27 o más, Counter too large
\AlphA, B, C …1–26. El 0 no imprime nada; negativos y 27 o más, Counter too large
\fnsymbol∗ † ‡ § ¶ ‖ ∗∗ †† ‡‡1–9; 10 da Counter too large. Funciona en texto corriente, sin envoltura matemática
\thefooEl formato oficial del contadorEs lo que se imprime; se cambia con \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}}

Como en el segundo ejemplo, entretejer la visualización de otro contador en una definición \the… produce números compuestos del tipo «2.3». Lo que eso revela es el principio de diseño: la apariencia de un número (la definición de \thefigure) y el vínculo que decide cuándo se reinicia (el argumento padre de \newcounter) se fijan de forma totalmente independiente. Escribir solo \renewcommand{\thefigure}{\thesection.\arabic{figure}} no hace, pues, que los números de figura reinicien en cada sección: para eso hay que establecer además la relación padre-hijo, por ejemplo con \counterwithin{figure}{section}.

Los contadores estándar y secnumdepth / tocdepth como valores de configuración

LaTeX predefine toda una familia de contadores y los mueve automáticamente mientras lee el documento. También se pueden tocar con \setcounter: así se renumeran capítulos a mitad de camino o se logra que las figuras de un apéndice se lean A.1. Cree un solo contador en un article vacío y su registro interno aterriza hacia \count196, señal de que ya hay varias docenas de contadores estándar en marcha. El TeX clásico solo permitía 256 registros de cada tipo; e-TeX elevó ese techo, así que asignar 400 contadores seguidos hoy no provoca queja alguna (el último cayó en \count662).

  • Seccionado: part, chapter, section, subsection, subsubsection, paragraph, subparagraph (cuáles existen depende de la clase)
  • Flotantes y ecuaciones: figure, table, equation
  • Notas al pie: footnote, mpfootnote (notas dentro de un minipage)
  • Listas: enumi, enumii, enumiii, enumiv (los cuatro niveles de anidamiento de enumerate)
  • Página: page
  • Valores de configuración: secnumdepth (hasta qué profundidad se numera) y tocdepth (hasta qué profundidad llegan las entradas al índice)

Los dos últimos son de otra especie: diales enteros que gobiernan la «profundidad» de la numeración y del índice, no recuentos de nada. Cada orden de seccionado tiene un número de nivel (section es 1, subsection es 2, …) y un nivel se numera solo mientras ese número sea menor o igual que secnumdepth. El valor por defecto ronda 2, así que \setcounter{secnumdepth}{1} elimina los números por debajo del nivel de sección y \setcounter{tocdepth}{1} recorta el índice hasta las secciones. Estos dos lo muestran bien: un contador no es solo un recuento, sino también un valor de configuración que dirige la composición.

\newlength asigna un registro skip, no un dimen

Bajo \newlength{\mylen} se esconde \newskip. La definición del núcleo es la única línea \@ifdefinable#1{\newskip#1}, y \meaning lo confirma respondiendo algo como \skip50. Eso importa, porque un registro skip puede llevar estiramiento y encogimiento: por eso \setlength{\parskip}{1em plus 2pt minus 1pt} es una asignación perfectamente legal. Medido, \the\parskip devuelve entonces 10.00002pt plus 2.0pt minus 1.0pt, con las tres componentes conservadas. A diferencia de un contador, el argumento es un nombre de orden con barra invertida (\mylen) y el valor inicial es 0pt. Declarar dos veces el mismo nombre da ! LaTeX Error: Command \mylen already defined. Y como \a, \b, \c, \d y \t ya existen como órdenes de acento, esa es justamente la razón de que \newlength{\a} no pase.

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 permite leer el estiramiento y el encogimiento directamente en el registro. Fuerce una caja de anchura natural 60pt hasta 70pt y obtendrá glue set 1.0: se consumió todo el plus 10pt. Compríma­la a 55pt y obtendrá glue set - 1.0, agotado todo el minus 5pt. Esa cifra glue set es un aliado potente al investigar la maquetación: un valor de 2 o 3 indica que la caja se está estirando mucho más allá de lo cómodo. Cómo actúan plus y minus a escala de página entera —la historia de \fill y \flushbottom— se trata con detalle en la página de longitudes y espaciado.

Por qué \setcounter es global y \setlength es local

La razón es una sola palabra en las definiciones de latex.ltx. \setcounter y \addtocounter llevan \global; \setlength y \addtolength no. Esa única diferencia genera comportamientos completamente distintos. Cambie un contador y una longitud dentro del mismo grupo y, al cerrarse, el contador conserva su valor nuevo mientras la longitud ha vuelto al viejo: medido, una longitud llevada de 1cm a 9cm volvió a 28.45274pt (es decir, 1cm) en el instante en que terminó el grupo. La asimetría tiene sentido: un número de capítulo es estado del documento que no debe olvidarse al acabar un entorno, mientras que un retoque temporal de un margen solo debería regir en su propio tramo.

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}

Esta diferencia empieza a importar en cuanto uno escribe sus propias herramientas con \newcommand o \newenvironment. Un \setlength{\parindent}{0pt} dentro de un entorno se revierte solo en el \end, sin necesidad de limpieza. A la inversa, si un recuento hecho dentro del entorno debe sobrevivir fuera, hace falta \setcounter (o un \global explícito). Los dos fallos clásicos —«mi ajuste desapareció al acabar el entorno» y «mi cambio de margen se escapó fuera del entorno»— se explican ambos por la presencia o ausencia de esa única palabra. Que un entorno forme un grupo de por sí se trata en la página sobre escribir entornos.

Calcular con longitudes: \dimexpr, calc, \fpeval y las conversiones de unidades

De entre la suma, la multiplicación y la división, las que se escriben sin paquete alguno son las primitivas de e-TeX \numexpr y \dimexpr. En un article por defecto, \the\dimexpr\textwidth/3\relax devuelve 115.0pt y \the\dimexpr 2cm+4pt\relax da 60.9055pt. Una advertencia: la división de \numexpr redondea, no trunca: \the\numexpr 7/2\relax es 4 y -7/2 es −4. Para una sintaxis de sabor más latexiano, cargue el paquete calc, que permite escribir una expresión como \setlength{\x}{(\textwidth - 2em)/2} directamente dentro de \setlength (medido: 162.49998pt). Solo el escalado por un factor real se escribe de otro modo: se intercala \real{}, como en \widthof{Total:} * \real{0.5}.

Se escribeQué necesitaResultado medido bajo TeX Live 2024
\dimexprnada (primitiva de e-TeX)\the\dimexpr\textwidth/3\relax da 115.0pt
\numexprnada (primitiva de e-TeX)7/2 da 4, -7/2 da -4: redondeo, no truncamiento
calc\usepackage{calc}(\textwidth - 2em)/2 da 162.49998pt
\real\usepackage{calc}\widthof{Total:} * \real{0.5} da 12.91669pt
\fpevalel núcleo de LaTeX (xfp no hace falta)\fpeval{345/7}pt da 49.28572pt; \fpeval{sqrt(2)} da 1.414213562373095

Saber qué son realmente las unidades evita accidentes al calcular. La referencia de TeX es pt, el punto TeX, con 1in = 72,27pt. \the\dimexpr 1in\relax responde 72.26999pt, no exactamente 72,27, porque TeX guarda toda dimensión como un número entero de sp, el punto escalado, con 1pt = 65536sp; \the\dimexpr 65536sp\relax vale exactamente 1.0pt. El bp de la familia PostScript vale 1/72 in, así que 1bp son 1.00374pt y 72bp vuelve a 72.26999pt, justo 1in. Y una cosa más: em se evalúa en el momento de la asignación y queda congelado. Escriba \setlength{\x}{1em} en el preámbulo y entrará 10.00002pt; entre luego en un grupo \Large y \x seguirá siendo 10.00002pt, mientras que un 1em medido allí mismo da 14.09984pt. El catálogo de unidades en sí, y \hspace/\vspace, corresponden a la página de longitudes y espaciado.

Medir lo compuesto: \settowidth, \settoheight, \settodepth

La operación de longitud más potente consiste en componer algo, medir el resultado y guardarlo en una longitud. \settowidth{\mylen}{text} compone internamente el texto dado y asigna su anchura a \mylen. Del mismo modo, \settoheight mide la altura por encima de la línea base y \settodepth la profundidad por debajo. Los valores medidos hacen vívida la distinción: Total: mide 25.83339pt de ancho, 6.94444pt de alto y 0.0pt de profundidad; xyz mide 4.30554pt de alto (la altura de la x) y 1.94444pt de profundidad; gjpqy mide 6.67859pt de alto y 1.94444pt de profundidad. Se ve aparecer la profundidad en el instante mismo en que un descendente entra en el texto.

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

Al cargar calc, la misma idea queda disponible dentro de expresiones: \widthof{text}, \heightof y \depthof se escriben directamente como valores. La diferencia es de papel: \settowidth es una orden que asigna el resultado a una longitud, mientras que \widthof es un valor que se inserta en una expresión. Y recuerde el síntoma de olvidar calc: \setlength{\mylen}{\textwidth/3} no produce ni un solo error. \mylen recibe simplemente el valor de \textwidth (345.0pt en un article por defecto) y el /3 sobrante se compone en el documento como texto. Uno se entera cuando aparece «/3» en el PDF, así que para dividir cargue calc o use \dimexpr, que no necesita paquete alguno.

Todo junto: un número de ejercicio propio al que se pueda referenciar

Por último, todas las piezas en un mismo lugar. Cree un contador question, fije su forma impresa como «Q1.» definiendo \thequestion, mida el ancho de la etiqueta con \settowidth para que los números se alineen, y avance el contador con \refstepcounter para que \label y \ref funcionen: con esos cuatro puntos se obtiene un artefacto numerado apto para el uso real. Como el formato vive en un único sitio, la definición de \thequestion, cambiar más adelante la «Q» por otra cosa es una edición de una línea.

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}

Ese pequeño ejemplo condensa el truco de usar contadores y longitudes como herramientas de programación: no escriba los valores a pelo; póngales nombre y gestiónelos en un solo sitio. Guarde los formatos de número en \the… y las dimensiones en un nombre declarado con \newlength; cuando cambie la especificación, editará un único punto. Construya las dimensiones a partir de longitudes existentes siempre que pueda: 0.8\textwidth o 2\baselineskip sobreviven a un cambio de tamaño de papel o de márgenes. Escriba 8cm a pelo y, desde ese instante, el valor tiene cita con la rotura en el siguiente retoque de maquetación.