Smalltalk80LanguageImplementation:Chapter 08

From 흡혈양파의 번역工房
Jump to: navigation, search
Chapter 8 Numerical Classes

Numerical Classes

Object

    Magnitude
        Character
        Date
        Time

        Number***
            Float***
            Fraction***
            Integer***
                LargeNegativeInteger***
                LargePositiveInteger***
                SmallInteger***

        LookupKey
            Association

    Link

        Process

    Collection

        SequenceableCollection
            LinkedList

                Semaphore

            ArrayedCollection
                Array

                Bitmap
                    DisplayBitmap

                RunArray
                String
                    Symbol
                Text
                ByteArray

            Interval
            OrderedCollection
                SortedCollection
        Bag
        MappedCollection
        Set
            Dictionary
                IdentifyDictionary

    Stream
        PositionableStream
            ReadStream
            WriteStream
                ReadWriteStream
                    ExternalStream
                        FileStream

        Random***

    File
    FileDirectory
    FilePage

    UndefinedObject
    Boolean
        False
        True

    ProcessorScheduler
    Delay
    SharedQueue

    Behavior
        ClassDescription
            Class
            MetaClass

    Point
    Rectangle
    BitBit
        CharacterScanner

        Pen

    DisplayObject
        DisplayMedium
            Form
                Cursor
                DisplayScreen
        InfiniteForm
        OpaqueForm
        Path
            Arc
                Circle
            Curve
            Line
            LinearFit
            Spline


One of the major goals of the Smalltalk programming system is to apply a single metaphor for information processing as uniformly as possible. The Smalltalk metaphor, as described in earlier chapters, is one of objects that communicate by sending messages. This metaphor is very similar to the one used in Simula for implementing simulation systems. One of the greatest challenges to the application of the Smalltalk metaphor to all aspects of a programming system has been in the area of arithmetic. Simula used the object/message metaphor only for the higher level interactions in the simulations it implemented. For arithmetic, as well as most algorithmic control structures, Simula relied on the embedded Algol programming language with its built-in number representations, operations, and syntax. The contention that even the addition of two integers should be interpreted as message-sending met with a certain amount of resistance in the early days of Smalltalk. Experience has demonstrated that the benefits of this extreme uniformity in the programming language outweigh any inconvenience in its implementation. Over several versions of Smalltalk, implementation techniques have been developed to reduce the message-sending overhead for the most common arithmetic operations so that there is now almost no cost for the benefits of uniformity.


Objects that represent numerical values are used in most systems done in Smalltalk (as with most other programming languages). Numbers are naturally used to perform mathematical computations; they are also used in algorithms as indices, counters, and encodings of states or conditions (often called flags). Integral numbers are also used as collections of binary digits (bits) that perform boolean masking operations with each other.


Each different kind of numerical value is represented by a class. The number classes have been implemented so that all numbers behave as if they were of the most general type. The actual class of a particular number object is determined by how much of the full generality is needed to represent its value. Therefore the external protocol of all number objects is inherited from the class Number. Number has three subclasses: Float, Fraction, and Integer. Integer has three subclasses: SmallInteger, LargePositiveInteger, and LargeNegativeInteger. Integral numbers provide further protocol to support treating the number as a sequence of bits. This protocol is specified in the class Integer. Numbers in the system are instances of Float, Fraction, SmallInteger, LargePositiveInteger, or LargeNegativeInteger. Classes Number and Integer specify shared protocol, but they do not specify particular representations for numeric values. Therefore no instances of Number or Integer are created.


Unlike other objects that may change their internal state, the only state of a number is its value, which should never change. The object 3, for example, should never change its state to 4, or disastrous effects could occur.


Protocol of the Number Classes

Number defines the protocol of all numeric objects. Its messages support standard arithmetic operations and comparisons. Most of these must be implemented by subclasses of Number since they depend on the actual representation of values.


The protocol of arithmetic messages consists of the usual binary operators such as +, -, * and /, and several unary and keyword messages for computing the absolute value of a number, the negation of a number, or the integer quotient or remainder of a number. The category for arithmetic messages is as follows.

arithmetic
+ aNumber Answer the sum of the receiver and the argument, aNumber.
- aNumber Answer the difference between the receiver and the argument, aNumber.
* aNumber Answer the result of multiplying the receiver by the argument, aNumber.
/ aNumber Answer the result of dividing the receiver by the argument, aNumber. Note that since as much precision as possible is retained, if the division is not exact, the result will be an instance of Fraction.
// aNumber Answer the integer quotient defined by division with truncation toward negative infinity.
\\ aNumber Answer the integer remainder defined by division with truncation toward negative infinity. This is the modulo operation.
abs Answer a Number that is the absolute value (positive magnitude) of the receiver.
negated Answer a Number that is the negation of the receiver.
quo: aNumber Answer the integer quotient defined by division with truncation toward zero.
rem: aNumber Answer the integer remainder defined by division with truncation toward zero.
reciprocal Answer 1 divided by the receiver. Report an error to the user if the receiver is 0.
Number instance protocol


Some examples follow.

expression result
1 +10 11
5.6 -3 2.6
5 - 2.6 2.4
(-4) abs 4
6 / 2 3
7 /2 (7/2), a Fraction with numerator 7 and denominator 2
7 reciprocal (1/7), a Fraction with numerator 1 and denominator 7


Arithmetic messages that return integral quotients and remainders from a division operation follow two conventions. One convention truncates the resulting number toward zero, the other toward negative infinity. These are the same for positive results since zero and negative infinity are in the same direction. For negative results, the two conventions round in different directions. The protocol for Number provides for both conventions.


The following table shows the relationships among the selectors.

result truncate toward negative infinity truncate toward zero
quotient // quo:
remainder \\ rem:


Examples include:

expression result
6 quo: 2 3
7 quo: 2 3
(7 quo: 2) + 1 4
7 quo: 2 + 1 2
7 rem: 2 1
7 // 2 3
7 \\ 2 1
7 \\ 2 + 1 2


The result of quo:, rem:, or // is always to return a value whose sign is positive if the receiver and argument have the same sign, and negative if their signs are different. \\ always produces a positive result.


Additional mathematical functions are

mathematical functions
exp Answer a floating point number that is the exponential of the receiver.
In Answer the natural log of the receiver.
log: aNumber Answer the log base aNumber of the receiver.
floorLog: radix Answer the floor of the log base radix of the receiver, where the floor is the integer nearest the receiver toward negative infinity.
raisedTo: aNumber Answer the receiver raised to the power of the argument, aNumber.
raisedToInteger: anInteger Answer the receiver raised to the power of the argument, anInteger, where the argument must be a kind of Integer.
sqrt Answer a floating point number that is the positive square root of the receiver.
squared Answer the receiver multiplied by itself.
Number instance protocol


Some examples are

expression result
2.718284 In 1.0
6 exp 403.429
2 exp 7.38906
7.38906 In 1.99998 (that is, 2)
2 log: 2 1.0
2 floorLog: 2 1
6 log: 2 2.58496
6 floorLog: 2 2
6 raisedTo: 1.2 8.58579
6 raisedToInteger: 2 36
64 sqrt 8.0
8 squared 64


Properties of numbers that deal with whether a number is even or odd and negative or positive can be tested with the following messages.

testing
even Answer whether the receiver is an even number.
odd Answer whether the receiver is an odd number.
negative Answer whether the receiver is less than 0.
positive Answer whether the receiver is greater than or equal to 0.
strictlyPositive Answer whether the receiver is greater than 0.
sign Answer 1 if the receiver is greater than 0, answer -1 if less than 0, else answer 0.
Number instance protocol


Properties of numbers that deal with truncation and round off are supplied by the following protocol.

truncation and round off
ceiling Answer the integer nearest the receiver toward positive infinity.
floor Answer the integer nearest the receiver toward negative infinity.
truncated Answer the integer nearest the receiver toward zero
truncateTo: aNumber Answer the next multiple of the argument, aNumber, that is nearest the receiver toward zero.
rounded Answer the integer nearest the receiver.
roundTo: aNumber Answer the multiple of the argument, aNumber, that is nearest the receiver.
Number instance protocol


Whenever a Number must be converted to an Integer, the message truncated can be used. So we have

expression result
16.32 ceiling 17
16.32 floor 16
16.32 truncated 16
16.32 truncateTo:5 15
16.32 truncateTo:5.1 15.3
16.32 rounded 16
16.32 roundTo: 6 18
16.32 roundTo:6.3 18.9


The protocol provided in class Number includes various messages for converting a number into another kind of object or a different unit of representation. Numbers can represent various unit measurements such as degrees and radians. The following two messages perform conversions.

converting
degreesToRadians The receiver is assumed to represent degrees. Answer the conversion to radians.
radiansToDegrees The receiver is assumed to represent radians. Answer the conversion to degrees.
Number instance protocol


So that

30 degreesToRadians = 0.523599
90 degreesToRadians = 1.5708


Trigonometric and logarithmic functions are included in the protocol for mathematical functions. The receiver for the trigonometric functions cos, sin, and tan is an angle measured in radians; the result of the functions arcCos, arcSin and arcTan is the angle measured in radians.


In the following examples, 30 degrees is given as 0.523599 radians; 90 degrees is 1.5708 radians.

expression result
0.523599 sin 0.5
0.523599 cos 0.866025
0.523599 tan 0.57735
1.5708 sin 1.0
0.57735 arcTan 0.523551
1.0 arcSin 1.5708


When a kind of Integer is asked to add itself to another kind of Integer, the result returned will naturally also be a kind of Integer. The same is true for the sum of two Floats; the class of the result will be the same as the class of the operands. If the two operands are SmallIntegers and the absolute value of their sum is too large to be represented as a SmallInteger, the result will be a LargePositiveInteger or a LargeNegativeInteger. The determination of the appropriate class of result when the operands are of different classes is somewhat more complicated. Two design criteria are that there be as little loss of information as possible and that commutative operations produce the same result regardless of which operand is the receiver of the message and which is the argument. So for example, 3.1 * 4 will return the same result as 4 * 3.1.


The appropriate representation for the result of operations on numbers of different classes is determined by a numerical measure of generality assigned to each class. Classes said to have more generality will have a larger number for this generality measure. Each class must be able to convert its instances into equal-valued instances of more general classes. The measure of generality is used to decide which of the operands should be converted. In this way, the arithmetic operations obey the law of commutativity with no loss of numerical information. When the differences between two classes of numbers are only a matter of precision (where "precision" is a measure of the information provided in a number), the more precise class is assigned a higher degree of generality. We have arbitrarily assigned approximate numbers a higher generality in cases where precision was not the issue (so, Float is more general than Fraction).


The generality hierarchy for the kinds of numbers in the Smalltalk-80 system, with most general listed first, is

Float
Fraction
LargePositiveInteger, LargeNegativeInteger
SmallInteger


The messages in the Number protocol designed to support the necessary coercions are categorized as "coercing" messages.

coercing
coerce: aNumber Answer a number representing the argument, aNumber, that is the same kind of Number as the receiver. This method must be defined by all subclasses of Number.
generality Answer the number representing the ordering of the receiver in the generality hierarchy.
retry: aSymbol coercing: aNumber An arithmetic operation denoted by the symbol, aSymbol, could not be performed with the receiver and the argument, aNumber, as the operands because of the difference in representation. Coerce either the receiver or the argument, depending on which has the lower generality, and then try the arithmetic operation again. If the symbol is the equals sign, answer false if the argument is not a Number. If the generalities are the same, then retry:coercing: should not have been sent, so report an error to the user.
Number instance protocol


Thus if we try to evaluate 32.45 * 4, the multiplication of a Float by a SmallInteger will result in evaluating the expression

32.45 retry: #* coercing: 4


and the argument 4 will be coerced to 4.0 (Float has higher generality than SmallInteger). Then the multiplication will be carried out successfully.


Defining a hierarchy of the numbers in terms of a numerical measure of generality works for the kinds of numbers provided in the basic Smalltalk-80 system because the generality is transitive for these kinds of numbers. However, it does not provide a technique that can be used for all kinds of numbers.


Intervals (described in detail in Chapter 10) can be created by sending one of two messages to a number. For each element of such an interval, a block can be evaluated with the element as the block value.

intervals
to: stop Answer an Interval from the receiver up to the argument, stop, with each next element computed by incrementing the previous one by 1.
to: stop by: stop Answer an Interval from the receiver up to the argument, stop, with each next element computed by incrementing the previous one by step.
to: stop do: aBlock Create an Interval from the receiver up to the argument, stop, incrementing by 1. Evaluate the argument, aBlock, for each element of the Interval.
to: stop by: step do: aBlock Create an Interval from the receiver up to the argument, stop, incrementing by step. Evaluate the argument, aBlock, for each element of the Interval.
Number instance protocol


Thus if we evaluate

a  0.
10 to: 100 by: 10 do: [ :each | a  a + each]


the final value of a will be 550.


If a is the array #('one" 'two" 'three' "four' "five'), then each element of the array can be accessed by indices that are in the interval from 1 to the size of the array. The following expression changes each element so that only the initial characters are kept.

1 to: a size do: [:index | a at: index put: ((a at: index) at: 1)]


The resulting array is #('o' 't' 't' "f' "f'). Note that, like an array, elements of a string can be accessed using the messages at: and at:put:. Messages to objects like strings and arrays are detailed in Chapters 9 and 10.


Classes Float and Fraction

The classes Float and Fraction provide two representations of non-integral values. Floats are representations of real numbers that may be approximate; they represent about 6 digits of accuracy with a range between plus or minus 10 raised to the power plus or minus 32. Some examples are

8.0
13.3
0.3
2.5e6
1.27e - 30
-12.987654e12


Fractions are representations of rational numbers that will always be exact. All arithmetic operations on a Fraction answer a reduced fractional result.


Instances of Float can be created by literal notation in methods (for example, 3.14159) or as the result of an arithmetic operation, one argument of which is another Float.


Instances of Fraction can be created as a result of an arithmetic operation if one of the operands is a Fraction and the other is not a Float. (If it were a Float, the result would be a Float since the generality number of Float is higher than that of Fraction). Instances of Fraction can also be created when the mathematical division operation (/) is performed on two Integers and the result is not integral. In addition, class protocol for Fraction supports sending a message of the form numerator: numInteger denominator: denInteger in order to create an instance.


Float responds to the message pi to return the corresponding constant. It adds truncation and round off protocol to return the fraction and integer parts of the receiver (fractionPart and integerPart), and it adds converting protocol to convert the receiver to a Fraction (asFraction). Similarly class Fraction adds converting protocol to convert the receiver to a Float (asFloat).


Integer Classes

Class Integer adds protocol particular to integral numbers. It has three subclasses. One is class SmallInteger, which provides a space-economical representation for a substantial range of integral values that occur frequently in counting and indexing. The representation limits the range to a little less than the magnitudes representable by a single machine word. Large integers, which are represented by instances of LargePositiveInteger or kargeNegativeInteger depending on the sign of the integer, do not have a limit to their magnitude. The cost in providing the generality of large integers is longer computation time. Thus if the result of an arithmetic operation on a large integer is representable as a small integer, it will in fact be a small integer.


In addition to the messages inherited from the class Number, class Integer adds converting protocol (asCharacter, asFloat and asFraction), further printing (printOn: aStream base: b, radix: baseInteger), and enumeratingprotocol. Thus 8 radix: 2 is 2r1000.


For enumerating, it is possible to evaluate a block repetitively an integral number of times using the message timesRepeat: aBlock. Take as an example

a  1.
10 timesRepeat: [a  a + a]


where the block has no arguments. The resulting value of a is 2^10, or 1024.


Class Integer provides factorization and divisibility protocol not specified for numbers in general.

factorization and divisibility
factorial Answer the factorial of the receiver. The receiver must not be less than 0.
gcd: anInteger Answer the greatest common divisor of the receiver and the argument, anInteger.
lcm: anInteger Answer the least common multiple of the receiver and the argument, anInteger.
Integer instance protocol


Examples are

expression result
3 factorial 6
55 gcd: 30 5
6 lcm: 10 30


In addition to the numerical properties of integers, some algorithms make use of the fact that integers can be interpreted as a sequence of bits. Thus protocol for bit manipulation is specified in Integer.

bit manipulation
allMask: anInteger Treat the argument anInteger as a bit mask. Answer whether all of the bits that are 1 in anInteger are 1 in the receiver.
anyMask: anInteger Treat the argument anInteger as a bit mask. Answer whether any of the bits that are 1 in anInteger are 1 in the receiver.
noMask: anInteger Treat the argument anInteger as a bit mask. Answer whether none of the bits that are 1 in anInteger are 1 in the receiver.
bitAnd: anInteger Answer an Integer whose bits are the logical and of the receiver's bits and those of the argument anInteger.
bitOr: anInteger Answer an Integer whose bits are the logical or of the receiver's bits and those of the argument anInteger.
bitXor: anInteger Answer an Integer whose bits are the logical xor of the receiver's bits and those of the argument anInteger.
bitAt: index Answer the bit (0 or 1) at position index of the receiver.
bitInvert Answer an Integer whose bits are the complement of the receiver.
highBit Answer the index of the high order bit of the binary representation of the receiver.
bitShift: anInteger Answer an Integer whose value (in two's-complement representation) is the receiver's value (in two's-complement representation) shifted left by the number of bits indicated by the argument, anInteger. Negative arguments shift right. Zeros are shifted in from the right in left shifts. The sign bit is extended in right shifts.
Integer instance protocol


Some examples follow. Note that the default radix for printing an Integer is 10.

expression result
2r111000111000111 29127
2r101010101010101 21845
2r101000101000101 20805
2r000111000111000 3640
29127 allMask: 20805 true
29127 allMask: 21845 false
29127 anyMask: 21845 true
29127 noMask: 3640 true
29127 bitAnd: 3640 0
29127 bitOr: 3640 32767
32767 radix: 2 2r111111111111111
29127 bitOr: 21845 30167
30167 radix: 2 2r111010111010111
3640 bitShift: 1 7280


Class Random: A Random Number Generator

Many applications require random choices of numbers. Random numbers are useful, for example, in statistical applications and data encryption algorithms. Class Random is a random number generator that is included in the standard Smalltalk-80 system. It provides a simple way of obtaining a sequence of random numbers that will be uniformly distributed over the interval between, but not including, 0.0 and 1.0.


An instance of class Random maintains a seed from which the next random number is generated. The seed is initialized in a pseudo-random way. An instance of Random is sent the message next whenever a new random number is desired.

A random number generator can be created with the expression

rand  Random new


The expression

rand next


can then be evaluated whenever a new random number is needed. The response is a number (Float) between 0.0 and 1.0.


The implementation of next is based on Lehmer's linear congruential method as presented in Knuth, Volume 1 [D. E. Knuth, The Art of Computer Programming: Fundamental Algorithms, Volume 1, Reading, Mass: Addison Wesley, 1968].

next
| temp |
    "Lehmer's linear congruential method with modulus m = 2 raisedTo: 16, a = 27181 odd, and 5 = a \\ 8, c = 13849 odd, and c / m approximately 0.21132"
    [seed  13849 + (27181 * seed) bitAnd: 8r177777.
    temp  seed / 65536.0.
    temp = 0] whileTrue.
    temp


It is also possible to send an instance of class Random the messages next: anInteger, to obtain an OrderedCollection of anInteger number of random numbers, and nextMatchFor: aNumber, to determine whether the next random number is equal to aNumber.


Suppose we want to select one of 10 integers, 1, ..., 10, using the random number generator rand. The expression to be evaluated is

(rand next * 10) truncated + 1


That is,

expression result
rand next a random number between 0 and 1
rand next * 10 a random number between 0 and 10
(rand next * 10) truncated an integer > = 0 and < = 9
(rand next * 10) truncated + 1 an integer > = 1 and < = 10


Notes