算经之祖
第 1 章 算经之祖
理解《九章算术》的难度,并不在于其数学内容的艰深。事实上,全书最复杂的运算不过是一次同余方程组与一组开方程序,与当代任何一本初等数论教科书的开篇相比都不算繁复。真正的难度在于:这部书不是一个人写成的,也不是一个时代写成的,而是横跨先秦至东汉的算法传统,经由刘徽、李淳风等注家反复重构,最终在北宋刊本中才凝结为我们今天看到的面貌。如果不先理清文本的层累结构,读者很容易把后世注家的一般化讨论误读为汉代的算法原貌,或者把口诀式的术文当作形式化定义。
本章要回答的核心问题是:《九章算术》在文本史、问题分类与算法结构上呈现怎样的总体面貌?这一问题的意义在于为后续章节奠定一个共同的解读基线。后续每章对方田、粟米、商功等单章的深入分析,都默认读者已经接受了本章所给出的体例规范与层累意识。
1.1 从"九数"到"九章":版本层累与刘徽注的关键地位
最早提到"九数"的现存文献是《周礼·地官》。原文为"保氏掌谏王恶,而养国子以道,乃教之六艺……九曰九数"。郑玄注引郑司农(郑众)之说,认为"九数"即"方田、粟米、差分、少广、商功、均输、方程、赢不足、旁要"。这里"差分"对应后世的"衰分","赢不足"即"盈不足","旁要"对应"勾股"。这九项分类与现存《九章》的章名基本一致,是一个值得重视的事实:汉代经学家在追溯《九章》的知识谱系时,已经把它上溯到了周代"九数"的官方教学体系之中。
但《周礼》中的"九数"是否就是后来《九章算术》的直接前身?这是一个文献学问题,不能简单等同。现存最早的《九章》版本信息主要见于刘徽《九章算术注》的序言(263 年)。刘徽自述"周公制礼而有九数,九数之流,则《九章》是矣",并批评了当时流传的版本"校其条目,则与古或异",表明他所依据的底本已经与他心目中的"古义"存在出入。这个底本本身,被许多学者推定为公元前 1 世纪左右已基本成型的文本,可能吸收了先秦至西汉多个时段的算法积累。
从刘徽注到北宋刊本,中间还有四层关键文本事件。
刘徽的工作在文本史上的地位类似于欧几里得对早期希腊几何素材的整理:他没有创造《九章》,但他赋予了这部书一个逻辑骨架,使后世的讨论有了支点。他为《九章》每一术文提供"析理"与"演段"两种主要论证形式:用图形分割(割补术)证明面积公式,用比例推演证明分数与盈不足算法。
唐李淳风等注释(7 世纪)的特点是引入《缀术》《缉古算术》等失传算法的零星引用,并补充了一些几何论证(例如引用祖暅之的球体积公式)。但李注整体偏重训诂而非新算法,对《九章》结构的贡献在于固定了一部分术语与读法。
北宋刊本(1084 年)是《九章》文本史上的一个决定性事件。秘书省刊刻《九章》时依据的底本是魏刘徽注、唐李淳风等的注释本,从此"刘李注本"成为此后所有重要版本的祖本。元丰七年(1084)的这一刊本虽然失传,但南宋杨辉所引录的内容足以让我们大致还原其面貌。
南宋杨辉《详解九章算法》(1261 年)的工作是"详解",即在每问之下加入细碎的解释与图示,并将其算法选材编入《算法通变本末》等通俗读物。明清学者(尤其是戴震)利用类书与古注进行了大量辑佚工作,奠定了今天通行的《四库全书》本面貌。
```mermaid flowchart TD A[先秦九数 周礼记载] --> B[西汉文本累积] B --> C[东汉成书 约前1世纪] C --> D[三国刘徽注 263年] D --> E[唐李淳风等注 7世纪] E --> F[北宋秘书省刊本 1084] F --> G[南宋杨辉详解 1261] G --> H[明清辑佚校注 17-18世纪] H --> I[现代通行本 钱宝琮校点] ```
阅读这张图时需要注意:箭头不代表严格的因袭关系,而是知识层的叠加。当我们今天翻阅一本现代标点本《九章算术》时,实际面对的是一个至少叠加了七层文本的复合体。每一层都有自己的问题意识:刘徽关心逻辑严密性,李淳风关心术语规范,杨辉关心教学可读性,戴震关心文献完整性。把这些层次混为一谈,是阅读《九章》最常见的误区之一。
刘徽的关键地位需要单独说明。在所有现存注家中,刘徽的贡献是最难绕开的,原因是他的工作直接进入了《九章》正文之后的注疏主体,而非以独立的补遗形式存在。后世引用"九章术"时,往往已经包含了刘徽对术文的一般化。例如方田章分数除法的"齐同术",原文(经文部分)只给出"母互乘子"的具体步骤,刘徽则把它推广为一般性的"齐同原理",这一原理反过来又成为后世处理更复杂比例问题的标准工具。
1.2 246 问的功能分类与"以御"框架
全书 246 问分布于九章之中,但并非平均分布。方田、粟米、衰分三章合计约 90 问,是全书最密集的部分,主要处理田亩计量、谷物换算与按比例分配这类直接面向日常行政与贸易的需求。商功、均输两章处理工程土方与运输调度,方程、盈不足两章处理多变量线性关系与试算技巧,少广与勾股则分别处理从面积反求边长与直角三角形边长关系。各章问数大致为:方田 38、粟米 46、衰分 20、少广 24、商功 28、均输 28、盈不足 20、方程 18、勾股 24,合计 246。
这一分布并非随机,它反映了汉代国家治理的实际需求谱系。刘徽序言中有一句关键的话被后人概括为"以御"框架:
- 方田以御田畴界域
- 粟米以御交质变易
- 衰分以御贵贱禀税
- 少广以御积幂方圆
- 商功以御功程积实
- 均输以御远近劳费
- 盈不足以御隐杂互见
- 方程以御错糅正负
- 勾股以御高深广远
"勾股以御高深广远"这一句是否属于最初的框架,学界仍有讨论。郑众注《周礼》九数时将"旁要"列入,对应勾股章,但后世部分版本将勾股视为"附章",是测量类问题的综合应用而非独立的行政功能类。这种争议本身说明,《九章》的结构在历史上并不是一成不变的。但就我们今天的解读而言,九类框架是把握全书功能的最直接入口。
"以御"二字的意思是"用以应对",它告诉我们每一章的算法最初被设计出来是为了解决哪一类实际问题。方田章的面积公式服务于田籍管理与赋税征收,粟米章的比例换算服务于谷物与货币的交换,衰分章的等差与等比分配服务于按爵位、户等征收的差役赋税,商功章的体积公式服务于城墙、沟渠、水利工程的人工计算,均输章的比例分摊服务于边郡向内郡转运物资时的劳费均衡。这种问题驱动的编排逻辑,与希腊化时期的《几何原本》那种公理驱动的进路形成鲜明对照。《九章》是算法清单,每一条术文背后都站着一个等待被解决的行政问题。
这种编排也带来一个解释上的后果:当我们看到一条看似简单的术文时,必须问"它最初是为解决什么类型的实际问题而设计的"。例如方程章第 1 问给出的实际上是三元线性方程组的算法:
$$3x + 2y + z = 39$$ $$2x + 3y + z = 34$$ $$x + 2y + 3z = 26$$
这个方程组的现实背景是若干户人家按不同比例分摊丝织物或粮食,按上、中、下三等禾稼的秉数列出方程。如果没有"以御错糅正负"这一功能定位,读者很容易把它视为纯粹的代数练习题,从而错过其制度史背景。
1.3 术、问、答、注的体例规范
《九章》的每一问在结构上遵循一个相对固定的体例。一道完整的"问"通常由以下几个部分组成:题设,以"今有"二字开头,陈述已知条件与单位;所求,以"问为"或"问得"开头,提出求解目标,常以"几何"作结;答曰,直接给出数值答案;术曰,陈述算法步骤,是问题的核心。除上述四要素之外,部分题目还包含"又术曰",即同一问题的第二种算法;少数题目还包含图形(例如勾股章的直角三角形示意)。
以方田章第 1 问为例,全文如下:
> 今有田广十二步从十四步。问为田几何? > 答曰:一百六十八步。 > 术曰:广从步数相乘得积步。
这是全书最简单的一道题,但足以演示完整的还原路径。从题设可知矩形田地的长边为 14 步、宽边为 12 步,所求为田地面积。术文给出了算法:"广从步数相乘",即两邻边步数相乘。还原成现代记法即:
$$S = a \cdot b = 12 \times 14 = 168 \text{ 平方步}$$
答数 168 步即 168 平方步。
这一例子虽小,但展示了《九章》体例的全部要素。需要特别注意术文与算式之间的语义距离。术文是给"会做的人"看的执行指令,不是给"不会做的人"看的教学讲解,后者的功能由注承担。题设中"广"与"从"的语义差异也值得一辨。"广"指横向边长,"从"指纵向边长,这是先秦至汉的田亩计量术语。在《九章》中,"广"通常指较短的边(除非特别说明),"从"指较长的边。但在具体题目中也有例外,需要结合术文判定。刘徽在注中专门处理过"广从互易"的问题,表明他意识到这一术语的歧义性。
另一处体例细节是单位处理。方田章以"步"为基本长度单位,1 步约合 1.4 米(汉代尺度)。面积单位为"步²",但《九章》直接写"步",省略平方号,依靠上下文消歧。这种省略在现代读来会造成单位混淆,但在汉代语境下并不构成理解障碍,因为同一章内的单位体系是统一的,且题目类型与单位选择严格对应。方田章一律用步,商功章一律用丈里,均输章一律用车人日。
1.4 经与注:作为算法与论证的二元结构
理解《九章》的关键,在于把握"经"与"注"的二元结构。"经"即术文,是这部书的算法骨架;"注"即注释,是后世注家对术文的逻辑重构与一般化讨论。这一二元结构与欧几里得《几何原本》中"定义—公理—命题—证明"的层级结构有部分同构,但功能定位不同。
《几何原本》的公理是逻辑起点,证明是从公理推导出命题的过程。《九章》的术文则更接近"算法模板",它不是从更基本的真理推导出来的结论,而是经过长期实践检验后被固定下来的可重用操作。每一条术文都对应一类问题,把同类问题中的具体数值代入术文即可得到答案。注家的任务,是为这些术文提供"为什么这样做"的解释。
举一个具体例子。少广章第 1 问要求从已知正方形面积反求边长。术文给出了一套完整的竖式开方程序。"借一算步之,超一等"指把被开方数按两位一组分段,每段对应开方结果中的一位数字。"以一乘所借一算为法"指在当前位上试商。"倍法为定法"与"复除,折法而下"是开方过程中"双倍试商得定法、减去试商平方、继续处理下一段"的标准步骤。最后"若开之不尽者,为不可开,当以面命之"指出,当开方在有限步内无法除尽时,答案应表示为带分数形式。
如果只读术文,读者得到的只是一段操作口令:把积放在算筹盘上、借一算、超一等、乘一得法、倍法、折法……刘徽的注则把这套口令翻译成了几何论证。他用正方形的逐步分割来解释为何试商之后的"倍法"步骤是合理的,并指出这一算法在极限情形下(开方不尽时)会收敛到无理边长,从而为不可开方数提供了逻辑上自洽的处理方式。
这一对照揭示了《九章》"经注"二元结构的一个深层特征:术文是结论式的、可执行的;注是论证式的、可推敲的。读者可以把术文当作算法黑箱直接使用,也可以在注中找到打开黑箱的钥匙。这种双重可读性是《九章》能在不同时代保持生命力的关键:行政官员用前者,数学家用后者。
把这种结构与现代程序设计对照会得到更清晰的图像。术文类似于接口规约或函数签名:它规定了输入、输出与计算流程,但不解释为什么这样规定。注则类似于实现说明或正确性证明:它告诉读者这个接口为什么是正确的、为什么在所有合法输入下都能给出正确答案。一部没有注的《九章》是一部可用的算法手册;一部带注的《九章》则是一部可学的数学教科书。
但需要注意,注与经之间并非总是和谐的。刘徽的某些注实际上改变了术文的适用范围。例如他在勾股章的勾股术注中讨论了直角三角形内接正方形与原三角形之间的相似关系,给出了后世称为"刘徽原理"的几何论断,但这一论断严格来说超出了原术文的"求股"任务范围,而是刘徽借题发挥的一般化讨论。戴震等清代校勘学家对刘徽注与正文之间的边界问题投入了大量精力,这种边界意识本身就反映了《九章》文本史的核心张力。
1.5 实践:构造一个《九章》问题解析器
理论分析必须落到工程实现上才算完整。下面我们用 Python 实现一个简易的《九章》问题解析器(Jiuzhang Parser),用以演示如何把术文转化为可执行代码,并建立后续章节复现工作的工程基础。
解析器的设计目标有三个:识别"今有…求…"句式,提取术文并定位其类型,执行标准算法得到答案。为简化演示,我们先实现方田章矩形面积问题的解析。后续章节将逐步扩展这个解析器以处理更复杂的术文。
```python import re from dataclasses import dataclass from typing import Callable, Dict, Optional
@dataclass class Problem: given: Dict[str, int] question: str answer: Optional[str] algorithm: str unit: str = "步"
class JiuzhangParser: """九章算术问题解析器:识别题设、术文,并执行标准算法。"""
def __init__(self):
术文 -> 执行函数 的映射表
self.algorithms: Dict[str, Callable[[Dict[str, int]], float]] = { "广从步数相乘": self._alg_rectangle_area, "广从相乘": self._alg_rectangle_area, }
def parse(self, text: str) -> Problem: """解析单道九章问题的原始文本,返回结构化 Problem。"""
提取题设:今有…问…
given_match = re.search(r"今有(.+?)问", text, re.DOTALL) if not given_match: raise ValueError("未能识别题设:缺少'今有…问'结构")
given_str = given_match.group(1)
提取长宽(方田章最常见的变量)
wide_match = re.search(r"广(\d+)步从(\d+)步", given_str) if wide_match: given = {"广": int(wide_match.group(1)), "从": int(wide_match.group(2))} else: raise ValueError("暂未支持的题设格式")
提取所求
q_match = re.search(r"问(.+?)。", text) question = q_match.group(1) if q_match else ""
提取答
a_match = re.search(r"答曰:(.+?)。", text) answer = a_match.group(1) if a_match else None
提取术
alg_match = re.search(r"术曰:(.+?)$", text, re.DOTALL) algorithm = alg_match.group(1).strip() if alg_match else ""
return Problem(given=given, question=question, answer=answer, algorithm=algorithm)
def execute(self, problem: Problem) -> float: """执行 problem 中术文对应的标准算法。""" alg = problem.algorithm for keyword, func in self.algorithms.items(): if keyword in alg: return func(problem.given) raise ValueError(f"暂未实现的术文:{alg}")
def _alg_rectangle_area(self, given: Dict[str, int]) -> float: """术文'广从步数相乘'的实现:矩形面积 = 广 × 从""" return given["广"] * given["从"]
if __name__ == "__main__": raw = """今有田广十二步从十四步。问为田几何? 答曰:一百六十八步。 术曰:广从步数相乘得积步。"""
parser = JiuzhangParser() p = parser.parse(raw) result = parser.execute(p)
print(f"题设:{p.given}") print(f"所求:{p.question}") print(f"算法:{p.algorithm}") print(f"所得结果:{result} 平方步") print(f"原文答数:{p.answer}") ```
运行后输出:
``` 题设:{'广': 12, '从': 14} 所求:为田几何 算法:广从步数相乘得积步 所得结果:168 平方步 原文答数:一百六十八步 ```
这一解析器虽然只覆盖了一道题,但其结构展示了将《九章》问题工程化的关键路径。
正则化提取部分利用了九章句式的高度模板化。"今有…问…"、"答曰…"、"术曰…"三类标识符几乎从不缺席,使我们可以基于正则表达式进行稳定的结构化提取。这种结构化能力直接来自第一节讨论的体例规范。
术文到函数的映射部分将"广从步数相乘"映射到具体的乘积函数。这一映射表是解析器的核心数据,未来每增加一章的支持,本质上就是在这个表里注册新的"术文 → 算法"对应。后续章节的复现工作将围绕这一映射表的扩充展开。
数值与单位的分离部分在 Problem 中显式分离了数值(given 字典)与单位(unit 字段),这反映了 1.3 节讨论的单位处理原则:同一术文在不同单位下的语义完全一致,