연습문제 2.7 ~ 2.8
구간이라고 하는 데이터와 사칙연산 프로세스를 구현하는 문제이다.
구간 값의 뺄셈 sub-interval x y 는 x의 상한 - y의 하한을 상한으로, x의 하한 - y의 상한을 하한으로 하는 구간을 리턴한다.
; 연습문제 2.7
(define (make-interval x y) (cons x y))
(define (lower-bound i) (car i))
(define (upper-bound i) (cdr i))
; 집중과제
(define (add-interval x y)
(make-interval (+ (lower-bound x) (lower-bound y))
(+ (upper-bound x) (upper-bound y))))
(define (mul-interval x y)
(let ((p1 (* (lower-bound x) (lower-bound y)))
(p2 (* (lower-bound x) (upper-bound y)))
(p3 (* (upper-bound x) (lower-bound y)))
(p4 (* (upper-bound x) (upper-bound y))))
(make-interval (min p1 p2 p3 p4)
(max p1 p2 p3 p4))))
(define (div-interval x y)
(if (or (= 0 (upper-bound y)) (= 0 (lower-bound y)))
(display "Error: Division by 0!!") ; 연습문제 2.10
(mul-interval x
(make-interval (/ 1.0 (upper-bound y))
(/ 1.0 (lower-bound y))))))
; 테스트
(define interval1 (make-interval 5 50))
(define interval2 (make-interval 10 35))
(add-interval interval1 interval2)
(mul-interval interval1 interval2)
(div-interval interval1 interval2)
연습문제 2.9
구간 x 의 폭을 (x.u - x.l) / 2 라고 할 때,
두 구간 x, y 를 더해서 나온 구간의 폭은 ((x.u + y.u) - (x.l + y.l)) / 2 로 두 구간 x, y 각각의 폭의 합과 같다.
또한 두 구간 x, y 를 빼서 나온 구간의 폭은 ((x.u - y.l) - (x.l - y.u)) / 2 로 두 구간 x, y 각각의 폭의 합과 같다.
하지만 두 구간의 곱 연산은 구간 양 극단의 부호에 따라 어떤 값이 상/하한이 될지가 달라지기 때문에, 구간의 곱/나누기 연산의 폭은 위와 같은 방법으로 구할 수 없다.
연습문제 2.10
상/하한에 0이 포함된 구간으로 구간 나누기 연산을 할 경우 경고를 하는 프로시저를 위에 추가하였다.
연습문제 2.11 / 2.12
갑자기 클라이언트가 이게 아니라고 해서 코드를 새로 짜는 문제이다(…). 양 끝값이 아닌 중앙값과 오차범위를 퍼센트로 받아 구간으로 만든다.
; 연습문제 2.11
(define (make-center-width c w)
(make-interval (- c w) (+ c w)))
(define (center i)
(/ (+ (upper-bound i) (lower-bound i)) 2))
(define (width i)
(/ (- (upper-bound i) (lower-bound i)) 2))
; 연습문제 2.12
(define (make-center-percent c p)
(make-interval (- c (* c (/ p 100.0)))
(+ c (* c (/ p 100.0)))))
(define (percent i)
(* 100 (/ (width i) (center i))))
연습문제 2.14
위에서 구현한 프로그램으로 대수적으로 동일한 두 프로시저를 계산하면 다른 값이 나오는데, 그 이유를 찾는 문제다.
대수적으로 동일한 두 식은 다음과 같다(병렬 저항 공식이라고 한다…).
(R1 * R2) / (R1 + R2)
1 / (1/R1 + 1/R2)
; 연습문제 2.14
(define (par1 r1 r2)
(div-interval (mul-interval r1 r2)
(add-interval r1 r2)))
(define (par2 r1 r2)
(let ((one (make-interval 1 1)))
(div-interval one
(add-interval (div-interval one r1)
(div-interval one r2)))))
(define cp1 (make-center-percent 5 1.1))
(define cp2 (make-center-percent 3 0.7))
(par1 cp1 cp2) ; (1.824065750371471 . 1.9272028016153455)
(par2 cp1 cp2) ; (1.8590554013124685 . 1.8909305349182761)
(div-interval cp1 cp1) ; (.9782393669634026 . 1.0222446916076844)
실제 두 값이 다름을 볼 수 있다. 아마 div-interval 연산에서 1.0을 나누는 부분에서 실수 나눗셈 연산이 잘못되었기 때문에 부정확한 수가 발생한 문제라고 생각된다.
그렇다면 par2 의 경우 나눗셈 연산이 1을 나누는 연산만 있고 두 수를 나누는 연산이 없기 때문에 더 나은 프로그램인걸까? 아직 잘 모르겠다…ㅠ
연습문제 2.16
아주 어렵다고 경고까지 붙어있는 문제.
즉 우리가 만든 데이터는 일반적인 수가 아닌데, 일반적인 수에 적용해야 하는 공식을 적용하려 하다 보니 생기는 문제라는 것이다.
예를 들어, 구간 a = (2, 8) 이 있다고 가정하자.
이때 a / a 는 몇인가? 일반적인 나눗셈에서 자기 자신을 자기 자신으로 나누면 1이 나와야 하지만, 실제로는 (0.25, 4) 라는 구간값이 나온다.