SICP 全笔记

Exercise 1.7. The good-enough? test used in computing square roots will not be very effective for finding the square roots of very small numbers. Also, in real computers, arithmetic operations are almost always performed with limited precision. This makes our test inadequate for very large numbers. Explain these statements, with examples showing how the test fails for small and large numbers. An alternative strategy for implementing good-enough? is to watch how guess changes from one iteration to the next and to stop when the change is a very small fraction of the guess. Design a square-root procedure that uses this kind of end test. Does this work better for small and large numbers?

之前给出的 good-enough? 过程是去判断现在得到的 $guess^2$x 的差值是否小于某个数(示例中这个数为 0.001),如果小于的话现在的 guess 就是最终结果。

(define (ineffective-sqrt x)
  (define (sqrt-iter guess x)
    (if (good-enough? guess x)
        guess
        (sqrt-iter (improve guess x)
                   x)))

  (define (improve guess x)
    (average guess (/ x guess)))

  (define (average x y)
    (/ (+ x y) 2))

  (define (good-enough? guess x)
    (< (abs (- (square guess) x)) 0.001))

  (sqrt-iter 1.0 x))

这个方法的问题在于

  • 对于非常小的数字,比如 0.000000001,我们可能第一次的 guess 就已经符合我们的 0.001 的差值了,所以没有办法得到精确的值,比如:

    1 ]=> (ineffective-sqrt 0.000000000001)
    
    ;Value: 3.1250000010656254e-2
    
    1 ]=> (sqrt 0.000000000001)
    
    ;Value: .000001
    

    最后的结果是完全不准确的(0.0030.000001 的比较)。

  • 对于非常大的数字,因为计算机的计算本身就是有误差的,导致了计算机本身的误差可能与我们之前设置的 0.001 的误差没有办法分辩。

于是我们的新办法是,两次 guess 之间如果“差值的比率”小到一定程度,或者说,两次 guess 差别不大的话,我们就把最后的 guess 作为结果。

(define (new-sqrt x)
  (define (sqrt-iter last-guess guess x)
    (if (good-enough? last-guess guess)
        guess
        (sqrt-iter guess (improve guess x)
                   x)))

  (define (improve guess x)
    (average guess (/ x guess)))

  (define (average x y)
    (/ (+ x y) 2))

  (define (good-enough? last-guess guess)
    (< (/ (abs (- last-guess guess)) guess) 0.001))

  (sqrt-iter 0 1.0 x))

再去计算非常小的数字时就不会出现这个问题了:

1 ]=> (new-sqrt 0.000000000001)

;Value: 1.0000001034612418e-6

1 ]=> (sqrt 0.000000000001)

;Value: .000001