类 log 函数与 mod

\mathrm{sin}\sin 印在纸上看起来完全是同一个 “sin”。可前者依然是错的,差别是 3.33325pt。在 LaTeX 中测量 $a\operatorname{op}b$ 得到 23.46638pt,$a\mathrm{op}b$ 则是 20.13313pt——直立的字形一模一样,但 \mathrm 前后不带任何空白。这 3.33325pt 不是装饰,而恰好是两个细空白,也就是 TeX 在 Op 原子(atom) 左右各放的一个。本页从这个原子讲起,接着定位内置的 32 个函数名写在 LaTeX 源码的哪里,解释为何恰好有 10 个把下标放在正下方,说明 \DeclareMathOperator 的星号改变了什么,并用实测宽度区分 \bmod\pmod\mod\pod 这四种模运算写法。

为什么 \mathrm{sin} 是错的:原子类别决定间距

在数学模式中,每个英文字母都会被当作变量名并以数学斜体排版,所以输入 sin x 得到的不是“正弦”,而是 sinx 四个量的乘积。这一点广为人知,很多人想到的补救办法是 \mathrm{sin}——因为字形变直立后,问题看起来就解决了。但 \mathrm 改变的 只有字体。在 TeX 看来,原子类别仍然是 Ord,也就是普通符号。

TeX 把公式的各个成分分成 原子类别——Ord(普通)、Op(算子)、Bin(二元运算符)、Rel(关系符)等——并且完全根据相邻的是哪两类来决定间距。Ord 与 Ord 之间没有空白;Op 与 Ord 之间有一个 细空白(\thinmuskip,3mu)\sin 是 Op,\mathrm{sin} 是 Ord,差别由此而来。在 10pt 正文中测量下面四项,这套机制会直接化为数字。

measured on TeX Live 2024, 10pt article
\DeclareMathOperator{\myop}{op}
% \sbox0{$...$}\message{\the\wd0}

$a\operatorname{op}b$        23.46638pt
$a\myop b$                   23.46638pt   % identical to \operatorname
$a\mathop{\mathrm{op}}b$     23.46638pt   % same font as \mathrm, but Op class
$a\mathrm{op}b$              20.13313pt   % 3.33325pt narrower

$\mkern3mu$  1.66663pt        % one thin space; two of them = 3.33326pt

第三行是决定性的。\mathop{\mathrm{op}} 使用的字体与 \mathrm 完全相同,宽度却与 \operatorname 精确到十万分之一磅一致。可见造成差别的不是字体,而是 \mathop 赋予的 Op 类别。而 3.33325pt 的差恰好是两个 1.66663pt 的细空白——a 与 “op” 之间一个,“op” 与 b 之间一个。“\mathrm 会让间距出错”这句流传已久的说法,至此精确到小数点后每一位都得到了解释。实用结论很简单:函数名一律用专用命令或 \operatorname 书写,绝不用 \mathrm

内置的 32 个函数名,以及它们写在哪里

\log\sin 的定义并不在某个宏包里,而在 LaTeX 本身的格式文件 latex.ltx 中。在 TeX Live 2024 里,它们占据第 12487 行到第 12518 行——正好 32 行——每行形如 \DeclareRobustCommand\log{\mathop{\operator@font log}\nolimits}。这份清单还要更古老:几乎同样的一组就在高德纳的 plain.tex 第 1058–1085 行。所以能写 \sin 并不是 amsmath 的功劳,它从一开始就可用,无需任何宏包(只有自定义算子时才需要 amsmath,见下文)。

  • 三角函数\sin \cos \tan \cot \sec \csc,以及反函数 \arcsin \arccos \arctan
  • 双曲函数\sinh \cosh \tanh \coth——注意 \sech\csch 并不包含在内,需要时请自行声明。
  • 对数与指数\log(一般)、\ln(自然对数)、\lg(以 2 为底,信息论常用)、\exp
  • 代数、几何等\deg(次数)、\dim(维数)、\ker(核)、\hom\arg(幅角)、\det(行列式)。
  • 把标记放在正下方的 10 个\lim \limsup \liminf \max \min \sup \inf \det \Pr \gcd

底数和指数直接使用普通的上下标机制:\log_2 x 把 2 放在 “log” 右下,\sin^2\theta 把 2 放在右上,得到惯用的 sin²θ。有一个不读源码就不会注意的小细节:\limsup 的定义是 \mathop{\operator@font lim\,sup}——“lim” 与 “sup” 之间手工放了一个 \, 细空白。它不是一个连写的词,而是两个被刻意稍稍分开的词。

latex
\[
  \sin^2\theta + \cos^2\theta = 1, \qquad
  \log_2 8 = 3, \qquad \ln e = 1.
\]

把标记放在正下方的 10 个,以及为何 \limits 无效

32 个当中有 22 个在定义末尾带 \nolimits,因此标记出现在右下方。其余 10 个——\lim \limsup \liminf \max \min \sup \inf \det \Pr \gcd——没有写 \nolimits。由于 \mathop 在陈列样式下的默认行为是“标记放在正下方”,所以正是这 10 个会在陈列公式中把 x→0 放在 \lim 之下。极限和最大值应当醒目地显示“在什么范围上”,这条数学惯例被直接写进了定义。在文本样式(行内)下,这 10 个也退回到右下方。

latex
\[
  \lim_{x \to 0} \frac{\sin x}{x} = 1, \qquad
  \max_{1 \le i \le n} a_i .
\]

这时很自然会想:既然 \limits\sum\int 有效,把它用在函数名上不就行了。可是 在加载 amsmath 的情况下,\log\limits_{k} 什么也不会发生——没有报错,没有警告,被静静忽略。原因在 amsopn.sty 第 27 行:\def\nolimits@{\@ifnextchar\limits{\nolimits\@gobble}{\nolimits}}。函数名的定义在末尾调用这个 \nolimits@,而 如果下一个记号是 \limits,它就先输出 \nolimits,再把那个 \limits 吃掉。实测也可佐证。

measured on TeX Live 2024, 10pt article
% depth of the box tells us where the script went (bigger = below the operator)

% WITHOUT amsmath — \log is \mathop{...}\nolimits, and \limits overrides it
$\displaystyle\log_{k}x$          d = 2.44443pt
$\displaystyle\log\limits_{k}x$   d = 9.47220pt   % moved below

% WITH amsmath — the \limits is swallowed by \nolimits@
$\displaystyle\log_{k}x$          d = 2.44443pt
$\displaystyle\log\limits_{k}x$   d = 2.44443pt   % unchanged: nothing happened

也就是说,\limits 的一般规则(可用于 \sum 等大型算子,详见“求和、积分与大型算子”)并不适用于 amsmath 下的函数名。反方向的 \nolimits 则能顺利通过,所以 \lim\nolimits_{k} 确实可以把 \lim 的标记赶到右下方。若想把标记放到正下方,正确答案不是 \limits,而是 下一节的带星声明

\DeclareMathOperator\operatorname:星号决定什么

当你要排一个不在清单里的名字——符号函数 sgn、迹 tr、秩 rankess supargmax——就轮到 amsmath 出场了。临时使用写 \operatorname{sgn} x;反复使用则在导言区 声明\DeclareMathOperator{\sgn}{sgn}。正如本页开头的实测所示,声明得到的 \sgn\operatorname{sgn} 宽度完全一致——它们调用同一个 \qopname,字面上就是同一样东西。声明的好处不在质量,而在于 集中于一处:以后想改记号,只需改导言区的一行。

preamble
\usepackage{amsmath}
\DeclareMathOperator{\sgn}{sgn}            % scripts to the right, like \log
\DeclareMathOperator*{\argmax}{arg\,max}   % scripts underneath, like \lim

% in the body:
% \[ \sgn x, \qquad \argmax_{x \in S} f(x) \]

星号决定的只有标记的位置。 不带星号得到的是 \log 一族(右下);带星号得到的是 \lim 一族(在陈列公式中位于正下方)。临时使用时,\operatorname*{…} 效果相同。这一点同样可以量出来:把同一个 op 排在陈列公式中并加上 _{n\to\infty},盒子的深度在无星号时为 2.44443pt,有星号时为 8.94444pt。多出来的深度,正是垂在符号下方的那个标记。名字里夹的 \,arg\,max)与 \limsup 的定义出于同一理由:在两个词之间留出恰当的空白。

amsmath 还补充了上下极限一带的词汇。\varlimsup\varliminf 是在 “lim” 上方(下方)加一条横线 的变体,比 \limsup 的 “lim sup” 拼法更具符号性。\injlim\projlim 把正极限与逆极限排成 “inj lim”“proj lim”;\varinjlim\varprojlim 则给出范畴论中常见的形态,在 “lim” 下方铺一支 →(或 ←)。它们和带星声明一样,标记都放在正下方。选哪一个应遵循所在领域的惯例;拿不准时,先看看投稿目标的样式文件是否已经定义过。

\bmod \pmod \mod \pod:用实测区分四种模写法

四种模写法之所以让人犹豫,是因为它们都打印出 “mod”,区别只在空白与括号\bmod\pmod 属于标准 LaTeX,\mod\pod 来自 amsmath。设计上的差异从定义一眼可见:只有 \bmod 是用 \mathbin{…} 定义的——也就是作为 Bin 原子,与 +- 同一类别——其余三个只是先插入一段空白再排出内容,相当于一条后置的注记。

amsmath.sty, lines 905-912
\DeclareRobustCommand{\bmod}{\nonscript\mskip-\medmuskip\mkern5mu\mathbin
  {\operator@font mod}\penalty900
  \mkern5mu\nonscript\mskip-\medmuskip}
\DeclareRobustCommand{\pod}[1]{\allowbreak
  \if@display\mkern18mu\else\mkern8mu\fi(#1)}
\DeclareRobustCommand{\pmod}[1]{\pod{{\operator@font mod}\mkern6mu#1}}
\DeclareRobustCommand{\mod}[1]{\allowbreak\if@display\mkern18mu
  \else\mkern12mu\fi{\operator@font mod}\,\,#1}

请注意 \if@display\pod\pmod陈列公式中放 18mu,行内放 8mu\mod 则是 18mu 与 12mu\bmod 没有这个分支——作为二元运算符,它只是左右对称地各放一个 \mkern5mu——因此它是四者中 唯一在陈列与行内宽度相同的一个。用 10pt 测量同一个式子 a … n,设计就直接化为数字。

命令输出宽度 行内 → 陈列使用场合
a \bmod na mod n36.01039pt → 36.01039pt(不变)当你要写作为 的余数时。属 Bin 原子,故左右对称。标准 LaTeX
a \pmod{n}a (mod n)46.01036pt → 51.56578pt同余式 x \equiv y \pmod{n} 末尾的常规写法。标准 LaTeX
a \mod{n}a mod n40.45473pt → 43.78798pt用于不喜欢括号的同余写法;前置空白比 \bmod 更宽。需要 amsmath
a \pod{n}a (n)23.51038pt → 29.06580pt省去 “mod” 只保留括号,适用于模数在上下文中显而易见时。需要 amsmath

把行内宽度从陈列宽度中减去,定义就精确地重现出来。\pmod\pod 的差是 5.55542pt——即 18mu − 8mu = 10mu,在 10pt 下为 9.99976 − 4.44434 = 5.55542pt。\mod 的差是 3.33325pt,对应 18mu − 12mu = 6mu。\bmod 的差为 0。二者的取舍取决于含义。若你说的是 作为值的余数,用 \bmod5 \bmod 3 = 2);若你断言 两数在模 n 下同余,用 \pmod17 \equiv 5 \pmod{12})。把这两者弄混,读者就无法分辨你说的是“值”还是“关系”。

latex
\[
  5 \bmod 3 = 2, \qquad
  17 \equiv 5 \pmod{12}.
\]