1.1.7 例子:牛顿法求平方根

上述介绍的程序更像是普通的数学函数。他们指定一个值,被一个或者是多个参数确定的。
但是,在数学函数与计算机程序之间有一个重要的区别。程序必须是有操作性的。
有一个例子,是计算平方根的问题。我们能够定义平方根的函数如下:
X的平方根等于Y 满足Y大于0,Y的平方等于X。 这就描述一个完美的合法的数学函数。
我们能够使用它来判断一个数是否是另一个数的平方根,或者在不失全面性的前提下,
推导关于平方根的事实。另一方面,这个定义不能用来描述一个程序。实际上,关于如何
实际地找到一个数的平方根,我们没有得到任何的信息。用LISP的伪代码重复这个定义
不能帮助解决问题。
(define (sqrt x )
   (the y (and (>= y 0)
         (= (square y) x))))
这仅仅是在重复问题。
数学函数与计算机程序之间的相反方面是在描述事情的属性与描述如何做事的根本性的区别
反应。有时候也是说,声明性的知识与操作性的知识的区别。
在数学方面,我们通常关注声明性的描述,在计算机方面,我们通常关注操作性的描述。

如何计算一个数的平方根呢? 最普通的方式是使用连续逼近的牛顿法,它说,当我们猜一个数Y
是X的平方根时,我们能够用 Y和X/Y的平均值作为更好的猜测值的方式,我们执行这一系列
简单的操作就得到了更好的结果值(一个更接近平方根的值)。例如,我们计算2的平方根,
如下:假定我们的最初的猜测值是1.
猜测值    倒数值        平均值
1        (2/1)=2       (2+1)/2=1.5
1.5        2/1.5=1.3333   (1.5+1.3333)/2=1.4167
1.1467     (2/1.4167)=1.4118  (1.4167+1.4118)/2=1.4142
1.4142    ....
继续这个过程,我们就得到了越来越好的平方根的逼近值。
现在,让我们来用程序的方式,来规范化这个流程。我们开始于
一个输入值,和一个猜测的值。对于我们的目的来说,如果猜测值足够的好,就结束了。如果
没有,我们必须重复流程提供一个新的更好的猜测值。在程序中我们写这个基本的策略,如下:
(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))
我们也不得不说出关于足够好的含义是什么。接下来的做法是为了演示的,并不是真的是一个
好的测试。(见练习1.7) 想法是这样的,选择更好的答案,直到它足够地接近真实值。
判断的条件是它的平方与原输入值的差小于一个预定的阀值。这里是0.001.
(define (good-enough? guess x)
      (< (abs (- (square guess) x)) 0.001))
最后我们需要一种方式开始。例如,我们能够总是猜测,任何一个数的平方根从1 开始。
(define (sqrt x)
      (sqrt-iter 1.0 x)
)
如果我们输入这些定义到解释器,我们能够使用sqrt像其它程序一样。
(sqrt 9)
3.00009155413138
(sqrt (+ 100 37))
11.70469917758145
(sqrt (+ (sqrt 2) (sqrt 3)))
1.7739279023207892
(square (sqrt 1000))
1000.000369924366
SQRT程序也演示出我们已经介绍的简单的编程语言是足够写一个
用C语言或者是pascal语言能够实现的纯数字的程序的。这似乎让人吃惊,
因为我们没有在我们的语言中包括任何迭代的(循环)结构来让计算机重复地做事。
sqrt-iter在另一个方面,演示了没有使用特别的结构仅通过调用一个程序的这种普通的能力,
如何实现迭代。

练习1.6
AlyssaP.Hacker这个人没有看到需要提供if这种条件语句作为关键字的原由。
她问“为什么我不能用cond来定义它,把它作为普通的程序呢”。她的朋友Eva
的确实现了这个愿望。定义的if的新版本如下:
(define (new-if predicate then-clause else-clause)
   (cond (predicate then-clause)
         (else else-clause)))

Eva为Alyssa演示了程序
(new-if (= 2 3) 0 5)
5

(new-if (= 1 1) 0 5)
0

很好。Alyssa 使用new-if 改写了求平方根的程序如下:
(define (sqrt-iter guess x)
    (new-if (good-enough? guess x)
         guess
  (sqrt-iter (improve guess x) x)
     )
)
当Alyssa使用这个程序求平方根时,发生了什么?怎么回事?

练习1.7
在求平方根的程序中,good-enough?这个子程序对于找到非常小的数的平方根
不是很有效。而且在实际的计算机中,算术操作也几乎总是执行很有限的精度。
这让我们对很大的数也测试不充分。用例子解释一下这些语句,对于很小的数和
很大的数是怎么测试失败的? 实现good-enough?的一个可选的策略是看guess
程序从一次迭代到下一次有多大的改变,当改变值是guess的非常小的小数部分时
就停止下来了。设计一个求平方根的程序,使用这种停止条件的测试。对于很大
的数和很小的数,这种方式更有效吗?


练习1.8
牛顿法求立方根是基于如下的事实,这个事实就是如果y是x的立方根的合适的值,
一个更好的值满足如下的公式。

y1=(x/(y*y)+2*y)/3

使用这个公式实现立方根的程序与求平方根的程序是很相似的。(在1.3.4部分中
我们能够看到在通用性的方面,把这些平方根和立方根的程序抽象后,是如何实现了
牛顿法)

猜你喜欢

转载自blog.csdn.net/gggwfn1982/article/details/81384698