5

I want a macro which can generate units consisting of several base units. Let the macro be \unit. For example, I want \unit{kg} to mean \mathrm{kg} and \unit{kg}{m}{s^{-2}} to mean \mathrm{kg \cdot m \cdot s^{-2}} etc. (though how to represent exponentiation is yet to be decided).

First, I tried the following code but this didn't work. The error log says Runway Argument? ! File ended while scanning use of \@unit. <inserted text> \par.

\documentclass[uplatex]{jsarticle}
\makeatletter
\def\unit#1{\@unit#1}
\def\@unit#1{%
    \ifx\bgroup#1
        \mathrm{#1 \cdot}%
    \else
        \relax
    \expandafter\@unit\fi}
\makeatother
%
\begin{document}
\unit{{kg}{m}}
\end{document}

How should I define the desired \unit?

Merzong
  • 589
  • 5
  • 11
  • 3
    I think, siunitx would be a better approach –  May 23 '15 at 17:16
  • You can have a comma-separated list that is looped through, but given the actual output you want, use siunitx. – jon May 23 '15 at 17:29
  • Actually I wanted to add two extra functions to my \unit. One function is that I want to change connector between base units by \def\useunitmultidot{\let\unitmulti=\cdot} \def\useunitmultispace{\let\unitmulti=\ } and then declaring \useunitmultidot or \useunitmultispace (in this case, \cdot in the question becomes \unitmulti.). The other is that I want to give \unit an optional parameter in order to choose whether or not to enclose the unit in brackets. These are why I want to define \unit instead of using siunitx. – Merzong May 23 '15 at 17:51
  • I think that what you want is already provided by siunitx, that has many more features. – egreg May 23 '15 at 18:01

2 Answers2

5

Assuming you want input syntax

\unit{kg}{m}

what you need to do is use the TeX primitive \futurelet to search for an upcoming {. If there is one, we can grab a (braced) argument, typeset it in math mode then loop. Note that this approach requires braces around each argument.

\documentclass{article}
\makeatletter
\def\unit{%
  $%
  \futurelet\@let@token\@unit
}
\def\@unit{%
  \ifx\@let@token\bgroup
    \expandafter\@@unit
  \else
    \expandafter$%
  \fi
}
\def\@@unit#1{%
  \mathrm{#1}%
  \futurelet\@let@token\@@@unit
}
\def\@@@unit{%
  \ifx\@let@token\bgroup
    \cdot
    \expandafter\@@unit
  \else
    \expandafter$%
  \fi
}
\makeatother
\begin{document}
\unit{kg}{m}
\end{document}

To allow for insertion of the \cdot I've used two auxiliaries: one is used only for the first unit an omits the \cdot, the second one inserts the required \cdot.

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
  • The outer braces are just typos, sorry for confusing. This is what I wanted except for the second function mentioned in the comment above (I'll try to make that function later). This code is a bit difficult for me to understand but good opportunity to improve my skill. – Merzong May 23 '15 at 19:50
4

The problem when you do \def\@unit#1 is that the argument will never be the brace, but all that goes from the open brace to the matching closed one and your test is never successful.

You can do it with \futurelet, but you'll be tied to a very inflexible system of input. Here's an expl3 implementation that's possibly clearer than \futurelet.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\unit}{}
 {
  % if we're not in math mode open it and remember to close it
  \mode_if_math:F { $ \bool_set_true:N \l_merzong_close_bool }
  % clear the container for the units
  \seq_clear:N \merzong_units_seq
  % start looking forward for an open brace
  \merzong_scan_arg:
 }

\cs_new_protected:Npn \merzong_scan_arg:
 {
  \peek_catcode:NTF \c_group_begin_token
   {% the next token is an open brace, absorb the argument
    \merzong_add_unit:n
   }
   {% the next token is not an open brace, deliver the units
    % absorbed so far, with \unitmulti between them
    \seq_use:Nn \merzong_units_seq { \unitmulti }
    % close math mode if we started it
    \bool_if:NT \l_merzong_close_bool { $ }
   }
 }
\cs_new_protected:Npn \merzong_add_unit:n #1
 {
  % absorb the argument and add it to the sequence
  \seq_put_right:Nn \merzong_units_seq { \mathrm{#1} }
  % restart the recursion
  \merzong_scan_arg:
 }

\seq_new:N \merzong_units_seq
\ExplSyntaxOff

\newcommand\useunitmultidot{\let\unitmulti=\cdot}
\newcommand\useunitmultispace{\let\unitmulti=\,}
\useunitmultidot % initialize

\begin{document}

\section{Centered dot}

\unit{kg}{m^2}{s^{-2}}

\section{Space}
\useunitmultispace

\unit{kg}{m^2}{s^{-2}}

\end{document}

enter image description here

What you want to do is already done by siunitx. Using it ensure great consistency in your usage of units.

\documentclass{article}
\usepackage{siunitx}

\begin{document}

\section{Centered dot}

\sisetup{inter-unit-product=\ensuremath{{}\cdot{}}}

\SI{3}{\kilo\gram\meter\squared\per\second\squared} or \SI{3}{kg.m^2.s^{-2}}

\section{Thin space}

\sisetup{inter-unit-product=\,}

\SI{3}{\kilo\gram\meter\squared\per\second\squared} or \SI{3}{kg.m^2.s^{-2}}

\section{Fraction for negative exponent}

\sisetup{per-mode=symbol}

\SI{3}{\kilo\gram\meter\squared\per\second\squared}

\end{document}

enter image description here

egreg
  • 1,121,712
  • I didn't know siunitx has already had the changing-inter-unit-product function, but \sisetup{inter-unit-product=\cdot} somehow results in Missing $ inserted. And is there any feature equivalent to my second idea in siunitx? – Merzong May 23 '15 at 18:51
  • @Merzong We'll need an example to know why you are getting an error: the demo posted here works fine. The package doesn't do adding brackets around units as that is never right :-) If you really want it, a small wrapper around \si would work. – Joseph Wright May 23 '15 at 19:16
  • @JosephWright Only changed \documentclass{article} to \documentclass[uplatex]{jsarticle}. The rest is the same as the demo. – Merzong May 23 '15 at 19:24
  • @Merzong No other errors? I have a feeling you are using platex or similar, and I'm not sure it has the primitives needed by expl3. – Joseph Wright May 23 '15 at 19:26
  • @JosephWright This page (http://d.hatena.ne.jp/acetaminophen/20150428/1430231114) says expl3 misrecognizes pTeX and upTeX as pdfTeX. However, I don't know how this has an effect on the error. – Merzong May 23 '15 at 19:39
  • @Merzong If I change the \documentclass line to the one you showed, I get no error. – egreg May 23 '15 at 20:24
  • The siunitx manual (page 40) suggests using inter-unit-product=\ensuremath{{}\cdot{}} for proper spacing, for what it's worth. – wchargin May 23 '15 at 23:20
  • @WChargin You're right. I'll fix it. – egreg May 23 '15 at 23:22
  • @WChargin The error has gone when using \sisetup{inter-unit-product=\ensuremath{{}\cdot{}}}. Thank you. – Merzong May 24 '15 at 11:00