0

I'm very new to LaTeX, and need to compare a length I've defined with a default value (and set another length accordingly). Consequently, I'm exploring the murky (to me anyway) world of LaTeX conditionals.

Can anyone explain to me why, in the below, \ifdim produces the expexted behaviour, but \if doesn't, even though I'm expanding \test to some text before I perform the comparison? As I have understood \if it is supposed to expand tokens until it has two that can't be expanded any further, and then operate on those - shouldn't that mean it would compare 0.0pt (as text) with 0.0pt and evaluate as true?

\newlength\test
\setlength\test{0.0pt}
\edef\tempa{\the\test}
\edef\tempb{0.0pt}

% this would evaluate false, even though \tempa and \tempb both print 0.0pt when expanded
\if\tempb\tempa

% this works
\ifdim\tempb=\tempa
{
    Using max width
}
\else
{
    Using assigned width (\the\test)
    \typeout{Tempa is \tempa ; Tempb is \tempb ;}
}
\fi

How should I be doing these kinds of comparisons if I really do want to compare the character sequences stored in \tempa and \tempb? And is there a way to avoid e.g. the assignment of temporary variables entirely? Ideally, after all, I'd want:

\ifthenelse{\equals{\the\test}{0.0pt}}
{
   True
}
{
   False
}

but that also didn't work when I tried it!

4
  • 3
    The if will compare 0 with . as these are the next tokens. Beside this: \if and \ifdim are not latex but tex primitives. If you want to use latex commands check the etoolbox package or expl3. Jan 27 '20 at 12:00
  • 1
    propbably better to use the etoolbox package and the comparisons you find there
    – daleif
    Jan 27 '20 at 12:00
  • To be honest, I don't mind too much if I'm using TeX or LaTeX per se... I just want to be able to compare two strings and do a thing when I've done so! I've played around with etoolbox but also getting the hang of the various commands it exposes. Is \ifdefstrequal the one I'd use if one piece of text were in a macro and another were constant?
    – tobriand
    Jan 27 '20 at 12:19
  • @tobriand it depends a little on how your input is defined/stored at times you'll need to manually expand the argument before the the comparison is made.
    – daleif
    Jan 27 '20 at 13:03
5

These things are probably difficult if you are “new to LaTeX.” Take your time to learn about tokens, character codes, category codes, expansion, etc. The TeXbook is a good reference to learn about all these.

Explanation of the \if test

The TeX primitive \if recursively expands what follows in the input stream until there are two unexpandable tokens. If these tokens are both character tokens, which is the case in your example as we'll see below, the \if test is true if, and only if the character tokens have the same character code. This may sound non-obvious, but that is how \if works: it compares character codes.

(Other famous tests are \ifcat which compares category codes, \ifnum which compares 〈number〉s, \ifdim which compares 〈dimen〉s, and \ifx which compares the meanings of two tokens.)

In your case, you have \if\tempb\tempa.... After \if has performed one expansion step according to the above description, the front of the input stream is 0.0pt\tempa.... Therefore, your code behaves the same as this:

\if 0.0pt\tempa...

(which is identical to \if0.0pt\tempa... since the first 0 is a non-letter).

So, after this expansion step automatically performed by \if, there are already two unexpandable tokens immediately following in the input stream: 0 and ., both with category code 12 (other), because they come from \edef\tempb{0.0pt}, where the replacement text of \tempb (0.0pt) has been tokenized under the standard category code régime. As a consequence, TeX compares the character codes of 0 and ., which are obviously different (cf. the ASCII code table), therefore the result of the \if test is false—as you observed.

Summary: you wanted to somehow compare \tempb and \tempa, but you actually compared 0 and . from the front of the expansion of \tempb, the rest 0pt\tempa being left at the beginning of the true clause of the \if test, thus ignored here since the test is false (TeX will skip over tokens without expanding anything until it finds a matching \else or \fi).

How to compare lengths and strings

There are many ways to compare lengths and strings in LaTeX. Here are some examples with the ifthen and etoolbox packages. You may also be interested in xifthen and expl3 (well, it may be too early for the latter, but it is very powerful and convenient).

One noteworthy difference between ifthen and etoolbox is that the string equality test \equal from package ifthen recursively expands its arguments but doesn't change the category codes, whereas the \ifstrequal test from package etoolbox doesn't expand its arguments but normalizes the category codes (a priori using \detokenize). This takes great importance because when \test is a 〈dimendef token〉 or a 〈skipdef token〉, which is the case here, \the\test expands to character tokens with category code 12 (other), except for space tokens which are assigned category code 10 (space).1 0.0pt in “all category code 12” is not the same as 0.0pt tokenized with the standard category codes, that is: 12 for 0 and ., 11 (letter) for p and t. That is why comparing \the\test to 0.0pt-tokenized-with-the-standard-catcodes using ifthen's \equal “string” equality test yields false even when \test has been set to 0.0pt. However, if we compare \the\test with \detokenize{0.0pt} using the same \equal comparison function of package ifthen, the result is true when \test has been set to 0.0pt, since \the and \detokenize both assign category codes to the character tokens they expand to in exactly the same way—that is, 12 for every character token except for spaces, which get 10. This way of assigning category codes is what is called “converting to a string” in the expl3 language, by the way.

Here are the examples with the etoolbox package:

\documentclass{article}
\usepackage{etoolbox}

\newlength{\test}
\setlength{\parindent}{0pt}

\begin{document}

\setlength{\test}{0pt}
Length comparisons:\\
1. \ifdimcomp{\test}{=}{0.0pt}{Equal}{Not equal}\\
2. \ifdimcomp{\test}{=}{0pt}{Equal}{Not equal}

\bigskip
String comparisons (\verb|\the\test| expands to \the\test):\\
3. \ifstrequal{\the\test}{0.0pt}{Equal}{Not equal}\\
4. \expandafter\ifstrequal\expandafter{\the\test}{0.0pt}{Equal}{Not equal}

\bigskip
\setlength{\test}{0.1pt}
Length then string comparison with \verb|0.1pt|:\\
5. \ifdimcomp{\test}{=}{0.0pt}{Equal}{Not equal}\\
6. \expandafter\ifstrequal\expandafter{\the\test}{0.0pt}{Equal}{Not equal}

\end{document}

examples with etoolbox

And finally, the promised examples with the ifthen package:

\documentclass{article}
\usepackage{ifthen}

\newlength{\test}
\setlength{\parindent}{0pt}

\begin{document}

\setlength{\test}{0pt}
Length comparisons:\\
1. \ifthenelse{\lengthtest{\test=0.0pt}}{Equal}{Not equal}\\
2. \ifthenelse{\lengthtest{\test=0pt}}{Equal}{Not equal}

\bigskip
String comparisons (\verb|\the\test| expands to \the\test):\\
3. \ifthenelse{\equal{\the\test}{0.0pt}}{Equal}{Not equal}\\
4. \ifthenelse{\equal{\the\test}{\detokenize{0.0pt}}}{Equal}{Not equal}\\
5. \ifthenelse{\equal{\the\test}{\detokenize{0pt}}}{Equal}{Not equal}

\bigskip
\setlength{\test}{0.1pt}
Length then string comparison with \verb|0.1pt|:\\
6. \ifthenelse{\lengthtest{\test=0.0pt}}{Equal}{Not equal}\\
7. \ifthenelse{\equal{\the\test}{0.0pt}}{Equal}{Not equal}

\end{document}

examples with ifthen


Footnote

  1. You can obtain space tokens from expanding \the\test after doing for instance:

    \newlength{\test}%
    \setlength{\test}{0.1pt plus 3pt minus 1pt}
    

After executing this, using expl3's \tl_analysis_show:n function to analyse each token in the expansion of \the\test:

```latex
\documentclass{article}
\usepackage{expl3}

\newlength{\test}
\setlength{\test}{0.1pt plus 3pt minus 1pt}

\ExplSyntaxOn
\cs_generate_variant:Nn \tl_analysis_show:n { o }
% One expansion step on \the\test, result passed to \tl_analysis_show:n
\tl_analysis_show:o { \the\test }
\ExplSyntaxOff

\begin{document}
\end{document}
```

produces the following output on the terminal:

```none
>  0 (the character 0)
>  . (the character .)
>  1 (the character 1)
>  p (the character p)
>  t (the character t)
>    (blank space  )
>  p (the character p)
>  l (the character l)
>  u (the character u)
>  s (the character s)
>    (blank space  )
>  3 (the character 3)
>  . (the character .)
>  0 (the character 0)
>  p (the character p)
>  t (the character t)
>    (blank space  )
>  m (the character m)
>  i (the character i)
>  n (the character n)
>  u (the character u)
>  s (the character s)
>    (blank space  )
>  1 (the character 1)
>  . (the character .)
>  0 (the character 0)
>  p (the character p)
>  t (the character t).
```
2

First off, conditionals in TeX don't want braces around the “true” and ”false” branches. Braces will make groups, that might even ruin everything, if nested conditionals are used, but not only in this case.

Now the theory. The conditional \if expands the token(s) following it until finding two nonexpandable tokens, which then compares by character code.

So in your case

\if\tempb\tempa

becomes

\if0.0pt\tempa

and TeX compares 0 to ., finding out they're different, so the “false branch” is followed and 0pt\tempa becomes part of the “true branch”, hence ignored.

If you want to compare the meaning of two macros, you can use \ifx, but… Yes, the test

\ifx\tempb\tempa

returns false. TeX compares the first level expansion of the two macros, but they're different. Indeed, \tempb expands to

012 .12 012 p11 t11

whereas \tempa expands to

012 .12 012 p12 t12

(subscripts denote category code), because \tempa is defined with \the See https://tex.stackexchange.com/a/38680/4427 for the reason. In order for \ifx to return true, the two macros must expand to the same text token by token and these two macros don't.

On the other hand, \ifdim compares two dimensions with expansion of the following tokens until finding something of the form

<dimen> <comparator> <dimen>

where <comparator> is one among <12, =12 or >12 (with optional spaces on either side). The <dimen> must be a dimension either explicit, like 0.0pt, or implicit (a dimension register). In an explicit dimension the two letter representing the unit (which is mandatory) can be of category code 11 or 12. Thus your \ifdim\tempb=\tempa becomes

\ifdim 012 .12 012 p11 t11 =12 012 .12 012 p12 t12

and returns true.

Can you compare strings? Yes. There is a “category code agnostic” test:

\ifnum\pdfstrcmp{\tempb}{\tempa}=0

will return true. Note that \pdfstrcmp is \strcmp in XeTeX and does not exist in LuaTeX, but we can get a version working for all known engines with

\usepackage{pdftexcmds}

or \input pdftexcmds.sty in plain TeX, so you can use

\ifnum\pdf@strcmp{\tempb}{\tempa}=0

(of course in an environment where @ has category code 11); \pdf@strcmp will be translated to the primitive (or Lua function) appropriate for the current engine.

Or you can use higher level methods with etoolbox or expl3.

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.