Skip to content

Commit

Permalink
修正练习 1.7 解答的错误,感谢 sen 提供的解答:
Browse files Browse the repository at this point in the history
  • Loading branch information
huangzworks committed Nov 13, 2012
1 parent 2a98738 commit 82aa25d
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 32 deletions.
31 changes: 6 additions & 25 deletions chp1/7.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
练习 1.7
=============

.. warning:: 本题的实现有误,正等待修复。

先将书本 16 页的 ``sqrt`` 函数完整敲下来:

.. literalinclude:: code/p16-sqrt.scm
Expand All @@ -23,27 +21,26 @@
;... done
;Value: sqrt

1 ]=> (sqrt 0.00009) ; 应该是 0.003 而不是 0.03

1 ]=> (sqrt 0.00009) ; 正确答案应该是 9.486832980505138e-3
;Value: .03220324381282134

1 ]=> (sqrt 90000000000000000000000000000000000000000) ; 对于大数没有问题

;Value: 3e20

可以发现,对于特别小的数,比如 ``(sqrt 0.00009)`` ,计算所给出的精度只能到前两位,这是因为在 ``good-enough?`` 中使用 ``0.001`` 作为检测精度造成的问题
可以发现,对于特别小的数,比如 ``0.00009`` ,书本给出的 ``sqrt`` 并不能计算出正确的答案

要避免检测受到指定精度的限制,我们按照练习所说,对 ``good-enough?`` 进行修改不再检测猜测值 ``guess`` 的平方与 ``x`` 之间的差,而是检查新旧两次猜测值之间的差
要避免这一错误,我们按照练习所说,对 ``good-enough?`` 进行修改不再检测猜测值 ``guess`` 的平方与 ``x`` 之间的差,而是检测新旧两次猜测值之间的比率,当比率变化非常小时,程序就停止 ``improve``

新的 ``good-enough?`` 定义如下:

.. literalinclude:: code/7-good-enough.scm

使用新的 ``good-enough?`` 重新定义 ``sqrt`` 函数,大部分的函数定义和原来的一样,只是 ``sqrt-iter`` 和 ``good-enough?`` 两个函数更改了:
使用新的 ``good-enough?`` 重新定义 ``sqrt`` 函数 —— 大部分的代码和原来的一样,只是 ``sqrt-iter`` 和 ``good-enough?`` 两个函数更改了:

.. literalinclude:: code/7-sqrt.scm

新的 ``sqrt`` 函数即使在测试非常小的值的时候都不会出现错误了
新的 ``sqrt`` 函数对于非常小的测试值也能给出正确答案

::

Expand All @@ -66,24 +63,8 @@
;... done
;Value: sqrt-iter

1 ]=> (sqrt 0.0009)

;Value: 3.0000012746348552e-2

但是,事实上,即使这个新的 ``sqrt`` 也并非完美,假如我们继续缩小 ``sqrt`` 的实参,比如说,调用 ``(sqrt 0.00009)`` ,那么新的 ``sqrt`` 将会再次出错:

::

1 ]=> (sqrt 0.00009)

;Value: 9.487978730289174e-3

不过这次的出错原因和之前的出错原因不同:这次出错是因为计算所需的精度超出了解释器实现对浮点数精度的支持,和程序本身(的正确性)无关。

这也可以看出,即使程序被正确地写出来,但最终计算的精度还是会被解释器的实现所限制。

.. note:: 练习原本要求我们对比新旧两个猜测值之间的比率,但是这里使用对比两个猜测值之间的差作为检测手段,其实结果都是类似的,而且对比比率这种想法比对比差要来得更糟糕,因为比率的精度更差,且计算比率所需的除法要比减法慢得多。

.. note:: 在新的 ``sqrt-iter`` 的定义中, ``(improve guess x)`` 被重复调用了多次,这是因为书本到这个练习为止还没有介绍 ``let`` 结构。

以下是一个使用 ``let`` 结构重写的、无重复调用的 ``sqrt-iter`` :
Expand All @@ -92,6 +73,6 @@

(define (sqrt-iter old-guess x)
(let ((new-guess (improve old-guess x)))
(if (good-enough? old-guess new-guess)
(if (good-enough? old-guess new-guess x)
new-guess
(sqrt-iter new-guess x))))
11 changes: 8 additions & 3 deletions chp1/code/7-good-enough.scm
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
;;; 7-good-enough.scm

(define (good-enough? old-guess new-guess)
(< (abs (- old-guess new-guess))
0.001))
(define (good-enough? old-guess new-guess x)
(< (abs (- (/ (* old-guess new-guess)
x)
1)
)
0.001
)
)
4 changes: 2 additions & 2 deletions chp1/code/7-sqrt.scm
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
(load "p15-improve.scm") ; 确保后面定义的重名函数可以覆盖它们
(load "p15-average.scm")

(load "7-good-enough.scm")
(load "7-good-enough.scm") ; 载入新的 good-enough?

(define (sqrt-iter guess x)
(if (good-enough? guess (improve guess x)) ; 改动了这一行
(if (good-enough? guess (improve guess x) x) ; 调用新的 good-enough?
(improve guess x)
(sqrt-iter (improve guess x)
x)))
4 changes: 2 additions & 2 deletions chp1/code/test-7-good-enough.scm
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

(define-each-check

(false? (good-enough? 1 3))
(false? (good-enough? 1 3 1))

(good-enough? 1 1)
(good-enough? 1 1 1)

)

Expand Down

0 comments on commit 82aa25d

Please sign in to comment.