34

How do I test if a given parameter is a number, in order to apply a different style? And if not, just ignore... Like:

\domorestuffifnumber{things} -> things
\domorestuffifnumber{123}    -> \emph{123}

Thanks.

4
  • 10
    You accepted my answer pretty quickly. That might discourage others from providing better answers. It'd probably be best to wait a day (or at least several hours) before accepting an answer.
    – TH.
    Commented Dec 15, 2010 at 23:57
  • I have a number testing code in my tikz-timing package. See the source code if you are interested. Commented May 21, 2012 at 7:30
  • In many situations (La)TeX does form numbers by expanding expandable tokens. Thus your request implies also checking whether the tokens forming the "given parameter" at the stage of expansion form an algorithm which terminates at all and which does not trigger errors. This is the halting problem. Commented May 18, 2019 at 8:42

6 Answers 6

22

Here's a slightly flawed, but slightly more generic thing than you're asking for.

\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\newcommand\ifnumber[1]{%
        \begingroup
        \edef\temp{#1}%
        \expandafter\ifstrempty\expandafter{\temp}
                {\endgroup\@secondoftwo}
                {\expandafter\ifnumber@i\temp\@nnil}%
}
\def\ifnumber@i#1#2\@nnil{%
        \if-#1%
                \ifstrempty{#2}
                        {\def\temp{X}}
                        {\def\temp{#2}}%
        \else
                \def\temp{#1#2}%
        \fi
        \afterassignment\ifnumhelper
        \count@0\temp\relax\@nnil
        \endgroup
}

\def\numrelax{\relax}%
\def\ifnumhelper#1\@nnil{%
        \def\temp{#1}%
        \ifx\temp\numrelax
                \aftergroup\@firstoftwo
        \else
                \aftergroup\@secondoftwo
        \fi
}
\makeatother

\newcommand\testnumber[1]{#1: \ifnumber{#1}{Number}{Not a number}\par}
\begin{document}
\def\foo{-55}
\testnumber{1234}
\testnumber{\foo}
\testnumber{-}
\testnumber{}
\testnumber{1}
\testnumber{1234abc}
\testnumber{abc1234}
\end{document}

It's slightly complicated by checking if the first token in the expansion of the argument is a -. Unfortunately, it does not work if the argument is a register. (It probably doesn't work in other cases too.)

But from the \ifnumber macro, you should easily be about to build what you want.

\newcommand\domorestuffifnumber[1]{\ifnumber{#1}{\emph{#1}}{#1}}
1
  • It may be a feature not to work if the argument is a register: with an argument of \count@ 0, we wouldn't want \count@ to be unpacked. In a situation where we want to get a number at any cost, then the only non-expandable control sequences which are allowed are registers, so we can test \ifcat\relax and throw in a \the to unpack. Commented May 2, 2011 at 16:18
25
\documentclass{article}

\def\isnum#1{%
  \if!\ifnum9<1#1!\fi%
     \emph{#1}%
  \else#1%
  \fi}

\begin{document}

\isnum{dummy}
\isnum{123}

\end{document}

If #1 is a number, we have \ifnum9<1xxx, which is true and therefore empty, which leads to \if!!, which is also true, and \emph{#1} is the output. In the other case we have (#1 mybe 0a) \ifnum9<10a, which is true and leaves a. Therefore we compare \if!a, which is wrong, the reason why now the \else part is the output.

Caveat: Does not work for numbers > 999999999.

4
  • That's pretty clever but it doesn't handle negative numbers and like mine, doesn't handle registers.
    – TH.
    Commented Dec 15, 2010 at 21:43
  • why the underscore after the first else?
    – yannisl
    Commented May 2, 2011 at 15:27
  • @Yiannis: Ah, a copy and past error from another macro where I used it in another way. That part is never reached, you can use it without else_ or with any other text.
    – user2478
    Commented May 2, 2011 at 15:44
  • I thought so! Your construction would also work with ifcat rather than the if. Very clever.
    – yannisl
    Commented May 2, 2011 at 17:27
12

A LuaTeX solution:

\documentclass{article}
\begin{document}

\directlua{ dofile("myluastuff.lua") }

\newcommand\domorestuffifnumber[1]{%
  \directlua{ 
    domorestuffifnumber("#1") 
}}

\def\foo{-55}
\domorestuffifnumber{1234}
\domorestuffifnumber{\foo}
\domorestuffifnumber{-}
\domorestuffifnumber{}
\domorestuffifnumber{1}
\domorestuffifnumber{1234abc}
\domorestuffifnumber{abc1234}
\end{document}

and the myluastuff.lua file:

function domorestuffifnumber( arg )
  if tonumber(arg) then 
    tex.sprint("\\emph{" .. arg .. "}") 
  else
    tex.sprint(arg or "")
 end
end

I think the solution is quite readable.

12

This is a somewhat late answer, but I am including it here for completeness. When TeX is expecting a number a trailing zero will be ignored if it is followed by another number. However, if the 0 is followed by a non-number it will stop the scanning and insert the letter in the stream. The macro that follows capitalizes on this fact. We set a counter this way within a box. If it is a number the input gets fully absorbed and the width of the box is zero. If it is not a number the box will contain the non-numbers and hence its width will be greater than zero. By testing for the width of the box we can know if the input was a number or not.

\documentclass{article}
\makeatletter
\def\add@zero#1{0#1}
\def\isNum#1{%
   \sbox\z@{\@tempcnta=0#1\relax}
   %\setbox0\hbox{\expandafter\@tempcnta\expandafter\add@zero\numtest\relax}
   \ifdim\wd0>\z@\relax\@latex@warning{Not a number!}\else is numeric\fi
}

\begin{document}
\isNum{13}

% Handles registers
\isNum{\the\@tempcnta}

%warning for not a number
\isNum{dummy}
\makeatother
\end{document}

Empty input is treated a zero, and this can be useful in many situations.

(Edit: simplified as per egreg's comments)

6
  • \sbox\z@{\@tempcnta=\number0#1\relax} is just as good and doesn't require \add@zero and \numtest. Moreover, using \sbox is safer if color might be involved. Of course this tests only for non negative numbers.
    – egreg
    Commented May 1, 2011 at 21:59
  • @egreg Thanks, I guess it is simpler and I like the idea of the sbox; problem it will fail on \isNum{\the\@tmpcnta} although I guess it can be fixed by an appropriate number of \expandafters.
    – yannisl
    Commented May 1, 2011 at 22:09
  • also yours fails on \@tempcnta because of the @ without \makeatletter. The \expandafter in your code is useless, because TeX expands tokens when looking for a number (which it does after finding \@tempcnta). Indeed also \number in my code is redundant.
    – egreg
    Commented May 1, 2011 at 22:31
  • @egreg I corrected the \makeatletter, I had it correct on my compversion and copied wrongly in the post. I will have another look at your suggestions for which I thank you and edit the code later.
    – yannisl
    Commented May 1, 2011 at 22:38
  • 1
    Careful when using this code in environments that suppress output. For example, in a tikzpicture one would have to include the \@tempcnta=0#1\relax inside of a \pgfinterruptpicture, \endpgfinterruptpicture} pair as otherwise any input is set to a box with width 0. Commented Apr 15, 2022 at 15:08
10

The xstring package provides two commands: \IfInteger and \IfDecimal. Each of them uses the syntax

\IfInteger{<value being tested>}{<result if true>}{<result if false>}

If you don't want to differentiate between integers and decimals, we could do

\documentclass{article}
\usepackage{xstring}

\newcommand{\DoStuff}[1]{\emph{#1}}

\newcommand{\IfNumber}[1]
{%
  \IfInteger{#1}
    {\DoStuff{#1}}
    {%
      \IfDecimal{#1}
        {\DoStuff{#1}}
        {#1}%
    }%
}

\begin{document}

\IfNumber{123}

\IfNumber{123.123}

\IfNumber{abc}

\end{document}
1
  • \IfDecimal is enough I think!
    – Say OL
    Commented Jul 30, 2015 at 4:25
1

I'd like to present another solution that is able to parse integer and floating point numbers. Usage of the main macro is

\ifnumber[<optional setup>]{<tokens>}{<true part>}{<false part>}

where <tokens> is the string to be tested whether it represents a number. <optional setup> is an optional list of definitions to modify the way numbers are parsed (see below). By default numbers are parsed as floating point numbers with optional signs.

Pros

  • Pure LaTeX implementation, no extra libraries required.
  • Provides some basic customization. Currently supported are
    • enabling/disabling of parsing optional signs (\let\parsesign=\iftrue/\iffalse),
    • enabling/disabling of parsing the floating point part (\let\parsefloat=\iftrue/\iffalse), and
    • definition of tokens that represent +, - and . (\parse@num@minus, \parse@num@plus and \parse@num@point, respectively).
  • The main parsing macro \parse@num is fully expandable, given that the setup macros are in scope.
  • The parsing macros correspond to states in a DFA, so more extensions should be quite easy to implement.

Cons

  • Register values cannot be passed directly to \ifnumber but have to be expanded via \the or \number before.
  • Macros in the string are not expanded properly.

Full example:

\documentclass{article}

\makeatletter

% Some conditional tests
\def\@genericif#1{#1\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
\def\@ifdigit#1{\@genericif{\ifnum1<1\noexpand#1\relax}}
\def\@ifempty#1{\@genericif{\if\relax\detokenize{#1}\relax}}

% The main parsing macros
\def\parse@num#1{%
    \@ifempty{#1}%
        {\parse@num@false}%
        {\@genericif{\parsesign}%
            {\parse@num@sign#1{}\@end}%
            {\parse@num@dig#1{}\@end}%
        }%
}
% Parse sign
\def\parse@num@sign#1#2\@end{%
    \@genericif{\ifx\parse@num@minus#1}%
        {\@ifempty{#2}{\parse@num@false}{\parse@num@dig#2\@end}}%
        {\@genericif{\ifx\parse@num@plus#1}%
            {\@ifempty{#2}{\parse@num@false}{\parse@num@dig#2\@end}}%
            {\parse@num@dig#1#2\@end}%
        }%
}
% Parse first digit
\def\parse@num@dig#1#2\@end{%
    \@ifdigit{#1}%
        {\@ifempty{#2}{\parse@num@true}{\parse@num@digs#2\@end}}%
        {\parse@num@false}%
}
% Parse optional following digits
\def\parse@num@digs#1#2\@end{%
    \@ifdigit{#1}{%
        \@ifempty{#2}%
            {\parse@num@true}%
            {\parse@num@digs#2\@end}%
    }{%
        \@genericif{\parsefloat}{%
            \@genericif{\ifx\parse@num@point#1}%
                {\@ifempty{#2}{\parse@num@false}{\parse@num@decs#2\@end}}%
                {\parse@num@false}%
        }{\parse@num@false}%
    }%
}
% Parse decimal places
\def\parse@num@decs#1#2\@end{%
    \@ifdigit{#1}{%
        \@ifempty{#2}%
            {\parse@num@true}%
            {\parse@num@decs#2\@end}%
    }{\parse@num@false}%
}

% User interface
\newcommand\ifnumber[4][]{%
    \begingroup
    \let\parsesign=\iftrue
    \let\parsefloat=\iftrue
    \let\parse@num@minus=-%
    \let\parse@num@plus=+%
    \let\parse@num@point=.%
    #1%
    \def\parse@num@true{\endgroup#3}%
    \def\parse@num@false{\endgroup#4}%
    \parse@num{#2}%
}

\makeatother

\begin{document}
\newcommand\testnum[2][]{%
    \makebox[5em][l]{#2}%
    \makebox[5em][l]{\ifnumber[#1]{#2}{yes}{no}}%
}
\ttfamily
default setting\par
\testnum{}
\testnum{0}\par
\testnum{1}
\testnum{12345}\par
\testnum{+}
\testnum{+123}\par
\testnum{-}
\testnum{-123}\par
\testnum{-0}
\testnum{+0}\par
\testnum{0.}
\testnum{0.123}\par
\testnum{-.1}
\testnum{-+123}\par
\testnum{-0.123}
\testnum{+0.123}\par
\testnum{abc}
\testnum{123abc}\par

\medskip
\string\parsesign\ = \string\iffalse\par
\testnum[\let\parsesign=\iffalse]{123.45}\par
\testnum[\let\parsesign=\iffalse]{+123.45}\par
\testnum[\let\parsesign=\iffalse]{-123.45}\par

\medskip
\string\parsefloat\ = \string\iffalse\par
\testnum[\let\parsefloat=\iffalse]{123}
\testnum[\let\parsefloat=\iffalse]{123.45}\par
\testnum[\let\parsefloat=\iffalse]{+123}
\testnum[\let\parsefloat=\iffalse]{+123.45}\par
\testnum[\let\parsefloat=\iffalse]{-123}
\testnum[\let\parsefloat=\iffalse]{-123.45}\par

\medskip
\makeatletter
\string\parse@num@point\ = ,\par
\testnum[\let\parse@num@point=,]{-123,45}
\testnum[\let\parse@num@point=,]{0.12}\par
\makeatother
\end{document}

enter image description here

1
  • I am using an adaptation of your snippet above to highlight digits in my listings and it works great. But as I posted in this question, numbers are not highlighted when they are followed by characters such as ;, :, ,, ], or ), or preceeded by :, [, or(. This means that numbers do not get highlighted in situations such as range(3), if x > 1:, foo[2:4], or in languages with ; delimiter, x = 3;, etc. Would you have some suggestion on how to improve this? Commented Nov 8, 2020 at 13:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .