数学技巧

LaTeX 中用于微调公式的工具,几乎都出自同一个想法:把外观与该元素所占的尺寸(高度、深度、宽度)分开\smash 让高个子的高度算作零,\vphantom 什么也不打印却预留高度,\rlap 打印字符却占零宽度。因此像「只有含根号的那一行行距被撑开」「相邻两个 \sqrt 的顶端对不齐」「求和号下的条件把式子横向撑宽」这些症状,都能用同一套思路解决。本页按症状编排,逐一给出 \smash\phantom\mathstrut\rlap\raisebox,以及 \mathrm\operatorname 的差别,并附实测数值。

按症状找工具

先给出一览。它们都是用来在最后阶段整理印刷结果的工具,不会改变式子的含义。因此正确的做法是:先老老实实把公式写好,只在真的出现可见瑕疵的地方再补上这些。若提前到处撒 \smash\phantom,一旦更换字体或改变正文宽度,反而会错位。第一列的命令名本身就是检索词,请先想好症状再横向查阅。

命令能修的症状出处
\smash高个子元素把那一行的行距撑开LaTeX 内核([t][b] 来自 amsmath)
\vphantom相邻的根号或各行高度不齐plain TeX(LaTeX 中可直接使用)
\mathstrut同上,但无需参数:预留一个圆括号的高度plain TeX;其定义就是 \vphantom(
\phantom想让多行的列对齐,但左侧长度不同plain TeX;只要宽度则用 \hphantom
\mathclap求和或连乘下的长条件把整个式子撑宽mathtools(\mathllap\mathrlap 同属一组)
\rlap想打印内容但不让它计入宽度plain TeX;向左伸出的 \llap 同理
\overset想在 = 之类的正上方放任意符号amsmath(正下方用 \underset
\operatorname自定义的多字母算子前后没有间距amsmath;要把极限放到下方用 \operatorname*
\raisebox只想让某个元素上下移动固定的量LaTeX 内核;还能覆盖其高度与深度
\!符号之间离得太远,例如重积分 \int\intplain TeX;与 \, 正好相反,为 −3mu

只有含根号的那一行行距变宽 — \smash

\smash{...} 会让 TeX 把内容当作高度与深度都为零的盒子。墨迹照常打印,但由于 TeX 看不到纵向尺寸,就不会再决定加大行距。实测 $\sqrt{1-x^2}$ 的高度 9.13329pt、深度 1.26668pt、宽度 35.75691pt。用 \smash 包住后变成高度 0.0pt、深度 0.0pt,而宽度仍是 35.75691pt。数字清楚表明:只有纵向被归零,横向完全没动。

latex
% the radical inside the fraction pushes this line away from the one above
$y = \frac{1}{\sqrt{1 - x^2}}$

% zero its vertical size, and the leading returns to normal
$y = \frac{1}{\smash{\sqrt{1 - x^2}}}$

加载 amsmath 后,\smash 会获得一个可选参数,从而能分别处理上下。\smash[t]{...} 只把高度归零而保留深度;\smash[b]{...} 只把深度归零而保留高度。实测 \smash[t]{\sqrt{1-x^2}} 为高度 0.0pt、深度 1.26668pt,\smash[b]{\sqrt{1-x^2}} 为高度 9.13329pt、深度 0.0pt——恰好只消掉你指定的那一侧。这个 [t][b] 并不在 LaTeX 内核的 \smash 里,而是 amsmath 覆盖该命令后添加的(不带参数的 \smash 本身可追溯到 plain TeX,LaTeX 内核中也有)。

latex
% the descender of y drops the middle radical below the other two
$\sqrt{x} + \sqrt{y} + \sqrt{z}$

% smash away only the depth of y, and all three line up
$\sqrt{x} + \sqrt{\smash[b]{y}} + \sqrt{z}$

amsmath 自己给出的例子把这个差别展示得很干净。在 \sqrt{x} + \sqrt{y} + \sqrt{z} 中,y 带有下伸部,因此中间那个根号会略微下沉。用数字看,$\sqrt{y}$ 高 7.0305pt、深 3.36946pt,而 $\sqrt{x}$ 高 8.00272pt、深 2.39725pt。可是量一下 $\sqrt{\smash[b]{y}}$,得到 高 8.00272pt、深 2.39725pt——与 $\sqrt{x}$ 连小数位都完全一致。仅仅消掉深度,根号就回到了与 x 之上完全相同的尺寸。

有两点要注意。其一,消失的只是尺寸,墨迹仍在,所以被压平的元素可能撞到上一行。在行距较紧的正文中,务必用眼睛确认结果。其二,旧的说明里常写「在段首使用 \smash 时要先加 \leavevmode」,但在如今的 LaTeX 内核中已无此必要latex.ltx 中的 \finsm@sh\leavevmode@ifvmode\box\z@ 结尾,会自行处理垂直模式。实际在段首使用 \smash 并没有报错。

相邻根号的顶端对不齐 — \vphantom\mathstrut

如果说 \smash 是「把高的压下来」,那么 \vphantom 就是「把矮的抬上去」的相反操作\vphantom{...} 造出一个与参数同高同深、但宽度为零且什么都不打印的盒子。实测 $\vphantom{a^2}$ 高 8.14003pt、宽 0.0pt。与之对应的 $\hphantom{a^2}$ 高 0.0pt、宽 9.77202pt,二者兼备的 $\phantom{a^2}$ 高 8.14003pt、宽 9.77202pt。可见只管纵向、只管横向、两者兼管这三件工具配得整整齐齐。把较高内容的 \vphantom 放进较矮的元素里,高度就在什么都看不见的情况下对齐了。

latex
% level the tops of several radicals with one strut each
$\sqrt{\mathstrut a}\;\sqrt{\mathstrut a^2}\;\sqrt{\mathstrut b}$

% or match a specific height by passing the taller content
$\sqrt{a} \;\sqrt{\vphantom{a^2}\,a}$

若每次都要写出对齐目标的内容太麻烦,还有一个不带参数的捷径 \mathstrut。它在 plain TeX 中的定义是 \def\mathstrut{\vphantom(}内容不过是「一个看不见的左圆括号」。数学中的圆括号高度适中,又会随字号变化,因此正好可以当作对齐行高的标尺。实测也吻合:$($ 高 7.5pt、深 2.5pt。$a$ 高 4.30554pt、深 0.0pt,但 $\mathstrut a$ 变成 高 7.5pt、深 2.5pt——恰是圆括号的尺寸——而宽度仍是 5.28589pt 未变。在每个元素前放一个,就都对齐到一个圆括号的高度。

align 中对齐 = 的位置 — \phantom\MoveEqLeft

把想对齐的那个表达式本身传给 \phantom 连着写变形过程时,第二行起往往想省去左端、直接从 = 开始;但若用普通空格推过去,一旦字体或式子变化就会错位。写 \phantom{f(x)},就得到与实际排出 f(x) 完全同宽的空白,于是 = 自然纵向对齐。与手工数出来的一串 \quad 不同,内容变了空白的宽度也跟着变,这正是它的长处。

latex
\begin{align*}
  f(x)           &= x^2 + 2x + 1 \\
  \phantom{f(x)} &= (x + 1)^2
\end{align*}

% mathtools: when the first line is too long to keep on one row
\begin{align*}
  \MoveEqLeft f(x, y, z) = a + b \\
              &= c + d
\end{align*}

反过来,当第一行的左端太长、放不进 = 的位置时,可以用 mathtools 的 \MoveEqLeft。它把首行推到对齐点的左侧,并把列排布好,使后续各行自然落在其下方。只需放在行首,不必自己去数 &。另外,\phantom\hphantom 的一般机制,以及数学间距的整体内容,「角标与间距」页面有详细讨论。

放上去却不占宽度 — \rlap\llap\mathclap

这一组是 \smash横向版本\rlap{...} 会打印内容,同时把盒子的宽度设为零,于是内容向右溢出。plain TeX 中的定义只有一行——\def\rlap#1{\hbox to\z@{#1\hss}}——「造一个零宽的盒子,把多出来的部分用可无限伸展的胶水推向右边」,仅此而已。实测 \rlap{XX} 的宽度是 0.0pt,而裸写的 XX 是 15.00003pt。向左溢出的 \llap 只是把 \hss 放到前面。它们常用于溢出表格列的注记,以及把行号推到版心之外。

想在公式内部做同样的事,就用 mathtools 的 \mathclap(左右对称溢出)、\mathllap(向左)、\mathrlap(向右)。典型场景是求和号下的长条件:只要包成 \sum_{\mathclap{1 \le i \le j \le n}} a_{ij},式子的宽度就回到运算符本身的宽度。这一组连同 \smashoperator 以及正文用的 \clap,在 mathtools 页面有详细讨论。另外,若不想横向压缩、而是想把条件堆成多行,正解是 amsmath 的 \substack{...}——写 \sum_{\substack{0<i<m \\ 0<j<n}},两个条件就以 \\ 分隔,成为求和号下方的两行。

在公式中写文字 — \text\mathrm\operatorname 的区别

三者的差别只在两点:是否继承周围字体,以及是否加入算子间距。先说字体。\text(由 amsmath 附带的 amstext 定义)会原样继承周围的正文字体,而 \mathrm始终固定为正体罗马字。实测很鲜明:在 \textit{...} 内,\text{ab} 为 14.24867pt,而 \mathrm{ab} 为 14.61809pt。在 \textbf{...} 内,\text{ab} 为 15.64928pt,而 \mathrm{ab} 依旧是 14.61809pt——\mathrm 无论上下文如何变化,结果一位不差地相同。希望与正文同一语气阅读的词用 \text,必须始终正体的单位和下标标签用 \mathrm

第二点——间距——才是分开 \mathrm\operatorname 的关键。\operatorname(由 amsmath 附带的 amsopn 定义)会把内容当作算子(\mathop处理,因此前后会自动出现一口气的空隙。实测 $\mathrm{max} x$ 为 24.32645pt,$\operatorname{max} x$ 为 25.99307pt——相差 1.66662pt,恰好是一个 3mu 的细空。两侧都有运算对象时,$a \mathrm{mod} b$$a \operatorname{mod} b$ 是 28.74428pt 对 32.07753pt,相差 3.33325pt 即 6mu(左右各 3mu)。有趣的是,后面紧跟 ( 时,$\mathrm{max}(x)$$\operatorname{max}(x)$ 都是 32.10425pt,完全相同——因为 TeX 的规则在算子与左括号之间不插入空隙。所以只看带括号的例子就断言「一样」为时过早。若想把极限放到符号下方,就用带星的 \operatorname*{max}_{x}(实测 18.61118pt,而不带星的为 23.6459pt,角标排在旁边)。

latex
\[
  x_{\text{total}} \quad x_{\mathrm{total}}
  \qquad
  a \operatorname{mod} b \quad a \mathrm{mod} b
\]

% define it once in the preamble instead of repeating \operatorname
\DeclareMathOperator{\rank}{rank}
\DeclareMathOperator*{\argmax}{arg\,max}

若同一个算子要反复出现,在导言区一次性定义比每次敲 \operatorname 更稳妥。amsmath 的 \DeclareMathOperator{\rank}{rank} 会把 \rank 造成算子,带星的 \DeclareMathOperator*{\argmax}{arg\,max} 则造出把极限放到下方的版本。名字与拼写集中在一处,日后要改写法也只需改一行。

上下移动与收紧 — \raisebox\!

\raisebox{量}{内容}把内容抬高指定的量(传负值则下移)。实测 X 高 6.83331pt,\raisebox{2pt}{X} 高 8.83331pt——正好抬高 2pt。再给两个可选参数写成 \raisebox{量}[高度][深度]{内容},还能覆盖它向周围申报的高度与深度。量一下 \raisebox{2pt}[0pt][0pt]{X},高度 0.0pt、深度 0.0pt——先移动、再变成与 \smash 相同的「零尺寸」,一招两用。当符号位置需要极细微的修正、或换了字体导致上下错位时,它就派上用场。

最后是符号之间离得太远的情形。负细空 \! 在 plain TeX 中定义为 \def\!{\mskip-\thinmuskip},即 \,(3mu)的精确反面,−3mu。实测 $ab$ 为 9.57755pt,$a\,b$ 为 11.24417pt(+1.66662pt),$a\!b$ 为 7.91092pt(−1.66663pt)——对称得很干净。典型场合是重积分:$\int\int$ 量得 14.99997pt,空得像两个分开的积分。夹进两个 \! 后,$\int\!\!\int$ 量得 11.66672pt。而 amsmath 的专用命令 $\iint$ 也是 11.66672pt——连小数位都完全一致。也就是说,老派的 \int\!\!\int 与专用命令给出分毫不差的结果。话虽如此,既然有意图明确的 \iint\iiint,用它们更易读;\! 宜视作没有现成命令时的工具。

latex
\[
  \int\int f \, dA
  \qquad
  \int\!\!\int f \, dA
  \qquad
  \iint f \, dA
\]

% an arbitrary symbol set squarely above another
\[
  a \overset{!}{=} b, \qquad x \overset{?}{=} y,
  \qquad A \overset{f}{\longrightarrow} B
\]

最后一项:把符号放在 = 的正上方。用 ^_ 写的角标会附在符号的右上与右下,绝不会正落在上方或下方。amsmath 的 \overset{上}{底}\underset{下}{底} 承担这个职责,\overset{!}{=} 是「理应相等」的惯用写法,\overset{?}{=} 则是「这相等吗」。第二个参数是底座,第一个是要叠加的东西。更进一步的用法——用于箭头、彼此嵌套——在「上下装饰」页面讨论。