11

It may seem rather silly, but I can't find an answer for this question. (I've been looking for one for a while already).

Suppose I want to have a macro with a single parameter. When this parameter equals 1, I want the macro to append the singular form of a word to the parameter, otherwise use the plural form.

I have the following code:

\newcommand{\sth}[1]{\textbf{(#1 
\if1#1thing)\else things)\fi}}

It works fine for any parameter except when it is a number greater than 1 but starting with 1 (e. gr. 11).

I am aware \if compares two tokens which in this case are characters. I am also aware there are solutions that either assume #1 is always a number (e.g. using \ifnum) or use additional packages (like ifthen or pdftexcmds). I don't want to assume #1 is a number, because it seems cleaner to me, and I still wonder whether there is a core LaTeX solution to this (w/o using further packages).

Edit: the question is, is there a way of knowing if the argument is exactly '1' without assuming it is a number nor using additional packages?

Thanks in advance

  • 1
    \if isn't the right tool for comparing numbers. Using \ifnum is the "core TeX" way. – Heiko Oberdiek Apr 26 '16 at 17:44
  • 2
    If the argument #1 can also be a non-number, then the comparison with 1 can be done with \ifnum\pdf@strcmp{#1}{1}=0 ... (\pdf@strcmp of package pdftexcmds (pdfTeX, XeTeX, LuaTeX). – Heiko Oberdiek Apr 26 '16 at 17:48
  • 1
    Right, but \ifnum will throw an error message if I try \sth{no}, for example. I don't want to assume #1 is a number. I'm also asking if there is a way without using additional packages. – Rafael Apr 26 '16 at 17:49
  • @Rafael: I think your question is a little bit unclear – user31729 Apr 26 '16 at 17:53
  • @ChristianHupfer thanks, I just added a clarification – Rafael Apr 26 '16 at 17:58
15

Here is an \ifnum comparison that can handle non-numeric inputs. It does it by comparing 1 with 0#1. If #1 is a number, the \ifnum comparison works in the standard way, since the leading 0 does not affect the numerical value.

If #1 is not a number, then the \ifnum argument expansion stops after the 0 with a false comparison (having already printed out #1), and then prints out things.

\documentclass{article}
\newcommand{\sth}[1]{\textbf{(#1 \ifnum1=0#1\relax thing\else things\fi)}}
\begin{document}
\sth{1}  vs. \sth{2} vs. \sth{11} vs. \sth{no}
\end{document}

enter image description here

  • Pretty straightforward! Just a question: I thought \relax meant the same as {}, however, in this context it seems to mark the ending of the if's condition. Why is that? – Rafael Apr 26 '16 at 18:54
  • 1
    @Rafael As I understand it, \relax is unexpandable, and provides a hard stop to the argument expansion of \ifnum. In this case it is not really an issue, since a t would also be understood not to be numeric. Where it is necessary is if you wanted to print out a 9 if the argument was a 1... \ifnum 1=0#19\fi would take the 9 as part of the compared-to number and not part of the what-to-do-if. Thus, \ifnum 1=0#1\relax9\fi would be needed. Perhaps {} serves the same function here, though in general, \relax and {} are not interchangeable. – Steven B. Segletes Apr 26 '16 at 18:58
  • However, when calling the original macro \sth{11}, it gives (11 1thing), meaning that what is not compared is considered part of the what-to-do-when-true statement. Why is it then that #1 is considered part of the comparison even when non-numeric? – Rafael Apr 26 '16 at 19:05
  • 2
    @Rafael an \if comparison is different from an \ifnum comparison. An \if comparison compares the first 2 tokens from the expanded list. Anything following those first two tokens is part of the if-true action. So \if1#1thing expands to \if111thing. The first two 1's compare as true, and so 1thing is the resulting output. Pp. 209-210 of the TeXbook talks about all the various comparisons of TeX. I would hasten to add the \if comparisons work on non-numeric tokens, whereas \ifnum does not. – Steven B. Segletes Apr 26 '16 at 19:13
  • 2
7

\pdfstrcmp of pdfTeX expands two token groups and compares them as strings. If they are equal, \pdfstrcmp expands to 0, which can be tested with \ifnum or \if:

\ifnum\pdfstrcmp{#1}{1}=0 % space ends the number zero here
  ...
\else
  ...
\fi

or

\if0\pdfstrcmp{#1}{1}%
  ...
\else
  ...
\fi

Package pdftexcmds' \pdf@strcmp hides the different names and methods of the different TeX engines (pdfTeX, XeTeX, LuaTeX). "Vanilla" TeX is not supported.

If #1 can contain fragile stuff, then the LaTeX way is a protected expansion, e.g.:

\protected@edef\param{#1}%
\def\one{1}%
\ifx\one\param
  ...
\else
  ...
\fi

Or if the argument should not be expanded at all:

\def\param{#1}%
\def\one{1}%
\ifx\param\one
  ...
\else
  ...
\fi
  • If I understand well, the two latest options do not require using pdftexcmds while the first two do, is that right? – Rafael Apr 26 '16 at 18:07
  • 2
    \pdfstrcmp was first introduced in pdfTeX, XeTeX uses \strcmp as name and LuaTeX requires an implementation via \directlua, therefore using pdftexcmds is a little more comfortable. – Heiko Oberdiek Apr 26 '16 at 18:12
0

Tests using \if..\else..\fi can be outmaneuvred by using arguments that contain unbalanced \if.. or \or or \else or \fi.

The following test works without additional packages only by means of expansion without any \if-conditionals. Everything is done by means of delimited macro arguments.

Of course you cannot successfully apply test-macros on arguments that contain \outer tokens.

You can easily enhance the test to fork whether "1" or "2" or something else/emptiness.

Ulrich

%%-------------------------------------------------------------------------
%% Paraphernalia:
%%.........................................................................
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
%%-------------------------------------------------------------------------
%% Check whether argument is empty:
%%.........................................................................
%% \CheckWhetherNull{<Argument which is to be checked>}%
%%                  {<Tokens to be delivered in case that
%%                    argument which is to be checked is empty>}%
%%                  {<Tokens to be delivered in case that
%%                    argument which is to be checked is not empty>}%
\long\def\CheckWhetherNull#1{%
  \expandafter\secondoftwo\string{\expandafter\secondoftwo
  \expandafter{\expandafter{\string#1}\expandafter\secondoftwo
  \string}\expandafter\firstoftwo\expandafter{\expandafter
  \firstoftwo\expandafter\secondoftwo\expandafter}\string
  }\firstoftwo
}%
%%-------------------------------------------------------------------------
%% Check whether argument contains no exclamation-mark on top-brace-level:
%%.........................................................................
%% \CheckWhetherNoExclamationMark{<Argument which is to be checked>}%
%%                  {<Tokens to be delivered in case that
%%                    argument which is to be checked does not contain !>}%
%%                  {<Tokens to be delivered in case that
%%                    argument which is to be checked does contain !>}%
\long\def\RemoveToExclamationMark#1!{}%
\long\def\CheckWhetherNoExclamationMark#1{%
  \expandafter\CheckWhetherNull\expandafter{\RemoveToExclamationMark#1!}%
}%
%%-------------------------------------------------------------------------
%% \ForkDigitOne{<Argument to check>}
%%              {<tokens in case argument to check is a
%%                single 1-character token of catcode 12>}
%%              {<tokens in case argument to check is not a
%%                single 1-character token of catcode 12>}
%%.........................................................................
\long\def\AtForkDigitOne#1!!1!#2#3!!!!{#2}%
\long\def\ForkDigitOne#1#2#3{%
   \CheckWhetherNoExclamationMark{#1}{%
    \AtForkDigitOne
    !#1!1!{#3}%<-EMPTY
    !!#1!{#2}%
    !!1!{#3}%<-ELSE
    !!!!%
  }{#3}%<-ELSE
}%

\ForkDigitOne{1}{One}{Not One}

\ForkDigitOne{\fi}{One}{Not One}

\ForkDigitOne{111}{One}{Not One}

\ForkDigitOne{!ttt{}111!!}{One}{Not One}

\ForkDigitOne{}{One}{Not One}

\ForkDigitOne{w22e2e}{One}{Not One}

\ForkDigitOne{ }{One}{Not One}

\bye 

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.