31

I have a command with a parameter. I want to check if its value is a positive number and perform actions based on that.

\newcommand{\mycommand}[1]{
    \ifnum#1>0%
        %some actions
    \fi
}

But I receive errors when the value of the parameter is not a number, e.g.:

\mycommand{*}

How can I check if the value of #1 is a number?

3

4 Answers 4

26

Update:

Since it appears that the \IfInteger from the xtring package perceives blank strings as integers (the empty string {} is ok, but not { }), I have defined a modified macro \IsInteger which handles that case:

enter image description here

\documentclass{article}

\newcommand*{\IsInteger}[3]{%
    \IfStrEq{#1}{ }{%
        #3% is a blank string
    }{%
        \IfInteger{#1}{#2}{#3}%
    }%
}%
\usepackage{xstring}
\begin{document}
$2$ is \IsInteger{2}{an integer}{not an integer}\par
$2.0$ is \IsInteger{2.0}{an integer}{not an integer}

$-7$ is \IsInteger{-7}{an integer}{not an integer}\par
$-7.0$ is \IsInteger{-7.0}{an integer}{not an integer}

$2.1$ is \IsInteger{2.1}{an integer}{not an integer}\par
$-7.1$ is \IsInteger{-7.1}{an integer}{not an integer}

a is \IsInteger{a}{an integer}{not an integer}

Empty String is \IsInteger{}{an integer}{not an integer}\par
Blank String is \IsInteger{       }{an integer}{not an integer}
\end{document}

You can use IfInteger from the xstring package to test if it is an integer number:

enter image description here

There is also \IfDecimal which works similarly.

\documentclass{article}
\usepackage{xstring}
\begin{document}
2 is \IfInteger{2}{integer}{not an integer}

a is \IfInteger{a}{integer}{not an integer}
\end{document}
5
  • Thank you, this solved the problem. By the way, is there a TeX macro similar to \ifnum that I can use? I would prefer to keep the number of packages I am using to minimum.
    – Kaveh
    Mar 30, 2012 at 19:10
  • I just noticed that \IfInteger interprets the empty string as an integer.
    – Kaveh
    Mar 30, 2012 at 19:16
  • 1
    @Kaveh: Had created a modified macro \IsInteger to compensate for that. Mar 31, 2012 at 3:31
  • It also appears that \ifinteger interprets negative numbers as non-integer.
    – Rob Hall
    May 6, 2020 at 16:50
  • @RobHall: Seems to work fine for negative values as well. See updated MWE. May 6, 2020 at 18:25
22

It depends mostly on the expected input and also on the context where you want to use the command.

If your expected input is either a number or something that doesn't start with digits, then

\newcommand{\mycommand}[1]{%
  \ifnum0<0#1\relax
    #1 is a positive number%
  \else
    #1 is not a positive number%
  \fi}

will do. For example, \mycommand{42} will do the comparison 0<042 which is true; instead, with \mycommand{*} TeX will see \ifnum0<0*\relax and it will test 0<0, which is false, so the * will be ignored as part of the "true text". Also the test from \mycommand{0} will evaluate to false.

On the other hand, with \mycommand{1x} the test will evaluate to true and give wrong results.

Another expandable way can be

\def\mycommand#1{%
  \if\relax\detokenize\expandafter{\romannumeral-0#1}\relax
    #1 is a number%
  \else
    #1 is not a number%
  \fi
}

but \mycommand{0} would test true. Here \mycommand{1x} would answer that 1x is not a number.

However, the argument should not contain "dangerous" items: \mycommand{\textbf{x}} would fail miserably.

A non-expandable test can be

\makeatletter
\def\mycommand#1{%
  \afterassignment\get@args\count@=0#1\hfuzz#1\hfuzz}
\def\get@args#1\hfuzz#2\hfuzz{%
  \if\relax\detokenize{#1}\relax
    #2 is a number%
  \else
    #2 is not a number%
  \fi
}
\makeatother

This works also with input such as \mycommand{\textbf{1}} (the test will evaluate to false).

8
  • \mycommand{\undefined\@undefined} will fail. I am sure you know why I have used two undefined tokens.
    – Ahmed Musa
    Mar 31, 2012 at 0:27
  • Thank you. :) I decided to use the solution given by Peter.
    – Kaveh
    Mar 31, 2012 at 4:56
  • @AhmedMusa All the proposed macros fail with only one undefined token.
    – egreg
    Mar 31, 2012 at 8:26
  • Very nice solution. Your first example is exactly what I needed today (with slight modification to handle negative numbers too).
    – A.Ellett
    Mar 27, 2014 at 2:57
  • 1
    @jessepeng In the first solution, substitute #1 is a number by \expandafter\@firstoftwo and #1 is not a number by \expandafter\@secondoftwo. Similarly for the second solution.
    – egreg
    Jun 14, 2019 at 15:25
4

Here is an expandable solution that accepts even undefined control sequences. It is admittedly acrobatic.

Some of the commands in catoptions package are redefined here for the sake of speed and experimentation.

\documentclass[a4paper]{article}
\usepackage{catoptions}
\makeatletter
\new@def\cptifdef#1{%
  \cptifblank{#1}{%
    \@secondoftwo
  }{%
    \csname @\ifx#1\@undefined second\else
    \ifx#1\relax second\else first\fi\fi oftwo\endcsname
  }%
}
\new@def\cptifundef#1{\cptifdef{#1}\@secondoftwo\@firstoftwo}
\new@def\cptifleftbraced#1{%
  \cptifblank{#1}{%
    \@secondoftwo
  }{%
    \csname @\if\expandafter\cpt@car\detokenize{#1}\car@nil
      \expandafter\cpt@car\string{\car@nil\ifnum0=`}\fi
      first\else second\fi oftwo\endcsname
  }%
}
\new@def\cptifxpandable#1{%
  \cptifleftbraced{#1}{%
    \@secondoftwo
  }{%
    \expandafter\ifx\noexpand#1#1%
      \expandafter\@secondoftwo
    \else
      \expandafter\@firstoftwo
    \fi
  }%
}
\newcommand\cpttwooftwo[2]{#1#2}
\newcommand\cptifblank[1]{%
  \expandafter\ifx\expandafter\noboundary\@gobble#1.\noboundary
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\newcommand\cptifcmdeq[2]{%
  \ifx#1#2\cpt@quark
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\long\def\cptifsolo#1{%
  \if0\pdfstrcmp{\detokenize\expandafter
    {\cpttwooftwo#1{}{}}}{\detokenize{#1{}}}%
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\new@def\cptxpand#1{%
  \romannumeral-`\q\cptifblank{#1}{\space}{\cpt@xpand#1\xpand}%
}
\def\cpt@xpand#1#2\xpand{%
  \cptifsolo{#1}{%
    \cptifundef{#1}{%
      \cpt@@xpand{#2}{\noexpand#1}%
    }{%
      \cptifxpandable{#1}{%
        \cpt@@xpand{#2}{\expandafter\cpt@xpand#1\xpand}%
      }{%
        \cpt@@xpand{#2}{\noexpand#1}%
      }%
    }%
  }{%
    \cpt@@xpand{#2}{\cpt@xpand#1\xpand}%
  }%
}
\def\cpt@@xpand#1{%
  \expandafter\cptswap\expandafter{\romannumeral-`\q
  \cptifblank{#1}{\space}{\cpt@xpand#1\xpand}}%
}
\def\ifinteger#1{%
  \if\cptifblank{#1}{1}{\expandafter\ifinteger@a\romannumeral-`\q
  \cpt@xpand#1\xpand\cpt@nnil}00%
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\def\ifinteger@a#1{%
  \cptifcmdeq#1\cpt@nnil{}{%
    \ifinteger@b#1\cpt@nil0123456789\cpt@nnil
  }%
}
\def\ifinteger@b#1\cpt@nil#2#3\cpt@nnil{%
  \expandafter\cptifcmdeq\cpt@car#1\car@nil#2{%
    \ifinteger@a
  }{%
    \cptifblank{#3}{%
      1\cpt@removetonnil
    }{%
      \ifinteger@b#1\cpt@nil#3\cpt@nnil
    }%
  }%
}

% Tests
\def\cmda{01}
\def\cmdb{xy}
\let\cmdc\undefined
\edef\cmdd{\ifinteger{\cmda}{True}{False}}
\edef\cmde{\ifinteger{\cmdc}{True}{False}}
\edef\cmdf{\ifinteger{\cmda\cmdb\cmdc}{True}{False}}
\show\cmdf
2

I know, i'm "a bit late" to the party, but this seems to work fine:

\makeatletter
\def\mycommand#1{%
  \sbox\z@{\@tempcnta=0#1\relax}%
  \expandafter\ifdim\wd\z@>\z@\relax
    #1 is NOT an integer
  \else
    #1 is an integer
  \fi}
\makeatother

This works as long as you try to find positive integers (i.e., counters), not floats or negative numbers.

2
  • Didn't work for me. Instead, the page I used the command on disappeared and it gave no error messages :/
    – MD004
    Sep 13 at 19:18
  • @MD004 Sounds like \box0 is already occupied in your context. Try replacing \sbox\z@ with \sbox\@tempboxa and \wd\z@ with \wd\@tempboxa or maybe declare your own box register (\newbox\mybox) and use that for measuring, instead.
    – Lupino
    Sep 14 at 20:23

You must log in to answer this question.

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