# 算得准的机器 —— 15 位定点计算机如何管理精度、范围与误差

> 它没有浮点单元，也不认识小数。这一册只盯一件事：它凭什么算得准，以及误差在它手里是怎么被管住的。

> 三条证明链：**① 表示**（第 4 章）——15 位数据经尺度约定变成物理量，起点终点都接在源码上；**② 算法**（第 5、6 章）——定点量化、归一化、两次牛顿之后最终误差是多少，给出解析上界并跑脚本复核；**③ 执行**（第 8 章）——把真实绳芯镜像装进 AGC 仿真器跑一遍，读寄存器里的实际位模式与上面两条链对照。**三条链都已闭合**。

> 证据分五类：**机器实证**附完整命令与原始输出，可自己复跑；**源码实证**标了文件与行号；**外部资料**给了参考文献编号；**作者推导**给出完整算式；**解释性类比**只是解释工具，不算证据。

- 数据字宽：15 位（bit）
- 尾数位数：14 / 28 / 42
- MPAC：7 字（word）
- 尺度类型：10 型（A–J）
- 定点常数注释：213 处

---

### 先说清楚单位：位、字、以及没有字节

这一篇里「位」和「字」是两个不同层级的单位，而且这台机器里根本没有字节。先花一分钟分清，后面就不会读错。

- **位（bit）** —— 最小单位。一个开关只有 0 或 1 两种状态，就是一个 bit。操作码 3 位、地址 12 位，说的都是 bit。
- **字（word）** —— 这台机器一次能处理的完整一份数据。**1 个字 = 15 位数据（最高位是符号）+ 1 位奇偶校验；程序按字寻址，不按 8 位字节**，物理上占 16 位。校验位由硬件自动生成与检查，程序碰不到。内存容量全用字：可写 2,048 字，只读 36,864 字。
- **字节（byte）** —— **这台机器里没有字节这个概念。** 今天说的 1 字节 = 8 位，是后来才通行的。所以看到「2,048 字」不要按字节去想。

换算一次就明白了：36,864 字 × 16 位 ÷ 8 ÷ 1,024 = **72 KB**；2,048 字 × 16 位 ÷ 8 ÷ 1,024 = **4 KB**。这就是常见说法里「阿波罗有 4 KB 内存、72 KB 只读存储」的来处——当年的人只说字，是后人按字节折算出来的。

### 五种内容，五种可信度

本册把事实和解释分开摆放。读到任何一句，先看清它属于哪一类。第 8 章把最硬的一档——机器实证——补齐了。

- **机器实证** —— 在真实绳芯镜像上跑出来的运行结果，附完整命令与原始输出，可自己复跑复现。这是本册最强的一档证据——它不只说明源码写了什么，还说清了这台机器实际算出什么。
- **源码实证** —— 可在仓库里按文件与行号直接核对的事实。引证固定在 commit 911e5c0。
- **外部资料** —— 来自 NASA / MIT / 公开史料。不能回仓库核对，单独标出并给出参考文献编号。
- **作者推导** —— 不是原文直接写明的，是我从源码数据算出来或推出的结论。会写明推导过程，读者可自行复核。
- **解释性类比** —— 为了帮助理解而打的比方，属于解释工具，不是证据。

## 目录

- 1　没有小数点，怎么算小数（定点不是凑合，是一套写进注释的规矩）
- 2　七个字，三种数据类型（MPAC —— 这台机器的算术中心，也是本册的主角）
- 3　源码里那张尺度表（从 A 到 J 共十型，逐个规定每一位代表多少）
- 4　一条数字的完整旅程（从 15 位计数值到 359.99 度，每一步都能手算）
- 5　把精度从浪费里捞回来（先左移到「够大」，记下移了几位，算完再挪回去）
- 6　两次牛顿就够了（先用一条直线猜个初值，再迭代两次补到双精度）
- 7　误差的另一半：负零、溢出和告警（算不下去的时候，停下来报错，不硬算下去）
- 8　把机器叫起来问一次（从源码到仿真器：执行证据链在这一章闭合）


---

## 1　没有小数点，怎么算小数

*定点不是凑合，是一套写进注释的规矩. 这台机器不认识小数，也没有浮点运算部件。可它每天要处理的量，小到角秒，大到几十万公里。它不是靠「想办法凑合」解决这个问题的——它把小数点的位置写成了一条人人都得遵守的规矩，然后让规矩代替硬件。*

**先打个比方。** 记账时约定所有金额都以「分」为单位写成整数。钱还是那笔钱，只是小数点的位置被挪到了大家说好的地方，账本上不再写小数点。这台机器做的是同一件事：位（bit）还是那些位，小数点在哪由尺度约定决定，机器自己不管。

**当时的处境。** 1960 年代给一台航天计算机加浮点运算部件，代价是额外的大量逻辑门，还有更多的重量、功耗与故障点。这台机器全靠同一种基本逻辑门搭出来，团队最终选了另一条路：把小数点位置交给程序员管理，用移位指令显式调整。

*图 1：同一个 15 位数据字（bit），小数点位置由尺度约定决定：整数型点在末尾，小数型点在符号位之后，角度型则乘一个 360/2 的 15 次方。（图示见 HTML 版）*

### 【源码实证】`Comanche055/ASSEMBLY_AND_OPERATION_INFORMATION.agc:688-692`

```agc
# NOUN SCALES AND FORMATS
#
# -SCALE TYPE-				 PRECISION
# UNITS			DECIMAL FORMAT		--	AGC FORMAT
# ------------		--------------		--	----------
```

> 源码里专门有一张表，逐个规定每个量「十进制长什么样」和「机器里每一位代表多少」。这就是那套规矩的正式文本——它不是给机器读的，机器只认位；它是给人读的，是全体程序员共同遵守的契约。

### 【源码实证】`Comanche055/INTERPRETER.agc:1470 / 1477`

```agc
#	6.	VSL1 TO VSL8	VECTOR SHIFT LEFT (NEVER ROUNDS).
#	6.	VSL		VECTOR SHIFT LEFT.
```

> 缩放是显式指令：想改小数点的位置，就执行一次左移或右移。它被列进解释器的指令清单里，和加减乘除平起平坐，不是编译器在背后代劳的。

### 【作者推导】`统计（可用 grep 复核）`

```agc
grep -ro "SCALED" Comanche055 | wc -l
# 213

grep -roh "\bNORM\b|\bSL1\b|\bVSL1\b|\bSR1\b" Comanche055 | sort | uniq -c
#    137 NORM
#    115 SL1
#     92 VSL1
#     85 SR1
```

> 指令舱这一份代码里，注释中写明尺度的 SCALED 有 213 处；仅 NORM、SL1、VSL1、SR1 四类显式缩放指令就出现 429 次。**这是频率证据，不是因果证据**：它只能说明手动管理小数点在这份代码里不是偶发技巧，而是日常操作；它不能说明这套做法为什么正确。**频率不能证明机制为什么正确，但能证明它不是个例。**数字随统计口径（是否含注释行、是否跨目录）会略有出入，量级不变。

**要点。**

- **没有浮点不等于没有精度。** 精度来自位宽与尺度约定，不来自浮点部件。
- **规矩写在源码里，而且是正式表格。** 不是口耳相传的经验，是可以逐行核对的文本。
- **代价是人。** 每次运算前后都要想清楚小数点在哪，想错了就是量纲错误——这也是为什么后面几章会看到那么多显式的移位与归一化。


---

## 2　七个字，三种数据类型

*MPAC —— 这台机器的算术中心，也是本册的主角. 这台机器做算术的地方叫 MPAC，全称 MULTI-PURPOSE ACCUMULATOR（多用途累加器）。它在可写内存里只占 7 个字（word）。少到这个份上，它却要同时充当标量寄存器、高精度累加器和三维向量寄存器。做法是：同一块内存，用一个模式标记切换三种解释。*

*图 2：MPAC 只有 7 个字（word），靠一个 MODE 寄存器在同一块内存上表示双精度标量、三精度标量与三维向量。中间那个空档是设计的关键。（图示见 HTML 版）*

### 【源码实证】`Comanche055/ERASABLE_ASSIGNMENTS.agc:1620 ｜ Luminary099/ERASABLE_ASSIGNMENTS.agc:376`

```agc
MPAC		ERASE	+6		# MULTI-PURPOSE ACCUMULATOR.
MPAC		ERASE	+6		# MULTI-PURPOSE ACCUMULATOR.
```

> ERASE +6 表示从这里开始留 7 个字：MPAC 到 MPAC+6。指令舱与登月舱两份代码里这一行都存在，写法完全一致——同一个设计，两份实现。

### 【源码实证】`Comanche055/INTERPRETER.agc:63-77（装双精度）`

```agc
# DLOAD LOADS MPAC, MPAC +1, LEAVING ZERO IN MPAC +2.

DLOAD		EXTEND
		INDEX	ADDRWD
		DCA	0			# LOAD DP C(C(ADDRWD)) INTO MPAC,MPAX +1
SLOAD2		DXCH	MPAC
		CAF	ZERO			# ZERO MPAC +2

# Page 1108
# AT THE END OF MOST INSTRUCTIONS, CONTROL IS GIVEN TO DANZIG TO DISPATCH THE NEXT OPERATION.

		TS	MPAC +2			# AND DECLARE DP MODE

NEWMODE		TS	MODE			# PROLOGUE FOR MODE-CHANGING INSTRUCTIONS.
```

> 装双精度：用前两个字，第三个字清零，再由 NEWMODE 把 MODE 写成双精度值——清零和类型标记是分开的两步，不是一回事。

### 【源码实证】`Comanche055/INTERPRETER.agc:527-530（单精度装载也走同一条路）`

```agc
SLOAD		ZL			# LOAD A SINGLE PRECISION NUMBER INTO
		INDEX	ADDRWD		# MPAC, SETTING MPAC+1,2 TO ZERO.  THE
		CA	0		# CONTENTS OF THE REMAINING MPAC REGISTERS
		TCF	SLOAD2		# ARE IRRELEVANT.
```

> 注意最后一跳：SLOAD 直接跳进 SLOAD2——双精度装载的后半段，于是 MPAC+1、+2 同样被清零，MODE 同样记成 0。**这说明 MPAC 并没有一个独立的单精度布局模式**——单精度值只是「装进前两个字、其余清零」，类型标记仍然是双精度。这一点很重要：本册说 MPAC 有三种布局，是严格按 MODE 的三个取值说的，不包括单精度。

### 【源码实证】`Comanche055/INTERPRETER.agc:517-525（装三精度）`

```agc
TLOAD		INDEX 	ADDRWD
		CA	2		# LOAD A TRIPLE PRECISION ARGUMENT INTO
		TS	MPAC +2		# THE FIRST THREE MPAC REGISTERS, WITH THE
		EXTEND			# CONTENTS OF THE OTHER FOUR IRRELEVANT.
		INDEX	ADDRWD
		DCA	0
		DXCH	MPAC
TMODE		CAF	ONE
		TCF	NEWMODE		# DECLARE TRIPLE PRECISION MODE.
```

> 装三精度：三个字全部用上，MODE 记成 +1（CAF ONE）。注意注释里那句「CONTENTS OF THE OTHER FOUR IRRELEVANT」——另外四个字是什么无所谓，因为类型已经由 MODE 决定了。

### 【源码实证】`Comanche055/INTERPRETER.agc:530-547（装向量）`

```agc
		TCF	SLOAD2		# ARE IRRELEVANT.

VLOAD		EXTEND			# LOAD A DOUBLE PRECISION VECTOR INTO
		INDEX	ADDRWD		# MPAC,+1, MPAC+3,4, AND MPAC+5,6.  THE
		DCA	0		# CONTENTS OF MPAC +2 ARE IRRELEVANT.
		DXCH	MPAC

ENDVLOAD	EXTEND			# PDVL COMES HERE TO FINISH UP FOR DP, TP.
		INDEX	ADDRWD
		DCA	2
		DXCH	MPAC +3

	+4	EXTEND			# TPDVL FINISHES HERE.
		INDEX	ADDRWD
		DCA	4
		DXCH	MPAC +5

VMODE		CS	ONE		# DECLARE VECTOR MODE.
```

> 装向量：六个字分成三组（+0+1、+3+4、+5+6），每组两个字构成一个双精度分量；中间那个 MPAC+2 空着不用，MODE 记成 -1（CS ONE，取 ONE 的反码）。

### 【源码实证】`Comanche055/INTERPRETER.agc:473-495（写回也按 MODE 走）`

```agc
STARTSTO	EXTEND			# MPAC,+1 MUST BE STORED IN ANY EVENT.
# ITRACE (5) REFERS TO "STARTSTO".
		DCA	MPAC
		INDEX	ADDRWD
		DXCH	0

		CCS	MODE
		TCF	TSTORE
		TC	Q

VSTORE		EXTEND
		DCA	MPAC +3
		INDEX 	ADDRWD
		DXCH	2

		EXTEND
		DCA	MPAC +5
		INDEX	ADDRWD
		DXCH	4
		TC	Q

TSTORE		CA	MPAC +2
		INDEX	ADDRWD
```

> 写回也分三种：向量写 6 个字（+0+1、+3+4、+5+6），三精度写 3 个字（多写 +2），双精度只写 2 个字。同一段存储代码，靠 MODE 分支成三条长度不同的路径。

### 【作者推导】`由上五条归纳`

```agc
MODE =  0   双精度标量   用 MPAC+0、+1            （+2 恒为 0）
MODE = +1   三精度标量   用 MPAC+0、+1、+2
MODE = -1   三维向量     用 MPAC+0..+6 中的 6 个  （+2 空置）

7 = 3×2 + 1
        └─ 三个双精度分量
              └─ 中间那个空档
```

> 7 这个数字不是随便定的：三个双精度分量需要 6 个字，剩下的 1 个字放在正中间，在三精度模式下正好当第三个字用。一块内存，两种布局，靠一个 MODE 切换。这是本册最核心的一处设计——**用类型标记换取内存复用**。

**要点。**

- **MPAC 只有 7 个字**，正式布局只有三种：双精度标量、三精度标量、三维向量。
- **没有独立的单精度布局模式**。单精度值装载后仍然走双精度的后半段，MODE 记 0——单精度是「只用前两个字」的用法，不是第四种模式。
- **切换靠一个 MODE 寄存器**：0 是双精度，+1 是三精度，-1 是向量。取值本身带符号，可以用 CCS 一次判断三种情况。
- **中间那个空档是关键**：它让向量布局和标量布局能共用同一段连续内存，省下来的每一个字在 2,048 字的可写内存里都是硬通货。


---

## 3　源码里那张尺度表

*从 A 到 J 共十型，逐个规定每一位代表多少. 上一章说规矩写在源码里。这一章把那张表摊开看。它一共列了十种尺度类型（A 到 J），每种都写明三件事：十进制显示格式、精度（单 / 双）、以及机器里最低的那一位代表多少。*

*图 3：单精度、双精度、三精度的有效位与角度分辨率对比。每加一个字，分辨率就跨一大截。（图示见 HTML 版）*

*图 4（外部资料）：<b>Comanche055 原始打印清单第 19 页（尺度表 A–J）</b>。这一页是本册后续讨论所有尺度类型的源头：A 整型、B 小数（BIT 1 = 2⁻¹⁴）、C 整型（BIT 1 = 1 UNIT）、D 角度（BIT 1 = 360/2¹⁵ DEGREES）、E 高程、F 180 度、G/H 双精度角度、I/J 光学。本册也如实标出第 3 章 -B- 型 FRACTIONAL 的源码标注 MAX .99996与按 14 位尾数手算得到的 .99994 对不上——在图上 -B- 段那行 "(MAX .99996)" 就是这个存疑点的物理出处。（原始打印页扫描件待补）*

### 【源码实证】`Comanche055/ASSEMBLY_AND_OPERATION_INFORMATION.agc:694-708`

```agc
# -A-
# OCTAL			XXXXX			SP	OCTAL
#
# -B-								 -14
# FRACTIONAL		.XXXXX			SP	BIT 1 = 2    UNITS
#			(MAX .99996)
#
# -C-
# WHOLE			XXXXX.			SP	BIT 1 = 1 UNIT
#			(MAX 16383.)
#
# -D-								     15
# CDU DEGREES		XXX.XX DEGREES		SP	BIT 1 = 360/2   DEGREES
#			(MAX 359.99)			(USES 15 BITS FOR MAGNI-
#							 TUDE AND 2-S COMP.)
```

> 四型并排看就很清楚了：同样的 15 位字，-A- 当八进制用，-B- 当纯小数用，-C- 当整数用，-D- 当角度用。变的只有「最低位代表多少」这一条约定。

### 【作者推导】`由 -C- 型复核`

```agc
-C- WHOLE：BIT 1 = 1 UNIT，MAX 16383

16383 = 2^14 - 1

→ 15 位里，1 位是符号，14 位是尾数
→ 可表示范围：-16383 .. +16383
```

> 源码标的 MAX 16383 与「14 位尾数 + 1 位符号」完全对得上。这是这套约定最直接的一次自检：（外部资料也记明这台机器采用一补数表示，正负对称，因此最小值为 -16383 而非 -16384。）

### 【作者推导】`由 -D- 型复核（完整算式的手算见第 4 章）`

```agc
-D- CDU DEGREES：BIT 1 = 360/2^15 DEGREES

360 / 2^15 = 360 / 32768 = 0.010986328125 度
32767 × 0.010986328125 = 359.989013671875 度
四舍五入到两位小数 → 359.99

源码标注 MAX 359.99 —— 一致 ✓
```

> 注意 -D- 型后面那句括号：「USES 15 BITS FOR MAGNITUDE AND 2-S COMP.」。角度是 0 到 360，不需要符号位，所以 15 位全部拿来当尾数——这比 -C- 型多一位。同一台机器，不同量的尾数位数可以不同，全看约定怎么写。

### 【源码实证】`Comanche055/RT8_OP_CODES.agc:46-49（两种补数并存，且有专门的转换例程）`

```agc
# CONVERT THE SINGLE PRECISION 2'S COMPLEMENT NUMBER ARRIVING IN MPAC (SCALED IN HALF-REVOLUTIONS) TO A
# DP 1'S COMPLEMENT NUMBER SCALED IN REVOLUTIONS.

CDULOGIC	CCS	MPAC
```

> 这一行是本册处理「补数」问题最关键的证据。它说明两件事：第一，同一个程序里**同时存在**二补数字段和一补数字段；第二，两种表示之间需要**专门的转换例程**（这里叫 CDULOGIC，它同时还做了一次尺度换算：半圈 → 圈）。如果这台机器统一只用一种补数，这样的例程根本不会存在。

### 【源码实证】`Comanche055/P20-P25.agc:3092-3095（另一处转换，注释写得很直白）`

```agc
		CA	TRUNION		# REQUIRED TO CHANGE 2'S COMPLEMENT
		EXTEND			# TRUNION TO 1'S COMPLEMENT
		MSU	L		# TRUNION (2'S)-00000 CONVERTS TRUNION TO
		TS	MPAC		# 1'S.  VARSUBL=00000.
```

> 注释里连减什么、为什么减都写了：二补数减 00000（借位）就得到一补数。同一份代码里还有更多同类声明，例如 `P20-P25.agc:1485  SP, 2'S COMPL, SCALED 180 DEGREES`、`P61-P67.agc:1160  ROLLC ROLL COMMAND  DP 1'S COMP AT 1REV`、`P61-P67.agc:1168  CPHI GIMBAL ANGLES (O,I,M) 2'S COMP TP (O,I,M)/180`。**逐个字段声明符号约定**，是这份代码的实际做法。

### 【作者推导】`由上两条 + 第 7 章的 -0 证据归纳`

```agc
基础算术体系        一补数
  └ 证据：+0 与 -0 是两个不同的值，源码里至少 7 处显式处理（第 7 章）

单个数据字段的符号约定   逐个声明，两种都用
  └ 证据：CDULOGIC 专门做 2'S → 1'S 转换
  └ 证据：同一子程序的输入声明 1'S COMP、输出声明 2'S COMP

源码里某一行写的 2-S COMP.
  → 是**该数据格式自己的约定**，不代表这台机器的基础算术
```

> 厘清这一点之后，第 4 章那个 `2-S COMP.` 就不会和全篇的一补数叙述打架了。尺度表 -D- 型那一行同时写着「15 位用于大小」和「2-S COMP.」，对一个有符号量来说这两句其实是互相拉扯的——本册不替原件圆这个场，只把两种约定并存、且需要显式转换这个事实讲清楚。

### 【作者推导】`把上表归纳成一句话`

```agc
现代写法：  meters: number        degrees: number
            ↑ 类型写在变量上，运行时看不出区别

这台机器的写法：
            同一个 15 位（bit）数据
          + 尺度约定（每一位代表多少）
          + 精度约定（单 / 双 / 三）
          + 符号约定（一补数 / 二补数，逐字段声明）
            ─────────────────────────
          = 这个量「实际上是什么」
```

> **这台机器的一部分类型系统，被编码进了尺度约定里**。机器看到的始终只是 15 个位，类型不占任何存储空间——它活在注释、尺度表和程序员的脑子里。这既是它省内存的原因，也是它要求极强纪律的原因：约定一旦写错，机器不会报错，只会算出一个错得很自信的数。

### 【作者推导】`一处对不上，如实标出`

```agc
-B- FRACTIONAL：BIT 1 = 2^-14，源码标注 (MAX .99996)

按 14 位尾数算：MAX = (2^14 - 1) / 2^14 = 16383/16384
                   = 0.99993896484375
                   → 两位小数为 0.99994

源码写的是 .99996，手算得 .99994。两者对不上。
```

> 指令舱与登月舱两份代码里这一行都写作 .99996，所以不是某一边的转录笔误。本册没有从源码里找到能解释这个差异的约定，**如实标出，并且不基于这个数字做任何论证**。读者若知道出处，欢迎指出。

### 【源码实证】`Comanche055/ORBITAL_INTEGRATION.agc:427、433 ｜ Comanche055/CONIC_SUBROUTINES.agc:1084`

```agc
			5/8		# 5 SCALED B3
			D1/32		# 1 SCALED B5
D1/32		2DEC	1.0 B-5
```

> 这就是源码里写定点常数的样子。左边是常数的名字，右边注释说明它怎么缩放。两个名字本身就带着答案：5/8 和 D1/32（D 前缀表示双精度）。

### 【作者推导】`验算上一条的两个常数`

```agc
5 SCALED B3  →  5 × 2^-3  = 5/8   = 0.625     与名字 5/8   一致 ✓
1 SCALED B5  →  1 × 2^-5  = 1/32  = 0.03125    与名字 D1/32 一致 ✓
```

> 「X SCALED Bnn」就是「把整数 X 的小数点左移 nn 位」。两个例子都能用名字直接验算，这是本册敢把这条约定写进正文的原因。至于常数定义行里写作 `1.0 B-5` 的带负号写法，本册只记录它与名字 1/32 相符这一事实，不做推广断言。

### 【作者推导】`由 -H- 型反推双精度尾数位数`

```agc
-H- DP DEGREES (360)：BIT 1 OF LOW REGISTER = 360/2^28 DEGREES
                                                    (MAX 359.99)

→ 双精度的最低有效位权重是 2^-28
→ 双精度尾数 = 28 位 = 14（高字）+ 14（低字）

单精度 14 位，双精度 28 位：每加一个字，尾数多 14 位 ✓
```

> 这是一次反向验证：源码没有直接写「双精度有 28 位尾数」，但它写了双精度角度最低位代表 360/2^28 度，从这一行就能反推出来，而且正好符合「每多一个字多 14 位」的规律。

**要点。**

- **十种尺度类型，规定的是同一件事**：这个量的最低位代表多少。
- **尾数位数可以按需分配**：整数型 14 位（留 1 位给符号），角度型 15 位（不需要符号）。
- **一部分类型系统被编码进了尺度约定**：机器只看得见 15 个位，「这个量是什么」活在注释和尺度表里，不占任何存储。
- **两种补数并存，且逐个字段声明**：基础算术是一补数（+0 / -0 为证），但具体字段可以声明为二补数，两者之间有专门的转换例程。源码里某一行写的 `2-S COMP.` 是字段级约定，不等于「这台机器用二补数」。
- **有一处源码与手算对不上**（-B- 型的 MAX），本册如实标出，不用它做论证。
- **SCALED Bnn 就是「小数点左移 nn 位」**，两个常数都能用名字当场验算。


---

## 4　一条数字的完整旅程

*从 15 位计数值到 359.99 度，每一步都能手算. 光看规矩容易空。这一章挑一个真实的数字，让它从源码里走进来、走完尺度换算、再走出去变成一个能自己核对的角度。全程只有乘法和四舍五入，一支笔就能跟完。**先说清这一章证明了什么**：它证明的是**表示层自洽**——源码规定的编码规则能把自己标注的量程原样还原出来。它还没有证明「这台机器在真实运算中把误差控制住了」，那是第 5、6 章的事。*

*图 5：一个 15 位（bit）的 CDU 计数值走完尺度约定、换算、显示格式三步，落到源码自己标注的 359.99 度上。（图示见 HTML 版）*

### 【源码实证】`Comanche055/ASSEMBLY_AND_OPERATION_INFORMATION.agc:705-708`

```agc
# -D-								     15
# CDU DEGREES		XXX.XX DEGREES		SP	BIT 1 = 360/2   DEGREES
#			(MAX 359.99)			(USES 15 BITS FOR MAGNI-
#							 TUDE AND 2-S COMP.)
```

> 这一行把三件事一次说清：最低位的权重（360/2^15 度）、能表示的最大值（359.99）、以及位是怎么分配的（15 位全给大小——源码括注 USES 15 BITS FOR MAGNITUDE）。防杠注：常见科普说角度是「尾数 14 位、一圈 = 2^14 格」，那是把尺度表里的 180° 档（180/2^14，MAX 179.99）记串到了全圈档上——360° 全圈档源码明确括注 USES 15 BITS FOR MAGNITUDE，1 格 = 360/2^15 ≈ 0.011 度，最大 359.99 正由 (2^15−1)×360/2^15 得出。本册的整条证据链就从这里出发。同行末尾那个 `2-S COMP.` 按第 3 章的处理：**它是这个数据格式自己的符号约定**，不等于这台机器的基础算术是二补数——这台机器同时存在一补数字段（第 7 章有证），两者之间还要靠 CDULOGIC 这类例程专门转换。

### 【源码实证】`Comanche055/ASSEMBLY_AND_OPERATION_INFORMATION.agc:723-726`

```agc
# -H-
# DP DEGREES (360)	XXX.XX DEGREES		DP	BIT 1 OF LOW REGISTER =
#			        			     28
#			(MAX 359.99)			360/2   DEGREES
```

> 同一个角度量的双精度版本。显示格式一样（都是 XXX.XX），但最低位权重从 360/2^15 变成 360/2^28——这就是多用一个字换来的东西。

### WALKTHROUGH：一个 CDU 计数值怎么变成 359.99 度

取这个量能表示的最大计数值——15 位全是 1，即十进制 32767——让它按源码规定的尺度走一遍。最后拿结果和源码自己在同一行标注的最大值对照。

**已知条件**

- 源码规定：CDU DEGREES 型，单精度，**最低位（BIT 1）= 360/2 的 15 次方度**
- 15 位全部用来表示大小，最大计数值 = 2 的 15 次方 − 1 = 32767
- 十进制显示格式为 XXX.XX，保留两位小数

| 步 | 代码在算什么 | 代入本例 | 结果 |
| --- | --- | --- | ---: |
| 1 | `N = 32767`（15 位全 1，就是这个量能取到的最大计数） | `2^15 - 1` | **32767** |
| 2 | `BIT 1 = 360/2^15`（源码尺度表 -D- 型给出的最低位权重） | `360 / 32768` | **0.010986328125 度** |
| 3 | `N × BIT 1`（计数值乘最低位权重，得到物理角度） | `32767 × 0.010986328125` | **359.989013671875 度** |
| 4 | `XXX.XX`（按十进制格式四舍五入到两位小数） | `359.989013671875 → 359.99` | **359.99 度** |
| 5 | `MAX 359.99`（与源码自己在同一行标注的最大值对照） | `359.99 = 359.99` | **一致 ✓** |

**换一条路再算一遍**

```
# 不乘小数，直接用比例
N / 2^15 × 360
= 32767 / 32768 × 360
= 0.999969482421875 × 360
= 359.989013671875 度
→ 四舍五入 359.99

# 源码标注：MAX 359.99
# Comanche055/ASSEMBLY_AND_OPERATION_INFORMATION.agc:705
```

> 两条路都算出 359.989013671875 度，四舍五入之后与源码标注的 359.99 吻合。这不是巧合：源码写的 MAX 359.99，正是按这套换算算出来的。**这条链的价值在于它两头都接在源码上**——起点是源码规定的位权，终点是源码标注的最大值，中间只用了乘法。

**换成双精度，分辨率差多少**

```
# 单精度 CDU：最低位 = 360/2^15 度
360 / 32768            = 0.010986328125 度
× 3600                 = 39.55078125 角秒

# 双精度角度：最低位 = 360/2^28 度
360 / 268435456        = 1.341104507446289e-06 度
× 3600                 = 0.004827977... 角秒

# 两者之比
39.55078125 / 0.0048279778 = 8192 = 2^13
```

**要点。**

- **这条链两头都接在源码上**：起点是源码规定的位权，终点是源码标注的最大值。
- **中间只用乘法**，所以谁都能复核。这也是本册选它当主证据链的原因。
- **多用一个字，角度分辨率提高 8,192 倍**（2 的 13 次方）——从约 39.6 角秒变成约 0.0048 角秒。


---

## 5　把精度从浪费里捞回来

*先左移到「够大」，记下移了几位，算完再挪回去. 15 位能表达的精度是固定的，可数字有大有小。如果一个数很小，它高位上的 0 就占了地方却没派上用场。这台机器的处理办法很朴素：先把数左移到够大为止，记下移了几位，算完再按记录挪回去。左移本身不产生任何新信息，只是把已有的位挪到能派上用场的位置上。*

*图 6：归一化前后位模式对比：以 .000244 (.25/2^10) 为例，原值在 15 位中只占低 4 位有效位，左移 10 位后用满 14 位尾数，左侧空白消失。（图示见 HTML 版）*

### 【源码实证】`Comanche055/INTERPRETER.agc:1628-1652`

```agc
# TSLC -- TRIPLE SHIFT LEFT AND COUNT.  SHIFTS MPAC LEFT UNTIL GREATER THAN .5 IN MAGNITUDE, LEAVING
# THE COMPLEMENT OF THE NUMBER OF SHIFTS REQUIRED IN X.

TSLC2		TS	MPTEMP		# START BY ZEROING SHIFT COUNT (IN A NOW).
		TC	BRANCH		# EXIT WITH NO SHIFTING IF ARGUMENT ZERO.
		TCF	+2
		TCF	ENDTSLC		# STORES ZERO SHIFT COUNT IN THIS CASE.

		TC	TPAGREE		# MAY CAUSE UPSHIFT OF ONE EXTRA PLACE.

		CA	MPAC		# BEGIN NORMALIZATION LOOP.
		TCF	TSLCTEST

TSLCLOOP	INCR	MPTEMP		# INCREMENT SHIFT COUNTER.
		EXTEND
		DCA	MPAC +1
		DAS	MPAC +1
		AD	MPAC
		ADS	MPAC
TSLCTEST	DOUBLE			# SEE IF (ANOTHER) SHIFT IS REQUIRED
		OVSK
		TCF	TSLCLOOP	# YES -- INCREMENT COUNT AND SHIFT AGAIN.

ENDTSLC		CS	MPTEMP
		TCF	STORE1		# STORE SHIFT COUNT AND RETURN TO DANZIG.
```

> 注释把契约写得清清楚楚：左移到「绝对值大于 .5」为止，把移位次数记下来（存的是补数，最后 CS MPTEMP 取反）。循环体就是一次左移（DOUBLE 加进位）。**在这一个循环里**，溢出标志被主动当成「已经左移到目标幅值」的判定信号：OVSK 一跳，循环就结束。这是算法在消费溢出，不是把溢出当故障——但这句话只对这一段成立，不能推广成「这台机器把溢出当正常结果」（真正的非法状态走的是第 7 章的 POODOO）。

### 【源码实证】`Comanche055/INTERPRETER.agc:2509-2513`

```agc
		XCH	MPAC +2		# MPAC IS ZERO -- SHIFT LEFT 14.
		XCH	MPAC +1
		TS	MPAC
		CAF	SEVEN		# AUGMENT RIGHT SHIFT COUNTER.
		TS	MPTEMP
```

> 开平方例程里的一段：把三个字整体左移 14 位（一次字交换），然后把右移补偿计数加上 7。注意这个 7——下一章会用到。

### 【作者推导】`由上一条的 14 与 7 推出`

```agc
左移 14 位  →  数值 × 2^14
开平方      →  指数减半：sqrt(x × 2^14) = sqrt(x) × 2^7
所以补偿    →  结果右移 7 位

源码里：CAF SEVEN / TS MPTEMP   —— 正好是 7
```

> 这处细节最能说明「移位计数」是怎么被当成一个显式的量来管理的：左移 14 位之后，开方带来的指数变化是减半，所以右移补偿必须是 7 而不是 14。源码用 CAF SEVEN 直接写了 7，没有多余的解释——因为对写它的人来说这是常识。

### 【源码实证】`Comanche055/ORBITAL_INTEGRATION.agc:424-433 ｜ 同文件:921`

```agc
					# AS VECTOR.
		PUSH	DMP		# STORE COSPHI**2 SCALED B2 IN 8D
# Page 1343
			5/8		# 5 SCALED B3
		PDDL	SR2		# PUT 5 COSPHI**2, D5, IN 8D. GET
					# COSPHI**2 D2 FROM 8D
		DAD	BDSU		# END UP WITH (1-7 COSPHI**2), B5
			8D		# ADDING COSPHI**2 B4 SAME AS COSPHI**2
					# X 2 D5
			D1/32		# 1 SCALED B5
5/8		2DEC	5 B-3
```

> 这一段的价值在于：注释把每一次运算之后的标度都写明了，于是可以拿源码自己的记账去对账。乘法的标度指数**相加**（B2 × B3 → B5），加法和减法要求两个操作数**同标度**——所以常数 1 必须专门写成 B5 的 D1/32。这两条规则就是这台机器上「小数点跟着运算走」的全部内容。

### WALKTHROUGH：一次定点乘法，标度是怎么记账的

取上面那段代码里的第一次乘法走一遍。全程用整数算，不引入任何小数——这正是这台机器实际在做的事。

**已知条件**

- COSPHI**2 在 **B2** 标度下，本例存整数 **2** → 实际值 2 × 2⁻² = 0.5
- 常数 5/8 定义为 `2DEC 5 B-3`，存整数 **5** → 实际值 5 × 2⁻³ = 0.625
- 源码注释写明：这次乘法的结果落在 **B5**

| 步 | 代码在算什么 | 代入本例 | 结果 |
| --- | --- | --- | ---: |
| 1 | `DMP`（整数相乘，机器眼里没有小数点） | `2 × 5` | **10** |
| 2 | `标度指数相加`（2⁻² × 2⁻³ = 2⁻⁵，所以 B2 × B3 = B5） | `2 + 3` | **B5** |
| 3 | `10 × 2⁻⁵`（把整数结果按新标度还原成实际值） | `10 / 32` | **0.3125** |
| 4 | `0.5 × 0.625`（直接用真值相乘，作为独立校验） | `0.5 × 0.625` | **0.3125 ✓** |

**用整数运算独立验算**

```
# 全程整数，不用小数
A = 2, 标度 2^-2   → 实际值 0.5
B = 5, 标度 2^-3   → 实际值 0.625
A × B = 10, 标度 2^-(2+3) = 2^-5
10 × 2^-5 = 10 / 32 = 0.3125
0.5 × 0.625              = 0.3125          一致 ✓

# 源码依据
# ORBITAL_INTEGRATION.agc:425-427   COSPHI**2 SCALED B2 / 5/8 # 5 SCALED B3
# ORBITAL_INTEGRATION.agc:429       PUT 5 COSPHI**2, D5, IN 8D
# ORBITAL_INTEGRATION.agc:921       5/8   2DEC   5 B-3
```

> 两条路都得到 0.3125。**乘法的标度指数相加，加法的标度必须对齐**——这两条规则覆盖了这台机器上绝大多数的「小数点管理」工作，也解释了为什么注释里到处都是 B 后面跟一个数字。

**换成加减法，规则就不一样了**

```
# 加减法不产生新的标度，但要求两个操作数本来就在同一标度上
# 所以源码要把常数 1 专门写成 B5 的 D1/32：
#
#   D1/32    1 SCALED B5    存整数 1  → 1 × 2^-5
#
# 而不是写成 1。写成 1 的话，它在这个上下文里的实际值
# 就成了 1 × 2^-5 = 1/32，与参与运算的另一个 B5 量对不上。
```

**要点。**

- **归一化解决的问题是浪费**：小数字的高位 0 不携带信息，却占了位置。
- **移位次数必须记下来**，否则算完就还原不回去了。源码用一个 MPTEMP 专门存它。
- **在这一个归一化循环里，溢出被用作「到位」的信号**：TSLCTEST 靠 OVSK（溢出则跳过下一条）判断已经够大。这是算法主动消费溢出标志，只适用于这一段。
- **对照第 7 章**：真正算不下去时走的是完全不同的路——POODOO 加一个告警码，停在那里等上层处理。「可以消费的溢出」和「必须中止的非法状态」是两回事，这份代码把两者的边界切得很清楚。


---

## 6　两次牛顿就够了

*先用一条直线猜个初值，再迭代两次补到双精度. 前面几章讲的都是规矩。规矩好不好用，得拿一个真实例程从头到尾走一遍才知道。**这一章的实验台是开平方**——不是因为开平方本身有多重要，而是因为它把归一化、线性近似、定点量化、误差收敛这几件事集中在一个不到 100 行的例程里，全过程都看得见。主角仍然是由尺度约定、归一化、多精度与软件算术构成的**数值系统**，开平方只是把它摆到台面上的那件样品。*

*图 7：开平方的四个阶段：归一化、线性近似取初值、一次单精度牛顿、一次双精度牛顿，最后按记录的移位次数还原。（图示见 HTML 版）*

*图 8：ARGHI（x ∈ [.25, .5]，S=.5884、B=.2096）与 ARGLO（x ∈ [.125, .25]，S=.8324、B=.1487）两段全区间误差曲线。最大值出现在区间端点（解析上界），最小值在 x*=B/S 处（导数为零的平稳点）。（图示见 HTML 版）*

*图 9（外部资料）：<b>Comanche055 原始打印清单第 1184 页（SQRTSUB 第一页）</b>。这是 1969 年 4 月 1 日那批绳芯源代码的物理复印件，由 MIT 仪器实验室在装订时刻印。图上能看到：注释把整段契约写在最上面，CAF ZERO 把右移计数先清零，CAF SEVEN 把 7 写进 MPTEMP（与第 5 章那段 14/7 的除法对应），最末两行 SQRTABRT TC POODOO 与 OCT 1302 是算不下去时的告警路径。本册第 6 章引用的行号（2023–2055）就是这张图上能数到的物理行号。（原始打印页扫描件待补）*

### 【源码实证】`Comanche055/INTERPRETER.agc:2497-2499`

```agc
# DOUBLE PRECISION SQUARE ROOT ROUTINE.  TAKE THE SQUARE ROOT OF THE TRIPLE PRECISION (MPAC +2 USED ONLY
# IN NORMALIZATION) CONTENTS OF MPAC AND LEAVE THE NORMALIZED RESULT IN MPAC (C(MPAC) GREATER THAN OR EQUAL TO
# .5).  THE RIGHT SHIFT COUNT (TC UNNORMALIZE) IS LEFT IN MPTEMP.
```

> 三行注释把主契约说完了：输入是三精度，输出是归一化形式（≥ .5），右移补偿次数留在 MPTEMP 里交给调用方。注释之外，这段代码还处理了四件边界事：第三字（MPAC+2）只在归一化时用；参数为零直接返回零（SQRT(0) = 0）；负的小值（幅值小于 10⁻⁴）按零处理；出口前专门检查不留负零（FIXROOT）。输入输出边界写得这么清楚，是这份代码给我印象最深的地方之一。

### 【源码实证】`Comanche055/INTERPRETER.agc:1748、2186、1721、1964（四个常数）`

```agc
SLOPEHI		DEC	.5884		# SQRT CONSTANT.
SLOPELO		DEC	.8324
BIASLO		DEC	.2974 B-1	# SQRT CONSTANT
BIASHI		DEC	.4192 B-1	# SQRT CONSTANTS.
```

> 两组斜率和截距，对应归一化之后的两段区间。源码把它们叫做 SQRT CONSTANT，没有解释怎么来的——但可以用代入验算反推，见下面的手算。

### 【源码实证】`Comanche055/INTERPRETER.agc:2556-2559（线性近似取初值）`

```agc
ARGHI		CAF	SLOPEHI		# ARGUMENT BETWEEN .25 AND .5, GET A
		EXTEND			# LINEAR APPROXIMATION FOR THIS RANGE.
		MP	MPAC
		AD	BIASHI		# X0/2 = (MPAC/2)(SLOPHI) + BIASHI/2.
```

> 就三行：取斜率、乘上自变量、加截距。注释里连公式都写好了——X0/2 = (MPAC/2)×SLOPEHI + BIASHI/2。注意这里算的是 X0 的一半，后面所有运算都保持在「半值」上，为的是不溢出。

### 【源码实证】`Comanche055/INTERPRETER.agc:2561-2582（两次牛顿迭代）`

```agc
	+4	TS	BUF		# X0/2 (ARGLO ENTERS HERE).
		CA	MPAC		# SINGLE-PRECISION THROUGHOUT.
		ZL
		EXTEND
		DV	BUF		# (MPAC/2)/(X0/2)
		EXTEND
		MP	HALF
		ADS	BUF		# X1 = X0/2 + .5(MPAX/2)/(X0/2)

		EXTEND
		MP	HALF		# FORM UP X1/2.
		DXCH	MPAC		# SAVE AND BRING OUT ARGUMENT.
		EXTEND			# TAKE DP QUOTIENT WITH X1.
		DV	BUF
		TS	BUF +1		# SAVE MAJOR PART OF QUOTIENT.
		CAF	ZERO		# FORM MINOR PART OF QUOTIENT USING
		XCH	L		# (REMAINDER,0).
		EXTEND
		DV	BUF
		TS	L		# IN PREPARATION FOR DAS.
		CA	BUF +1
		DAS	MPAC		# X2 = X1/2 + (MPAC/2)X1
```

> 第一次迭代那行写着 SINGLE-PRECISION THROUGHOUT（全程单精度），用的是单精度除法 DV；第二次迭代改用双精度，还专门处理了余数（CAF ZERO / XCH L / DV BUF 那段）。**先粗后精，各花各的代价。**

### 【作者推导】`verify_accurate.py（本册配套脚本，可自己跑）`

```agc
$ python verify_accurate.py

# B 段：全区间误差上界（解析求极值）
ARGHI  →  全区间最大 |e| = 0.8900%（出现在 x = 0.250）
ARGLO  →  全区间最大 |e| = 1.1000%（出现在 x = 0.125）

# C 段：实数域牛顿 vs 定点量化牛顿
量化方式：截断
  实数域牛顿，最大相对误差     = 7.7046e-10  = 0.21 个双精度最低位
  定点量化牛顿，最大相对误差   = 5.2568e-09  = 1.41 个双精度最低位
  量化带来的额外误差           = 6.8 倍
```

> 脚本只用标准库，直接在命令行跑。它做的两件事，正好是这一章最需要补的两个洞：把「抽样验证」升级成**解析上界**，以及把「实数域收敛」和「定点实现误差」拆开算。**必须说清楚它的边界**：这是本册按源码约定的位宽（单精度 14 位尾数、双精度 28 位尾数）建立的量化模型，**不是**在真实 AGC 或模拟器上跑出来的结果。它证明的是「在这套位宽约定下，这个算法的误差量级是多少」，不是「yaAGC 跑出来就是这个数」。真正的执行证据在第 8 章：那里让真实的 Luminary 099 绳芯镜像跑了 72 点，最坏 0.925 个双精度最低位——**比这里的模型上界还小**，说明这个模型确实是覆盖全情形的上界，而不是一句估摸。

### WALKTHROUGH：源码里那条直线，最坏能差多少

不抽样、不碰运气。把源码给的两个常数当成一条数学函数，直接求它在整个规定区间上的**最坏相对误差**——这是一个闭式解，不是估计。

**已知条件**

- 源码公式：X0/2 = SLOPE × x + BIASHI；真值：f(x) = √(2x) / 2
- ARGHI 段：SLOPEHI = .5884，BIASHI 本册取 **.2096**（源码写作 `DEC .4192 B-1`），x ∈ [.25, .5]
- ARGLO 段：SLOPELO = .8324，BIASLO 本册取 **.1487**（源码写作 `DEC .2974 B-1`），x ∈ [.125, .25]
- 相对误差：e(x) = (S·x + B) / f(x) − 1 = √2 · (S·√x + B/√x) − 1
- BIASHI / BIASLO 取值的依据：只有取成 .2096 / .1487，这条直线在各自区间两端才都落在 1% 量级；这是代入验证，源码未写明该常数的取值方向

| 步 | 代码在算什么 | 代入本例 | 结果 |
| --- | --- | --- | ---: |
| 1 | `e′(x) = (S·x − B) / (2·x^1.5)`（对相对误差函数求导，找极值点） | `令 e′(x) = 0` | **唯一驻点 x = B/S** |
| 2 | `x* = B / S`（导数在 x* 左侧为负、右侧为正 ⇒ 这是**极小值**，不是极大值） | `ARGHI：.2096/.5884` | **0.356220** |
| 3 | `最大值只能在端点`（区间内只有一个极小值、没有极大值，所以最坏情况必在两端） | `比较 e(左端) 与 e(右端)` | **取绝对值较大者** |
| 4 | `ARGHI 两端`（x = .25 与 x = .5） | `e(.25) = +0.8900%　e(.5) = +0.7600%` | **最坏 0.8900%** |
| 5 | `ARGLO 两端`（x = .125 与 x = .25，同一套做法） | `e(.125) = +1.1000%　e(.25) = +0.9183%` | **最坏 1.1000%** |

**跑一遍脚本，自己复核**

```
$ python verify_accurate.py        # 本册配套脚本，只用标准库

ARGHI  区间 [0.250, 0.500]   S=0.5884  B=0.2096
  导数零点 x* = B/S                 = 0.356220  （极小值点）
  左端点 x=0.250  e = +0.8900%
  右端点 x=0.500  e = +0.7600%
  极小值      e = -0.6708%
  → 全区间最大 |e| = 0.8900%（出现在 x = 0.250）

ARGLO  区间 [0.125, 0.250]   S=0.8324  B=0.1487
  导数零点 x* = B/S                 = 0.178640  （极小值点）
  左端点 x=0.125  e = +1.1000%
  右端点 x=0.250  e = +0.9183%
  极小值      e = -0.4900%
  → 全区间最大 |e| = 1.1000%（出现在 x = 0.125）
```

> **初始最坏相对误差：ARGHI 段 0.8900%，ARGLO 段 1.1000%。**这两个数是解析上界，不是抽样碰运气。需要说明的是：本册只证明「这条直线在区间上的误差不超过这两个值」，**没有论证它是不是某种最优拟合**——源码没有给出拟合准则，任何更强的说法都是猜的。

**关键一问：实数域牛顿，和这台机器上的定点牛顿，是一回事吗**

```
$ python verify_accurate.py        # 同一脚本的 C 段

双精度最低位权重 2^-28 = 3.7253e-09

量化方式：截断
  实数域牛顿，最大相对误差     = 7.7046e-10  = 0.21 个双精度最低位
  定点量化牛顿，最大相对误差   = 5.2568e-09  = 1.41 个双精度最低位
  量化带来的额外误差           = 6.8 倍

量化方式：四舍五入
  实数域牛顿，最大相对误差     = 7.7046e-10  = 0.21 个双精度最低位
  定点量化牛顿，最大相对误差   = 5.6968e-09  = 1.53 个双精度最低位
  量化带来的额外误差           = 7.4 倍
```

**要点。**

- **开平方是实验台，不是主角。** 它只是把归一化、线性近似、定点量化、误差收敛集中在一个短例程里，方便我们把整条链看清楚。
- **初始最坏误差是解析上界**：ARGHI 段 0.8900%、ARGLO 段 1.1000%，由 e′(x) 的符号确定极值只能落在端点，不是抽样碰运气。
- **实数域牛顿和定点牛顿不是一回事**：实数域两次迭代只有 7.7×10⁻¹⁰（约 0.2 个双精度最低位），把 14 位／28 位定点量化加进去之后变成约 5×10⁻⁹（1.4~1.5 个双精度最低位）。**最终误差的大头来自定点量化，不是牛顿收敛。**
- **两次真的够了**：再迭代一次，收益会被双精度自身的分辨率吃掉。这也解释了源码为什么只写两次。
- **本册没有论证那条直线是最优拟合**，只给出它在规定区间上的误差上界。源码没有给出拟合准则，更强的说法本册不写。


---

## 7　误差的另一半：负零、溢出和告警

*算不下去的时候，停下来报错，不硬算下去. 前面六章都在讲怎么算得准。这一章讲另一半：算不下去的时候怎么办，以及把前六章那些零散的结论收成一个模型。这台机器对「算不下去」的回答很干脆——停下，写一个告警码，交给上层。它甚至要为「负零」这种东西单独写处理代码，因为在这台机器上，零确实有两个写法。*

### 【源码实证】`Comanche055/INTERPRETER.agc:733-735`

```agc
#	L+1	IF MPAC IS GREATER THAN ZERO.
#	L+2	IF MPAC IS EQUAL TO +0 OR -0.
#	L+3	IF MPAC IS LESS THAN ZERO.
```

> 分支指令的出口说明里，把「等于 +0 或等于 -0」单列成一路。如果只有一个零，这里完全不需要写两个。这一行本身就是一补数的直接证据。

### 【源码实证】`Comanche055/INTERPRETER.agc:2529`

```agc
		TCF	FIXROOT		# DO NOT LEAVE SQRTSUB WITH -0 IN MPAC.
```

> 开平方例程结束前专门检查一遍：不能给调用方留下 -0。一条注释、一次跳转，防的是一个只在特定表示法下才存在的坑。

### 【源码实证】`Comanche055/INTERPRETER.agc:1793、1837、1853、2331（除法里的符号处理）`

```agc
		TS	DVSIGN		# +-1 FOR POSITIVE QUOTIENT -- -0 FOR NEG.
		INCR	DVSIGN		# NOW -0.
		INCR	DVSIGN		# NOW +1 OR -0.
UMPAC-		CS	ZERO		# IF NEGATIVE, SET -0 IN DVSIGN FOR FINAL
```

> 除法里 -0 被当成一个**有意义的状态**在用：CS ZERO 得到 -0，当作「结果为负」的记号，后面 INCR 一句就能在 -0 和 +1 之间切换。把 -0 当哨兵用，是这段代码里的常见手法。

### 【源码实证】`Comanche055/INTERPRETER.agc:2540-2541（算不下去的时候）`

```agc
SQRTABRT	TC	POODOO
		OCT	1302
```

> 开平方遇到负数（或负得离谱、修正后仍超范围），不硬算，直接跳 POODOO 并带上八进制码 1302。POODOO 是这份代码里的「放弃并报警」例程——相当于今天抛出一个带编号的致命异常。**给每个失败一个编号**，是这套系统能被诊断、能被复盘的前提。

### 【源码实证】`Comanche055/INTERPRETER.agc:229`

```agc
		AD	-OCT10		# (NO -0).
```

> 注释只有四个字：（NO -0）。写这行的人知道这里不会出现负零，并且把这个判断写进了注释——这样下一个人读到这里就不用再推一遍。

### 【作者推导】`由上五条归纳`

```agc
本册在 Comanche055/INTERPRETER.agc 里找到的 -0 相关显式处理：
  :734   分支出口把 +0 与 -0 并列
  :1793  除法符号位用 -0 表示「负」
  :1837  INCR 之后变成 -0
  :1853  INCR 之后变成 +1 或 -0
  :2331  CS ZERO 造出 -0 当哨兵
  :2529  开平方结束前检查不能留下 -0
  :229   注释声明此处不会出现 -0

→ 一补数有两个零，这件事在源码里不是理论，是日常
```

> 七处，分散在分支、除法、开平方三个不同的地方。这说明「负零」不是某个例程的局部怪癖，而是这套表示法给整个代码库带来的普遍负担——每一处判断符号的地方都得想着它。

### 【作者推导】`把前七章归纳成一个模型`

```agc
这台机器一次数值计算的误差，只有六个来源

  ① 表示误差   定点量化。尾数 14 / 28 / 42 位，装不下的部分只能丢掉
  ② 运算误差   乘法与除法的截断、舍入
  ③ 缩放误差   左移右移、归一化与反归一化带来的位移损失
  ④ 迭代误差   牛顿这类迭代算法的收敛残差
  ⑤ 范围错误   上溢、下溢、定义域非法
  ⑥ 接口错误   两个量的尺度没对齐、符号约定没对齐

前四项是**精度**问题，机器不报错，只会安静地算出一个略微偏差的数；
后两项是**正确性**问题，机器会停下来报错。
```

> 把前七章串起来看，结论其实不是「没有浮点也能算准」，而是：**这台机器没有消灭误差，它把误差变成了一个可以被设计、被传播、被检查、被约束的量。**精度类误差靠位宽、尺度约定和归一化去压——压到多少是可以算出来的（第 6 章给出了具体数字）；正确性类误差靠显式检查去拦——拦下来就报一个编号（POODOO 加告警码）。**能算清楚的让它安静地算，算不清楚的让它响亮地停。**这比「凭惊人智慧克服了硬件限制」这种说法准确得多，也更值得学习。

**要点。**

- **这台机器用一补数，所以零有两个写法**：+0 和 -0。源码里至少七处为它单独写处理。
- **-0 不只是麻烦，也被当成工具用**：除法里拿它当「结果为负」的哨兵，一个 INCR 就能切换状态。
- **算不下去就报编号**：开平方遇到负数直接跳 POODOO 并带码 1302。给失败编号，才谈得上诊断。


---

## 8　把机器叫起来问一次

*从源码到仿真器：执行证据链在这一章闭合. 前面七章的结论，都能靠读源码加手算核对，第 6 章还把它们写成了脚本。但那终究是按源码约定搭出来的**数值模型**，不是机器。这一章换一种问法：把当年那份真实的 Luminary 099 装进 AGC 仿真器，让机器自己算一遍 √x，然后把寄存器里的位模式读出来，看它和第 5、6 章的预测对不对得上。*

*图 10：72 个 x 值上机器实测的相对误差 e/x 分布（×2^-28 单位），与 ±1 个双精度最低位（DP LSB）对照。47 个点完全正确（四舍五入与 28 位正解一致），24 个点低 1 个 LSB，1 个点高 1 个 LSB，没有点超过 ±1 LSB。（图示见 HTML 版）*

*图 11（背景图，不作为证据）：<b>背景图：Apollo 11 登月舱上升段</b>。本章在 yaAGC 上跑的那份 Luminary 099，就是这台 LM 登月时实际在用的绳芯镜像。本图不作为任何数字的证据；它只用来把读者放在「这台机器真的飞过」的位置上。（原始打印页扫描件待补）*

### 【源码实证】`Apollo-11/README.md:101-109（Attribution 表）`

```agc
Comanche055    | ... `Assemble revision 055 of AGC program Comanche by NASA`
               |     `2021113-051. 10:28 APR. 1, 1969`
Luminary099    | ... `Assemble revision 001 of AGC program LMY99 by NASA`
               |     `2021112-061. 16:27 JUL. 14, 1969`
Assembler      | yaYUL
Contact        | Ron Burkey <info@sandroid.org>
Website        | www.ibiblio.org/apollo
```

> 这张表交代了三件事：两份程序的正式编号与装订记时、汇编器是 yaYUL、以及源码的数字化出自 MIT 博物馆藏硬拷贝。**用 yaYUL 不是本册挑的，是这份源码自己指定的。**本册引用的「16:27 JULY 14, 1969」也来自这里——它是源码自身的装订记时，和仓库 commit 的提交时间完全是两件事。

### 【外部资料】`参考文献 [15]：Virtual AGC 项目`

```agc
yaYUL  —— AGC 的交叉汇编器（当年 YUL 的开源重实现）
yaAGC  —— AGC 的指令级模拟器
yaDSKY —— DSKY 显示键盘模拟器

项目：github.com/virtualagc/virtualagc
网站：virtualagc.github.io/virtualagc/ 与 ibiblio.org/apollo
```

> 这一档证据不能回仓库核对，所以单独标出来。它在本书里的角色很明确：**给真实源码准备一套能跑的机器。**yaYUL 负责把 MAIN.agc 变成 36 个 bank 的绳芯镜像，yaAGC 负责让这份镜像在今天的电脑上执行。两点边界要说清：yaAGC 只模拟计算机本体，不模拟外设（DSKY、IMU 等另有模拟器）；本册实验用的是从源码自建的版本，不是官方发布的二进制包，所以版本横幅里没有版本号，只有构建日期。

### 【机器实证】`工具链搭建与两份绳芯镜像产出（真实命令与输出）`

```agc
环境：Ubuntu 26.04 LTS（WSL2）× gcc 15.2.0
构建：yaYUL ── 290,264 B    yaAGC ── 170,952 B    两者 exit=0
      启动横幅：Apollo Guidance Computer (AGC) assembler, built Sep 10 2026, target AGC4

【登月舱】Luminary 099（装订记时 16:27 JUL. 14, 1969）
  $ yaYUL MAIN.agc > lum099.lst 2> lum099.err
  yaYUL exit=0      清单 67,485 行    stderr 1 行
  MAIN.agc.bin  →  73,728 字节
  stderr 唯一一行：ERASABLE_ASSIGNMENTS.agc:987: Warning: May overflow memory bank.

【指令舱】Comanche 055（装订记时 10:28 APR. 1, 1969）
  $ yaYUL MAIN.agc > com055.lst 2> com055.err
  yaYUL exit=0      stderr 0 行
  MAIN.agc.bin  →  73,728 字节
```

> 73,728 字节这个数值得停一下：它正好等于 36,864 个字（word）× 16 位（bit）÷ 8 ÷ 1024 = **72 KB**，和源码文档里写的只读存储容量分毫不差。汇编器吐出来的不是一个「差不多」的文件，而是**把 36,864 个字的容器一个不多、一个不少地填满了**——这份 1969 年的代码，到今天仍然是完整可汇编的。（填满证明的是容器尺寸严丝合缝；与飞行绳芯的逐字关系取决于转录保真度，需另行比对。）本册实验用的镜像 MD5 为 `4531dff39d0b8379761f19eb2f67f792`。

### 【作者推导】`Luminary099/MAIN.agc:34、88（两处对不上的文件名）`

```agc
# MAIN.agc 的包含清单里写着：
$LAMBERT_AIMPOINT_GUIDANCE.agc			# pp. 651-653
$TRIM_GIMBAL_CNTROL_SYSTEM.agc			# pp. 1472-1484

# 而目录里实际躺着的文件是：
GENERAL_LAMBERT_AIMPOINT_GUIDANCE.agc
TRIM_GIMBAL_CONTROL_SYSTEM.agc
```

> 第一次汇编直接失败：`yaYUL exit=12`，0.156 秒，stderr 2,578 行，全是 `Symbol "ZEROVECS" undefined or offset bad` 这类符号找不到。顺着报错往回查，根因不在代码，而在**两个文件名**：清单里要的是 `CNTROL`（少一个 O，是当年硬拷贝上的拼写），目录里的文件被整理成了正确的 `CONTROL`；另一个则是目录里的文件前面多了 `GENERAL_`。把这两个文件改成清单里要的名字之后，`exit=0`，清单 67,485 行，stderr 只剩 1 行。**这条本册单独列出来，是因为它说明一件事**：读源码和跑源码是两种不同的检验。前者看内容对不对，后者才会撞上这类「内容全对但拼不上」的问题。至于那 2,578 行符号报错，全是这一个根因的下游噪音，改完自动消失。

### 【作者推导】`怎么让这台机器只算一次开平方（本册的做法）`

```agc
AGC 复位后从固定存储器 0o4000 开始执行（线性地址 0x800）：
  原样：  INHINT / CAF GOBB / XCH BBANK / TCF GOPROG    ← 开机自检的入口

本册把它替换成一段桩程序，直接调用 SQRTSUB：
  0o4000  INHINT      00004   ← 必须保留，否则 DOWNRUPT 会插进来打断
  0o4001  TC  SQRTSUB 03343
  0o4002  CA  MPAC    30154
  0o4003  TS  0o0371  54371
  0o4004  CA  MPAC+1  30155
  0o4005  TS  0o0372  54372
  0o4006  CA  MPAC+2  30156
  0o4007  TS  0o0374  54374
  0o4010  CA  MPTEMP  30135
  0o4011  TS  0o0373  54373
  0o4012  TC  0o4012  04012   ← 停在这里，等我们来读

线性地址换算（本册用四个断点实测反推并交叉验证）：
  固定存储器：0x800 + FB×1024 + (AGC地址 & 0o1777)
  可擦写内存：EBANK×256 + (AGC地址 & 0o0377)
```

> 这段桩程序是本册手写的，用的是**从真实汇编清单里反推出来的指令编码**（`TC`=0o00000、`CA`/`CAF`=0o30000、`TS`=0o54000、`INHINT`=0o00004…），不是照抄任何现成脚本。为什么要有 INHINT：第一轮尝试没有加它，机器确实执行了 `TC SQRTSUB`（中断记录里留着 `ZRUPT 0x0801`、`BRUPT 0x06e3` 的证据），但随即被 DOWNRUPT 打断，结果全是零。补回 INHINT 之后才拿到干净的值。**这一段失败与修正的经过，也一并留在这里。**

### 【机器实证】`72 点实测：让真实 SQRTSUB 自己算一遍 √x`

```agc
m       x        MPAC(八进制)      机器结果        真值         误差/LSB
--------------------------------------------------------------------------
4096  0.2500000  020000/000000    0.5000000000  0.5000000000    +0.000
4266  0.2603760  020250/010544    0.5102704912  0.5102704935    -0.613
……
11236 0.6857910  032400/000000    0.8281250000  0.8281250000    +0.000
……
16166 0.9866943  037622/024243    0.9933248870  0.9933248894    -0.647

共 72 点，输入区间 x ∈ [0.25, 0.9867]
  最大 |e|      = 0.925 个双精度最低位
                  出现在 x = 0.6650391（该点相对误差 4.2263e-09）
  平均 |e|      = 0.373 个双精度最低位
  最大 |e/x|    = 4.5464e-09
                  出现在 x = 0.5197754（该点绝对误差 0.880 个最低位）
  ※ 两个最大值不在同一个点上，本册分开列出，不合成一个数
  误差为负（低估）56 点，为正（高估）16 点

  与「28 位下正确舍入值」逐点比对：
    完全相同的点           47 点
    低 1 个最低位的点      24 点
    高 1 个最低位的点       1 点
    差 2 个最低位以上的点   0 点
  72 次运行，停机位置 Z 全部等于 0x080a（本册自陷循环首址）
  ⇒ 72 次 SQRTSUB 全部正常返回，没有一次被中断打断或卡死
```

> 这就是本册想要的**执行证据**：不是模型说它应该等于多少，而是机器自己算出来、我们去读寄存器。每个点都是一次独立的 yaAGC 启动：先把输入写进 MPAC，让桩程序调用真实的 SQRTSUB，然后读 MPAC 和 MPAC+1 两个字的实际位模式，按 28 位小数反解成十进制。**最坏情况：72 点全部落在 1 个双精度最低位以内**，其中 47 点与 28 位下的正确舍入值一模一样，剩下 25 点也只差 1 个最低位，没有任何一点差到 2 个最低位。选 72 个点而不是 3 个，是为了让输入**同时压住两条逼近分支**：x ≥ 0.5 的点折半后落进 ARGHI，x ∈ [0.25, 0.5) 的点折半后落进 ARGLO————第 5 章算出来最坏差 0.89% 与 1.10% 的那两条直线。两条都跑到，本章的结论才覆盖得住第 5 章。小数点另一头也不含糊：x = 0.25 和 x = 0.685791 这两个点上，机器给出的 0.5 与 0.828125 是**精确值**，低位字读出来就是 000000——不是巧合，是牛顿迭代收敛到了能精确表示的那个点上。

### 【作者推导】`实测 0.925 对模型 1.53：第 6 章的上界站住了，而且偏保守`

```agc
第 6 章数值模型的预测（脚本口径：最大相对误差）
  定点截断模型：5.2568e-09   （脚本读作 1.41 个双精度最低位）
  定点舍入模型：5.6968e-09   （脚本读作 1.53 个双精度最低位）

第 8 章真实机器的实测（同一口径：最大相对误差）
  4.5464e-09   出现在 x = 0.5197754
  实测 / 模型 = 0.80 倍   ⇒  模型没被实测顶破

换成绝对误差口径，再核一遍（以双精度最低位计，1 个 = 2^-28）
  模型  1.53 个最低位   （脚本口径，隐含「结果幅值 ≈ 1」）
  模型  1.08 个最低位   （按最坏点幅值 0.707 折算成真绝对误差）
  实测  0.925 个最低位  出现在 x = 0.6650391
  实测 / 模型 = 0.61 ～ 0.86 倍   ⇒  两个口径下模型都是上界

为什么是这个量级？把两条误差来源分开算
  ① 牛顿截断（源于第 5 章那条直线的最坏 1.1% 相对误差）
     手算（牛顿误差按平方缩）：第 1 次迭代后 ≈ 0.011² / 2 = 6.05e-05
                               第 2 次迭代后 ≈ (6.05e-05)² / 2 = 1.83e-09
     脚本算（第 6 章 verify_accurate.py，实数域牛顿，20 万点，仅覆盖 ARGHI 路径）：7.7046e-10
     两者同量级（相差 2.4 倍），说明「误差按平方缩」确实在起作用
  ② 定点量化底噪（28 位小数的表示精度）
     结果落在 [0.5, 1)，1 个最低位 ≈ 2^-28 / 0.5 = 7.45e-09

  ① 比 ② 小约 10 倍（按脚本值）⇒ 两次迭代之后，
  剩下的误差**几乎全是量化底噪**，堆不出更大的东西来。
```

> 这一段是本册和第 6 章对表的地方，也是三条证明链扣在一起的地方：第 5 章算出那条直线的近似最坏差 1.1%（解析解），第 6 章算出两次牛顿之后定点误差约 1.4–1.5 个最低位（数值模型），第 8 章让真机器跑出来 0.925 个最低位（机器输出）。这三个数不共用一个单位——1.1% 是近似阶段的相对误差，后两个才是输出端的最低位口径：模型与实测同为最低位口径、量级吻合；1.1% 讲的是误差的起点，不与最低位直接比大小，而且机器实测比模型预测更小——这正是守恒的表现：模型为了覆盖所有情形，必然比某一条具体轨迹略悲观。**如果实测反而比模型大一截，那就说明模型漏掉了某项误差来源，第 6 章的结论就得推翻重来。现在不用。**余量在两个口径下不一样（0.80 倍与 0.61～0.86 倍），原因不难解释：脚本是用相对误差表达结果的，而相对误差要除以结果幅值——同一个绝对误差，结果越小看起来就越大。本册把两个口径都列出来，不挑好看的那一个。

### 【方法论】`三条证明链，现在都闭合了`

```agc
✅ 证明链 1（表示）：15 位数据 → 尺度 → 物理量
    第 4 章   起点终点都接在源码上，中间只用乘法

✅ 证明链 2（算法）：定点 → 归一化 → 乘除 → 牛顿 → 量化 → 最终误差
    第 5、6 章  误差上界由解析式给出，配可复跑脚本 verify_accurate.py

✅ 证明链 3（执行）：源码 → yaYUL → 绳芯镜像 → yaAGC → 寄存器里的实际数值
    第 8 章   真实 Luminary 099 镜像，72 点实测，最坏 0.925 个双精度最低位

证据分层（本册用五档，第 8 章用到了最高一档）
  ① 机器实证   真实绳芯镜像上的运行结果，可复跑     ← 本册最强的一档
  ② 源码实证   仓库里按文件行号可核对的事实
  ③ 外部资料   NASA / MIT / 公开史料，给参考文献编号
  ④ 作者推导   我从源码数据算出来或推出的结论，写明算式
  ⑤ 解释性类比 解释工具，不算证据
```

> 把这三条链分开，是本册的证据纪律：一个结论是「源码这么写的」、是「我算的」、还是「机器跑出来的」，可信度并不一样，读者有权知道自己读到的是哪一种。第 6 章的脚本再严谨，也只是链 2；它不能冒充链 3，所以本册在这一版之前一直把链 3 标成未闭合。现在链条接上了，本册把「机器实证」单列一档放在最前面——**证据越硬，越应该被单独标出来，而不是混在「作者推导」里含糊过去。**

### 控制流：实际跑过的四步（命令都是真跑过的，不是路径示意）

*环境 Ubuntu 26.04 LTS (WSL2) × gcc 15.2.0；工具链为 Virtual AGC 源码自建*

1. **取源码并固定版本** —— 克隆 Apollo-11 仓库，切到本册固定的 commit。行号以这个版本为准。同时取 Virtual AGC 的源码树，用它的 yaYUL / yaAGC。　`git checkout 911e5c0283c629c50cb97666f34065e8c07d71a5`
2. **汇编，拿到绳芯镜像** —— 在程序目录里对 MAIN.agc 跑 yaYUL。这里会撞上两个文件名对不上的问题，改完名字之后 exit=0，产出 73,728 字节的镜像——正好把 36,864 个字填满。　`cd Luminary099 && yaYUL MAIN.agc > lum099.lst 2> lum099.err`
3. **把复位向量改成一段桩程序** —— 复位后机器会从 0o4000 开始执行开机自检。本册把它换成「INHINT → TC SQRTSUB → 把 MPAC / MPAC+1 / MPAC+2 / MPTEMP 抄到暂存 → 停住」。地址换算用固定存储器的公式：0x800 + FB×1024 + (AGC地址 & 0o1777)。　`set *0x801=1763   # TC SQRTSUB（这之前先 set *0x800=4 补回 INHINT）`
4. **启动、单步、读寄存器，逐点扫描** —— 每点独立启动一次 yaAGC，把输入写进 MPAC，用 --command 脚本自动走完全程，再读 MPAC 与 MPAC+1 的实际位模式。72 点扫完，停机位置全部落在自陷循环里，说明每次 SQRTSUB 都正常返回。　`yaAGC --exec=lum099.bin --symbols=lum099.symtab --command=one.cmd --no-resume --inhibit-alarms`

**要点。**

- **用 yaYUL 不是本册挑的，是源码自己指定的**：chrislgarry/Apollo-11 仓库 README 的 Attribution 表里，Assembler 一栏写的就是 yaYUL。
- **读源码和跑源码是两回事**：这份 1969 年的代码内容完好，却因为两个文件名拼不上而过不了汇编——静态阅读永远撞不见这类问题。
- **第 3 条证明链闭合了**：真实 Luminary 099 绳芯镜像在 yaAGC 上跑出 √x，72 点全部落在 1 个双精度最低位以内，最坏 0.925 个最低位。
- **模型是上界，不是预言**：第 6 章的 1.41–1.53 个最低位压住了实测的 0.925，这是模型该有的样子——覆盖所有情形，因此比具体某一条轨迹略悲观。
- **上界与实测的关系是判断模型对错的尺子**：实测若反超上界，就说明模型漏了误差来源，结论要推翻；这次没有。
- **本册有一处存疑如实标出，且与本章无关**：第 3 章 -B- 型 FRACTIONAL 的源码标注 MAX .99996 与按 14 位尾数手算得到的 .99994 对不上，本册不用它做任何论证。
- **还有一处尚未追到底，一并标出**：本册另外拿 Apollo-11 仓的 Luminary099 目录（只改了那两个文件名）编了一份镜像，与 Virtual AGC 维护版逐字节比对，73,728 字节里只有 4 个字不同，首差在第 36,343 字节。这 4 个字所在的地址在汇编清单里没有任何源码行与之对应，——落在没被源码填满的存储区里；本册没能进一步定位根因，如实记下，不用它做任何结论。


---

### 怎么复核本册的每一条

下面几条命令可以直接粘贴执行。原始源码为公有领域，任何人都能拿到同一份底本。

```bash
git clone https://github.com/chrislgarry/Apollo-11
cd Apollo-11
git checkout 911e5c0283c629c50cb97666f34065e8c07d71a5

# 核对尺度表（A–J 十型）与 CDU 角度那一行
sed -n '686,730p' Comanche055/ASSEMBLY_AND_OPERATION_INFORMATION.agc

# 核对 MPAC 只有 7 个字，以及三种装载方式
grep -n 'MPAC' Comanche055/ERASABLE_ASSIGNMENTS.agc | head -5
sed -n '63,74p;517,547p' Comanche055/INTERPRETER.agc

# 核对归一化的契约与开平方的四个常数、两次牛顿迭代
sed -n '1629,1651p;2497,2516p;2556,2579p' Comanche055/INTERPRETER.agc
grep -n 'SLOPEHI\|BIASHI\|SLOPELO\|BIASLO' Comanche055/INTERPRETER.agc

# 核对定点常数的写法（5/8 与 D1/32）
grep -n '5/8\|D1/32' Comanche055/ORBITAL_INTEGRATION.agc Comanche055/CONIC_SUBROUTINES.agc

# 核对源码里两种补数并存、且有专门的转换例程
grep -rn "2'S COMPLEMENT\|1'S COMPLEMENT\|2'S COMP\|1'S COMP" Comanche055

# 把本册两条推导跑一遍（只用标准库）
python verify_accurate.py

# 把执行证据链跑一遍（第 8 章，需自行编译 Virtual AGC）
# 1. 建工具链（yaYUL 的 Makefile 用老式小写 ${cc}，所以显式传 cc=gcc）
sudo apt-get install -y build-essential libreadline-dev
cd virtualagc/yaYUL && make cc=gcc && cd ../yaAGC && make cc=gcc
# 2. 汇编，拿到绳芯镜像（应为 73,728 字节）
cd Luminary099 && ../../yaYUL/yaYUL MAIN.agc > lum099.lst 2> lum099.err
stat -c %s MAIN.agc.bin
# 3. 把复位向量改成调用 SQRTSUB 的桩程序，再单步读 MPAC
echo 'set *0x800=4'        >  one.cmd   # INHINT
echo 'set *0x801=1763'     >> one.cmd   # TC SQRTSUB
echo 'set *0154=8192'      >> one.cmd   # x = 0.5 写进 MPAC
seq 1 140 | sed 's/.*/step/' >> one.cmd
echo 'print *0154'         >> one.cmd   # 结果高位字
echo 'print *0155'         >> one.cmd   # 结果低位字
yaAGC/yaAGC --exec=MAIN.agc.bin --symbols=MAIN.agc.symtab \
  --command=one.cmd --no-resume --inhibit-alarms --quiet
# 期望：0154 读出 026501（八进制），0155 读出 07463
```

- 「**机器实证**」条目附了完整命令与原始输出，照抄即可复跑一遍；若你复跑出来的数与本册不同，以你的为准，并请指出错处。
- 所有「源码实证」条目都标了文件与行号。行号以 commit `911e5c0` 为准；仓库后续若有变动，请先 `git checkout 911e5c0` 再核对。
- 「外部资料」条目不能回仓库核对，都给了参考文献编号，按编号去原文查。
- 「作者推导」条目给出了完整算式，你可以自己重算一遍。若结果与本册不同，以源码为准。
- 「解释性类比」只是解释工具，不是证据，不必去源码里找。

## 参考文献

全部列出，便于复现本册的研究路径。

- **[1]** Apollo-11 源码仓库（Luminary099 登月舱 / Comanche055 指令舱）
  - chrislgarry（整理），MIT Museum 藏硬拷贝数字化（Paul Fjeld 扫描） · 源码
  - https://github.com/chrislgarry/Apollo-11，commit 911e5c0283c629c50cb97666f34065e8c07d71a5。Public domain（公有领域）。源码自身的装订记时为 16:27 JULY 14, 1969；仓库 commit 的提交时间为 2026-07-15（该日期为仓库 commit 的提交时间，与源码内容无关，可自行用 git log -1 核实）。两者相差 57 年，不要混为一谈。
  - 用于：全文所有「源码实证」条目的出处。
- **[2]** MIT Instrumentation Laboratory Report R-500：Apollo Guidance and Navigation — Part VI, Guidance Computer Design
  - MIT Instrumentation Laboratory · 一手技术报告
  - MIT，1960 年代原始设计报告（klabs.org 等处存有扫描件）。
  - 用于：指令字格式（3 位操作码 + 12 位地址）、EXTEND 与 partial codes 两种扩充手法、Block I 指令清单。
- **[3]** NASA SP-8015：Guidance and Navigation for Entry Vehicles（原稿误引条目，已更正）
  - NASA · 一手技术报告
  - NASA NTRS 19690010164（1968）。本次对 NTRS 原页核验：该号实为再入飞行器导航专册，与 AGC 无关；原稿据其引述的 AGC「通用计算机」表述，已改由 [4] 的 Appendix F 承担（该附录取自 Draper Lab Report R-700，对页核验通过）。
  - 用于：文献纠错记录。
- **[4]** Apollo GN&C System Components（Appendix F）
  - NASA · 一手技术报告
  - NASA NTRS 20230005922（NASA/TP-20230005922，2023）。Appendix F 取自 Draper Lab Report R-700；PDF p288 含 AGC「general purpose computer…priority driven operating system」原话，p292（印刷页 271）含 AGC 规格表——均已对页核验。
  - 用于：机载制导导航控制系统的职责边界描述。
- **[5]** Apollo Onboard Navigation Techniques
  - NASA · 一手技术报告
  - NASA NTRS 20090016292。
  - 用于：机载导航与地面测控的分工、滤波方法的工程化过程。
- **[6]** Apollo 11 Mission Report / 任务相关技术文件
  - NASA · 一手技术报告
  - NASA NTRS 19700008096（MSC-00171）。原稿误引的 19700014507 经 NTRS 原页核验为月球大地测量报告，与任务报告无关，已更正；表 5-I 为着陆事件时间线（含 1202/1201 序列）
  - 用于：任务时间线与测控（MSFN）定轨职责的表述。
- **[7]** Apollo 11 Lunar Surface Journal — Program Alarms（1201 / 1202）
  - NASA 历史部（ALSJ） · 史料 / 综述
  - nasa.gov 历史档案，含当事软件人员对 1202/1201 的回顾。
  - 用于：1202/1201 与 core set / VAC 耗尽的关联，及重启后恢复的口述佐证。
- **[8]** Core rope memory（含其中引用的 Ken Shirriff 等人资料）
  - Wikipedia 条目及其引用 · 史料 / 综述
  - 公开条目。注意：该条目关于存储密度的部分自身带有 citation needed 标记。
  - 用于：仅用于 LOL memory 这个行话外号。原条目给出的密度数字 72 KB/ft³ 因来源过弱已从正文删除。
- **[9]** A New Approach to Linear Filtering and Prediction Problems
  - R. E. Kalman · 史料 / 综述
  - Journal of Basic Engineering, 1960。
  - 用于：最优估计的理论起点。
- **[10]** James Potter 讣闻（MIT News）
  - MIT News · 史料 / 综述
  - news.mit.edu，2005。
  - 用于：平方根形式滤波的提出者及其在阿波罗上的作用。
- **[11]** Apollo 11 任务时间线
  - Planetary Society / 史密森尼国家航空航天博物馆 · 史料 / 综述
  - 两处公开资料。
  - 用于：第 4 章时间线。两源在地月转移点火时刻上存在约一分钟差异，正文已标明。
- **[12]** Apollo Guidance Computer 公开技术综述（Wikipedia 等）
  - Wikipedia 等 · 史料 / 综述
  - 公开条目。其中「Minuteman II 混用多种逻辑族」一句自身标有 citation needed。
  - 用于：Block I / Block II 芯片数与封装、整机外形尺寸。等级偏低，正文已单独标注；功耗重量与容量改用 NASA NTRS 20230005922 原文。
- **[13]** AGC Block II Instructions（Digital Development Memo #193）
  - Albert Hopkins，MIT Instrumentation Laboratory · 一手技术报告
  - MIT 仪器实验室内部备忘录，ibiblio.org/apollo/Documents/dd_memo_193.pdf。
  - 用于：Block II 指令集扩充的两条机制（extension 与 partial codes）及 quarter code 的位来源。
- **[14]** Apollo Guidance Computer Block II — CMC Data Cards（基本指令与操作码表）
  - F. Bernardini 整理，基于 CMC/AGC 原始手册 · 一手技术报告
  - ibiblio.org/apollo/CMC_data_cards_15_Fabrizio_Bernardini.pdf（整理稿，非 NASA 原件）。
  - 用于：基本指令的操作码八进制值（TC=00、CCS=01、CA=03、AD=06 等）与 EXTEND=0.0006。
- **[15]** Virtual AGC 项目：yaYUL 汇编器与 yaAGC 模拟器
  - Ron Burkey 等，开源项目 · 公开网页
  - github.com/virtualagc/virtualagc；项目网站 virtualagc.github.io/virtualagc/ 与 ibiblio.org/apollo。chrislgarry/Apollo-11 仓库 README 的 Attribution 表里，Assembler 一栏写的就是 yaYUL（2026-09-15 对其 raw README 逐字核对）。
  - 用于：把本册的源码实证变成可执行证据：汇编与仿真复现的工具链出处。
- **[16]** chrislgarry/Apollo-11 仓库 README 的 Attribution 表（源码来历与装订记时）
  - chrislgarry/Apollo-11 仓库 README · 源码
  - Attribution 表在 chrislgarry/Apollo-11 的 README（不是 virtualagc 仓库的 README）。2026-09-15 逐字核对：Luminary099 一栏写明 「Assemble revision 001 of AGC program LMY99 by NASA」与「2021112-061. 16:27 JUL. 14, 1969」；Comanche055 一栏为「10:28 APR. 1, 1969」。
  - 用于：装订记时、程序正式编号；汇编器为 yaYUL。

**两个日期，不要混。** 源码自身的装订记时 `16:27 JULY 14, 1969`，写在源码头部，指这份程序当年装订的那个时刻；仓库 commit `911e5c0283c629c50cb97666f34065e8c07d71a5` 的提交时间是 2026-07-15，只是仓库整理者的提交时间，与源码内容无关，可用 `git log -1 911e5c0` 自行核实。两者相差 57 年。若仓库后续变动，请以该 commit 为准核对行号。

## 术语速览

| 术语 | 一句话解释 |
| --- | --- |
| 位（bit） | 最小单位。一个开关只有 0 或 1 两种状态，就是一个 bit。位宽、操作码位数说的都是它。 |
| 字（word） | 这台机器一次能处理的完整一份数据。1 个字 = 15 位数据 + 1 位奇偶校验，物理上占 16 位。 |
| 定点（fixed-point） | 小数点的位置固定，并且由人来约定。机器只认整数位，小数点的位置写在注释和尺度表里。 |
| 尺度（scale） | 「每一位代表多少」的约定。同一个 15 位字，按不同尺度读，可以是整数、小数，也可以是角度。 |
| 尾数（magnitude bits） | 去掉符号位之后，真正用来表示大小的那些位。单精度 14 位，双精度 28 位，三精度 42 位。 |
| MPAC | 多用途累加器（MULTI-PURPOSE ACCUMULATOR）。这台机器做算术的地方，可写内存里只有 7 个字。 |
| MODE | 可写内存里的一个字（不是硬件寄存器），记录 MPAC 当前被当作哪种类型用：0 双精度、+1 三精度、-1 三维向量。 |
| 归一化（normalize） | 把一个数左移到「够大」为止（通常要求 ≥ .5），让有效位不浪费在高位的 0 上，同时记下移了几位。 |
| 反归一化 | 算完之后按记录的移位次数挪回去，把规范形式还原成真正的数值。 |
| 一补数（one's complement） | 一种表示负数的方法，特点是 0 有两个写法：+0 和 -0。这台机器用的就是它。 |
| POODOO | 源码里的「放弃并报警」例程，后面跟一个八进制告警码。相当于今天抛出一个致命异常。 |
| SCALED Bnn | 源码注释里的定点写法：整数 X 配上 Bnn，表示 X × 2 的负 nn 次方。例：5 SCALED B3 = 5/8。 |
| 牛顿迭代 | 求平方根的经典迭代：x' = (x + N/x)/2。误差按平方收敛——第一次迭代后误差约是原来的平方的一半。 |
| yaYUL | 当代开源的 AGC 汇编器。chrislgarry/Apollo-11 仓库 README 的 Attribution 表里，Assembler 一栏写的就是它。 |
| yaAGC | 当代开源的 AGC 指令级模拟器，配 yaYUL 用，可以在今天的电脑上跑当年的程序。 |

---

**证据来源。** 源码实证来自本地 Apollo-11 仓库（chrislgarry/Apollo-11，公有领域）。机器实证来自第 8 章：Virtual AGC 的 yaYUL / yaAGC（Ubuntu 26.04 × gcc 15.2.0 源码自建），命令、原始输出与镜像 MD5 都写在正文里，可自行复跑。其余外部资料为 NASA、MIT 公开技术报告。

**一处存疑。** 第 3 章 -B- 型 FRACTIONAL 的源码标注 MAX .99996，与按 14 位尾数手算得到的 .99994 对不上，本册如实标出，且不用它做任何论证。

**一处未追到底。** 本册另用 Apollo-11 仓的 Luminary099 目录（仅修正两个文件名）编了一份镜像，与 Virtual AGC 维护版逐字节比对，73,728 字节中仅 4 个字不同。这 4 个字所在地址在汇编清单里没有对应源码行，本册未能进一步定位根因，如实记下，不用它做任何结论。

**图片。** Markdown 版不含插图。本册共四张照片：第 1 章起首是 1965 年 AGC 主机（背景图，不作为任何数字的证据）；第 3 章和第 6 章各放一张 MIT 硬拷贝扫描件，分别对应 Comanche055 原始打印清单的 page 19（尺度表 A–J）与 page 1184（SQRTSUB 第一页），来自 ibiblio.org/apollo，公共领域；第 8 章起首是Apollo 11 登月舱上升段（背景图，不作为证据）。扫描件与背景图的区别在图注里用「外部资料」和「背景」两档标出。