# 精确数 · 有理数、大整数与根式

> 符号计算的底气，是它的数从不四舍五入。1/3 就是 1/3，不是 0.333；50 的阶乘一位不差；√2 平方精确等于 2。这一章讲清 SymPy 的整数（任意精度）、有理数（精确分数）和根式，以及「精确」这件事到底意味着什么。

- 作者：David（道雾轩）
- 专栏：符号计算的艺术（https://daiw.org/manual/symbolic-computing.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/symbolic-computing/exact-numbers
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 精确数 · 有理数、大整数与根式

[上一章](https://daiw.org/manual/symbolic-computing/expression-tree)说表达式是树，叶子是符号与数。这一章专看**数**这类叶子——它是符号计算和数值计算分道扬镳的地方：符号计算里的数，**永远精确，从不四舍五入**。

## 有理数：1/3 就是 1/3

[数值计算](https://daiw.org/manual/numerical-computing/floating-point)里，$\frac13$ 只能存成 `0.3333333333`（近似），于是有了 `0.1 + 0.2 != 0.3` 那个经典的坑。符号计算根本不碰这个坑，因为它用**精确的有理数**：

```python
from sympy import *

print(Rational(1, 3) + Rational(1, 6))     # 1/2   —— 精确!
print(Rational(1,10) + Rational(2,10) == Rational(3,10))   # True
```

`Rational(1, 3)` 是**分子 1、分母 3 的精确分数**，不是小数。分数相加时，SymPy 通分、约分，给出**精确的** $\frac12$。数值计算里 `0.1+0.2` 那 $10^{-17}$ 的误差，在这里**压根不会出现**——因为这里没有浮点数。

<Callout type="warn">
  **小心 Python 的 `/`。** 在 SymPy 里直接写 `1/3` 会先被 **Python** 算成浮点数 `0.333…`，精确性就丢了。要得到精确有理数，用 `Rational(1,3)`，或者用 `S(1)/3`（`S` 把整数变成 SymPy 对象，于是 `/` 走符号除法）：

  ```python
  print(1/3)        # 0.3333333333333333   —— Python 浮点,已经不精确
  print(S(1)/3)     # 1/3                  —— SymPy 有理数,精确
  ```

  这是新手最常见的一个坑：**精确性从你敲下 `/` 的那一刻起就可能已经丢了。**
</Callout>

## 大整数：要多大有多大

符号计算的整数是**任意精度（bignum）**的——不像普通编程语言的 `int` 有 64 位上限，它能表示**要多大有多大**的整数，一位都不差：

```python
print(factorial(30))     # 265252859812191058636308480000000
print(Integer(2)**100)   # 1267650600228229401496703205376
```

$30!$ 有 33 位，$2^{100}$ 有 31 位，普通 64 位整数早就溢出了，而符号计算**精确无误**地给出每一位。这是数论、密码学、组合计算的刚需——那里的数动辄几百上千位，差一位就全盘皆错。（Python 的内置 `int` 恰好也是任意精度的，这点上和 SymPy 一致；但多数语言的整数会溢出。）

## 根式与常数：符号地保留

最能体现「符号」二字的，是**无理数**。$\sqrt2$、$\pi$、$e$ 这些没法用有限小数精确写出的数，符号计算干脆**不算它们的小数**，而是把符号**原样保留**，让它参与精确运算：

```python
print(sqrt(8))               # 2*sqrt(2)   —— 自动提取出完全平方因子
print(sqrt(2)**2)            # 2           —— 精确,不是 2.0000000001
print(expand((sqrt(2)+1)*(sqrt(2)-1)))   # 1   —— (√2+1)(√2-1)=2-1=1,精确
print(pi.is_irrational)      # True
```

$\sqrt2$ 保留成符号，于是 $(\sqrt2)^2$ **精确**等于 $2$，$(\sqrt2+1)(\sqrt2-1)$ **精确**等于 $1$。要是用[数值](https://daiw.org/manual/numerical-computing/machine-epsilon)算，$\sqrt2\approx1.41421356$，平方回去会得到 `2.0000000000000004`——又是舍入误差。符号计算把这些误差**从源头消灭**了。

## 需要小数时：要多少位给多少位

当然，最后你常常还是想要个小数（比如画图、报告结果）。符号计算能在**你指定的任意精度**下，把符号数转成小数——想要 40 位就给 40 位：

```python
print(N(pi, 40))     # 3.141592653589793238462643383279502884197
print(N(sqrt(2), 50))
```

注意这和浮点数的区别：浮点数**固定** 15~16 位有效数字，多了没有；而 `N(pi, 1000)` 能精确给出 $\pi$ 的**前 1000 位**。精度由你说了算，这叫**任意精度浮点**。

## 精确，意味着什么

回头看，「精确数」给了符号计算三样数值计算给不了的东西：

| | 数值（浮点） | 符号（精确） |
| --- | --- | --- |
| $\frac13$ | `0.333…`（近似） | $\frac13$（精确） |
| $50!$ | 溢出 / 近似 | 精确的 65 位整数 |
| $\sqrt2^2$ | `2.0000…04` | 精确的 $2$ |
| 误差 | 每步累积 | **完全没有** |
| 代价 | 快、省内存 | 慢、可能很占内存 |

没有误差，是符号计算能**做代数**的前提——你没法在「带随机误差的数」上做因式分解、判断两个式子是否恒等。当然，精确不是免费的：有理数要存分子分母、大整数要存每一位、根式越滚越长，**又慢又占内存**——这个代价会在[表达式膨胀](https://daiw.org/manual/symbolic-computing/expression-swell)那章找上门来。

## 小结与下一章

符号计算的数**永远精确**：**有理数**（$\frac13$ 精确，别被 Python 的 `/` 坑了，用 `Rational` 或 `S(1)/3`）、**任意精度大整数**（$50!$ 一位不差）、**根式与常数**（$\sqrt2,\pi$ 符号保留，$\sqrt2^2$ 精确等于 2）。需要小数时用 `N(expr, 位数)` 要多少给多少。**无误差，是做代数的前提。**

树的结构有了、精确的数也有了，那 SymPy 会不会**自动**把式子化到最简？答案出人意料：**只化一部分，而且必须如此**。为什么 `x+x` 自动变 `2x`，`sin²+cos²` 却不自动变 `1`？下一章揭晓，这背后藏着一个深刻的不可能性。👉 [自动化简的边界](https://daiw.org/manual/symbolic-computing/auto-simplification)。

## 参考文献

- [1] Donald Knuth.《The Art of Computer Programming, Vol. 2: Seminumerical Algorithms》—— 任意精度算术与有理数。
- [2] SymPy 文档.「Gotchas: Rational, S(), N().」https://docs.sympy.org/latest/tutorials/intro-tutorial/gotchas.html
- [3] von zur Gathen & Gerhard.《Modern Computer Algebra》, ch. 5 —— 精确算术的实现。
