Data Conversion: The Strings Lab mentioned Scheme procedures for converting between strings and lists and between strings and symbols. The following table extends the collection of data conversion procedures mentioned there.
| Procedure | Sample Call | Result of Example | Comment |
|---|---|---|---|
| string->list | (string->list "example") | (#\e #\x #\a #\m #\p #\l #\e) | makes a list of the characters in a string |
| list->string | (list->string '(#\e #\x #\a #\m #\p #\l #\e)) | "example" | makes a string of the characters in a list |
| symbol->string | (symbol->string 'example) | "example" | change a given symbol to a string |
| string->symbol | (string->symbol "example") | example | convert a given string to a symbol |
| number->string | (number->string 3.14159) | "3.14159" | convert the number to an string [in base 10] |
| string->number | (string->number "3.3") | 3.3 | convert the [base 10] number to a string |
| char->integer | (char->integer #\F) | 70 | convert a character to its corresponding integer code |
| integer->char | (integer->char 70) | #\F | return the character with the given integer code |
(number->string 2/3) (number->string -3/6) (number->string 1234567890)Be sure to check if all the results are as you expected.
In Scheme, this problem is resolved by allowing the implementation to specify a standard case. For Chez Scheme, symbols normally are considered to be in lower case.
(symbol->string 'Example) (symbol->string 'example) (symbol->string 'ExAmPlE)In each case, what is printed?
(string->symbol (symbol->string 'ExAmPlE))
(string->symbol "variable") (string->symbol "VARIABLE") (string->symbol "VaRiAbLe") (symbol->string (string->symbol "variable")) (symbol->string (string->symbol "VARIABLE")) (symbol->string (string->symbol "VaRiAbLe"))In each case, describe what is printed?
(define first 10) (define Second 20) (define ThIrD 30) first second third (eval first) (eval Second) (eval ThIrD) (eval (string->symbol "first")) (eval (string->symbol "Second")) (eval (string->symbol "second")) (eval (string->symbol "ThIrD")) (eval (string->symbol "third"))In each case, explain the result.
(/ 1.0 8.0) (/ 1.0 7.0) (/ 1.0 3.0) (number->string (/ 1.0 8.0)) (number->string (/ 1.0 7.0)) (number->string (/ 1.0 3.0))In each case, check how many digits are included in the string.
Note: Actually, within the machine, Scheme stores numbers in a binary (base 2) representation, and real numbers are stored to a limited number of significant digits. The number of digits printed involves the precision needed to distinguish one real number from another in base 2 -- for the specified number of significant digits. Thus, while the number of digits printed or included in a string may be helpful from the standpoint of the storage of a real number, the format of the real number in string form may vary from number to number.
Formatted Output: We now turn to the question of how to format output nicely, so we can place numbers in appropriate columns when we print tables. For example, suppose we want to print a table of equivalent values of quarts and liters:
Quarts Liters
1 0.94
2 1.89
3 2.84
4 3.78
5 4.73
6 5.68
7 6.62
8 7.57
9 8.52
10 9.46
11 10.41
12 11.36
Here, we want to print integer quarts values and real numbers to 2 decimal
place accuracy for the liter equivalents.To generate this table, our general approach can follow the development we described earlier in the Input and Output Lab. While the formatting of integers contains relatively few surprise, some comments may be helpful. The formatting of reals turns out to be rather tricky.
Formatting Integers: To print integers in a consistent format, we normally need to decide how many characters to allow for a number field, and we want the integer right justified within that space. In the sample table above, for example, we allocated 6 characters for the quarts integer. Thus, 5 spaces are inserted before the integers 1, 2, 3, ..., 9, and 4 spaces are inserted before 10, 11, and 12.
This suggests we want a procedure formatted-integer which takes both an integer and a number of characters (a field width) as parameters. Procedure formatted-integer should return a string of the specified length, with the integer at the end. If the integer requires more characters than the width specified, we will allow formatted-integer to return a longer string.
The development of formatted-integer is reasonably straightforward, if we start with an integer and if we use the number->string procedure. After obtaining a string, we just add the correct number of spaces at the start. Such a procedure definition is given below:
(define formatted-integer
(lambda (int-value width)
(if (not (integer? int-value))
(error 'formatted-integer "cannot format non-integer as integer"))
(let* ((int-string (number->string int-value))
(int-length (string-length int-string))
(number-init-blanks (max (- width int-length) 0))
(blank-string (make-string number-init-blanks #\space)))
(string-append blank-string int-string)
)
)
)
In this procedure, make-string constructs a string of the given
length (number-init-blanks), where each character in the string is
#\space.
(formatted-integer 41264 5) (formatted-integer 41264 10) (formatted-integer 41264 20) (formatted-integer -41264 20) (formatted-integer 41264 2) (formatted-integer 412.64 10)
In considering how to write write-real, it turns out that the details are rather complicated, because many cases arise. For example, it might seem natural to divide a real number into its whole number (integer) part and its fractional part (to the right of the decimal point). In such an approach, the real number 123.456 would be treated as the integer 123 followed by the fractional part 456 . Some examples follow:
| Real Number | Integer Part | Fractional Part |
|---|---|---|
| 123.456 | 123 | 456 |
| -23.456 | -23 | 456 |
| 0.456 | 0 | 456 |
| -0.456 | 0 | 456 |
| 123.006 | 123 | 006 |
| -23.056 | -23 | 056 |
For example, the table shows that the integer part of 0.456 and -0.456 are the same. If one just takes the integer part of a real number between -1 and 1, then one may not be able to distinguish between positives and negatives. To resolve this problem it is necessary to handle the sign explicitly. If a number is negative, we must put in the minus sign directly -- we cannot rely upon formatted-integer to do the job.
The table also reminds us that a fractional part may contain initial zeros. Thus, printing 006 as an integer would give only the value 6, and we cannot rely upon formatted-integer or integer->string to print the factional part either.
A further complication arises in that real numbers are not stored exactly within the computer; rather only a specified number of digits are kept. Further, the number is stored using a base 2 (or binary) representation, rather than the familiar decimal. While the details of conversion from a binary form to decimal are beyond what we can consider in this course, one direct consequence is that specific care must be taken not to introduce printing errors when converting from an internal, binary format to a decimal string format.
One complete version of write-real may be found in file /home/stone/courses/scheme/spring97/examples/write-real.ss. In addition to handling the various subtle cases mentioned above, this write-real procedure allows printing to any output port, and write-real performs considerable error checking of parameters.
While the resulting procedure is rather long and complex, we can use the procedure easily. First, we include the file containing this procedure into our Scheme environment with the statement:
(load "/home/stone/courses/scheme/spring97/examples/write-real.ss")Then we can use write-real without further trouble, just as when we have defined any procedures ourselves.
This document is available on the World Wide Web as
http://www.math.grin.edu/~walker/courses/151/lab-formatted-output.html