Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
The TeX FAQ

Frequently Asked Question List for TeX

Programming

Is the argument a number?

TeX’s own lexical analysis doesn’t offer the macro programmer terribly much support: while category codes will distinguish letters (or what TeX currently thinks of as letters) from everything else, there’s no support for analysing numbers.

The simple-minded solution is to compare numeric characters with the characters of the argument, one by one, by a sequence of direct tests, and to declare the argument “not a number” if any character fails all comparisons:

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

which one would then use in a tail-recursing macro to gobble an argument. One could do slightly better by assuming (pretty safely) that the digits’ character codes are consecutive:

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

again used in tail-recursion. However, these forms aren’t very satisfactory: getting the recursion “right” is troublesome (it has a tendency to gobble spaces in the argument), and in any case TeX itself has mechanisms for reading numbers, and it would be nice to use them.

Donald Arseneau’s cite package offers the following test for an argument being a strictly positive integer:

\def\IsPositive#1{%
  TT\fi
  \ifcat_\ifnum0<0#1 _\else A\fi
}

which can be adapted to a test for a non-negative integer thus:

\def\IsNonNegative{%
  \ifcat_\ifnum9<1#1 _\else A\fi
}

or a test for any integer:

\def\gobbleminus#1{\ifx-#1\else#1\fi}
\def\IsInteger#1{%
  TT\fi
  \ifcat_\ifnum9<1\gobbleminus#1 _\else A\fi
}

but this surely stretches the technique further than is reasonable.

If we don’t care about the sign, we can use TeX to remove the entire number (sign and all) from the input stream, and then look at what’s left:

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

(which technique is due to David Kastrup; the trick for avoiding the errors, noted in an earlier version of this answer, was suggested by Andreas Matthias). In a later thread on the same topic, Michael Downes offered:

\def\IsInteger#1{%
  TT\fi
  \begingroup \lccode`\-=`\0 \lccode`+=`\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
}

which relies on \romannumeral producing an empty result if its argument is zero. Sadly, this technique has the unfortunate property that it accepts simple expressions such as 1+2-3; this could be solved by an initial \gobbleminus-like construction.

All the complete functions above are designed to be used in TeX conditionals written “naturally” — for example:

\if\IsInteger{<subject of test>}%
  <deal with integer>%
\else
  <deal with non-integer>%
\fi

The LaTeX memoir class has an internal command of its own, \checkifinteger{num}, that sets the conditional command \ifinteger according to whether the argument was an integer.

Of course, all this kerfuffle would be (essentially) void if there was a simple means of “catching” TeX errors. Imagining an error-catching primitive \ifnoerror, one might write:

\def\IsInteger#1{%
  TT%
  \ifnoerror
    \tempcount=#1\relax
% carries on if no error
    \expandafter\iftrue
  \else
% here if there was an error
    \expandafter\iffalse
  \fi
}

thus using TeX’s own integer-parsing code to do the check. It’s a pity that such a mechanism was never defined (it could be that it’s impossible to program within TeX!).

FAQ ID: Q-isitanum
Tags: macros