# 1.3.3通用方法

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

我们在 [1.1.4](6)[1.3.1](19) 部分的`integral`函数，我们开始看到一种更强大的抽象：用于表示通用计算方法的函数 ，与所涉及的特定功能无关。 在本节中，我们将讨论两个更详细的示例-查找函数零和固定点的通用方法-并说明如何将这些方法直接表示为函数。

## 半间隔法求方程的根

_半间隔方法_是一种简单但功能强大的技术，用于查找方程式![f%28x%29%3D0](img/918c3ada2e14a24ab0dc2d7e53ccf874.jpg)的根，其中![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)是连续函数。 这个想法是，如果给我们赋予![a](img/070b1af5eca3a5c5d72884b536090f17.jpg)和![b](img/6872867a863714d15d9a0d64c20734ce.jpg)点，使得![f%28a%29%20%3C%200%20%3C%20f%28b%29](img/e58c992c511e9d7a34528c1cdf8f30c2.jpg)，则![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)在![a](img/070b1af5eca3a5c5d72884b536090f17.jpg)和![b](img/6872867a863714d15d9a0d64c20734ce.jpg)之间必须至少有一个零。 为了找到零，令![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)为![a](img/070b1af5eca3a5c5d72884b536090f17.jpg)和![b](img/6872867a863714d15d9a0d64c20734ce.jpg)的平均值，然后计算![f%28x%29](img/1dd5fe70f6ae332b21a84a290e35e5ed.jpg)。 如果为![f%28x%29%20%3E%200](img/9d3a908466b5321636b7b5a7f34e599f.jpg)，则![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)在![a](img/070b1af5eca3a5c5d72884b536090f17.jpg)和![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)之间必须为零。 如果为![f%28x%29%20%3C%200](img/2eae8cccd57070d8a8a3bf667d67a4f3.jpg)，则![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)在![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)和![b](img/6872867a863714d15d9a0d64c20734ce.jpg)之间必须为零。 以此方式继续，我们可以确定![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)必须为零的间隔越来越小。 当我们到达间隔足够小的点时，该过程停止。 由于不确定性的间隔在过程的每个步骤中减少了一半，因此所需的步骤数将随着![%5CTheta%28%5Clog%28%20L%2FT%29%29](img/6fe8adb45ac496c72f9281ecf3e990ef.jpg)的增加而增加，其中![L](img/db4a9fef02111450bf98261889de550c.jpg)是原始间隔的长度，而![T](img/5a047a5ca04e45726dba21b8302977da.jpg)是错误容忍度（即， 间隔的大小，我们将认为&lt;quote&gt;足够小&lt;/quote&gt;）。 这是实现此策略的功能： [[1]](21#footnote-1)

```js
function search(f, neg_point, pos_point) {
    const midpoint = average(neg_point,pos_point);
    if (close_enough(neg_point, pos_point)) {
        return midpoint;
    } else {
        const test_value = f(midpoint);
        if (positive(test_value)) {
            return search(f, neg_point, midpoint);
        } else if (negative(test_value)) {
            return search(f, midpoint, pos_point);
        } else {
            return midpoint;
        }
    }
}
```

我们假设最初给我们函数![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)以及其值为负和正的点。 我们首先计算两个给定点的中点。 接下来，我们检查给定的间隔是否足够小，如果是，则仅返回中点作为答案。 否则，我们将中点![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)的值计算为测试值。 如果测试值是正值，则以从原始负点到中点的新间隔继续该过程。 如果测试值为负，则继续从中点到正点的间隔。 最后，测试值可能为0，在这种情况下，中点本身就是我们要搜索的根。 要测试端点是否&lt;quote&gt;足够接近&lt;/quote&gt;，我们可以使用类似于 [ 1.1.7 ](9) 部分中使用的函数来计算平方根： [[ 2]](21#footnote-2)

```js
function close_enough(x,y) {
    return abs(x - y) < 0.001;
}
```

函数`search`难以直接使用，因为我们可能不小心给它指定![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)的值不具有必需符号的点，在这种情况下，我们将得到错误的答案。 取而代之的是，我们将通过以下函数使用`search`，该函数检查哪些端点具有负函数值，哪些端点具有正值，并相应地调用`search`函数。 如果函数在两个给定点上具有相同的符号，则不能使用半间隔方法，在这种情况下，函数会发出错误信号。 [ [3]](21#footnote-3)

```js
function half_interval_method(f, a, b) {
    const a_value = f(a);
    const b_value = f(b);
    return negative(a_value) && positive(b_value)
           ? search(f, a, b)
           : negative(b_value) && positive(a_value)
             ? search(f, b, a)
             : error("values are not of opposite sign");
}
```

The following example uses the half-interval method to approximate ![%5Cpi](img/868db68e71ec9cabbe333cb77dd676bc.jpg) as the root between 2 and 4 of ![%5Csin%5C%2C%20x%20%3D%200](img/8c90d27c1fd7dc91fa8bbe2813297132.jpg):

```js
half_interval_method(math_sin, 2.0, 4.0);
```

Here is another example, using the half-interval method to search for a root of the equation ![x%5E3%20-%202x%20-%203%20%3D%200](img/cb5762d2fc722faef9f7b748aa477743.jpg) between 1 and 2:

```js
half_interval_method(
  x => x * x * x - 2 * x - 3,
  1.0,
  2.0);
```

## 寻找功能的固定点

如果![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)满足等式![f%28x%29%3Dx](img/8dc969947bdb5a5ca71228c69bedd935.jpg)，则将数字![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)称为函数![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)的_不动点_。 对于某些功能![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)，我们可以从初始猜测开始并反复应用![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)，![%20f%28x%29%2C%20f%28f%28x%29%29%2C%20f%28f%28f%28x%29%29%29%2C%20%5Cldots%20](img/8267a7e8bfd8b63f32fb91f30e07cd73.jpg)直到值没有太大变化，从而找到一个固定点。 利用这种思想，我们可以设计一个函数`fixed_point`，该函数将一个函数和一个初始猜测作为输入，并生成对该函数固定点的近似值。 我们重复应用该函数，直到找到两个连续的值，其差小于某个规定的公差：

```js
const tolerance = 0.00001;
function fixed_point(f, first_guess) {
    function close_enough(x, y) {
        return abs(x - y) < tolerance;
    }
    function try_with(guess) {
        const next = f(guess);
        return close_enough(guess, next)
               ? next
               : try_with(next);
    }
    return try_with(first_guess);
}
```

例如，我们可以使用此方法来近似余弦函数的不动点，以1作为初始近似值： [[4]](21#footnote-4)

```js
fixed_point(math_cos, 1.0);
```

类似地，我们可以找到方程![y%3D%5Csin%20y%20%2B%20%5Ccos%20y](img/0dd7229b38c59d61780aaa69cb2be9eb.jpg)的解：

```js
fixed_point(
    y => math_sin(y) + math_cos(y),
    1.0);
```

定点过程让人想起我们在 [1.1.7](9) 部分中用于查找平方根的过程。 两者都是基于反复改善猜测直到结果满足某些标准的想法。 实际上，我们可以很容易地将平方根计算公式化为定点搜索。 计算某个数字![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)的平方根需要找到一个![y](img/c592009395c2de830215c39f7bb6f97b.jpg)这样的![y%5E2%20%3D%20x](img/f0899c1bef363f5949b6efb1d3b231a7.jpg)。 将这个方程式转化为等价形式![y%20%3D%20x%2Fy](img/53415977be6aee17cab1eced5d53e24f.jpg)，我们认识到我们正在寻找函数 [[5]](21#footnote-5) ![y%20%5Cmapsto%20x%2Fy](img/d11aa49c178cf017ca78223de49e426c.jpg)的固定点，因此我们可以尝试计算平方根为

```js
function sqrt(x) {
    return fixed_point(y => x / y, 1.0);
}
// warning: does not converge!
```

Unfortunately, this fixed-point search does not converge. Consider an initial guess ![y_1](img/75d94c5c78409a8b42337c55189230d4.jpg). The next guess is ![y_2%20%3D%20x%2Fy_1](img/5a3af1e48aa7931c3f05e07158056c85.jpg) and the next guess is ![y_3%20%3D%20x%2Fy_2%20%3D%20x%2F%28x%2Fy_1%29%20%3D%20y_1](img/6b9f07f12d9b77ee34cd11e0367b136e.jpg). This results in an infinite loop in which the two guesses ![y_1](img/75d94c5c78409a8b42337c55189230d4.jpg) and ![y_2](img/3e24079994b80e3bb7c5ca69f1661b61.jpg) repeat over and over, oscillating about the answer.

控制这种振荡的一种方法是防止猜测变化太大。 由于答案始终在我们的猜测![y](img/c592009395c2de830215c39f7bb6f97b.jpg)和![x%2Fy](img/8db5e9c9e4e65ef361a6aa73433b599d.jpg)之间，因此我们可以通过将![y](img/c592009395c2de830215c39f7bb6f97b.jpg)与![x%2Fy](img/8db5e9c9e4e65ef361a6aa73433b599d.jpg)进行平均，得出与![y](img/c592009395c2de830215c39f7bb6f97b.jpg)和![x%2Fy](img/8db5e9c9e4e65ef361a6aa73433b599d.jpg)相距不远的新猜测。 在![y](img/c592009395c2de830215c39f7bb6f97b.jpg)之后是![%5Cfrac%7B1%7D%7B2%7D%28y%2Bx%2Fy%29](img/b2cecddd1c4e647c8b790fe59c97ba55.jpg)，而不是![x%2Fy](img/8db5e9c9e4e65ef361a6aa73433b599d.jpg)。 进行这样一系列猜测的过程仅仅是寻找![y%20%5Cmapsto%20%5Cfrac%7B1%7D%7B2%7D%28y%2Bx%2Fy%29](img/e15c71dd01a99a0b09e80aa52d5e2130.jpg)固定点的过程：

```js
function sqrt(x) {
    return fixed_point(
               y => average(y, x / y),
               1.0);
}
```

(Note that ![y%3D%5Cfrac%7B1%7D%7B2%7D%28y%2Bx%2Fy%29](img/a8c36a9174546feba0dec3cd9dc11515.jpg) is a simple transformation of the equation ![y%3Dx%2Fy](img/f409fccb545a1575de1562463591e967.jpg); to derive it, add ![y](img/c592009395c2de830215c39f7bb6f97b.jpg) to both sides of the equation and divide by 2.)

通过此修改，平方根函数起作用。 实际上，如果我们解开定义，我们可以看到此处生成的平方根的近似序列与 [1.1.7节的原始平方根函数生成的近似序列完全相同。](9) 。 这种对解决方案的逐次逼近进行平均的方法（我们称为_平均阻尼_的一种技术）通常有助于定点搜索的收敛。

<exercise>Show that the golden ratio ![%5Cphi](img/b4591b72892f13da37e97ae1e729cd99.jpg) (section <ref name="sec:tree-recursion">[1.2.2](13)</ref>) is a fixed point of the transformation ![x%20%5Cmapsto%201%20%2B%201%2Fx](img/f6edd51eab9c0898c2e58df8b76afcc7.jpg), and use this fact to compute ![%5Cphi](img/b4591b72892f13da37e97ae1e729cd99.jpg) by means of the `fixed_point` function.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_21_1_div">Solution</button><solution>The fixed point of the function is ![%201%20%2B%201%20%2F%20x%20%3D%20x%20](img/90aaab395a22fbd3cf2cc9b71c96c0be.jpg) Solving for x, we get ![%20x%5E2%20%3D%20x%20%2B%201%20](img/770dfef55a78d0301aa33d1913dce363.jpg) ![%20x%5E2%20-%20x%20-%201%20%3D%200%20](img/cc0fcafb25eb78b203dc280751bd55a7.jpg) Using the quadratic equation to solve for ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg), we find that one of the roots of this equation is the golden ratio ![%281%2B%5Csqrt%7B5%7D%29%2F2](img/1511547fe7268e67c714e163a75e9740.jpg).

```js
fixed_point(x => 1 + (1 / x), 1.0);
```</solution></exercise> <exercise>Modify `fixed_point` so that it prints the sequence of approximations it generates, using the primitive function `display` shown in exercise <ref name="ex:search-for-primes">[1.22](17#ex_1.22)</ref>. Then find a solution to ![x%5Ex%20%3D%201000](img/948fb9f562903de7af0e88bd6dc1cf39.jpg) by finding a fixed point of ![x%20%5Cmapsto%20%5Clog%281000%29%2F%5Clog%28x%29](img/061aef07a780a022391ee3d1dac555c3.jpg). (Use the primitive function `math_log` which computes natural logarithms.) , Compare the number of steps this takes with and without average damping. (Note that you cannot start `fixed_point` with a guess of 1, as this would cause division by ![%5Clog%281%29%3D0](img/0d9f2111d96baa039095774a2f93dc6d.jpg).)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_21_2_div">Solution</button><solution>We modify the function `fixed_point` as follows:

```js
const tolerance = 0.00001;
function fixed_point(f, first_guess) {
    function close_enough(x, y) {
        return abs(x - y) < tolerance;
    }
    function try_with(guess) {
        display(guess);
        const next = f(guess);
        return close_enough(guess, next)
               ? next
               : try_with(next);
    }
    return try_with(first_guess);
}
```

Here is a version with average dampening built-in:

```js
function fixed_point_with_average_dampening(f, first_guess) {
    function close_enough(x, y) {
        return abs(x - y) < tolerance;
    }
    function try_with(guess) {
        display(guess);
        const next = (guess + f(guess)) / 2;
        return close_enough(guess, next)
               ? next
               : try_with(next);
    }
    return try_with(first_guess);
}
```</solution></exercise>  <exercise>*   无限的_连续分数_是![%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%3D%7B%5Cdfrac%7BN_1%7D%7BD_1%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cdfrac%7BN_2%7D%7BD_2%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cdfrac%7BN_3%7D%7BD_3%2B%5Ccdots%20%7D%7D%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20](img/fa818df52e62800d2e0ab8ce5ba3f2c9.jpg)形式的表达式。例如，可以显示无限大的![N_i](img/4236d8cccece7d17f3a004865adbf94d.jpg)和![D_i](img/e2e06948259ea689cc97b0abe3eb2543.jpg)等于1的连续分数展开产生![1%2F%5Cphi](img/287b7a9c4635e8327debcbb4d5b851f1.jpg) ，其中![%5Cphi](img/b4591b72892f13da37e97ae1e729cd99.jpg)是黄金比例（在 [ 1.2.2 ](13) 部分中进行了介绍）。 逼近无限连续分数的一种方法是在给定数目的项之后截断展开。 这种截断（即所谓的 _![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg)-有限连续分数_）的格式为![%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%5Cdfrac%7BN_1%7D%7BD_1%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cdfrac%7BN_2%7D%7B%5Cddots%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cdfrac%7BN_K%7D%7BD_K%7D%7D%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20](img/4a209b1950f280a05f773acc6711b5b3.jpg)。假设`n`和`d`是一个自变量的函数（术语索引![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg) ），返回连续分数项的![N_i](img/4236d8cccece7d17f3a004865adbf94d.jpg)和![D_i](img/e2e06948259ea689cc97b0abe3eb2543.jpg)。 声明函数`cont_frac`，以便对`cont_frac(n, d, k)`进行评估即可计算![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg)项有限连续分数的值。 通过使用

    ```js
    cont_frac(i =&gt; 1.0,
              i =&gt; 1.0,
              k);
    ```

    近似获取`k`的连续值来检查![1%2F%5Cphi](img/287b7a9c4635e8327debcbb4d5b851f1.jpg)的功能。 为了得到精确到小数点后4位的近似值，您必须制造多少`k`？
*   如果您的`cont_frac`函数生成一个递归过程，请编写一个生成迭代过程的函数。 如果它生成一个迭代过程，请编写一个生成递归过程的过程。

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_21_3_div">Solution</button> <solution>```js
//recursive process
function cont_frac(n, d, k) {
    function fraction(i) {
        return i > k
               ? 0
               : n(i) / (d(i) + fraction(i + 1));
    }
    return fraction(1);
}
```

```js
//iterative process
function cont_frac(n, d, k) {
    function fraction(i, current) {
        return i === 0
               ? current
               : fraction(i - 1, n(i) / (d(i) + current));
    }
    return fraction(k, 0);
}
```

```js
cont_frac(i => 1.0,
          i => 1.0,
          20);
```</solution></exercise> <exercise>In 1737, the Swiss mathematician Leonhard Euler published a memoir _De Fractionibus Continuis_, which included a continued fraction expansion for ![e-2](img/0c590df0d809257904e577dfcde23754.jpg), where ![e](img/be8982d125e27260b5c793cf0d39d70a.jpg) is the base of the natural logarithms. In this fraction, the ![N_i](img/4236d8cccece7d17f3a004865adbf94d.jpg) are all 1, and the ![D_i](img/e2e06948259ea689cc97b0abe3eb2543.jpg) are successively 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8, …. Write a program that uses your `cont_frac` function from exercise <ref name="ex:continued-fractions">[1.37](21#ex_1.37)</ref> to approximate ![e](img/be8982d125e27260b5c793cf0d39d70a.jpg), based on Euler's expansion.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_21_4_div">Solution</button> <solution>```js
2 + cont_frac(i => 1,  
              i => (i + 1) % 3 < 1 ? 2 * (i + 1) / 3 : 1,
              20);
```</solution></exercise> <exercise>A continued fraction representation of the tangent function was published in 1770 by the German mathematician J.H. Lambert: ![%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Ctan%20x%3D%7B%5Cdfrac%7Bx%7D%7B1-%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cdfrac%7Bx%5E2%7D%7B3-%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cdfrac%7Bx%5E2%7D%7B5-%0A%09%09%20%20%20%20%5Cdfrac%7Bx%5E2%7D%7B%20%5Cddots%20%7D%7D%7D%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20](img/bee3b46cfbbb2a258761c4464c30086d.jpg) where ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg) is in radians. Declare a function `tan_cf(x, k)` that computes an approximation to the tangent function based on Lambert's formula. As in exercise <ref name="ex:continued-fractions">[1.37](21#ex_1.37)</ref>, `k` specifies the number of terms to compute.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_21_5_div">Solution</button> <solution>```js
function tan_cf(x, k) {
    return cont_frac(i => i === 1 ? x : - x * x,  
                     i => 2 * i - 1,
                     k);
}
```</solution></exercise> 

* * *

[[1]](21#footnote-link-1) Note that we slightly extend the syntax of conditional statements described in section <ref name="sec:lambda">[1.3.2](20)</ref> by allowing another conditional statement in place of the block following `else`.

[[2]](21#footnote-link-2) We have used 0.001 as a representative <quote>small</quote> number to indicate a tolerance for the acceptable error in a calculation. The appropriate tolerance for a real calculation depends upon the problem to be solved and the limitations of the computer and the algorithm. This is often a very subtle consideration, requiring help from a numerical analyst or some other kind of magician.

[[3]](21#footnote-link-3) This can be accomplished using `error`, which takes as argument a string that is printed as error message along with the number of the program line that gave rise to the call of `error`.

[[4]](21#footnote-link-4) Try this during a boring lecture: Set your calculator to radians mode and then repeatedly press the ![%5Ccos](img/fa2b045b62057c1bcbb83e7cbeb60041.jpg) button until you obtain the fixed point.

[[5] ](21#footnote-link-5)![%5Cmapsto](img/955f3eab5102fcb2580e954c98789127.jpg) (pronounced <quote>maps to</quote>) is the mathematician's way of writing function definitions. ![y%20%5Cmapsto%20x%2Fy](img/d11aa49c178cf017ca78223de49e426c.jpg) means `y => x / y`, that is, the function whose value at ![y](img/c592009395c2de830215c39f7bb6f97b.jpg) is ![x%2Fy](img/8db5e9c9e4e65ef361a6aa73433b599d.jpg).

