We introduced compound procedures in
section 1.1.4 as a mechanism for abstracting
patterns of numerical operations so as to make them independent of the
particular numbers involved. With higher-order procedures, such as
the integral procedure of
section 1.3.1, we began to see a more
powerful kind of abstraction: procedures used to express general
methods of computation, independent of the particular functions
involved. In this section we discuss two more elaborate
examples — general methods for finding zeros and fixed points of
functions — and show how these methods can be expressed directly as procedures.
Finding roots of equations by the half-interval method
The half-interval method is a simple but powerful technique for
finding roots of an equation f(x) = 0, where f is a continuous function. The idea is that, if we are given points a and b such that f(a) < 0 < f(b), then f must have at least one zero between a and b. To locate a zero, let x be the average of a and b
and compute f(x). If f(x) > 0, then f must have a zero between a and x. If f(x) < 0, then f must have a zero between x and b. Continuing in this way, we can identify smaller and smaller
intervals on which f must have a zero. When we reach a point where
the interval is small enough, the process stops. Since the interval
of uncertainty is reduced by half at each step of the process, the
number of steps required grows as Θ(log( L/T)), where L is the
length of the original interval and T is the error tolerance
(that is, the size of the interval we will consider “small enough”).
Here is a procedure that implements this strategy:
(define (search f neg-point pos-point)
(let ((midpoint (average neg-point pos-point)))
(if (close-enough? neg-point pos-point)
midpoint
(let ((test-value (f midpoint)))
(cond ((positive? test-value)
(search f neg-point midpoint))
((negative? test-value)
(search f midpoint pos-point))
(else midpoint))))))
We assume that we are initially given the function f together with points at which its values are negative and positive. We first compute the midpoint of the two given points. Next we check to see if the given interval is small enough, and if so we simply return the midpoint as our answer. Otherwise, we compute as a test value the value of f at the midpoint. If the test value is positive, then we continue the process with a new interval running from the original negative point to the midpoint. If the test value is negative, we continue with the interval from the midpoint to the positive point. Finally, there is the possibility that the test value is 0, in which case the midpoint is itself the root we are searching for.
To test whether the endpoints are “close enough” we can use a procedure similar to the one used in section 1.1.7 for computing square roots:[55]
(define (close-enough? x y)
(< (abs (- x y)) 0.001))
Search is awkward to use directly, because
we can accidentally give it points at which f’s
values do not have the required sign, in which case we get a wrong answer.
Instead we will use search via the following procedure, which checks to see which of the endpoints has a negative function value and
which has a positive value, and calls the search procedure
accordingly. If the function has the same sign on the two given
points, the half-interval method cannot be used, in which case the
procedure signals an error.[56]
(define (half-interval-method f a b)
(let ((a-value (f a))
(b-value (f b)))
(cond ((and (negative? a-value) (positive? b-value))
(search f a b))
((and (negative? b-value) (positive? a-value))
(search f b a))
(else
(error "Values are not of opposite sign" a b)))))
The following example uses the half-interval method to approximate π
as the root between 2 and 4 of sin x = 0:
(half-interval-method sin 2.0 4.0)
3.14111328125
Here is another example, using the half-interval method to search for a root of the equation x3 - 2x - 3 = 0 between 1 and 2:
(half-interval-method (lambda (x) (- (* x x x) (* 2 x) 3))
1.0
2.0)
1.89306640625
Finding fixed points of functions
A number x is called a fixed point of a function f if x satisfies the equation f(x) = x. For some functions f we can locate a fixed point by beginning with an initial guess and applying f repeatedly,
![]()
until the value does not change very much. Using this idea, we can
devise a procedure fixed-point that takes as inputs a function and an initial guess and produces an approximation to a fixed point of
the function. We apply the function repeatedly until we find two
successive values whose difference is less than some prescribed tolerance:
(define tolerance 0.00001)
(define (fixed-point f first-guess)
(define (close-enough? v1 v2)
(< (abs (- v1 v2)) tolerance))
(define (try guess)
(let ((next (f guess)))
(if (close-enough? guess next)
next
(try next))))
(try first-guess))
For example, we can use this method to approximate the fixed point of the cosine function, starting with 1 as an initial approximation:[57]
(fixed-point cos 1.0)
.7390822985224023
Similarly, we can find a solution to the equation
y = sin y + cos y:
(fixed-point (lambda (y) (+ (sin y) (cos y)))
1.0)
1.2587315962971173
The fixed-point process is reminiscent of the process we used for finding square roots in section 1.1.7. Both are based on the idea of repeatedly improving a guess until the result satisfies some criterion. In fact, we can readily formulate the square-root computation as a fixed-point search. Computing the square root of some number x requires finding a y such that y2 = x. Putting this equation into the equivalent form y = x/y, we recognize that we are looking for a fixed point of the function[58] y → x/y, and we can therefore try to compute square roots as
(define (sqrt x)
(fixed-point (lambda (y) (/ x y))
1.0))
Unfortunately, this fixed-point search does not converge. Consider an initial guess y1. The next guess is y2 = x/y1 and the next guess is y3 = x/y2 = x/(x/y1) = y1. This results in an infinite loop in which the two guesses y1 and y2 repeat over and over, oscillating about the answer.
One way to control such oscillations is to prevent the guesses from changing so much. Since the answer is always between our guess y and x/y, we can make a new guess that is not as far from y as x/y by averaging y with x/y, so that the next guess after y is (1/2)(y + x/y) instead of x/y. The process of making such a sequence of guesses is simply the process of looking for a fixed point of y → (1/2)(y + x/y):
(define (sqrt x)
(fixed-point (lambda (y) (average y (/ x y)))
1.0))
(Note that y = (1/2)(y + x/y) is a simple transformation of the equation y = x/y; to derive it, add y to both sides of the equation and divide by 2.)
With this modification, the square-root procedure works. In fact, if we unravel the definitions, we can see that the sequence of approximations to the square root generated here is precisely the same as the one generated by our original square-root procedure of section 1.1.7. This approach of averaging successive approximations to a solution, a technique we that we call average damping, often aids the convergence of fixed-point searches.
error, which takes as arguments a number of items that are printed as error messages. [back]cos button until you obtain the fixed point. [back]lambda. y → x/y means (lambda(y) (/ x y)), that is, the function whose value at y is x/y. [back]Exercises




Comments
zpPtStHVBIJWtlSFeek
I could read a book about this wioutht finding such real-world approaches!
Post new comment