Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 56 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
56
Dung lượng
3,31 MB
Nội dung
-394- To understand the problems, it is best to agree on a fixed representation schema and to experiment with the number representations. Let's represent a fixed-size number with a structure that has three fields: (define-struct inex (mantissa sign exponent)) The first and last field contain the mantissa and exponent of the number, the sign field is +1 or - 1 and represents the sign of the exponent. This sign field enables us to represent numbers between 0 and 1 . Here is the data definition: An inex is a structure: (make-inex m s e) where m and e are natural numbers in [ 0 , 99 ] and s is +1 or -1 . Because the conditions on the fields of an inex structure are so stringent, we use the function create-inex to create these structures. Figure 94 contains the function definition for create- inex , which is a generalized constructor, that is, a checked constructor (see section 7.5). The figure also defines the function inex->number , which turns inex s into numbers according to the principles of our new notation. Let's translate the above example, 1200 , into our Scheme representation: (create-inex 12 +1 2) The alternative representation, 120 · 10 1 , is illegal in our Scheme world, however. If we evaluate (create-inex 120 +1 1) we get an error message because the arguments don't satisfy the stated data contract. For other numbers, though, we can find two inex equivalents. One example is 0.0000000000000000005 , which we can express as (create-inex 50 -1 20) and (create-inex 5 -1 19) Confirm the equivalence of these two representations with inex->number . The range of inex numbers is vast: (define MAX-POSITIVE (make-inex 99 +1 99)) (define MIN-POSITIVE (make-inex 1 -1 99)) That is, we can represent large numbers that consist of up to 101 digits in the standard decimal notation; we can also represent small positive fractions smaller than 1 down to the fraction 1 over 10 0 with 99 zeros. The appearances, however, are deceiving. Not all real numbers in the range between 0 and MAX-POSITIVE can be translated into an inex structure. In particular, any positive number less than TEAMFLY TEAM FLY PRESENTS -395- has no equivalent inex structure. Similarly, the inex representation has gaps in the middle. For example, the successor of (create-inex 12 +1 2) is (create-inex 13 +1 2) The first inex structure corresponds to 1200 , the second one to 1300 . Numbers in the middle, such as 1240 or 1260, can only be represented as one or the other. The standard choice is to round the number to the closest representable equivalent. In short, we must approximate such mathematical numbers as we translate into a chosen representation. Finally, we must also consider arithmetic operations on inex structures. Adding two inex representations with the same exponent means adding the two mantissas: (inex+ (create-inex 1 +1 0) (create-inex 2 +1 0)) = (create-inex 3 +1 0) Translated into mathematical notation, we have When the addition of two mantissas yields too many digits, we may have to find a suitable representation. Consider the example of adding to itself. Mathematically we get but we can't just translate this number naively into our chosen representation because 110 > 99. The proper corrective action is to represent the result as Or, translated into Scheme, we must ensure that inex+ computes as follows: (inex+ (create-inex 55 +1 0) (create-inex 55 +1 0)) = (create-inex 11 +1 1) More generally, if the mantissa of the result is too large, we must divide it by 10 and increase the exponent by one. TEAMFLY TEAM FLY PRESENTS -396- Sometimes the result contains more mantissa digits than we can represent. In those cases, inex+ must round to the closest equivalent in the inex world. For example: (inex+ (create-inex 56 +1 0) (create-inex 56 +1 0)) = (create-inex 11 +1 1) This corresponds to the precise calculation: Because the result has too many mantissa digits, the integer division of the result mantissa by 10 produces an approximate result: This is an example of the many approximations that make INEXACT ARITHMETIC inexact. We can also multiply numbers represented as inex structures. Recall that Thus we get: or, in Scheme notation: (inex* (create-inex 2 +1 4) (create-inex 8 +1 10)) = (make-inex 16 +1 14) As with addition, things are not always straightforward. When the result has too many significant digits in the mantissa, inex* has to increase the exponent: (inex* (create-inex 20 -1 1) (create-inex 5 +1 4)) = (create-inex 10 +1 4) In the process, inex* will introduce an approximation if the true mantissa doesn't have an exact equivalent in the class of inex structures: (inex* (create-inex 27 -1 1) (create-inex 7 +1 4)) = (create-inex 19 +1 4) Exercise 33.2.1. Develop the function inex+ , which adds inex representations that have the same exponent. The function must be able to deal with examples that increase the exponent. Furthermore, it must signal its own error if the result is out of range for inex representations. TEAMFLY TEAM FLY PRESENTS -397- Challenge: Extend inex+ so that it can deal with inputs whose exponents differ by 1 : (equal? (inex+ (create-inex 1 +1 0) (create-inex 1 -1 1)) (create-inex 11 -1 1)) Do not attempt to deal with larger classes of inputs than that without reading the following subsection. Exercise 33.2.2. Develop the function inex* , which multiplies inex representations. The function must be able to deal with examples that increase the exponent. Furthermore, it must signal its own error if the result is out of range for inex representations. Exercise 33.2.3. The section illustrated how an inexact representation system for real numbers has gaps. For example, 1240 was represented as (create-inex 12 +1 2) by rounding off the last significant digit of the mantissa. The problem is, round-off errors can accumulate. Develop the function add , which adds up n copies of #i1/185 . What is the result for (add 185) ? What should it be? What happens if we multiply the result of the second expression with a large number? Develop the function sub , which counts how often 1/185 can be subtracted from the argument until the argument is 0 . How often should the evaluation recur before (sub 1) and (sub #i1.) is evaluated? What happens in the second case? Why? 33.3 Overflow While the use of scientific notation expands the range of numbers we can represent with fixed- size chunks of data, it still doesn't cover arbitrarily large numbers. Some numbers are just too big to fit into a fixed-size number representation. For example, can't be represented, because the exponent 500 won't fit into two digits, and the mantissa is as large as it can be. Numbers that are too large for our representation schema can arise during a computation. For example, two numbers that we can represent can add up to a number that we cannot represent: (inex+ (create-inex 50 +1 99) (create-inex 50 +1 99)) = (create-inex 100 +1 99) which violates the data contract, or (inex+ (create-inex 50 +1 99) (create-inex 50 +1 99)) = (create-inex 10 +1 100) which also breaks the contract for inex structures. When the result of inex arithmetic produces numbers that are too large to be represented, we say (arithmetic) OVERFLOW occurred. TEAMFLY TEAM FLY PRESENTS -398- When overflow occurs, some language implementations signal an error and stop the computation. Others designate some symbol, called infinity, for all numbers that are too large. Arithmetic operations are aware of infinity and propagate it. Negative Numbers: If our inex structures had a sign field for the mantissa, then two negative numbers can add up to one that is so negative that it can't be represented either. This is also called overflow, though to emphasize the distinction people sometimes say overflow in the negative direction. Exercise 33.3.1. DrScheme's inexact number system uses an infinity value to deal with overflow. Determine the integer n such that (expt #i10. n) is still an inexact Scheme number and (expt #i10. (+ n 1)) is approximated with infinity. Hint: Use a function to compute n . 33.4 Underflow At the opposite end of the spectrum, we have already seen small numbers that cannot be represented with inex structures. For example, 10 - 500 is not 0, but it's smaller than the smallest non-zero number we can represent. An arithemtic UNDERFLOW arises when we multiply two small numbers and the result is too small to fit into our class of inex structures: (inex* (create-inex 1 -1 10) (create-inex 1 -1 99)) = (create-inex 1 -1 109) which causes an error. When underflow occurs, some language implementations signal an error; others use 0 to approximate the result. An approximation with 0 for underflow is qualitatively different from our ealier kinds of approximations. In approximating 1250 with (create-inex 12 +1 2) , we approximated by dropping significant digits from the mantissa, but we were left with a non-zero mantissa. The result is within 10% of the number we wanted to represent. Appromixating on underflow, however, means dropping the entire mantissa. The result is not within a predictable precentage range of the true result. Exercise 33.4.1. DrScheme's inexact number system uses #i0 to approximate underflow. Determine the smallest integer n such that (expt #i10. n) is still an inexact Scheme number and (expt #i10. (- n 1)) is approximated with 0 . Hint: Use a function to compute n . 33.5 DrScheme's Numbers Most programming languages support only inexact number representations (and arithmetic) for both integers and reals. Scheme, in contrast, supports both exact and inexact numbers and arithmetic. Of course, the base of the representation is 2, not 10, because Scheme uses the underlying computer's on-off machinery. As the note on page 5 explained, DrScheme's teaching levels interpret all numbers in our programs as exact rationals, unless they are prefixed with #i. Some numeric operations, though, produce inexact numbers. Plain Scheme, which is called Full Scheme in DrScheme, interprets all TEAMFLY TEAM FLY PRESENTS -399- numbers with a dot as inexact numbers; 66 it also prints inexact reals with just a dot, implying that all such numbers are inexact and possibly distant from the actual result. Scheme programmers can thus choose to use exact arithmetic or inexact arithmetic as necessary. For example, numbers in financial statements should always be interpreted as exact numbers; arithmetical operations on such numbers should be as precise as possible. For some problems, however, we may not wish to spend the extra time to produce exact results. Scientific computations are a primary example. In such cases, we may wish switch to inexact numbers and arithmetic. Numerical Analysis: When we use inexact numbers and arithmetic, it is natural to ask how much the program's results differs from the true results. Over the past few decades, the study of this complex question has evolved into an advanced topic, called numerical analysis. The discipline has become a subject of its own right in applied mathematics or in computer science departments. Exercise 33.5.1. Evaluate (expt 1.001 1e-12) in Full Scheme (any variant) and in Intermediate Student Scheme. Explain the observations. Exercise 33.5.2. Develop the function my-expt , which raises one number to the power of some integer. Using this function, conduct the following experiment. Add (define inex (+ 1 #i1e-12)) (define exac (+ 1 1e-12)) to the Definitions window. What is (my-expt inex 30) ? How about (my-expt exac 30) ? Which answer is more useful? Exercise 33.5.3. When we add two inexact numbers of vastly different orders of magnitude, we may get the larger one back as the result. For example, if we are using only 15 significant digits, then we run into problems when adding numbers which vary by more than a factor of 10 16 : but if the number system supports only 15 digits, the closest answer is 10 16 . At first glance, this doesn't look too bad. After all, being wrong by one part in 10 16 (ten million billion) is close enough to the accurate result. Unfortunately, this kind of problem can add up to huge problems. Consider the following list of inexact numbers: (define JANUS (list #i31 #i2e+34 #i-1.2345678901235e+80 #i2749 #i-2939234 #i-2e+33 #i3.2e+270 TEAMFLY TEAM FLY PRESENTS -400- #i17 #i-2.4e+270 #i4.2344294738446e+170 #i1 #i-8e+269 #i0 #i99)) Determine the values (sum JANUS) and (sum (reverse JANUS)). Explain the difference. Can we trust computers? 66 We can force Full Scheme to interpret numbers with a dot as exact by prefixing the numbers with #e. TEAMFLY TEAM FLY PRESENTS -401- Part VII Changing the State of Variables TEAMFLY TEAM FLY PRESENTS -402- Section 34 Memory for Functions No matter how often we use a function with one and the same argument, we always get the same result. Even an accumulator-style function produces the same result every time we apply it to the same argument, as long as the accumulator argument is also the same. Functions simply do not have any memory about their past uses. Many programs, though, must remember something about their past uses. Recall that a program typically consists of several functions. In the past we have always assumed that there is one main function and that all others are auxiliary and invisible to the user. In some cases, however, a user may expect more than one service from a program, and each service is best implemented as a function. When a program provides more than one function as a service to the user, it is common that, for sheer convenince or possibly because we add a graphical user interface, the functions must have memory. Because this point is difficult to grasp in the abstract, we study some examples. The first one concerns a program for managing telephone numbers in an address book. The standard address book software provides at least two services: 1. a service for looking up the phone number of some person; and 2. a service for adding a name and a phone number to the address book. Based on our guidelines, the program provides two functions to the user. The user can apply those functions in DrScheme's Interactions window to appropriate data. Or, we can develop a graphical user interface with text fields and buttons so that the user doesn't need to know anything about programming. Figure 95 displays such an interface. Figure 95: A phonebook GUI The two services roughly correspond to two functions: ;; lookup : list-of-symbol-number-pairs symbol -> number or false ;; to lookup the number associated with name in pb ;; if it doesn't find name, the function produces false (define (lookup pb name) ) ;; add-to-address-book : symbol number -> void ;; to add name and number to address-book (define (add-to-address-book name number) ) TEAMFLY TEAM FLY PRESENTS -403- (define ADDRESS-BOOK (list (list 'Adam 1) (list 'Eve 2))) We also introduce a variable definition for maintaing a list of name-number associations. The first function is a variant of our very first recursive function. A user applies it to a list of name-number associations, such as ADDRESS-BOOK , and a name. It produces a number, if the name is on the list, or false otherwise. The second function is radically different from what we have seen. The user would apply it to a name and a number; any future lookup of that name would then produce that number. Let's imagine an interaction in DrScheme: > (lookup ADDRESS-BOOK 'Adam) 1 > (lookup ADDRESS-BOOK 'Dawn) false > (add-to-address-book 'Dawn 4) > (lookup ADDRESS-BOOK 'Dawn) 4 The first two confirm that 'Adam has the phone number 1 and that we don't have a phone number for 'Dawn . The third one adds the phone number 4 for 'Dawn to ADDRESS-BOOK . And the last interaction shows that the very same use of lookup as before now produces the expected phone number. In the past, the only way we could have achieved this same effect is by editing the definition of ADDRESS-BOOK . But, we do not wish users to edit our programs. Indeed, they shouldn't even have access to our programs. We are therefore forced to provide an interface with a function that permits such changes. We could go even further and implement the graphical interface of figure 95. A dialogue equivalent to the above interaction would proceed as follows: 1. Type Adam into the text field, click the Lookup button, and ``1'' appears in the lower text field. 2. Enter Dawn into the text field, click the Lookup button, and some message concerning a missing number appears in the lower text field. 3. Replace the message with ``4'' and click the Add button. 4. Erase the ``4'' from the lower text field, click the Lookup and the ``4'' shows up again. In short, providing a convenient interface to a user forces us to develop a program whose functions know about each other's usage history. TEAMFLY TEAM FLY PRESENTS [...]... (begin (add -to- address-book 'Adam 1) (add -to- address-book 'Eve 2) (add -to- address-book 'Chris 6145 384 )) The first subexpression is a plain function application So, the first step relies on the usual law of substitution:69 (define address-book empty) (begin (set! address-book (cons (list 'Adam 1) address-book)) (add -to- address-book 'Eve 2) (add -to- address-book 'Chris 6145 384 )) The next expression to be evaluated... current-color to 'red, we follow a conventional rule of engineering to put devices into their least harmful state when starting it up.70 At first glance, these initializers don't seem to add much to our programs Both set the respective state variables to the values that are their defined values For both cases, however, it is easy to see that the initializer could do some additional useful work The... color is assigned to which square; they are guesses The first player's response to a guess is to compare the colors and to produce one of the following answers: Y L F M 1 'perfect!, if the first target is equal to the first guess and the second target is equal to the second guess; 2 'OneColorAtCorrectPosition, if the first guess is equal to the first target or the second guess is equal to the second target;... space and time, but it should be used carefully -415TEAM FLY PRESENTS Section 36 Designing Functions with Memory Section 34 motivated the idea of functions with memory; section 35 explained how variable definitions and set! together can achieve the effect of memory It is now time to discuss the design of programs with memory Designing functions with memory requires three important steps: 1 We must determine... graphical user interfaces, we are almost forced to think of programs as a collection of collaborating functions attached to various widgets in a window Finally, even programs that work in physical devices such as elevators or VCRs are forced to interact with the device in some fixed way, and that often includes keeping around information about the history of device-program interactions In short, the... It is intended to represent a list of entries, where each entry is a list of two items: a name and a number To document that address-book may represent only such lists, we add a contract as follows: Y L F M ;; address-book : (listof (list symbol number)) ;; to keep track of pairs of names and phone numbers (define address-book empty) By the definition of (listof X), it is permissible to use empty as... course, to accomplish this, hangman and check must have some memory about how often the ``Check'' button was used and how often it was used with a negative result With our current knowledge of Scheme, we cannot formulate functions such as add-toaddress-book, next, or check To fill this gap in our knowledge, the next section introduces 67 set! expressions This new form of expression permits functions to. .. demonstrates how to design programs whose effects depend on conditions; and the last one shows how effects can be useful in recursive functions The last two subsections provide opportunities for practicing what we've learned 37.1 Initializing State Recall the color-guessing game from exercise 5.1.5 One player picks two colors for two squares; we call those ``targets.'' The other one tries to guess which... PRESENTS = (lookup 'Adam (list (list 'Chris 6145 384 ) (list 'Eve 2) (list 'Adam 1)) = = 1 The comparison of this evaluation and the one at the beginning of the section shows how set! changes the meaning of address-book over time and how the two functions, add -to- addressbook and lookup, implement the services that we discussed in section 34 The exercises show how useful this collaboration of two functions... of memory it needs Fortunately it is relatively easy to recognize when programs need memory As discussed already, there are two situations The first involves programs that provide more than one service to users Each service corresponds to a function A user may apply these functions in DrScheme's Interactionswindow, or they may be applied in response to some user action in a graphical user interface The . ADDRESS-BOOK . But, we do not wish users to edit our programs. Indeed, they shouldn't even have access to our programs. We are therefore forced to provide an interface with a function that. Translated into mathematical notation, we have When the addition of two mantissas yields too many digits, we may have to find a suitable representation. Consider the example of adding to itself numbers. Some numbers are just too big to fit into a fixed-size number representation. For example, can't be represented, because the exponent 500 won't fit into two digits, and the mantissa