# 2.5.3示例：符号代数

> 译者： [https://sicp.comp.nus.edu.sg/chapters/46](https://sicp.comp.nus.edu.sg/chapters/46)

符号代数表达式的操纵是一个复杂的过程，它说明了大型系统设计中出现的许多最困难的问题。 通常，代数表达式可以看作是层次结构，即应用于操作数的运算符树。 我们可以通过从一组原始对象（例如常量和变量）开始，然后通过代数运算符（例如加法和乘法）将它们组合来构造代数表达式。 与其他语言一样，我们形成抽象使我们能够以简单的术语引用复合对象。 符号代数中的典型抽象是诸如线性组合，多项式，有理函数或三角函数之类的思想。 我们可以将它们视为化合物&lt;quote&gt;类型，&lt;/quote&gt;，这些类型通常可用于指导表达式的处理。 例如，我们可以将![%20x%5E%7B2%7D%5C%2C%20%5Csin%20%28y%5E2%2B1%29%2Bx%5C%2C%20%5Ccos%202y%2B%5Ccos%20%28y%5E3%20-2y%5E2%29%20](img/13d385c66405d1c30881fb2302c37e21.jpg)表达式描述为![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)中的多项式，其系数是![y](img/c592009395c2de830215c39f7bb6f97b.jpg)中多项式为整数的多项式的三角函数。

在这里，我们不会尝试开发完整的代数操纵系统。 这样的系统是极其复杂的程序，体现了深厚的代数知识和优雅的算法。 我们要做的是看一个简单但重要的代数运算部分：多项式的算术。 我们将说明这种系统的设计人员面临的各种决策，以及如何应用抽象数据和通用操作的思想来帮助组织这项工作。

## 多项式的算术运算

设计用于对多项式执行算术运算的系统的首要任务是确定多项式是什么。 多项式通常是相对于某些变量定义的（_不确定多项式的_）。 为简单起见，我们将自己限制为仅具有一个不确定的多项式（_单变量多项式_）。 [[1]](46#footnote-1) 我们将多项式定义为项的和，每个项要么是系数，不确定数的幂，要么是系数和不确定数的幂的乘积。 系数定义为不依赖于多项式的不确定性的代数表达式。 例如，![%205x%5E2%20%2B3x%20%2B7%20](img/a564a050dfd5a163a3f9770e6d80797b.jpg)是![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)中的简单多项式，![%20%28y%5E2%20%2B1%29x%5E3%20%2B%282y%29x%2B1%20](img/f48ef7b52d91f9b1690ffdfdfafc7645.jpg)是![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)中的系数是![y](img/c592009395c2de830215c39f7bb6f97b.jpg)中的多项式的多项式。

我们已经避开了一些棘手的问题。 这些多项式中的第一个是否与多项式![5y%5E2%20%2B3y%20%2B7](img/e5338897ab3702c55c9c8260dbf27526.jpg)相同？ 如果我们纯粹将多项式视为一个数学函数，则一个合理的答案可能是&lt;quote&gt;是，但是如果我们考虑将多项式视为一种语法形式，则可能为&lt;quote&gt;。&lt;/quote&gt; 第二多项式在数学上等同于![y](img/c592009395c2de830215c39f7bb6f97b.jpg)中的多项式，其系数是![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)中的多项式。 我们的系统是否应该认识到这一点？ 此外，还有其他方式来表示多项式-例如，作为因子的乘积，或（对于单变量多项式）表示为根的集合，或表示为指定点集上的多项式的值。 [[2]](46#footnote-2) 通过确定在我们的代数操纵系统中，&lt;quote&gt;多项式&lt;/quote&gt;将是一种特殊的语法形式，而不是其潜在的数学意义，可以解决这些问题。&lt;/quote&gt;

现在我们必须考虑如何对多项式进行算术运算。 在这个简单的系统中，我们将仅考虑加法和乘法。 此外，我们将坚持要结合的两个多项式必须具有相同的不确定性。

我们将遵循熟悉的数据抽象原则来进行系统设计。 我们将使用称为 _poly_ 的数据结构表示多项式，该数据结构由变量和项的集合组成。 我们假设我们有选择器`variable`和`term_list`从多边形中提取那些部分，以及构造函数`make_poly`从给定的变量和术语表中组合多边形。 变量只是一个符号，因此我们可以使用 [2.3.2](36) 部分的`is_same_variable`函数比较变量。 以下函数定义了多边形的加法和乘法：

```js
function add_poly(p1, p2) {
    return is_same_variable(variable(p1), variable(p2))
           ? make_poly(variable(p1),
                       add_terms(term_list(p1),
                                 term_list(p2)))
           : Error("Polys not in same var in add_poly",
                   list(p1, p2));
}
function mul_poly(p1, p2) {
    return is_same_variable(variable(p1), variable(p2))
           ? make_poly(variable(p1),
                       mul_terms(term_list(p1),
                                 term_list(p2)))
           : Error("Polys not in same var in mul_poly",
                   list(p1, p2));
}
```

要将多项式合并到我们的通用算术系统中，我们需要为它们提供类型标签。 我们将使用标记`"polynomial"`，并在运算表中的标记多项式上安装适当的运算。 我们会将所有代码嵌入到多项式软件包的安装函数中，类似于 [2.5.1](44) 部分中的代码：

```js
function install_polynomial_package() {
    // internal functions
    // representation of poly
    function make_poly(variable, term_list) {
        return pair(variable, term_list);
    }
    function variable(p) { return head(p); }
    function term_list(p) { return tail(p); }
    //   functions is_same_variable and
    //   is_variable from section 2.3.2
    // representation of terms and term lists
    //   functions adjoin_term ...coeff from text below

    function add_poly(p1, p2) {
        ...
    }
    //   functions used by add_poly
    function mul_poly(p1, p2) {
        ...
    }
    //   functions used by mul_poly
    // interface to rest of the system
    function tag(p) {
        return attach_tag("polynomial", p);
    }
    put("add", list("polynomial", "polynomial"),
        (p1, p2) => tag(add_poly(p1, p2)));
    put("mul", list("polynomial", "polynomial"),
        (p1, p2) => tag(mul_poly(p1, p2)));
    put("make", list("polynomial"),
        (variable, terms) =>
            tag(make_poly(variable, terms)));
    return "done";
}
```

Polynomial addition is performed termwise. Terms of the same order (i.e., with the same power of the indeterminate) must be combined. This is done by forming a new term of the same order whose coefficient is the sum of the coefficients of the addends. Terms in one addend for which there are no terms of the same order in the other addend are simply accumulated into the sum polynomial being constructed.

为了操作术语列表，我们将假定我们有一个返回空术语列表的构造函数`the_empty_termlist`和一个将新术语与术语列表相邻的构造函数`adjoin_term`。 我们还将假设我们有一个谓词`is_empty_termlist`来判断给定术语列表是否为空，一个选择器`first_term`从一个术语列表中提取最高阶术语，一个选择器`rest_terms`返回除 最高阶词。 为了处理术语，我们将假定我们有一个构造函数`make_term`，该构造函数以给定的顺序和系数构造一个术语，选择器`order`和`coeff`分别返回该术语的顺序和系数。 这些操作使我们可以将术语和术语列表都视为数据抽象，我们可以单独担心它们的具体表示形式。

这是构造两个多项式和的项列表的函数： [[3]](46#footnote-3)

```js
function add_terms(L1, L2) {
    if (is_empty_termlist(L1)) {
      return L2;
    } 
    else if (is_empty_termlist(L2)) {
      return L1;
    } 
    else {
      const t1 = first_term(L1);
      const t2 = first_term(L2);
      if (order(t1) > order(t2)) {
        return adjoin_term(t1, add_terms(rest_terms(L1), L2));
      } 
      else if (order(t1) < order(t2)) {
        return adjoin_term(t2, add_terms(L1, rest_terms(L2)));
      } 
      else {
        return adjoin_term(make_term(order(t1), 
                                     add(coeff(t1),
                                     coeff(t2))),
                          add_terms(rest_terms(L1),
                                    rest_terms(L2)));
      }
    }
}
```

The most important point to note here is that we used the generic addition function `add` to add together the coefficients of the terms being combined. This has powerful consequences, as we will see below.

为了将两个项列表相乘，我们反复使用`mul_term_by_all_terms`将第一个列表的每个项与另一个列表的所有项相乘，这会将给定项与给定项列表中的所有项相乘。 生成的术语列表（第一个列表的每个术语一个）被累加为一个和。 将两个项相乘可形成一个项，其阶次是因子阶数的总和，并且其系数是因子系数的乘积：

```js
function mul_terms(L1, L2) {
    return is_empty_termlist(L1)
           ? the_empty_termlist
           : add_terms(mul_term_by_all_terms(
                                 first_term(L1), L2),
                       mul_terms(rest_terms(L1), L2));
}
function mul_term_by_all_terms(t1, L) {
    if (is_empty_termlist(L)) {
        return the_empty_termlist;
    } else {
        const t2 = first_term(L);
        return adjoin_term(make_term(order(t1) + order(t2),
                               mul(coeff(t1), coeff(t2))),
                 mul_term_by_all_terms(t1, rest_terms(L)));
    }
}
```

这实际上就是多项式加法和乘法的全部。 请注意，由于我们使用通用函数`add`和`mul`对项进行运算，因此我们的多项式程序包能够自动处理通用算术程序包已知的任何类型的系数。 如果我们包含强制性机制，例如 [2.5.2](45) 中讨论的一种机制，那么我们还能够自动处理不同系数类型的多项式的运算，例如![%20%0A%20%20%20%20%20%20%20%20%7B%5Cleft%5B3x%5E2%20%2B%282%2B3i%29x%2B7%5Cright%5D%20%5Ccdot%20%5Cleft%5Bx%5E4%20%2B%5Cfrac%7B2%7D%7B3%7Dx%5E2%0A%20%20%20%20%20%20%20%20%2B%285%2B3i%29%5Cright%5D%7D%0A%20%20%20%20%20%20%20%20](img/cf306a54260809cad469d9b2bf24b981.jpg) ]因为我们在通用算术系统中安装了多项式加法和乘法函数`add_poly`和`mul_poly`作为`polynomial`类型的`add`和`mul`运算，所以我们的系统还能够自动处理多项式运算，例如 ![%0A%20%20%20%20%20%20%20%20%7B%5Cleft%5B%20%28y%2B1%29x%5E2%20%2B%28y%5E2%20%2B1%29x%2B%28y-1%29%5Cright%5D%5Ccdot%20%5Cleft%5B%28y-2%29%0A%20%20%20%20%20%20%20%20x%2B%28y%5E3%20%2B7%29%5Cright%5D%7D%0A%20%20%20%20%20%20%20%20](img/eb0dea9b1673fa65b14f4d12aa7ef0ab.jpg)

原因是当系统尝试合并系数时，它将通过`add`和`mul`进行调度。 由于系数本身是多项式（在![y](img/c592009395c2de830215c39f7bb6f97b.jpg)中），因此将使用`add_poly`和`mul_poly`组合这些系数。 结果是一种&lt;quote&gt;数据定向递归&lt;/quote&gt;，其中，例如，对`mul_poly`的调用将导致对`mul_poly`的递归调用，以使系数相乘。 如果系数的系数本身就是多项式（可以用来表示三个变量中的多项式），则数据方向将确保系统将遵循另一级递归调用，依此类推，直至达到 数据决定了。 [ [4]](46#footnote-4)

## 代表术语列表

最后，我们必须面对为术语表实现良好表示的工作。 实际上，术语列表是一组按术语顺序键入的系数。 因此，如 [2.3.3](37) 部分中所讨论的任何表示集的方法都可以应用于此任务。 另一方面，我们的函数`add_terms`和`mul_terms`始终按从高到低的顺序顺序访问术语列表。 因此，我们将使用某种有序列表表示形式。

我们应该如何构造代表术语列表的列表？ 一个考虑因素是我们打算处理的多项式的&lt;quote&gt;密度&lt;/quote&gt;。 如果多项式在大多数阶上具有非零系数，则称其为_密集型_。 如果它有很多零项，则称其为_稀疏_。 例如，![%20A%3A%5Cquad%20x%5E5%20%2B2x%5E4%20%2B3x%5E2%20-2x%20-5%20](img/610a0ad5016722c89ebb57edc57ae1b8.jpg)是一个密集多项式，而![%20B%3A%5Cquad%20x%5E%7B100%7D%20%2B2x%5E2%20%2B1%20](img/7291b5ce884e583f81b81f4e82fef28d.jpg)是稀疏的。

稠密多项式的术语列表最有效地表示为系数列表。 例如，上面的![A](img/efdb05f076173b39fdd26ef663e7b0d8.jpg)可以很好地表示为`list(1, 2, 0, 3, -2, -5)`。 该表示形式中项的顺序是从该项的系数开始的子列表的长度，减1。 [[5]](46#footnote-5) 对于稀疏多项式，例如![B](img/813135a6280e2672503128d3d2080d4a.jpg)，这将是一个糟糕的表示形式： 将会是一个巨大的零列表，其中包含一些孤独的非零项。 稀疏多项式的项列表的更合理表示是作为非零项的列表，其中每个项都是一个包含项顺序和该顺序系数的列表。 在这样的方案中，多项式![B](img/813135a6280e2672503128d3d2080d4a.jpg)被有效地表示为`list(list(100, 1), list(2, 2), list(0, 1))`。 由于大多数多项式操作都是在稀疏多项式上执行的，因此我们将使用此方法。 我们将假定术语列表表示为术语列表，从最高顺序到最低顺序排列。 一旦做出此决定，就可以轻松实现术语和术语列表的选择器和构造函数： [[6]](46#footnote-6)

```js
function adjoin_term(term, term_list) {
    return is_equal_to_zero(coeff(term))
           ? term_list
           : pair(term, term_list);
}
const the_empty_termlist = null;
function first_term(term_list) {
    return head(term_list);
}
function rest_terms(term_list) {
    return tail(term_list);
}
function is_empty_termlist(term_list) {
    return is_null(term_list);
}
function make_term(order, coeff) {
    return list(order, coeff);
}
function order(term) {
    return head(term);
}
function coeff(term) {
    return head(tail(term));
}
```

where `is_equal_to_zero` is as defined in exercise <ref name="ex:=zero?">[2.80](44#ex_2.80)</ref>. (See also exercise <ref name="ex:adjoin-term">[2.87](46#ex_2.87)</ref> below.)

多项式包的用户将通过以下函数创建（标记的）多项式：

```js
function make_polynomial(variable, terms) {
    return get("make", "polynomial")(variable, terms);
}
```

<exercise>Install `is_equal_to_zero` for polynomials in the generic arithmetic package. This will allow `adjoin_term` to work for polynomials with coefficients that are themselves polynomials.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Extend the polynomial system to include subtraction of polynomials. (Hint: You may find it helpful to define a generic negation operation.)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Define functions that implement the term-list representation described above as appropriate for dense polynomials.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Suppose we want to have a polynomial system that is efficient for both sparse and dense polynomials. One way to do this is to allow both kinds of term-list representations in our system. The situation is analogous to the complex-number example of section <ref name="sec:multiple-reps">[2.4](39)</ref>, where we allowed both rectangular and polar representations. To do this we must distinguish different types of term lists and make the operations on term lists generic. Redesign the polynomial system to implement this generalization. This is a major effort, not a local change.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>A univariate polynomial can be divided by another one to produce a polynomial quotient and a polynomial remainder. For example, ![%20%5Cfrac%7Bx%5E5-1%7D%7Bx%5E2%20-1%7D%3Dx%5E3%20%2Bx%2C%5Ctext%7B%20remainder%20%7Dx-1%20](img/fa197d8d833ff9df6326c4fbcab4af5f.jpg) Division can be performed via long division. That is, divide the highest-order term of the dividend by the highest-order term of the divisor. The result is the first term of the quotient. Next, multiply the result by the divisor, subtract that from the dividend, and produce the rest of the answer by recursively dividing the difference by the divisor. Stop when the order of the divisor exceeds the order of the dividend and declare the dividend to be the remainder. Also, if the dividend ever becomes zero, return zero as both quotient and remainder. We can design a `div_poly` function on the model of `add_poly` and `mul_poly`. The function checks to see if the two polys have the same variable. If so, `div_poly` strips off the variable and passes the problem to `div_terms` , which performs the division operation on term lists. `div_poly` finally reattaches the variable to the result supplied by `div_terms` . It is convenient to design `div_terms` to compute both the quotient and the remainder of a division. `div_terms` can take two term lists as arguments and return a list of the quotient term list and the remainder term list. Complete the following definition of `div_terms` by filling in the missing expressions. Use this to implement `div_poly` , which takes two polys as arguments and returns a list of the quotient and remainder polys.

```js
function div_terms(L1, L2) {
    if (is_empty_termlist(L1)) {
        return list(the_empty_termlist, the_empty_termlist);
    } else {
        const t1 = first_term(L1);
        const t2 = first_term(L2);
        if (order(t2) > order(t1)) {
            return list(the_empty_termlist, L1);
        } else {
            const new_c = div(coeff(t1), coeff(t2));
            const new_o = order(t1) - order(t2);
            const rest_of_result = ...
                // compute rest_of_result recursively
            ...
            // form complete result
        }
    }
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

## 符号代数中类型的层次

我们的多项式系统说明了一种类型的对象（多项式）实际上如何可能是具有许多不同类型的对象作为部分的复杂对象。 在定义通用操作时，这并不构成真正的困难。 我们只需要安装适当的通用操作即可对化合物类型的各个部分执行必要的操作。 实际上，我们看到多项式形成了一种&lt;quote&gt;递归数据抽象&lt;/quote&gt;，因为多项式的某些部分本身可能就是多项式。 我们的通用操作和以数据为导向的编程风格可以轻松解决这种复杂问题。

另一方面，多项式代数是不能自然地将数据类型排列在塔中的系统。 例如，可以在![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)中具有多项式，其系数是在![y](img/c592009395c2de830215c39f7bb6f97b.jpg)中的多项式。 也可能在![y](img/c592009395c2de830215c39f7bb6f97b.jpg)中具有多项式，其系数是![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)中的多项式。 这些类型中的任何一个都不以任何自然的方式高于，但常常需要将每个集合中的元素加在一起。 有几种方法可以做到这一点。 一种可能性是通过扩展和重新排列项将一个多项式转换为另一个多项式的类型，以使两个多项式具有相同的主变量。 可以通过对变量进行排序，从而在其上施加塔状结构，从而始终将任何多项式转换为优先级最高的变量和优先级较低的变量埋在系数中的&lt;quote&gt;规范形式&lt;/quote&gt;。 该策略工作得很好，除了转换可能会不必要地扩展多项式之外，使其难以阅读并且使用效率可能较低。 塔式策略对于该领域或用户可以使用各种组合形式（例如三角函数，幂级数和积分）的旧类型动态地发明新类型的任何领域当然都不是自然的。

在大型代数操纵系统的设计中，控制强制是一个严重的问题并不奇怪。 这种系统的许多复杂性都与各种类型之间的关系有关。 确实，可以说我们还没有完全理解强制。 实际上，我们还没有完全理解数据类型的概念。 然而，我们所知道的为我们提供了强大的结构化和模块化原则，以支持大型系统的设计。

<exercise>By imposing an ordering on variables, extend the polynomial package so that addition and multiplication of polynomials works for polynomials in different variables. (This is not easy!)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

## 扩展练习：理性功能

我们可以扩展通用算术系统以包括_有理函数_。 这些是&lt;quote&gt;分数&lt;/quote&gt;，其分子和分母是多项式，例如![%20%5Cfrac%7Bx%2B1%7D%7Bx%5E3%20-1%7D%20](img/84ff7c382824769bc4f33e2fd4798fee.jpg)。系统应能够对有理函数进行加，减，乘和除运算，并执行诸如![%20%5Cfrac%7Bx%2B1%7D%7Bx%5E3%20-1%7D%2B%5Cfrac%7Bx%7D%7Bx%5E2%20-1%7D%3D%5Cfrac%7Bx%5E3%20%2B2x%5E2%20%2B3x%20%2B1%7D%7Bx%5E4%20%2B%0A%20%20%20%20%20%20%20%20x%5E3%20-x-1%7D%20](img/962b03c6b1d12721c6c1f9b88576f28f.jpg)的计算（此处为 通过消除公因子来简化总和。普通的&lt;quote&gt;交叉乘法&lt;/quote&gt;会生成五阶多项式的四阶多项式。）

如果我们修改有理算术包以使其使用通用运算，则它将执行我们想要的操作，除了将分数减少到最低项的问题。

<exercise>Modify the rational-arithmetic package to use generic operations, but change `make_rat` so that it does not attempt to reduce fractions to lowest terms. Test your system by calling `make_rational` on two polynomials to produce a rational function

```js
const p1 = make_polynomial("x", list(list(2, 1),
                                     list(0, 1)));
const p2 = make_polynomial("x", list(list(3, 1),
                                     list(0, 1)));
const rf = make_rational(p2, p1);
```

Now add `rf` to itself, using `add`. You will observe that this addition function does not reduce fractions to lowest terms.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

我们可以使用与整数相同的想法将多项式分数减为最低项：修改`make_rat`，将分子和分母除以最大公约数。 &lt;quote&gt;最大公约数&lt;/quote&gt;的概念对多项式有意义。 实际上，我们可以使用本质上与整数相同的Euclid算法来计算两个多项式的GCD。 [[7]](46#footnote-7) 整数版本为

```js
function gcd(a, b) {
    return b === 0
           ? a
           : gcd(b, remainder(a, b));
}
```

Using this, we could make the obvious modification to define a GCD operation that works on term lists:

```js
function gcd_terms(a, b) {
    return is_empty_termlist(b)
           ? a
           : gcd_terms(b, remainder_terms(a, b));
}
```

where `remainder_terms` picks out the remainder component of the list returned by the term-list division operation `div_terms` that was implemented in exercise <ref name="ex:/terms">[2.91](46#ex_2.91)</ref>.<exercise>Using `div_terms` , implement the function `remainder_terms` and use this to define `gcd_terms` as above. Now write a function `gcd_poly` that computes the polynomial GCD of two polys. (The function should signal an error if the two polys are not in the same variable.) Install in the system a generic operation `greatest_common_divisor` that reduces to `gcd_poly` for polynomials and to ordinary `gcd` for ordinary numbers. As a test, try

```js
const p1 = make_polynomial("x", list(make_term(4, 1),
                                     make_term(3, -1),
                                     make_term(2, -2),
                                     make_term(1, 2)));
const p2 = make_polynomial("x", list(make_term(3, 1),
                                     make_term(1, -1)));
greatest_common_divisor(p1, p2);
```

and check your result by hand.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Define ![P_%7B1%7D](img/f954560a88daa6b69f273938c7500452.jpg), ![P_%7B2%7D](img/b75be69bfaf0534df62a2650ee6bed88.jpg), and ![P_%7B3%7D](img/5e87cbf518693a0dfb7719a83668953e.jpg) to be the polynomials \begin{align*} P_{1}: x^2 -2x+1 \\ P_{2}: 11x^2 +7 \\ P_{3}: 13x +5 \end{align*} Now define ![Q_1](img/33d9b99016585408b0870431806947fd.jpg) to be the product of ![P_1](img/85a3aae523949777146c6a5245104fa4.jpg) and ![P_2](img/74de7c930334b2fb24bb476a9b3d618f.jpg) and ![Q_2](img/d86a91044439e288d57d67e1603fe15a.jpg) to be the product of ![P_1](img/85a3aae523949777146c6a5245104fa4.jpg) and ![P_3](img/d13bde3f783eb31e110bc6a1989bc295.jpg), and use `greatest_common_divisor` (exercise <ref name="ex:remainder-terms">[2.94](46#ex_2.94)</ref>) to compute the GCD of ![Q_1](img/33d9b99016585408b0870431806947fd.jpg) and ![Q_2](img/d86a91044439e288d57d67e1603fe15a.jpg). Note that the answer is not the same as ![P_1](img/85a3aae523949777146c6a5245104fa4.jpg). This example introduces noninteger operations into the computation, causing difficulties with the GCD algorithm. To understand what is happening, try tracing `gcd_terms` while computing the GCD or try performing the division by hand.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

如果我们使用GCD算法的以下修改（仅在具有整数系数的多项式的情况下有效），就可以解决练习 [2.95](46#ex_2.95) 中出现的问题。 在GCD计算中执行多项式除法之前，我们将除数乘以整数常数因子，以确保除法过程中不会出现分数。 因此，我们的答案将与实际GCD相差一个整数常数因数，但这在将有理函数减少为最低项的情况下并不重要。 GCD将被用于分子和分母的除法，因此整数常数因子将被抵消。

更准确地说，如果![P](img/f0481b76bd881625dd55f21f6d81bbc8.jpg)和![Q](img/1d680db5f32fd278f8d48e5407691154.jpg)是多项式，则使![O_1](img/5f0bc25e66a82d5232a16e61623c9469.jpg)为![P](img/f0481b76bd881625dd55f21f6d81bbc8.jpg)的阶（即![P](img/f0481b76bd881625dd55f21f6d81bbc8.jpg)的最大项的阶），使![O_2](img/ac17c1d32393589bfd50fa2902acb3c8.jpg)为![Q](img/1d680db5f32fd278f8d48e5407691154.jpg)的阶。 ]。 令![c](img/32624581da7de65d68eb11d4201f9bef.jpg)为![Q](img/1d680db5f32fd278f8d48e5407691154.jpg)的前导系数。 然后可以证明，如果将![P](img/f0481b76bd881625dd55f21f6d81bbc8.jpg)乘以_整数因子_ ![c%5E%7B1%2BO_%7B1%7D%20-O_%7B2%7D%7D](img/4702fdea8ba26fb9045b843edb0aef9f.jpg)，则可以使用`div_terms`算法将所得多项式除以![Q](img/1d680db5f32fd278f8d48e5407691154.jpg)，而无需引入任何分数。 将股息乘以该常数然后除法的操作有时称为![Q](img/1d680db5f32fd278f8d48e5407691154.jpg)的_伪除法_和![Q](img/1d680db5f32fd278f8d48e5407691154.jpg)。 除法的其余部分称为_伪余数_。

 <exercise>1.  实现函数`pseudoremainder_terms`，该函数类似于`remainder_terms`，除了在调用`div_terms`之前将除数乘以上述整数因子。 修改`gcd_terms`以使用`pseudoremainder_terms`，并验证`greatest_common_divisor`现在在练习 [2.95](46#ex_2.95) 上的示例中生成的整数系数答案。
2.  GCD现在具有整数系数，但它们大于![P_1](img/85a3aae523949777146c6a5245104fa4.jpg)的系数。 修改`gcd_terms`，以便通过将所有系数除以它们的（整数）最大公因数来从答案的系数中删除公因数。

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

因此，这是将有理函数简化为最低项的方法：

*   使用练习中的`gcd_terms`版本计算分子和分母的GCD [2.96](46#ex_2.96) 。
*   获得GCD时，将分子和分母都乘以相同的整数因子，然后除以GCD，这样除以GCD不会引入任何非整数系数。 作为因子，您可以使用GCD的前导系数提高到幂![1%2BO_%7B1%7D%20-O_%7B2%7D](img/128bd3d23dd986ce9eccdbdef2ccfdc1.jpg)，其中![O_%7B2%7D](img/91b543e9c20bf63cfac224579a56730f.jpg)是GCD的阶数，![O_%7B1%7D](img/9d5700d0988367af060bd48606ae6f7f.jpg)是分子和分母的阶数最大值。 这将确保分子和分母除以GCD不会引入任何分数。
*   此操作的结果将是具有整数系数的分子和分母。 由于所有整数因子，系数通常会非常大，因此最后一步是通过计算分子和分母的所有系数的（整数）最大公因数并除以该因子来消除冗余因子。 。

 <exercise>1.  将此算法实现为函数`reduce_terms`，该函数将两个词项列表`n`和`d`作为参数并返回一个列表`nn`，`dd`，它们通过 上面给出的算法。 还编写一个类似于`add_poly`的函数`reduce_poly`，该函数检查两个多边形是否具有相同的变量。 如果是这样，`reduce_poly`剥离变量并将问题传递给`reduce_terms`，然后将变量重新附加到`reduce_terms`提供的两个术语列表。
2.  定义一个类似于`reduce_terms`的函数，该函数执行原始`make_rat`对整数的操作：

    ```js
    function reduce_integers(n, d) {
        const g = gcd(n, d);
        return list(n / g, d / g);
    }
    ```

    ，并将`reduce`定义为一个通用函数，该函数将`apply_generic`调用为 调度到`reduce_poly`（对于`polynomial`参数）或`reduce_integers`（对于`javascript_number`参数）。 现在，在组合给定的分子和分母以形成有理数之前，可以通过使`make_rat`调用`reduce`来使有理数算法程序包将分数最小化为最低项。 系统现在可以处理整数或多项式的有理表达式。 要测试您的程序，请在此扩展练习的开头尝试以下示例：

    ```js
    const p1 = make_polynomial("x", list(make_term(1, 1),
                                         make_term(0, 1)));
    const p2 = make_polynomial("x", list(make_term(3, 1),
                                         make_term(0, -1)));
    const p3 = make_polynomial("x", list(make_term(1, 1)));
    const p4 = make_polynomial("x", list(make_term(2, 1),
                                         make_term(0, -1)));

    const rf1 = make_rational(p1, p2);
    const rf2 = make_rational(p3, p4);

    add(rf1, rf2);
    ```

    看看您是否获得了正确的答案，并将其正确地简化为最低要求。

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_46_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

GCD计算是任何对有理函数进行操作的系统的核心。 上面使用的算法虽然在数学上很简单，但是却非常慢。 缓慢的部分原因是由于大量除法运算，部分原因是由伪除法生成的中间系数的大小很大。 代数操纵系统开发中的活跃领域之一是设计更好的算法来计算多项式GCD。 [[8]](46#footnote-8)

* * *

[[1]](46#footnote-link-1) On the other hand, we will allow polynomials whose coefficients are themselves polynomials in other variables. This will give us essentially the same representational power as a full multivariate system, although it does lead to coercion problems, as discussed below.

[[2]](46#footnote-link-2) For univariate polynomials, giving the value of a polynomial at a given set of points can be a particularly good representation. This makes polynomial arithmetic extremely simple. To obtain, for example, the sum of two polynomials represented in this way, we need only add the values of the polynomials at corresponding points. To transform back to a more familiar representation, we can use the Lagrange interpolation formula, which shows how to recover the coefficients of a polynomial of degree ![n](img/493731e423d5db62086d0b8705dda0c8.jpg) given the values of the polynomial at ![n%2B1](img/003fdff6c68a3686101a32ddf3a4ad8b.jpg) points.

[[3]](46#footnote-link-3) This operation is very much like the ordered `union_set` operation we developed in exercise <ref name="ex:union-set">[2.62](37#ex_2.62)</ref>. In fact, if we think of the terms of the polynomial as a set ordered according to the power of the indeterminate, then the program that produces the term list for a sum is almost identical to `union_set`.

[[4]](46#footnote-link-4) To make this work completely smoothly, we should also add to our generic arithmetic system the ability to coerce a <quote>number</quote> to a polynomial by regarding it as a polynomial of degree zero whose coefficient is the number. This is necessary if we are going to perform operations such as ![%0A%20%20%20%20%20%20%20%20%20%20%7B%5Cleft%5B%20x%5E2%20%2B%28y%2B1%29x%2B5%5Cright%5D%2B%20%5Cleft%5B%20x%5E2%20%2B2x%2B1%5Cright%5D%7D%0A%20%20%20%20%20%20%20%20%20%20](img/9b9fe0882975d32bf240cb3e0ac5ae69.jpg) which requires adding the coefficient ![y%2B1](img/d736a611ec8ee5d24e752a1e7120fd78.jpg) to the coefficient 2.

[[5]](46#footnote-link-5) In these polynomial examples, we assume that we have implemented the generic arithmetic system using the type mechanism suggested in exercise <ref name="ex:internal-type-system">[2.78](44#ex_2.78)</ref>. Thus, coefficients that are ordinary numbers will be represented as the numbers themselves rather than as pairs whose `head` is the symbol `javascript_number` .

[[6]](46#footnote-link-6) Although we are assuming that term lists are ordered, we have implemented `adjoin_term` to simply `pair` the new term onto the existing term list. We can get away with this so long as we guarantee that the functions (such as `add_terms` ) that use `adjoin_term` always call it with a higher-order term than appears in the list. If we did not want to make such a guarantee, we could have implemented `adjoin_term` to be similar to the `adjoin_set` constructor for the ordered-list representation of sets (exercise <ref name="ex:adjoin-set">[2.61](37#ex_2.61)</ref>).

[[7]](46#footnote-link-7) The fact that Euclid's Algorithm works for polynomials is formalized in algebra by saying that polynomials form a kind of algebraic domain called a _Euclidean ring_. A Euclidean ring is a domain that admits addition, subtraction, and commutative multiplication, together with a way of assigning to each element ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg) of the ring a positive integer <quote>measure</quote> ![m%28x%29](img/70824eafa2d5bfbe6f00f922ccf2c67a.jpg) with the properties that ![m%28xy%29%5Cgeq%20m%28x%29](img/3166e91a60c9496b55c8e186a089f91f.jpg) for any nonzero ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg) and ![y](img/c592009395c2de830215c39f7bb6f97b.jpg) and that, given any ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg) and ![y](img/c592009395c2de830215c39f7bb6f97b.jpg), there exists a ![q](img/807ceeb0b2cebef0db0b731ff4d59f51.jpg) such that ![y%3Dqx%2Br](img/3294fd698d343ab48b94fe1f6e98b103.jpg) and either ![r%3D0](img/6937cddf1a8c4f47df75a6977e1234d1.jpg) or ![m%28r%29%3C%20m%28x%29](img/af589268c525ff1a2d454f15a242d468.jpg). From an abstract point of view, this is what is needed to prove that Euclid's Algorithm works. For the domain of integers, the measure ![m](img/20ddd8181c2e0d0fb893637e8572d475.jpg) of an integer is the absolute value of the integer itself. For the domain of polynomials, the measure of a polynomial is its degree.

[[8]](46#footnote-link-8) One extremely efficient and elegant method for computing polynomial GCDs was discovered by Richard Zippel (1979). The method is a probabilistic algorithm, as is the fast test for primality that we discussed in chapter 1\. Zippel's book (1993) describes this method, together with other ways to compute polynomial GCDs.

