一人読書会 「計算機プログラムの構造と解釈」 (6)

 

1.1.7 Newton 法による平方根

 

問題 1.7

さて、ようやく問題の内容自体を理解できる準備が整ったので、考えてみる。問題の概要。

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

まず、動きを検証するのに、デバッグ文を仕込んだりしたいのだが、Scheme でどうするのかさっぱりわからないので、Python に上記を移植してみる。

# -*-encoding:utf-8-*-
'''
http://ja.wikipedia.org/wiki/IEEE_754
'''
from decimal import *

def init_env():
    '''環境の設定'''
    global use_decimal   # Decimal を利用するか
    global judge         # 計算打ち切り判定
    global initial_guess # 推測初期値
    
    use_decimal = True  
    getcontext().prec  = 6          
    judge              = dcm('0.00001') 
    initial_guess      = '1.0'         
    
def dcm(x):
    '''数値を扱う型を切り替える float, decimal'''
    if use_decimal:
        return Decimal(x)
    else:
        return float(x)
def square(x):
    return dcm(x) * dcm(x)
def good_enough(guess ,x):
    g2 = square(guess)
    v = g2 - dcm(x)
    if v < dcm('0'):
        v = v * dcm('-1')
    print '*** %s -  %s = %s ***' % (g2, dcm(x), v)
    return v < judge
def avarage(x, y):
    return (dcm(x) + dcm(y)) / dcm('2')
def improve(guess, x):
    av = avarage((dcm(x) / dcm(guess)), guess) 
    print av
    return av
def sqrt_itr(guess, x):
    if good_enough(guess, x):
        return dcm(guess)
    else:
        return sqrt_itr(improve(guess, x), x)
def sqrt2(x):
    init_env()
    return sqrt_itr(initial_guess, x)

if __name__ == '__main__':
    target = '0.00003'
    print sqrt2(target)

init_env 関数に、設定を行うようにしている。

1.good-enough? テストは、非常に小さい数の平方根をとるときには効果的ではない。

good-enough? テストは、誤差が 0.001 という定数 以下になったら、計算を打ち切るようになっている。

まず、定数になっていることから、直感的に非常に小さい値の場合、誤差の割合が相対的に大きくなると思われる。

また、判定基準が 0.001 となっているが、この値を比較するのは、平方根をとる前(とろうとしている)値のため、求める値が1以下の値の場合平方根もさらに小さな値になり、判定基準の値、0.001 が、求める値に対して相対的にかなり大きくなってしまう。

newton04_01

    use_decimal = True  
def init_env():
    (略)
    use_decimal = True                       # Decimal を使う  
    getcontext().prec  = 6                   # Decimal の有効桁数を 6桁に
    judge              = dcm('0.00001')      # 0.001 より精度をあげる
    initial_guess      = '1.0'               

if __name__ == '__main__':
    target = '0.0003'
    print sqrt2(target)

上記 Python の init_env の設定を上記にして検証してみる。

*** 予測値 – 平方根をとる値 = good-enough? の判定値

*** 1.00 –  0.0003 = 0.9997 ***
0.50015
*** 0.250150 –  0.0003 = 0.249850 ***
0.250375
*** 0.0626876 –  0.0003 = 0.0623876 ***
0.125786
*** 0.0158221 –  0.0003 = 0.0155221 ***
0.0640855
*** 0.00410695 –  0.0003 = 0.00380695 ***
0.0343834
*** 0.00118222 –  0.0003 = 0.00088222 ***     ← ここで判定OKとなってしまう。
0.0215542
*** 0.000464584 –  0.0003 = 0.000164584 ***
0.0177363
*** 0.000314576 –  0.0003 = 0.000014576 ***
0.0173254
*** 0.000300169 –  0.0003 = 1.69E-7 ***
0.0173254                                             

かなり(0.001より)精度が悪い結果になってしまっている。

 

2.限られた精度で実行される場合、非常に大きい数にも不適切。

翻って、非常に大きい値の場合。おそらく、内部数値表現の精度によって、対象が大きい値だと 0.001 という精度自体を保てないためだと思われるが、実際どうなのか確認してみる。

newton04_02

どんどん値を大きくしていくと、結果がかえってこなくなった。

Python で確認してみる。

    use_decimal = True  
def init_env():
    (略)
    use_decimal = False                      # Decimal は使わない(floatとなる)
    judge              = dcm('0.001')  
    initial_guess      = '1.0'               

if __name__ == '__main__':
    target = '33333333333333333333333333333333333'
    print sqrt2(target)

 

やはり、ある程度以上は、予測値が近づかず、変化しないため、無限ループになってしまっているようだ。

    (略)

*** 3.33333333333e+34 –  3.33333333333e+34 = 4.61168601843e+18 ***
1.82574185835e+17
*** 3.33333333333e+34 –  3.33333333333e+34 = 4.61168601843e+18 ***
1.82574185835e+17

    (同じ処理の繰り返し・・・)

Traceback (most recent call last):
  File “C:\test.py”, line 50, in <module>
    print sqrt2(target)
  File “C:\test.py”, line 46, in sqrt2
    return sqrt_itr(initial_guess, x)
  File “C:\test.py”, line 43, in sqrt_itr
   (略)
RuntimeError: maximum recursion depth exceeded while calling a Python object

Python で、 計算に利用する数値データ型を、float から Decimal に変えて、十分に桁数をとれば、処理は完了するので、あくまで 内部数値データ型の問題だろう。

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です