LaTeX 的数学模式里没有正文那种“切换字体”的概念。它有的是 数学字母表——\mathbf、\mathbb、\mathcal 之类的命令,把括起来的每个字符整体指派给另一种书体。麻烦在于名额有限:在经典引擎上,一个公式最多只能用 16 个。这个上限定于 1978 年,四十多年后依然生效,并且会突然以 ! LaTeX Error: Too many math alphabets used in version normal. 的形式冒出来。本页从无需宏包的七个命令讲起,经过需要 amssymb 的黑板粗体 \mathbb、需要 mathrsfs 的 \mathscr,到数学粗体 \bm,再到 unicode-math 的 \sym…,贯穿其中的只有一个问题:这 16 个名额该怎么分配。
数学字母表是「族」,不是字体
进入数学模式的字母会被逐个视为变量,并以 数学斜体 排版;这就是默认的数学字母表。用 \mathbf{v} 这样的命令包住,就只把这一段切换到另一个族——在 TeX 的术语里叫 \fam。这正是数学字母表与正文字体命令的根本差别。TeX 为公式中的每个字符保存「第几族、第几号」,所以书体不像正文那样是连续的流,而是 贴在每个符号上的标签。因此 \mathbf{fit} 不是单词 “fit”,而是三个粗体符号 f、i、t 的乘积,字母之间会插入变量之间的间距。要在公式里放一个真正的直立单词,请使用 amsmath 的 \text{…}。
标准 LaTeX 自带七个数学字母表命令。它们的名字都是 \math… 的形式,与正文模式的 \textbf、\textsf 等一一对应。容易混淆的是 \mathnormal 与 \mathit:\mathnormal(默认)把每个字母作为变量排版,变量之间的间距较宽;\mathit 是文本斜体,适合像 \mathit{diff} 这样把 多字母词作为一个整体的斜体。\mathrm 和 \mathbf 是直立罗马体,后者为粗体。\mathsf 常见于张量和范畴名称,\mathtt 则用于把类似代码的记号放进公式。
| 命令 | 外观 | 备注 |
|---|---|---|
\mathnormal | 默认数学斜体 | 内置;变量的默认外观,字距按变量间距 |
\mathrm | 直立罗马体 | 内置;用于直立的常数、运算符和缩写 |
\mathbf | 直立粗体 | 内置;因是直立体不适合变量,且对希腊字母无效 |
\mathsf | 无衬线体 | 内置;用于张量、范畴名称等 |
\mathtt | 等宽(打字机体) | 内置;公式中的代码式记号 |
\mathit | 文本斜体 | 内置;把多字母词作为整体倾斜,如 \mathit{diff} |
\mathcal | 花体;仅大写 | 内置;集合族、范畴、变换名。字形来自 Computer Modern 的 cmsy |
\mathbb、\mathscr、\mathfrak 分别需要哪个宏包
黑板粗体 \mathbb 和 Fraktur \mathfrak 需要 amssymb(或更轻的 amsfonts);script 体 \mathscr 需要 mathrsfs。只有 \mathcal 是内置的。\mathbb 是笔画带双线的粗体,源于在黑板上重复描线表示粗体的写法,用途几乎固定为数集:实数 ℝ、复数 ℂ、整数 ℤ、有理数 ℚ、自然数 ℕ。它只收录大写 A–Z,没有小写,也没有数字。
\mathcal 与 \mathscr 的取舍,知道来历之后就不再纠结。\mathscr 载入的 rsfs 是「Ralph Smith’s Formal Script」的缩写,1991 年 10 月发布。作者在 README 中说明,这是一种基于 18 世纪 Spencerian/Copperplate 手写体的 正式 书体,仿照字体设计师 R. Hunter Middleton 的作品,接近 Springer Verlag 排版中所见的样子。相比之下,\mathcal 是 Computer Modern 符号字体 cmsy 中那套更 随性 的装饰大写。所以 ℒ 与 𝓛 是「正式手写体」和「轻松花体」的对比,谈不上谁对谁错。泛函分析中的定义域 𝒟、场论中的拉格朗日量 ℒ 偏爱 rsfs,正是因为这份正式感。
\mathfrak 背后的字体也有名字。amssymb 写的是 \DeclareMathAlphabet{\mathfrak}{U}{euf}{m}{n},其中 euf 就是 Euler Fraktur——AMSFonts 的 README 指出,Euler 系列字体由 赫尔曼·察普夫 设计,最初发布于 1990 年代。同一份 README 还记载,察普夫重新调整了许多字形的 Euler 3.0 在 2008 年 1 月 10 日高德纳生日当天赠予本人。所以你用来写 Lie 代数 𝔤 和理想 𝔭 的那套棱角字体,出自 20 世纪最重要的字体设计师之手,而且不同于 \mathbb 和 \mathcal,它 大写小写都有。euscript 宏包提供的 Euler Script(eus)属于同一家族。
\usepackage{amssymb} % \mathbb and \mathfrak
\usepackage{mathrsfs} % \mathscr (Ralph Smith's Formal Script)
% ...
\[
\mathbb{R} \subset \mathbb{C}, \qquad
\mathcal{F}(f) = \hat{f}, \qquad
\mathscr{L}(f) = F, \qquad
\mathfrak{g} = \operatorname{Lie}(G).
\]| 命令 | 所需宏包 | 覆盖范围与典型用途 |
|---|---|---|
\mathbb | amssymb(或 amsfonts) | 仅大写 A–Z;数集 ℝ ℂ ℤ ℚ ℕ |
\mathcal | 内置(无需) | 仅大写;集合族、范畴、变换名。字形来自 cmsy |
\mathscr | mathrsfs(或 euscript) | 仅大写;更正式的手写体。字形来自 rsfs10 |
\mathfrak | amssymb / amsfonts(euf) | 大写与小写;Lie 代数、理想。Euler Fraktur |
\mathbb{2} 不会报错,而是排出另一个符号
想得到黑板粗体的 1 或 2 而写下 \mathbb{1}、\mathbb{2},结果是 既无警告也无错误,却排出了完全不同的符号。在 TeX Live 2024 上用 amssymb 排 $\mathbb{2}$,再把字符从 PDF 中取出,得到的是 U+22AD(⊭,not true)。原因就在数学字母表的机制本身:\mathbb 只是到 msbm 字体里取同一个位置,于是 msbm10 中 2 位上的符号——也就是 ⊭——原样输出。没有任何机制检查该字符是否存在。需要指示函数 𝟙 时,请用 dsfont 的 \mathds{1}(Type 1 字体 dsrom10),或用 mathalpha 宏包整体替换黑板粗体字母表。bbm 的 \mathbbm{1} 也很有名,但 TeX Live 附带的版本没有 Type 1 轮廓,会作为 Type 3 位图 嵌入 PDF(用 pdffonts 即可看到),因此在忌讳位图字体的投稿规范下最好避开。
% A blackboard-bold digit is NOT available from amssymb:
% $\mathbb{2}$ -> U+22AD, silently. Use one of these instead.
\usepackage{dsfont} % \mathds{1}, Type 1 outlines
\usepackage[bb=dsserif,scr=rsfs]{mathalpha} % replace the whole \mathbb alphabet
% ...
\[ \mathds{1}_{A}(x), \qquad \mathbb{1}_{A}(x) \]数学字母表最多 16 个 —— Too many math alphabets
不断添加字母表,迟早会撞上 ! LaTeX Error: Too many math alphabets used in version normal.。上限是 16。TeX 的数学字符编码只给族号留了 4 位,所以族只能是 0 到 15。latex.ltx 里直白地写着 \chardef\e@mathgroup@top=16,而在拥有 \Umathcode 的引擎(XeTeX、LuaTeX)上,这个值被提升到 256。一个空白的 article 启动时就已占用 4 个族(operators、letters、symbols、largesymbols),加上 amssymb 和 mathrsfs 变成 7 个。余下大约 9 个名额,\mathbf、\mathsf、\mathtt、\mathit、\mathcal、\mathbb、\mathfrak、\mathscr 会依次占据。这份预算与其说是「每个公式」,不如说是「每篇文档」——名额按首次使用的顺序填满,而且不会归还。
同样这 16 个名额还有第二道关卡。注册符号字体的 \DeclareSymbolFont 在 latex.ltx 中由 硬编码 的判断 \ifnum\count18<15 守着,超过就会得到 ! LaTeX Error: Too many symbol fonts declared.。这里是关键陷阱:15 这个数字与引擎无关,所以尽管 LuaLaTeX 允许 256 个字母表,符号字体仍然封顶在 16 个。写下第 17 个 \DeclareSymbolFont,在 pdfLaTeX 和 LuaLaTeX 下都会报同一个错。只有当你亲自调用底层的 \newfam 并越过上限时,才会看到 TeX 自己的 ! No room for a new \mathgroup.——注意是 \mathgroup 而不是 \fam,因为 LaTeX2e 把 \newfam 设成了 \new@mathgroup 的别名。
也有缓解办法。较新的 LaTeX 内核会 把 16 个名额中的最后两个预留为「循环使用」(\c@localmathalphabets 默认值为 2)。当正文中需要第 15、第 16 个字母表时,内核把名额借给那一个公式,公式结束后再收回。所以只要单个公式所需的额外字母表不超过两个,整篇文档越过 14 个也不会停下——实测中计数器就停在 14,且没有报错。反过来说,把三个以上的新字母表塞进同一个公式,就会在那里断掉。当错误只在某一个公式上出现时,请先怀疑这一点。isomath 的文档也明确说明,正是因为这 16 个上限,它才把自己的 \mathsfit 设为可选。如果不想再算这笔账,迁移到 XeLaTeX 或 LuaLaTeX 并使用 unicode-math 才是根本解决。
自己添加字母表 —— \DeclareMathAlphabet
新建一个数学字母表只需一行:\DeclareMathAlphabet{命令}{编码}{族}{系列}{形状}。它只能写在导言区。想给张量准备一套倾斜的无衬线体?写下 \DeclareMathAlphabet{\mathsfsl}{OT1}{cmss}{m}{sl} 即可。如果希望它在粗体数学(下文的 \mathversion{bold})中也自然加粗,再补上 \SetMathAlphabet{\mathsfsl}{bold}{OT1}{cmss}{bx}{sl}。若只是想给已注册为符号字体的东西起个别名,\DeclareSymbolFontAlphabet 不消耗名额 ——mathrsfs.sty 的全部内容实际上就是这两行。还有一点值得记住:\DeclareMathAlphabet 在声明时并不占用名额,而是在 第一次真正使用时 才分配。仅仅载入的宏包不会吃掉你的预算,这一点让排查容易得多。
% preamble only
\DeclareMathAlphabet{\mathsfsl}{OT1}{cmss}{m}{sl}
\SetMathAlphabet{\mathsfsl}{bold}{OT1}{cmss}{bx}{sl}
% costs no slot: just another name for a symbol font already declared
\DeclareSymbolFont{rsfs}{U}{rsfs}{m}{n}
\DeclareSymbolFontAlphabet{\mathscr}{rsfs}
% ...
\[ \mathsfsl{T}_{ij} \]让数学变粗 —— \mathbf、\boldsymbol、\bm 的区别
\mathbf 是 直立粗体,因此既不适合应当倾斜的变量,也不适合向量,而且 对希腊字母完全无效。实测让这一点变得具体:用 pdfLaTeX 编译一份只含 $\mathbf{\alpha}$ 的文档,再用 pdffonts 查看,嵌入的字体 只有一个:CMMI10。也就是说 α 仍然是普通数学斜体,而不是粗体的 CMMIB10。这是设计使然——\mathbf 切换的是族,而 α 所在的位置在切换后字形并不改变。既不报错也不警告,所以这类事故 常常在毫无察觉的情况下被提交出去。
想在保持斜体的同时加粗,第一候选是 amsmath(内部的 amsbsy)提供的 \boldsymbol,而实践中最稳健的是 bm 宏包的 \bm。bm 属于 LaTeX 必需工具集 latex-tools,标准 TeX 发行版都自带,无需额外安装。\bm{…} 的设计目标是「只给参数 增加字重,保持斜体等其他外观以及前后间距不变」,因此变量、希腊字母、符号,乃至复杂输入,几乎都能自然加粗。bm 还会把 \boldsymbol 重新定义为 \bm 的别名。但 \bm 并非免费:bm.sty 有一个名为 \bmmax 的上限(默认 4),用来限制它为粗体版本占用的族数。粗体数学字母表同样从那 16 个名额中扣除,所以它也要算进上一节的预算。不够用时可以用 \chardef\bmmax=6 提高,但普通字母表的余额会相应减少。
当整条公式都要变粗时——比如出现在标题里或图注中——切换的应该是 数学版本,而不是去找某个命令。\boldmath 和 \mathversion{bold} 正是干这件事的;标准 LaTeX 自带 normal 和 bold 两个版本。你也可以自建第三个:先 \DeclareMathVersion{heavy},再用 \SetMathAlphabet{…}{heavy}{…} 逐一登记,然后 \mathversion{heavy} 就能整体切换。bm 的 \hm(别名 \heavysymbol)就是面向这个 heavy 版本的命令,\hmmax 默认为 3。但要注意:如果你并未真正定义 heavy 版本,\hm 只是 \bm,所以 写 \hm 本身并不会比 \bm 更粗。
\usepackage{amsmath} % \boldsymbol
\usepackage{bm} % \bm -- recommended
% \chardef\bmmax=6 % raise bm's family budget if it runs out
% ...
\[
\mathbf{v} \neq \bm{v}, \qquad
\bm{\beta} = (\beta_1, \dots, \beta_n), \qquad
\nabla \bm{F} = \bm{0}.
\]
\section{\boldmath A whole heading in bold maths: $E = mc^2$}\mathbf— 直立粗体;内置,但不适合应为斜体的变量,也对希腊字母无效(上文已实测)。\boldsymbol— 来自 amsmath(amsbsy);保持斜体,并能加粗希腊字母和符号。\bm— 来自 bm 宏包;推荐。只增加字重,保持字形和前后间距。占用的族数受\bmmax(默认 4)限制。\boldmath/\mathversion{bold}— 切换整个数学版本而非某一段;用于标题和图注中的公式。
unicode-math 的 \sym… —— 字母表映射到 Unicode
在 XeLaTeX 或 LuaLaTeX 上载入 unicode-math,字母表命令就会被 \sym… 一族取代:\symbf(粗体)、\symbb(黑板粗体)、\symcal(花体)、\symscr(script)、\symfrak(Fraktur)、\symup(直立)、\symit(斜体)、\symsf(无衬线)。旧名称仍然保留,但 \mathbb 实际上已被重定义为 \symbb 的别名,\mathcal 则是 \symcal 的别名。决定性的差别在于:\sym… 不是切换族,而是映射到 Unicode 的数学字母数字符号。在 LuaLaTeX 下用 STIX Two Math 排 $\symbb{2}\ \symbb{r}\ \symcal{L}$,再把字符从 PDF 中取出,得到的是 U+1D7DA(𝟚)、U+1D563(𝕣)、U+2112(ℒ)。上一节里排出 ⊭ 的 \mathbb{2},在这里成了真正的黑板粗体 2,而且连小写也齐备。从 PDF 复制出的字符能保留语义,靠的正是这套映射。
这套映射有一个不提醒就一定会踩的后果:\symcal 与 \symscr 映射到同一批 Unicode 字符。Unicode 的数学字母数字符号中只有一套 script 字母,因此 \symcal{L} 和 \symscr{L} 都是 U+2112。想让两者在外观上区分开,就要通过 range= 调用字体的 OpenType 样式集。使用 STIX Two Math 时,在主 \setmathfont 之后补上 \setmathfont{STIX Two Math}[range=\symscr,StylisticSet=1];实测显示,这一行让 \symcal{L} 宽 6.99pt,而 \symscr{L} 宽 8.63pt——数字证实选中了不同的字形。传统方式靠占用两个字母表名额来区分的事,在 unicode-math 里变成了 指定一项字体特性。
% compile with xelatex or lualatex
\usepackage{amsmath}
\usepackage{unicode-math}
\setmathfont{STIX Two Math}
\setmathfont{STIX Two Math}[range=\symscr,StylisticSet=1] % a second script shape
% ...
\[
\symbb{R} \subset \symbb{C}, \qquad \symbb{2}, \qquad
\symcal{F} \neq \symscr{F}, \qquad \symfrak{g}, \qquad
\symbfit{v} = \symbf{0}.
\]最后关于在 unicode-math 环境下使用 \symbf 的一点提醒。在默认设置(bold-style=TeX)下,\symbf{v} 是粗体的「直立体」。实测中 \symbf{v} 得到 U+1D42F(粗体罗马 v),而 \symbfit{v} 得到 U+1D497(粗斜体 v)。若你的规范要求向量用粗斜体,请使用 \symbfit,或指定 bold-style=ISO。至于整篇文档的数学字体该怎么选——给 \setmathfont 传什么、如何与正文书体协调——那是另一页的主题。