SICP Exercise 2.11

Question

In passing, Ben also cryptically comments: “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.” Rewrite this procedure using Ben’s suggestion.

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 centre 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:

(define (make-centre-width c w)
  (make-interval (- c w) (+ c w)))

(define (centre i)
  (/ (+ (lower-bound i)
        (upper-bound i))
     2))

(define (width i)
  (/ (- (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.

Answer

The differentiation here lies in whether each interval is positive, negative, or spanning zero (one positive and one negative bound). The single case that requires more than two multiplications here is when both intervals span zero, in which case we need to basically use the same mul-interval functionality we had previously.

(define (mul-interval x y)
  (let ((x0 (lower-bound x))
        (x1 (upper-bound x))
        (y0 (lower-bound y))
        (y1 (upper-bound y)))
    (cond ((is-positive? x)
           (cond ((is-positive? y) (make-interval (* x0 y0) (* x1 y1)))
                 ((is-negative? y) (make-interval (* x1 y0) (* x0 y1)))
                 ((spans-zero? y) (make-interval (* x1 y0) (* x1 y1)))))
          ((is-negative? x)
           (cond ((is-positive? y) (make-interval (* x0 y1) (* x1 y0)))
                 ((is-negative? y) (make-interval (* x1 y1) (* x0 y0)))
                 ((spans-zero? y) (make-interval (* x0 y1) (* x0 y0)))))
          ((spans-zero? x)
           (cond ((is-positive? y) (make-interval (* x0 y1) (* x1 y1)))
                 ((is-negative? y) (make-interval (* x1 y0) (* x0 y0)))
                 ((spans-zero? y) (make-interval
                                   (min (* x0 y0) (* x0 y1) (* x1 y0) (* x1 y1))
                                   (max (* x0 y0) (* x0 y1) (* x1 y0) (* x1 y1)))))))))

(define (spans-zero? i)
  (not (= (/ (upper-bound i) (abs (upper-bound i)))
          (/ (lower-bound i) (abs (lower-bound i))))))
(define (is-positive? i)
  (and (>= 0 (upper-bound i))
       (>= 0 (lower-bound i))))
(define (is-negative? i)
  (and (< 0 (upper-bound i))
       (< 0 (lower-bound i))))

So, let’s test it:

(define i1 (make-interval 1 2))
(define i2 (make-interval -1 -2))
(define i3 (make-interval 1 -2))

(mul-interval i1 i1)
(mul-interval i1 i2)
(mul-interval i1 i3)
(mul-interval i2 i1)
(mul-interval i2 i2)
(mul-interval i2 i3)
(mul-interval i3 i1)
(mul-interval i3 i2)
(mul-interval i3 i3)

Results:

'(4 . 1)
'(-1 . -4)
'(1 . -2)
'(-1 . -4)
'(4 . 1)
'(2 . -1)
'(1 . -2)
'(2 . -1)
'(-2 . 4)