2.11 Exercise 2.11
Ben’s point is that there are nine different valid sign combinations for the lower and upper bounds of the two intervals being multiplied, and that a basic application of inequalities can will show that the bounds of the new interval are, in all cases except one, knowable directly, without having to get the min or max of a group of possible bounds. Making the new mul-interval procedure is not hard, but it is tedious and easy to make mistakes. Therefore, we will make a basic testing procedure to make sure that our new procedure agrees with the existing mul-interval for all sign combinations.
First, a useful procedure that should already have been defined, for determining if two intervals are the same:
(define (equal-intervals? x y) (and (= (lower-bound x) (lower-bound y)) (= (upper-bound x) (upper-bound y))))
We can use this to test if multiplied intervals by two different procedures are equal. Applying this to every combination of signs for the bounds of two intervals (whose bounds are chosen arbitrarily, and should not be important), we can make a procedure to exhaustively test the nine sign combination cases to make sure that our new procedure gets the same result as the old one every time.
(define (test-new-mul-interval new-mul-interval) (define (equal-products? x y) (equal-intervals? (mul-interval x y) (new-mul-interval x y))) (define (neg x) (* x -1)) (let ((lx 1) (ux 10) (ly 2) (uy 4)) (cond ((not (equal-products? (make-interval lx ux) (make-interval ly uy))) (error "new-mul-interval fails for lx > 0, ux > 0, ly > 0, uy > 0")) ((not (equal-products? (make-interval lx ux) (make-interval (neg ly) uy))) (error "new-mul-interval fails for lx > 0, ux > 0, ly < 0, uy > 0")) ((not (equal-products? (make-interval lx ux) (make-interval (neg ly) (neg uy)))) (error "new-mul-interval fails for lx > 0, ux > 0, ly < 0, uy < 0")) ((not (equal-products? (make-interval (neg lx) ux) (make-interval ly uy))) (error "new-mul-interval fails for lx < 0, ux > 0, ly > 0, uy > 0")) ((not (equal-products? (make-interval (neg lx) ux) (make-interval (neg ly) uy))) (error "new-mul-interval fails for lx < 0, ux > 0, ly < 0, uy > 0")) ((not (equal-products? (make-interval (neg lx) ux) (make-interval (neg ly) (neg uy)))) (error "new-mul-interval fails for lx < 0, ux > 0, ly < 0, uy < 0")) ((not (equal-products? (make-interval (neg lx) (neg ux)) (make-interval ly uy))) (error "new-mul-interval fails for lx < 0, ux < 0, ly > 0, uy > 0")) ((not (equal-products? (make-interval (neg lx) (neg ux)) (make-interval (neg ly) uy))) (error "new-mul-interval fails for lx < 0, ux < 0, ly < 0, uy > 0")) ((not (equal-products? (make-interval (neg lx) (neg ux)) (make-interval (neg ly) (neg uy)))) (error "new-mul-interval fails for lx < 0, ux < 0, ly < 0, uy < 0")) (else #t))))
The code is not pretty, but it works, and will pinpoint exactly what cases are in error. I made mistakes when I first wrote the new multiplication procedure, and this was helpful.
Below is the new multiplication procedure using a reduced number of multiplications. It uses a cond statement checking combinations of signs for all bounds (and as a stylistic choice, leaves the case with more than 2 multiplications for the else case).
(define (mul-interval-ben x y) (let ((lx (lower-bound x)) (ux (upper-bound x)) (ly (lower-bound y)) (uy (upper-bound y))) (cond ((and (> lx 0) (> ux 0) (> ly 0) (> uy 0)) (make-interval (* lx ly) (* ux uy))) ((and (> lx 0) (> ux 0) (< ly 0) (> uy 0)) (make-interval (* ux ly) (* ux uy))) ((and (> lx 0) (> ux 0) (< ly 0) (< uy 0)) (make-interval (* ux uy) (* lx ly))) ((and (< lx 0) (> ux 0) (> ly 0) (> uy 0)) (make-interval (* lx uy) (* ux uy))) ((and (< lx 0) (> ux 0) (< ly 0) (< uy 0)) (make-interval (* ux uy) (* lx uy))) ((and (< lx 0) (< ux 0) (> ly 0) (> uy 0)) (make-interval (* ux uy) (* lx ly))) ((and (< lx 0) (< ux 0) (< ly 0) (> uy 0)) (make-interval (* ux uy) (* ux ly))) ((and (< lx 0) (< ux 0) (< ly 0) (< uy 0)) (make-interval (* lx ly) (* ux uy))) (else (make-interval (min (* lx uy) (* ux ly)) (max (* lx ly) (* ux uy)))))))
And using our testing procedure, we can verify that this procedure is correct:
> (test-new-mul-interval mul-interval-ben) #t