3

I have learned at my own expense to be careful with scratch counters. However, I recently encountered a situation I cannot understand. Consider the following code

\documentclass{article}

% print the argument four times on numbered lines
\newcommand{\foo}[1]{1 #1\par 2 #1 \par 3 #1 \par 4 #1}

\makeatletter
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}}
\makeatother

\begin{document}
\foo{\baz[2]}
\end{document}

Instead of giving the desired output

1 BlaBla

2 BlaBla

3 BlaBla

4 BlaBla

the second and fourth line have only the number, but no "BlaBla".

I know at least two (possible) problems with the code given above.

1) A space or a \relax should come after the counter assignment \count@=#1, thus stopping TeX from expanding too far. (I know for sure of similar problems with \ifnum.)

2) Scratch registers like \count@ may be dangerous and should be used preferably within a group.

The solution should thus be to replace the definition of \baz by

\newcommand*{\baz}[1][1]{\begingroup\count@=#1\relax\@whilenum\count@>0\do{Bla\advance\count@\m@ne}\endgroup}

This actually gives the expected result. What, however, puzzles me is that both

\newcommand*{\baz}[1][1]{\begingroup\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}\endgroup}

(grouped but without \relax) and

\newcommand*{\baz}[1][1]{\count@=#1\relax\@whilenum\count@>0\do{Bla\advance\count@\m@ne}}

(\relaxed but not grouped) also work!

I'd appreciate any help to understand what is going on...

  • No answer yet, but if you use {1 {#1} \par 2 {#2} ...} it will work without \relax etc. I suppose that \count@ is used otherwise. For example, \newif uses \count@ as well – user31729 Oct 13 '15 at 12:39
  • There is a space missing after the #1: \count@=#1 and so tex is expanding the \@whilenum while trying to get the number (as one can see I listened to @egreg talk ...). – Ulrike Fischer Oct 13 '15 at 12:44
  • @UlrikeFischer: Oh, you were quicker... will you answer the question? – user31729 Oct 13 '15 at 12:45
  • @UlrikeFischer I have thought of that; I have addressed that in my point 1). But I don' t understand why this should fail only in the second and fourth line. Plus, I am still asking myself: Why is it working without space but within a group? – campa Oct 13 '15 at 12:47
  • @UlrikeFischer Missing required space syndrome ;-) – egreg Oct 13 '15 at 12:49
1

Let's see how \@whilenum is defined:

% latex.ltx, line 1089:
\long\def\@whilenum#1\do #2{\ifnum #1\relax #2\relax\@iwhilenum{#1\relax
     #2\relax}\fi}

The second call of \baz[2] does

\count@=2\@whilenum\count@>0\do{Bla\advance\count@\m@ne}\par

(where the \par comes from the definition of \foo) that so becomes

\count@=2\ifnum\count@>0\relax Bla\advance\count@\m@ne\relax
\@iwhilenum{\count@>0\relax Bla\advance\count@\m@ne\relax}\fi\par

but \count@ is still 0 from the first call. Note that the initial assignment to \count@ has not yet been performed, because TeX expands tokens looking for further digits when doing an assignment to a count register: here the expandable token is \ifnum. Now the \ifnum test returns false, so we're left with

\count@=2\fi\par

and \fi is again expanded in search for further digits. Now \par is unexpandable (provided it has the primitive meaning) and so the assignment is performed and nothing is printed because all tokens containing Bla have been removed. However, the third call to \baz[2] will start with \count@ having the value 2, and this means that the \ifnum test returns true.

You can test on the following document

\documentclass{article}

% print the argument four times on numbered lines
\newcommand{\foo}[1]{1 #1\par 2 #1 \par 3 #1 \par 4 #1}

\makeatletter
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}}
\makeatother

\begin{document}

\makeatletter\the\count@ \count@=0 \makeatother

\foo{\baz[2]}
\end{document}

that shows how \count@ happens to hold the value 92, but setting it to 0 before doing \foo{\baz[2]} makes the first call to be blank and the second one to print “BlaBla”.

enter image description here

This is also why surrounding the code with \begingroup and \endgroup appears to work, but again it's by pure luck that the starting value of \count@ is 92. If it's 0, the behavior is even worse: no “Bla” at all is printed.

\documentclass{article}

% print the argument four times on numbered lines
\newcommand{\foo}[1]{1 #1\par 2 #1 \par 3 #1 \par 4 #1}

\makeatletter
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{%
  \begingroup
  \count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}%
  \endgroup
}
\makeatother

\begin{document}

\makeatletter\the\count@ \count@=0 \makeatother

\foo{\baz[2]}
\end{document}

The reason is always the same: the \ifnum test is performed when a new value to \count@ has not yet been assigned, so the previous one (92 or 0) is used for \ifnum.

enter image description here

Removing \count@=0 will produce four “BlaBla” lines.

Of course, the correct code is

% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{%
  \count@=#1 \@whilenum\count@>0\do{Bla\advance\count@\m@ne}%
}

or with \relax instead of a space. The space will end the search for further digits and it will be gobbled by TeX rule.

Always leave a blank space (or \relax) after a numeric constant.

5

There is a space (or a \relax) missing after the #1: It should be \count@=#1 \@whilenum.... Due to the missing space tex is expanding the \@whilenum while trying to get the number.

As the \@whilenum is using \count@ this also means that the end value of the previous loop affects the next loop. Grouping avoids this side effect in your document. But is is not general solution. See e.g. this:

\documentclass{article}

% print the argument four times on numbered lines
\newcommand{\foo}[1]{1 #1\par 2 #1 \par 3 #1 \par 4 #1}

\makeatletter
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{\begingroup\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}\endgroup}
\count@=0
\makeatother

\begin{document}

\foo{\baz[2]}
\end{document}

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.