SICP問題1.7

元々のここまでで使用している平方根を求める手続き

(define (square x) (* x x))
(define (sqrt x)
  (sqrt-iter 1.0 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))
  • 小さい値の場合

小さい値の場合、予測値guessの自乗値が許容値(0.001)より小さくなってしまうため、十分な改善(improve)が行われない、と予想される。で実験。

(sqrt 0.01)
; => 0.10032578510960605
(square 0.10032578510960605)
; => 0.01006526315785885
(sqrt 0.001)
; => 0.04124542607499115
(square 0.04124542607499115)
; => 0.0017011851721075596
(sqrt 0.0001)
; => 0.03230844833048122
(square 0.03230844833048122)
; => 0.0010438358335233748
  • 大きい値の場合

次は大きい値の場合。有効桁数の精度を超えるので値の改善がなされないと予想。で実験。

(sqrt 10000)
; => 100.00000025490743
(sqrt 1000000)
; => 1000.0000000000118
(sqrt 1000000000)
; => 31622.776601684047
(square 31622.776601684047)
; => 1.0000000000000161e9
(sqrt 1000000000000)
; => 1000000.0
(square 1000000.0)
; => 1.0e12
(sqrt 2000000000000)
; => 1414213.5623730952
(square 1414213.5623730952)
; => 2.0000000000000005e12
(sqrt 1000000000000000)
; => 返って来ない!!

んーと、そういうことではナサゲですね。
どうやら、大きい数になると浮動小数点数の分解能が許容値(0.001)をこえてしまうため、improveで値の改善はなされるけど判定基準を満たせずに無限ループに陥るということのようです。

ということで((新しいguess - 前のguess)/前のguess)を変化の大きさとし、閾値以下なら十分改善したと見なすコードに変更してみました。
なお変化の大きさを(新しいguess/前のguess)とかでやると、やっぱり小さい値や大きい値で無限ループに陥るのでダメです。

(define (square x) (* x x))
(define (sqrt x)
  (sqrt-iter 1.0 x x))
(define (sqrt-iter guess last-guess x)
  (if (good-enough? guess last-guess)
      guess
      (sqrt-iter (improve guess x)
                 guess
                 x)))
(define (improve guess x)
  (average guess (/ x guess)))
(define (average x y)
  (/ (+ x y) 2))
(define (good-enough? guess last-guess)
  (< (abs (/ (- guess last-guess) last-guess)) 0.001))

で実験。

(sqrt 0.0001)
; => 0.010000000025490743
(sqrt 0.001)
; => 0.03162278245070105
(sqrt 1000000000000)
; => 1000000.103461242

OKそうです。