行间、对齐与编号公式

多行陈列公式在 LaTeX 中真正难的不是在哪里断行,而是 结果会带几个公式编号align 每行一个,multline 整条公式一个,而 split 自己一个都没有,要从外层结构借一个。搞错了,后面的编号全部错位,交叉引用也随之作废。本页就沿着「有几个编号」这条主线梳理 amsmath 的环境——equationaligngathermultlinesplitcases——再讲用 \notag\tagsubequations 控制编号,以及用 \label\eqref 引用。最后谈 eqnarray,并用一个实测数据说明为什么不该再用它。

先加载 amsmath——并且环境内绝不留空行

本页除 equation 之外的所有环境都来自 amsmath,所以导言区那一行是一切的前提。amsmath 一举填补了原生 LaTeX 几乎空白的多行公式领域,加入 alignalign*gathergather*multlinemultline*alignatalignat*flalignflalign* 以及 split。想把公式编号放在左边距就传 \usepackage[leqno]{amsmath},放在右边(横排默认)用 [reqno],要让陈列公式左对齐则用 [fleqn]

latex
\usepackage{amsmath}

在别的之前,先把第一颗地雷拆掉:这些环境内部绝对不能有空行。 一旦有,就会看到 ! Paragraph ended before \align was complete.。原因在历史里。AMS 的技术说明写道,这些陈列环境把内容 作为一个宏参数整体读入,再多趟扫描来确定宽度与位置——所用算法继承自 Michael Spivak 的 amstex.tex。空行对 TeX 而言就是 \par,而 \par 越不过未声明为 \long 的宏参数。这条报错正是 amsmath 至今仍流着 1980 年代 AMS-TeX 血液的直接证据。

带编号的一行:equation 环境

起点是 equation 环境。它把内容排在独立的一行上,并在右边距(横排默认)添加连续编号。编号由 equation 计数器保管,加上 \label{…} 后就能在正文中用 \ref{…}\eqref{…} 调用。若只是不想要编号,用带星号的 equation*——载入 amsmath 之后,它与 \[\] 完全是一回事。简言之,equation一行公式一个编号,不多也不少。

latex
\begin{equation}
  \label{eq:euler}
  e^{i\pi} + 1 = 0
\end{equation}

这会把欧拉恒等式居中排在独立一行上,行右端带一个类似 (1) 的编号(编号是文档中的连续计数,不一定是 1)。若公式一行放不下,或想对齐多条公式,请转向下面的环境。一个警告:绝不要把带编号的环境嵌进 \[\]。写成 \[ \begin{align} … \end{align} \] 会得到 ! Package amsmath Error: Erroneous nesting of equation structures;——外面那层 \[\] 纯属多余。

对齐多行:aligngather 的区别

align 按关系符号对齐,gather 不对齐、只把公式居中堆叠。差别仅此而已,两者都会给每一行编号(带星号的 align*gather* 不编号)。在 align 中,用 \\ 分行,并在要对齐的位置放一个 &——按惯例放在等号、不等号等 关系符号的正前方& 左边右对齐,右边左对齐,于是两侧正好在关系符号处相接。amsmath 手册明确写道:把 & 放在关系符号 之后 会破坏正常间距——而旧的 eqnarray 恰恰就是那样设计的。

latex
\begin{align}
  (x + y)^2 &= x^2 + 2xy + y^2 \\
  (x - y)^2 &= x^2 - 2xy + y^2
\end{align}

这会把两条公式竖直堆叠,两个等号处于同一水平位置,每行右端各有一个编号。最后一行之后不要再写 \\——amsmath 手册对此有明确警告,因为它会在公式下方添加多余的垂直空白。最常见的失误是在 gather 里写 &:那里没有对齐列,于是会得到 ! Extra alignment tab has been changed to \cr.。在 split 的一行里放两个 & 也会看到同一条报错。

align 也能 把多条公式横向并排& 同时充当对齐标记和列分隔符:一组写成「右对齐部分 & 关系符号加左对齐部分」,再用另一个 & 与下一组分开。列与列之间的间距由 amsmath 自动按固定宽度插入。若想自己决定这个间距,用 alignat(它以列对数为参数,列间不留空,由你用 \quad 之类自行插入);若想把列撑到版心整宽,则用 flalign

latex
\begin{align}
  x      &= y       & X      &= Y       & a   &= b + c \\
  x'     &= y'      & X'     &= Y'      & a'  &= b      \\
  x + x' &= y + y'  & X + X' &= Y + Y'  & a'b &= c'b
\end{align}

相对地,gather 完全不做对齐:每一行各自独立居中。行用 \\ 分隔,每行都有编号(gather* 则没有)。把 align 记作「在关系符号处对齐」,把 gather 记作「只是居中堆叠」,选择就不会犹豫。gather 的某一行里也可以放下面要讲的 split,让这一行再拆成多行。

latex
\begin{gather}
  a^2 + b^2 = c^2 \\
  e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!}
\end{gather}

当一个很长的 align 撞上分页处时,LaTeX 默认不会断开它:amsmath 把 \interdisplaylinepenalty 设成了最大值。如果文档里有几十行的推导,在导言区写 \allowdisplaybreaks 就能放宽这一禁令。可选参数从 [1][4],由勉强到宽松,默认是最宽松的 [4]。若想禁止在某一行之后断页,把该行的 \\ 写成 \\*

拆分一条长公式:multlinesplit

有两个环境用来拆分一行放不下的 一条公式,它们都与 aligngather 有决定性的不同:一条公式,一个编号。multline 不做任何对齐。它把 第一行靠左、最后一行靠右,中间各行居中。所谓「靠左」并不真的贴边:实测中第一行从左边距向内缩进 10pt,即 \multlinegap 的默认值。整条公式的编号出现在 最后一行leqno 下则在第一行)。因为是一条公式一个编号,所以 不要用 \notag 去消掉个别行。要把中间行推向某一侧,就把整行(不含随后的 \\)交给 \shoveleft{…}\shoveright{…}。无编号形式是 multline*

latex
\begin{multline}
  a + b + c + d + e + f \\
  + g + h + i + j + k + l + m + n
\end{multline}

若希望拆开的各行 相互对齐,就用 split。与 align 一样,split& 处对齐,但只允许一个对齐列(每行一个 &),并且 自身不带编号。把它放进 equation(或 aligngather)这样的带编号结构内部,整体由外层提供一个编号。单独使用会得到 ! Package amsmath Error: \begin{split} won't work here.split 的内容应占满外层结构的主体;只有像 \label 这类不产生输出的命令可以与它并列。它不能放进 multline 里。

latex
\begin{equation}
  \label{eq:cosh}
  \begin{split}
    \cosh x &= \frac{e^x + e^{-x}}{2} \\
            &= 1 + \frac{x^2}{2!} + \frac{x^4}{4!} + \cdots
  \end{split}
\end{equation}
环境公式与编号对齐主要用途
equation一条 / 一个一行陈列公式
align多条 / 每行&按关系符号堆叠并对齐
gather多条 / 每行无(各行居中)居中堆叠,不对齐
multline一条 / 一个(在最后一行)无(左到右)拆分长公式,不对齐
split一条 / 编号由外层提供一个 &对齐一条拆行公式的各行

分段定义:cases

按条件分段定义函数时用 cases 环境。左侧的大花括号会自动出现;每一行用 & 分开「值」和「条件」,行与行用 \\ 分隔。条件里的文字(如「if …」)若直接裸写,会因为仍处在数学模式而变成斜体且字距过紧——请用 \text{…} 包起来。cases 自身没有编号,通常放进 equation 里,由外层给整体一个编号。

latex
\begin{equation}
  |x| =
  \begin{cases}
    x  & \text{if } x \ge 0, \\
    -x & \text{if } x < 0.
  \end{cases}
\end{equation}

这会在一个大花括号右侧放两行——x(条件 x ≥ 0)与 −x(条件 x < 0)——并由 equation 给出一个编号。值列与条件列都左对齐。有一个坑:cases 的内容以文本样式排版。实测中,cases\frac{1}{2} 的数字高 6.19pt,而同一个分数放在普通陈列公式中是 8.85pt。若想让分数保持大号,请用 mathtools 包的 dcases——同样的环境,但以陈列样式排版。

控制编号:\notag\tagsubequations

在带编号的环境里,每行都会自动编号,而你可以逐行调整。有一个事实知道了会安心:\notag 的那一行不会「消耗」编号。 amsmath 重新定义了 \nonumber,抑制某行时会把计数器回退一格——amsmath.sty 里就写着 \addtocounter{equation}\m@ne。把三行 align 的第二行加上 \notag,编号会是 (1)、无、(2),而下一个公式是 (3)。编号序列不会留下空洞。

  • 只去掉某一行的编号:在该行的 \\ 前放 \notag。它的同义词是 \nonumber;在 amsmath 中,\notag 就是被定义为 \nonumber 的别名。
  • 自己决定编号\tag{…} 会用你给的内容代替自动编号排版,并自动补上括号。\tag*{…} 不加括号,按原样印出参数。\tag{$\star$} 得到 (⋆);\tag*{NP} 得到 NP。
  • 把相关公式编成 (4a)、(4b):用 subequations 环境包住,内部公式会得到父编号加字母。紧跟 \begin{subequations}\label 指向 父编号 (4),而非子编号 (4a)。
  • 按章或节重新起编:在导言区写 \numberwithin{equation}{section},第 2 节的第一个公式就变成 (2.1)。这是长文档中防止编号涨到四位数的常规做法。
latex
\begin{align}
  y &= (x + 1)^2    \notag \\
    &= x^2 + 2x + 1 \\
  E &= mc^2         \tag{$\star$}
\end{align}

\begin{subequations}
  \label{eq:max}
  \begin{align}
    \nabla \cdot \mathbf{E} &= \frac{\rho}{\varepsilon_0} \label{eq:gauss} \\
    \nabla \cdot \mathbf{B} &= 0
  \end{align}
\end{subequations}

上面的 align 中,第一行因 \notag 而无编号,第二行照常自动编号,第三行用 (⋆) 取代自动编号。把推导的中间步骤留作无编号、只给结论编号,是论文里最常见的写法。下面的 subequations 中,两条麦克斯韦方程被编为 (4a) 和 (4b);随后 \eqref{eq:gauss} 返回 (4a),而 \eqref{eq:max} 返回代表整组的 (4)。

\label\eqref 引用公式

给带编号的公式加上 \label{…},就能在正文里按编号调用。\ref{…} 返回裸编号(比如 3.2),而 amsmath 的 \eqref{…} 会补上括号,返回 (3.2)。在 amsmath.sty 里整个 \eqref 被包在 \textup{…} 中,所以即使周围是斜体,括号仍保持正体——你在定理陈述中第一次引用公式时就会注意到这个差别。多行环境里,把 \label 放在你想要编号的 那一行。引用要经过辅助文件,因此需要 编译两次 才能定下来。

latex
By~\eqref{eq:euler}, we have $e^{i\pi} = -1$.

% preamble: number equations within sections, giving (2.1), (2.2), ...
\numberwithin{equation}{section}

编号本身的样子由 \theequation 决定。article 的默认是 \arabic{equation}bookreport 的定义是「若章计数器大于零,就前置 \thechapter.,然后是连续编号」——book.cls 里就是这么写的,所以前置部分会得到没有章号的裸编号。你可以直接重定义格式,例如 \renewcommand{\theequation}{\thesection.\arabic{equation}},但通常还是 \numberwithin 更稳妥,因为它连计数器的重置也一并安排好了。

不要用 eqnarray:等号两侧宽了 3.6 倍

旧文档会用 eqnarray(以及 eqnarray*)排多行公式。不要用。理由可以用尺寸而非观感来说明。看 latex.ltx\eqnarray 的定义,关系符号那一列的两侧各有一个 \hskip \tw@\arraycolsep——而 \arraycolsep 默认是 5pt,也就是 每侧 10pt。同一条公式若用 alignequation 排,每侧得到的是 \thickmuskip,即 2.78pt。在 10pt 文档中实测:9.97pt 对 2.77pt,相差 3.6 倍。更糟的是,那 10pt 是从表格列距继承来的 刚性尺寸,数学字号变化时它不会跟着变。

第二项实害是公式编号。把长行放进 eqnarray,编号会 叠印在公式上。实测中最后一项占据横向 483 到 505pt,而编号 (1) 占 492 到 505pt——正好重叠。把同样的内容放进 align,amsmath 会检测到碰撞并把编号移到下一行。AMS 的 Short Math Guide for LaTeX 劝阻 eqnarray 时列出的正是这两条理由:等号间距不一致,以及不设法防止编号叠印在公式主体上。amsmath 手册还补充了第三条:定理宏包的 \qed\qedhere 在它内部无法工作。

替换是机械的。要对齐多条公式,用 align;要拆分一条公式,用 equationsplit& 的位置也要改——eqnarray 的设计是把 & 放在关系符号 之后,而 align 系列放在 之前,所以 x &=& y 要写成 x &= y。接手一份稿件时,这项替换是首先该做的事。