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(x, y); } 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