Alyssa P. Hacker is designing a system to help people solve engineering problems. One feature she wants to provide in her system is the ability to manipulate inexact quantities (such as measured parameters of physical devices) with known precision, so that when computations are done with such approximate quantities the results will be numbers of known precision.

Electrical engineers will be using Alyssa's system to compute electrical quantities. It is sometimes necessary for them to compute the value of a parallel equivalent resistance $R_{p}$ of two resistors $R_{1}$ and $R_{2}$ using the formula \[ R_{p}=\frac{1}{1/R_{1}+1/R_{2}} \]

Resistance values are usually known only up to some
tolerance
guaranteed by the manufacturer of the resistor. For example, if you
buy a resistor labeled 6.8 ohms with 10% tolerance

you can only
be sure that the resistor has a resistance between $6.8-0.68=6.12$ and
$6.8+0.68=7.48$ ohms. Thus, if you have a 6.8-ohm 10% resistor in
parallel with a 4.7-ohm 5% resistor, the resistance of the
combination can range from about 2.58 ohms (if the two resistors are
at the lower bounds) to about 2.97 ohms (if the two resistors are at
the upper bounds).

Alyssa's idea is to implement interval arithmetic

as a set of
arithmetic operations for combining intervals

(objects
that represent the range of possible values of an inexact quantity).
The result of adding, subtracting, multiplying, or dividing two
intervals is itself an interval, representing the range of the
result.

Alyssa postulates the existence of an abstract object called an
interval

that has two endpoints: a lower bound and an upper bound.
She also presumes that, given the endpoints of an interval, she can
construct the interval using the data constructor
`make_interval`.
Alyssa first writes a
function
for adding two intervals. She
reasons that the minimum value the sum could be is the sum of the two
lower bounds and the maximum value it could be is the sum of the two
upper bounds:

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

Alyssa also works out the product of two intervals by finding the
minimum and the maximum of the products of the bounds and using them
as the bounds of the resulting interval. (`math_min` and `math_max` are
primitives that find the minimum or maximum of any number of
arguments.)

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)); }

To divide two intervals, Alyssa multiplies the first by the reciprocal of the second. Note that the bounds of the reciprocal interval are the reciprocal of the upper bound and the reciprocal of the lower bound, in that order.

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

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

function make_interval(x, y) { return pair; } function lower_bound(x) { return head(x); } function upper_bound(x) { return tail(x); }

function sub_interval(x, y) { return make_interval(lower_bound(x) - upper_bound(y), upper_bound(x) - lower_bound(y)); }

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))); }

By testing the signs of the endpoints of the intervals, it is possible to breakRewrite this function using Ben's suggestion.mul_intervalinto nine cases, only one of which requires more than two multiplications.

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"); }

After debugging her program, Alyssa shows it to a potential user, who complains that her program solves the wrong problem. He wants a program that can deal with numbers represented as a center value and an additive tolerance; for example, he wants to work with intervals such as $3.5\pm 0.15$ rather than $[3.35, 3.65]$. Alyssa returns to her desk and fixes this problem by supplying an alternate constructor and alternate selectors:

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; }

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; }

After considerable work, Alyssa P. Hacker delivers her finished system. Several years later, after she has forgotten all about it, she gets a frenzied call from an irate user, Lem E. Tweakit. It seems that Lem has noticed that the formula for parallel resistors can be written in two algebraically equivalent ways: \[ \frac{R_{1}R_{2}}{R_{1}+R_{2}} \] and \[ \frac{1}{1/R_{1}+1/R_{2}} \] He has written the following two programs, each of which computes the parallel-resistors formula differently:

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))); }

betterprogram for parallel resistances than

2.1.4
Extended Exercise: Interval Arithmetic