# 2.1.4扩展练习：区间算术

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

Alyssa P. Hacker正在设计一个系统来帮助人们解决工程问题。 她要在系统中提供的一个功能是能够以已知的精度操纵不精确的数量（例如，物理设备的测量参数），以便在以这种近似数量进行计算时，结果将是已知精度的数量。

电气工程师将使用Alyssa的系统来计算电量。 有时他们需要使用公式![%0A%20%20%20%20%20%20%20%20%20%20R_%7Bp%7D%3D%5Cfrac%7B1%7D%7B1%2FR_%7B1%7D%2B1%2FR_%7B2%7D%7D%0A%20%20%20%20%20%20%20%20%20%20](img/9e052650e4e2e446c666c1bd02d898d0.jpg)计算两个电阻![R_%7B1%7D](img/b80dbfbe7b9053edb821cf7251a3f176.jpg)和![R_%7B2%7D](img/87cd80edbb24a7e46329fd2703de693a.jpg)的并联等效电阻![R_%7Bp%7D](img/1455d904bc147b543422f9f995743069.jpg)的值

通常仅在电阻制造商保证的一定公差范围内才知道电阻值。 例如，如果您购买标有 &lt;quote&gt;6.8欧姆且公差为10％&lt;/quote&gt;的电阻器，则只能确保该电阻器的电阻在![6.8-0.68%3D6.12](img/a25f7b97f5f75e758f4350d989a87259.jpg)和![6.8%2B0.68%3D7.48](img/8f5b94b51c5113bf87d66bb3d8bf23b7.jpg)欧姆之间。 因此，如果您有一个6.8欧姆的10％电阻与4.7欧姆的5％电阻并联，则组合的电阻范围可以从约2.58欧姆（如果两个电阻处于下限）到约2.97欧姆（ 如果两个电阻都在上限）。

Alyssa的想法是将&lt;quote&gt;区间算术&lt;/quote&gt;实施为一组算术运算，用于组合&lt;quote&gt;区间&lt;/quote&gt;（代表不精确数量的可能值范围的对象）。 将两个区间相加，相减，相乘或相除的结果本身就是一个区间，代表结果的范围。

Alyssa假定存在一个称为&lt;quote&gt;区间&lt;/quote&gt;的抽象对象，该抽象对象具有两个端点：下限和上限。 她还假定，给定间隔的端点，可以使用数据构造函数`make_interval`构造间隔。 Alyssa首先编写了一个用于添加两个间隔的函数。 她指出，总和的最小值可以是两个下限的和，而最大值可以是两个上限的总和：

```js
function add_interval(x, y) {
    return make_interval(lower_bound(x) + lower_bound(y),
                         upper_bound(x) + upper_bound(y));
}
```

Alyssa还通过找出边界乘积的最小值和最大值，并将它们用作结果区间的边界，计算出两个间隔的乘积。 （`math_min`和`math_max`是找到任意数量参数的最小或最大值的原语。）

```js
function mul_interval(x, y) {
    const p1 = lower_bound(x) * lower_bound(y);
    const p2 = lower_bound(x) * upper_bound(y);
    const p3 = upper_bound(x) * lower_bound(y);
    const p4 = upper_bound(x) * upper_bound(y);
    return make_interval(math_min(p1, p2, p3, p4),
                         math_max(p1, p2, p3, p4));
}
```

要划分两个间隔，Alyssa将第一个乘以第二个的倒数。 注意，倒数区间的边界按该顺序是上限的倒数和下限的倒数。

```js
function div_interval(x,y) {
    return mul_interval(x, make_interval(1 / upper_bound(y),
                                         1 / lower_bound(y)));
}
```

<exercise>Alyssa's program is incomplete because she has not specified the implementation of the interval abstraction. Here is a definition of the interval constructor:

```js
function make_interval(x, y) {
    return pair(x, y); 
}
// lower_bound ...
// upper_bound ...
```

Define selectors `upper_bound` and `lower_bound` to complete the implementation.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_1_div">Solution</button> <solution>```js
function make_interval(x, y) {
    return pair;
}
function lower_bound(x) { 
    return head(x); 
}
function upper_bound(x) { 
    return tail(x); 
}
```</solution></exercise> <exercise>Using reasoning analogous to Alyssa's, describe how the difference of two intervals may be computed. Define a corresponding subtraction function, called `sub_interval`.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_2_div">Solution</button> <solution>```js
function sub_interval(x, y) {
    return make_interval(lower_bound(x) - upper_bound(y),
                         upper_bound(x) - lower_bound(y));
}
```</solution></exercise> <exercise>The _width_ of an interval is half of the difference between its upper and lower bounds. The width is a measure of the uncertainty of the number specified by the interval. For some arithmetic operations the width of the result of combining two intervals is a function only of the widths of the argument intervals, whereas for others the width of the combination is not a function of the widths of the argument intervals. Show that the width of the sum (or difference) of two intervals is a function only of the widths of the intervals being added (or subtracted). Give examples to show that this is not true for multiplication or division.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_3_div">Solution</button><solution>Let us denote the width of interval ![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg) with ![W%28i%29](img/780ed659aea7f1c4d895fc92d1fbff67.jpg), and its lower and upper bound with ![L%28i%29](img/cc4617ececc520dab748acc7e57c9e6a.jpg) and ![U%28i%29](img/3a97da47cdec8a7fb69887676be50653.jpg), respectively. Two intervals ![i_1](img/cedf93bb23a6d813895fa259fb76d71d.jpg) and ![i_2](img/97566f775cc28793af3c663231f22c58.jpg) have by definition widths of ![%28U%28i_1%29%20-%20L%28i_1%29%29%2F2](img/d9686abc396f94acb02f96e52496e9cc.jpg) and ![%28U%28i_2%29%20-%20L%28i_2%29%29%2F2](img/aead9c6a1287dd8c03beb8e6bfdac038.jpg), respectively. Adding the two intervals leads to the interval ![%5B%20L%28i_1%29%20%2B%20L%28i_2%29%2C%20U%28i_1%29%20%2B%20U%28i_2%29%5D](img/b66bc5ceda1a03ea64bda70878895767.jpg), whose width is ![%28U%28i_1%29%20%2B%20U%28i_2%29%20-%20%28L%28i_1%29%20%2B%20L%28i_2%29%29%29%2F2](img/1192775c90599bb734f50a7ee1984dd9.jpg) ![%3D%20%28U%28i_1%29%20-%20L%28i_1%29%29%2F2%20%2B%20%28U%28i_2%29%20-%20L%28i_2%29%29%2F2](img/a399cf1f6d04f9c3828b378214eda0e0.jpg) ![%3D%20%28W%28i_1%29%20%2B%20W%28i_2%29](img/ab9bb93a42b5fe7502b15f37202621a9.jpg) The argument for subtraction is similar.The widths of the result of multiplying intervals does not have such a nice property. For example, multiplying any interval with the zero-width interval ![%5B%200%2C%200%20%5D](img/f2212878059a0d3362e29af68f406d79.jpg) yields a zero-width interval whereas multiplying any interval ![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg) with the zero-width interval ![%5B%201%2C%201%20%5D](img/6546ac569b373d9bea3b257c56bf5002.jpg) yields an interval with width ![W%28i%29](img/780ed659aea7f1c4d895fc92d1fbff67.jpg). The argument for division is similar.</solution></exercise><exercise>Ben Bitdiddle, an expert systems programmer, looks over Alyssa's shoulder and comments that it is not clear what it means to divide by an interval that spans zero. Modify Alyssa's program to check for this condition and to signal an error if it occurs.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_4_div">Solution</button> <solution>```js
function div_interval(x, y) {
    return lower_bound(y) <= 0 && upper_bound(y) >= 0
        ? error("Division error (interval spans 0)")
        : mul_interval(x, make_interval(1 / upper_bound(y),
                                        1 / lower_bound(y)));
}
```</solution></exercise> <exercise>In passing, Ben also cryptically comments: <quote>By testing the signs of the endpoints of the intervals, it is possible to break `mul_interval` into nine cases, only one of which requires more than two multiplications.</quote> Rewrite this function using Ben's suggestion.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_5_div">Solution</button> <solution>```js
function p(n) {
    return n >= 0;
}
function n(n) {
    return ! p(n);
}
function the_trouble_maker(xl, xu, yl, yu) {
    const p1 = xl * yl;
    const p2 = xl * yu;
    const p3 = xu * yl;
    const p4 = xu * yu;
    make_interval(math_min(p1, p2, p3, p4),
                  math_max(p1, p2, p3, p4));
}
function mul_interval(x, y) {  
    const xl = lower_bound(x);
    const xu = upper_bound(x);
    const yl = lower_bound(y);
    const yu = upper_bound(y);
    return p(xl) && p(xu) && p(yl) && p(yu)  
           ? make_interval(xl * yl, xu * yu)
           : p(xl) && p(xu) && n(yl) && p(yu)  
             ? make_interval(xu * yl, xu * yu)
             : p(xl) && p(xu) && n(yl) && n(yu)  
               ? make_interval(xu * yl, xl * yu)
               : n(xl) && p(xu) && p(yl) && p(yu)  
                 ? make_interval(xl * yu, xu * yu)
                 : n(xl) && p(xu) && n(yl) && n(yu)  
                   ? make_interval(xu * yl, xl * yl)
                   : n(xl) && n(xu) && p(yl) && p(yu)  
                     ? make_interval(xl * yu, xu * yl)
                     : n(xl) && n(xu) && n(yl) && p(yu)  
                       ? make_interval(xl * yu, xl * yl)
                       : n(xl) && n(xu) && n(yl) && n(yu)  
                         ? make_interval(xu * yu, xl * yl)
                         : n(xl) && p(xu) && n(yl) && p(yu)  
                           ? the_trouble_maker(xl, xu, yl, yu)
                           : error("lower larger than upper");
}
```</solution></exercise> 

调试完程序后，Alyssa将其显示给潜在用户，该用户抱怨她的程序解决了错误的问题。 他想要一个可以处理以中心值和加法公差表示的数字的程序。 例如，他想以![3.5%5Cpm%200.15](img/e704892eb7b4d1f21a9a6833a52ec16e.jpg)而不是![%5B3.35%2C%203.65%5D](img/ceff9a6ac36f4995b70bad55d6097551.jpg)等间隔工作。 Alyssa返回她的办公桌，并通过提供备用构造函数和备用选择器来解决此问题：

```js
function make_center_width(c, w) {
    return make_interval(c - w, c + w);
}
function center(i) {
    return (lower_bound(i) + upper_bound(i)) / 2;
}
function width(i) {
    return (upper_bound(i) - lower_bound(i)) / 2;
}
```

Unfortunately, most of Alyssa's users are engineers. Real engineering situations usually involve measurements with only a small uncertainty, measured as the ratio of the width of the interval to the midpoint of the interval. Engineers usually specify percentage tolerances on the parameters of devices, as in the resistor specifications given earlier.<exercise>Define a constructor `make_center_percent` that takes a center and a percentage tolerance and produces the desired interval. You must also define a selector `percent` that produces the percentage tolerance for a given interval. The `center` selector is the same as the one shown above.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_6_div">Solution</button> <solution>```js
function make_center_percent(center, percent) {
    const width = center * (percent / 100);
    return make_center_width(center, width);
}
function percent(i) {
    return (width(i) / center(i)) * 100;
}
```</solution></exercise> <exercise>Show that under the assumption of small percentage tolerances there is a simple formula for the approximate percentage tolerance of the product of two intervals in terms of the tolerances of the factors. You may simplify the problem by assuming that all numbers are positive.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_7_div">Solution</button><solution>Let us denote the maximal error of an interval with center ![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg) by ![%5CDelta%20i](img/0fee6ee75d1d69c0461165eb23112a06.jpg) the maximal error of an interval with center ![j](img/d8fdd0e28cfb03738fc5227885ee035a.jpg) by ![%5CDelta%20j](img/c91a7db354c57d8068aba57b1f2338ef.jpg), and the maximal error of the multiplication result with center ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg) by ![%5CDelta%20k](img/82c9208eceea871d4507564bbd001e79.jpg). Then: ![%20k%20%2B%20%5CDelta%20k%20%3D%20%28i%2B%5CDelta%20i%29%20*%20%28j%2B%5CDelta%20j%29%0A%20%20%20%20%20%20%20%3D%20%20ij%20%2B%20j%20%5CDelta%20i%20%2B%20i%5CDelta%20j%20%2B%20%5CDelta%20i%20%5CDelta%20j%20](img/4553afcaf7cf7760c94b6f5faf9d0c3a.jpg) Since ![k%20%3D%20i%20j](img/d3d8ea7002e84c80d931c285fb2c1458.jpg) ![%20%5CDelta%20k%20%3D%20j%5CDelta%20i%20%2B%20i%20%5CDelta%20j%20%2B%20%5CDelta%20i%5CDelta%20j%20](img/3f8d810f5ec1836928090ead590d7d13.jpg) Since we assume that ![%5CDelta%20i%20%5Cll%20i](img/8ed50213e0a3aa6347136db10d505c6b.jpg) and ![%5CDelta%20j%20%5Cll%20j](img/136ace79d3bb26a09869c267911bd951.jpg), we can neglect the term ![%5CDelta%20i%20%5CDelta%20j](img/4a28956c4b5439c2c87e27fd5d30f3de.jpg) and obtain ![%20%5CDelta%20k%20%3D%20j%20%5CDelta%20i%20%2B%20i%20%5CDelta%20j%20](img/bc347ef4016199695e693e6563ad55f5.jpg) Expressed in tolerances, we obtain: ![%20%5CDelta%20k%20%2F%20k%20%3D%20%28j%20%5CDelta%20i%20%2B%20i%20%5CDelta%20j%29%20%2F%20ij%20%3D%20%5CDelta%20i%2Fi%20%2B%20%5CDelta%20j%2Fj%20](img/c5b9b921b97dca6b0402c484eb704775.jpg) Thus, the tolerance of the result of an interval multiplication is (roughly) the sum of the tolerances of its arguments.</solution></exercise>

经过大量的工作，Alyssa P. Hacker交付了完整的系统。 几年后，在忘记了一切之后，她从一个愤怒的用户Lem E. Tweakit那里接到了疯狂的电话。 似乎Lem注意到可以用两种等效的代数方式来编写并联电阻器的公式：![%0A%20%20%20%20%20%20%20%20%20%20%5Cfrac%7BR_%7B1%7DR_%7B2%7D%7D%7BR_%7B1%7D%2BR_%7B2%7D%7D%0A%20%20%20%20%20%20%20%20%20%20](img/45b90fccd2641a23894411b936c3859a.jpg)和![%0A%20%20%20%20%20%20%20%20%20%20%5Cfrac%7B1%7D%7B1%2FR_%7B1%7D%2B1%2FR_%7B2%7D%7D%0A%20%20%20%20%20%20%20%20%20%20](img/3a0baad75c6f9b4b03a4374a184bdbde.jpg)他编写了以下两个程序，每个程序以不同的方式计算并联电阻器的公式：

```js
function par1(r1, r2) {
    return div_interval(mul_interval(r1, r2),
                        add_interval(r1, r2));
}
function par2(r1, r2) {
    const one = make_interval(1, 1);
    return div_interval(one,
                        add_interval(div_interval(one, r1),
                                     div_interval(one, r2)));
}
```

Lem complains that Alyssa's program gives different answers for the two ways of computing. This is a serious complaint.<exercise>Demonstrate that Lem is right. Investigate the behavior of the system on a variety of arithmetic expressions. Make some intervals ![A](img/efdb05f076173b39fdd26ef663e7b0d8.jpg) and ![B](img/813135a6280e2672503128d3d2080d4a.jpg), and use them in computing the expressions ![A%2FA](img/4fd0af865032d7abfe1b7f69b6197f72.jpg) and ![A%2FB](img/3d74445511cbfadc6adc4e44ff0d9566.jpg). You will get the most insight by using intervals whose width is a small percentage of the center value. Examine the results of the computation in center-percent form (see exercise <ref name="ex:make-center-percent">[2.12](28#ex_2.12)</ref>).<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_8_div">Solution</button><solution>The expression ![A%2FA](img/4fd0af865032d7abfe1b7f69b6197f72.jpg) is interesting, because if the interval is meant to represent a specific (albeit imprecisely known) value, the result should be exactly 1 (width 0), whereas the interval division will result in an interval with positive width. Multiple occurrences of the same term are not recognized as such in the approaches above and thus they will suffer from this problem.</solution></exercise><exercise>Eva Lu Ator, another user, has also noticed the different intervals computed by different but algebraically equivalent expressions. She says that a formula to compute with intervals using Alyssa's system will produce tighter error bounds if it can be written in such a form that no name that represents an uncertain number is repeated. Thus, she says, `par2` is a <quote>better</quote> program for parallel resistances than `par1`. Is she right? Why?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_9_div">Solution</button><solution>She is right. The so-called _dependency problem_ in interval arithmetic arises when the same input values (or intermediate terms) appear in a function on intervals. The second formulation is better because each input occurs only once, and therefore the result of a naive interval calculation is optimal.</solution></exercise><exercise>Explain, in general, why equivalent algebraic expressions may lead to different answers. Can you devise an interval-arithmetic package that does not have this shortcoming, or is this task impossible? (Warning: This problem is very difficult.)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_28_10_div">Solution</button><solution>The dependency problem in interval arithmetic is solved using linear and polynomial appoximations, leading to affine arithmetic and Taylor series methods, respectively.</solution></exercise>