So far we've only talked about signed single-length numbers. In this chapter we'll introduce unsigned numbers and double-length numbers, as well as a whole passel of new operators to go along with them.
This chapter is divided in two sections:
For beginners--this section explains how a computer looks at numbers and exactly what is meant by the terms signed or unsigned and by single-length or double-length.
For everyone--this section continues our discussion of Forth for beginners and experts alike, and explains how Forth handles signed and unsigned, single- and double-length numbers.
All digital computers store numbers in binary form. In Forth, the stack is (normally) thirty-two bits wide (a "bit" is a "binary digit"). Below is a view of thirty-two bits, showing the value of each bit:
If every bit were to contain a 1, the total would be 4294967295. Thus in 32 bits we can express any value between 0 and 4294967295. Because this kind of number does not let us express negative values, we call it an "unsigned number." We have been indicating unsigned numbers with the letter "u" in our tables and stack notations.
But what about negative numbers? In order to be able to express a positive or negative number, we need to sacrifice one bit that will essentially indicate sign. This bit is the one at the far left, the "high-order bit." In 31 bits we can express a number as high as 2147483647. When the sign bit contains 1, then we can go an equal distance back into the negative numbers. Thus within 32 bits we can represent any number from -2147483648 to +2147483647. This should look familiar to you as the range of a single-length number, which we have been indicating with the letter "n."
Before we leave you with any misconceptions, we'd better clarify the way negative numbers are represented. You might think that it's a simple matter of setting the sign bit to indicate whether a number is positive or negative, but it doesn't work that way.
To explain how negative numbers are represented, let's return to decimal notation and examine a counter such as that found on many WWW internet pages.
Let's say the counter has three digits, not five. As more people visit the page, the counter-wheels turn and the number increases. Starting once again with the counter at 0, now imagine you badly regret having visited the page and could "un-visit" it by rolling the counter wheels backward. The first number you see is 999, which is, in a sense, the same as -1. The next number will be 998, which is the same as -2, and so on.
The representation of signed numbers in a computer is similar.
Starting with the number
0000,0000,0000,0000,0000,0000,0000,0000and going backwards one number, we get
1111,1111,1111,1111,1111,1111,1111,1111 (thirty-two ones)which stands for 4294967295 in unsigned notation as well as for -1 in signed notation. The number
1111,1111,1111,1111,1111,1111,1111,1110which stands for 4294967294 in unsigned notation, represents -2 in signed notation.
Here's a chart that shows how a binary number on the stack can be used either as an unsigned number or as a signed number:
To show how this works, let's take a very simple problem:
2 -1Subtracting one from two is the same as adding two plus negative one. In single-length binary notation, the two looks like this:
0000,0000,0000,0000,0000,0000,0000,0010while negative-one looks like this:
1111,1111,1111,1111,1111,1111,1111,1111
The computer adds them up the same way we would on paper; that is when the total of any column exceeds one, it carries a one into the next column. The result looks like this:
0000,0000,0000,0000,0000,0000,0000,0010 +1111,1111,1111,1111,1111,1111,1111,1111 10000,0000,0000,0000,0000,0000,0000,0001
As you can see, the computer had to carry a one into every column all the way across, and ended up with a one in the thirty-third place. But since the stack is only thirty-two bits wide, the result is simply
0000,0000,0000,0000,0000,0000,0000,0001which is the correct answer, one.
We needn't explain how the computer converts a positive number to negative, but we will tell you that the process is called "two's complementing."
While we're on the subject of how a computer performs certain mathematical operations, we'll explain what is meant by the mysterious phrases back in Chap. 5: "arithmetic left shift" and "arithmetic right shift."
A Forth Instant Replay
2* ( n -- n*2 ) Multiplies by two (arithmetic left shift) 2/ ( n -- n/2 ) Divides by two (arithmetic right shift) LSHIFT ( n u -- n*2^u ) Logical left shift over u positions RSHIFT ( n -- n/2^-u ) Logical right shift over u positions
To illustrate, let's pick a number, say six, and write it in binary form:
0000,0000,0000,0000,0000,0000,0000,0110(4+2). Now let's shift every digit one place to the left, and put a zero in the vacant place in the one's column.
0000,0000,0000,0000,0000,0000,0000,1100
This is the binary representation of twelve (8+4), which is exactly double the original number. This works in all cases, and it also works in reverse. If you shift every digit one place to the right and fill the vacant digit with a zero, the result will always be half of the original value.
In arithmetic shift, the sign bit does not get shifted. This means that a positive number will stay positive and a negative number will stay negative when you divide or multiply it by two.
When the high-order bit shifts with all the other bits, the term is "logical shift." In Forth you can do a logical shift of up to 32 places with the words LSHIFT and RSHIFT.
The important thing for you to know is that most computers can shift digits much more quicky than they can go through all the folderol of normal division or multiplication. When speed is critical, it's much better to say
2*than
2 *and it may even be better to say
2* 2* 2*than
8 *depending on your particular model of computer, but this topic is getting too technical for right now.
A double-length number is just what you probably expected it would be: a number that is represented in sixty-four bits instead of thirty-two. Signed double-length numbers have a range of +/-18,446,744,073,709,551,615.
In Forth, a double-length number takes the place of two single-length numbers on the stack. Operators like 2DUP are useful either for double-length numbers or for pairs of single-length numbers.
One more thing we should explain: to the non-Forth-speaking computer world, the term "double word" means a 32-bit value, or four bytes. But in Forth, "word" means a defined command. So in order to avoid confusion, Forth programmers refer to a 32-bit value as a "cell." A double-length number requires two cells.
As you get more involved in programming, you'll need to employ other number bases besides decimal and binary, particularly hexadecimal (base 16) and octal (base 8). Since we'll be talking about these two number bases later on in this chapter, we think you might like an introduction now.
Computer people began using hexadecimal and octal numbers for one main reason: computers think in binary and human beings have a hard time reading long binary numbers. For people, it's much easier to convert binary to hexadecimal than binary to decimal, because sixteen is an even power of two, while ten is not. The same is true with octal. So programmers usually use hex or octal to express the binary numbers that the computer uses for things like addresses and machine codes. Hexadecimal (or simply "hex") looks strange at first since it uses the letters A through F.
Decimal | Binary | Hexadecimal |
---|---|---|
0 | 0000 | 0 |
1 | 0001 | 1 |
2 | 0010 | 2 |
3 | 0011 | 3 |
4 | 0100 | 4 |
5 | 0101 | 5 |
6 | 0110 | 6 |
7 | 0111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | A |
11 | 1011 | B |
12 | 1100 | C |
13 | 1101 | D |
14 | 1110 | E |
15 | 1111 | F |
Let's take a single-length binary number:
00000000000000000111101110100001
To convert this number to hexadecimal, we first subdivide it into eight units of four bits each:
| 0000 | 0000 | 0000 | 0000 | 0111 | 1011 | 1010 | 0001 |then convert each 4-bit unit to its hex equivalent:
|0|0|0|0|7|B|A|1|or simply 7BA1.
Octal numbers use only the numerals 0 through 7. Because nowadays most computers use hexadecimal representation, we'll skip an octal conversion example.
We'll have more on conversions in the section titled "Number Conversions" later in this chapter.
If the computer uses binary notation to store numbers, how does it store characters and other symbols? Binary, again, but in a special code that was adopted as an industry standard many years ago. The code is called the American Standard Code for Information Interchange, usually abbreviated ASCII.
Table 7-1 shows each ASCII character in the system, its ISO 646-1983, ISO 7-bit coded characterset for information interchange, International Reference Version equivalent (IRV), and its hexadecimal form.
The characters in the first column (ASCII codes 0-1F hex) are called "control characters" because
they indicate that the terminal or computer is supposed to do something like ring its bell,
backspace, start a new line, etc. The remaining characters are called "printing characters" because
they produce visible characters including letters, the numerals zero through nine, all available
symbols and even the blank space (hex 20). The only exception is DEL
(hex 7F) which is a signal
to the computer to ignore the last character sent.
In Chap. 1 we introduced the word EMIT. EMIT takes an ASCII code on the stack and sends it to the terminal so that the terminal will print it as a character. For example,
65 EMIT A ok 66 EMIT B oketc. (We're using the decimal, rather than the hex, equivalent because that's what your computer is most likely expecting right now.)
Why not test EMIT on every printing character, "automatically"?
: PRINTABLES 127 32 DO I EMIT SPACE LOOP ;
PRINTABLES will emit every printable character in the ASCII set; that is, the characters from decimal 32 to decimal 126. (We're using the ASCII codes as our DO loop index.)
PRINTABLES ! " # $ % & ' ( ) * + , - . / ... ok
Hex ASCII | Hex ASCII | Hex IRV ASCII | Hex IRV ASCII | Hex IRV ASCII | Hex IRV ASCII | Hex IRV ASCII | Hex IRV ASCII |
---|---|---|---|---|---|---|---|
00 NUL | 10 DLE | 20 | 30 0 0 | 40 @ @ | 50 P P | 60 ` ` | 70 p p |
01 SOH | 11 DC1 | 21 ! ! | 31 1 1 | 41 A A | 51 Q Q | 61 a a | 71 q q |
02 STX | 12 DC2 | 22 " " | 32 2 2 | 42 B B | 52 R R | 62 b b | 72 r r |
03 ETX | 13 DC3 | 23 # # | 33 3 3 | 43 C C | 53 S S | 63 c c | 73 s s |
04 EOT | 14 DC4 | 24 - $ | 34 4 4 | 44 D D | 54 T T | 64 d d | 74 t t |
05 ENQ | 15 NAK | 25 % % | 35 5 5 | 45 E E | 55 U U | 65 e e | 75 u u |
06 ACK | 16 SYN | 26 & & | 36 6 6 | 46 F F | 56 V V | 66 f f | 76 v v |
07 BEL | 17 ETB | 27 ' ' | 37 7 7 | 47 G G | 57 W W | 67 g g | 77 w w |
08 BS | 18 CAN | 28 ( ( | 38 8 8 | 48 H H | 58 X X | 68 h h | 78 x x |
09 HT | 19 EM | 29 ) ) | 39 9 9 | 49 I I | 59 Y Y | 69 i i | 79 y y |
0A LF | 1A SUB | 2A * * | 3A : : | 4A J J | 5A Z Z | 6A j j | 7A z z |
0B VT | 1B ESC | 2B + + | 3B ; ; | 4B K K | 5B [ [ | 6B k k | 7B { { |
0C FF | 1C FS | 2C , , | 3C < < | 4C L L | 5C \ \ | 6C l l | 7C | | |
0D CR | 1D GS | 2D - - | 3D = = | 4D M M | 5D ] ] | 6D m m | 7D } } |
0E SM | 1E RS | 2E . . | 3E > > | 4E N N | 5E ^ ^ | 6E n n | 7E ~ ~ |
0F SI | 1F US | 2F / / | 3F ? ? | 4F O O | 5F | 6F o o |
Beginners may be interested in some of the control characters as well. For instance, try this:
7 EMIT ok
You should have heard some sort of beep, which is the video terminal's version of the mechanical printer's "typewriter bell."
Other control characters that are good to know include the following:
name | operation | decimal equivalent |
---|---|---|
BS | backspace | 8 |
LF | line feed | 10 |
CR | carriage return | 13 |
Experiment with these control characters, and see what they do.
ASCII is designed so that each character can be represented by one byte. The tables in this book use the letter "c" to indicate a byte value that is being used as a coded ASCII character.
The words AND and OR (which we introduced in Chap. 4) use "bit logic"; that is, each bit is treated independently, and there are no "carries" from one bit-place to the next. For example, let's see what happens when we AND these two binary numbers:
0000,0000,0000,0000,0000,0000,1111,1111 0000,0000,0000,0000,0110,0101,1010,0010 AND 0000,0000,0000,0000,0000,0000,1010,0010
For any result-bit to be "1," the respective bits in both arguments must be "1." Notice in this example that the argument on top contains all zeroes in the high-order bytes and all ones in the low-order byte. The effect on the second argument in this example is that the low-order eight bits are kept but the high-order twenty-four bits are all set to zero. Here the first argument is being used as a "mask," to mask out the high-order bytes of the second argument.
The word OR also uses bit logic. For example,
1000,0100,0010,0001,1000,1001,0000,1001 0110,0110,0110,0110,0000,0011,1100,1000 OR 1110,0110,0110,0111,1000,1011,1100,1001
A "1" in either argument produces a "1" in the result. Again, each column is treated separately, with no carries.
By clever use of masks, we could even use a 32-bit value to hold 32 separate flags. For example, we could find out whether this bit
1000,0100,0010,0001,1000,1001,0000,1001 ^is "1" or "0" by masking out all other flags, like this:
1000,0100,0010,0001,1000,1001,0000,1001 0000,0000,0000,0000,1000,0000,0000,0000 AND 0000,0000,0000,0000,1000,0000,0000,0000
Since the bit was "1," the result is "true." Had it been "0," the result would have been "0" or "false."
We could set the flag to "0" without affecting the other flags by using this technique:
1000,0100,0010,0001,1000,1001,0000,1001 1111,1111,1111,1111,0111,1111,1111,1111 AND 1000,0100,0010,0001,0000,1001,0000,1001 ^
We used a mask that contains all "1"s except for the bit we wanted to set to "0." We can set the same flag back to "1" by using this technique:
1000,0100,0010,0001,0000,1001,0000,1001 0000,0000,0000,0000,1000,0000,0000,0000 OR 1000,0100,0010,0001,1000,1001,0000,1001 ^
Back in Chap. 1 we introduced the word NUMBER. If the word FIND can't find an incoming string in the dictionary, it hands it over to the word NUMBER. NUMBER then attempts to convert the string into a number expressed in binary form. If NUMBER succeeds, it pushes the binary equivalent onto the stack.
NUMBER does not do any range-checking. Because of this, NUMBER can convert either signed or unsigned numbers.
For instance, if you enter any number between 2147483648 and 4294967295, NUMBER will convert it as an unsigned number. Any value between -2147483648 and -1 will be stored as a two's-complement integer.
This is an important point: the stack can be used to hold either signed or unsigned numbers. Whether a binary value is interpreted as signed or unsigned depends on the operators that you apply to it. You decide which form is better for a given situation, then stick to your choice.
We've introduced the word ., which prints a value on the stack as a signed number:
4294967295 . -1 ok
The word U. prints the binary representation as an unsigned number:
4294967295 U. 4294967295 ok
U. | ( u -- ) | Prints the unsigned single-length number, followed by a space. |
In this book the letter "n" signifies signed single-length numbers, while the letter "u" signifies unsigned single-length numbers. (We've already introduced U.R, which prints an unsigned single-length number right-justified within a given column width.)
Here is a table of additional words that use unsigned numbers:
UM* | ( u1 u2 -- ud ) | Multiplies two single-length numbers. Returns a double-length result. All values are unsigned. | |
UM/MOD | ( ud u1 -- u2 u3 ) | Divides a double-length by a single-length number. Returns a single-length quotient u2 and remainder u3. All values are unsigned. | |
U< | ( u1 u2 -- f ) | Leaves true if u1 < u2, where both are treated as single-length unsigned integers. |
When you first start Forth, all number conversions use base ten (decimal), for both input and output.
You can easily change the base by executing one of the following commands:
HEX | ( -- ) | Sets the base to sixteen. |
OCTAL | ( -- ) | Sets the base to eight (available on some systems). |
DECIMAL | ( -- ) | Returns the base to ten. |
When you change the number base, its stays changed until you change it again. So be sure to declare DECIMAL as soon as you're done with another number base.
These commands make it easy to do number conversions in "calculator style."
For example, to convert decimal 100 into hexadecimal, enter
DECIMAL 100 HEX . 64 ok
To convert hex F into decimal (remember you are already in hex), enter
0F DECIMAL . 15 ok
Make it a habit, starting right now, to precede each hexadecimal value with a zero, as in
0A 0B 0F
This practice avoids mix-ups with possibly predefined words as DEADBEEF, BAD, DEC etc.
Double-length numbers provide a range of +/-18,446,744,073,709,551,615. ANS Forth systems support double-length numbers to some degree. The way to enter a double-length number onto the stack (whether from the keyboard or from a file) is to punctuate it with one of these five punctuation marks:
, . / - :
For example, when you type
200,000NUMBER recognizes the comma as a signal that this value should be converted to double-length. NUMBER then pushes the value onto the stack as two consecutive "cells" (cell is the Forth term for single-length), the high order cell on top.
The Forth word D. prints a double-length number without any punctuation.
D. | ( d -- ) | Prints the signed double-length number, followed by one space. |
In this book, the letter "d" stands for a double-length signed integer.
For example, having entered a double-length number, if you were now to execute D., the computer would respond:
D. 200000 ok
Notice that all of the following numbers are converted in exactly the same way:
12345. D. 12345 ok 123.45 D. 12345 ok 1-2345 D. 12345 ok 1/23/45 D. 12345 ok 1:23:45 D. 12345 ok
But this is not the same:
-12345because this value would be converted as a negative, single-length number. (This is the only case in which a hyphen is interpreted as a minus sign and not as punctuation.)
In the next section we'll show you how to define your own equivalents to D. which will print whatever punctuation you want along with the number.
$200.00 12/31/80 372-8493 6:32:59 98.6
The above numbers represent the kinds of output you can create by defining your own "number-formatting words" in Forth. This section will show you how.
The simplest number-formatting definition we could write would be
: UD. <# #S #> TYPE ;
UD. will print an unsigned double-length number. The words <# and #> (respectively pronounced bracket-number and number-bracket) signify the beginning and the end of the number-conversion process. In this definition, the entire conversion is being performed by the single word #S (pronounced numbers). #S converts the value on the stack into ASCII characters. It will only produce as many digits as are necessary to represent the number; it will not produce leading zeroes. But it always produces at least one digit, which will be zero if the value was zero. For example:
12,345 UD. 12345ok 12. UD. 12ok 0. UD. 0ok
The word TYPE prints the characters that represent the number at your terminal. Notice that there is no space between the number and the "ok." To get a space, you would simply add the word SPACE, like this:
: UD. <# #S #> TYPE SPACE ;
Now let's say we have a phone number on the stack, expressed as a double-length unsigned integer. For example, we may have typed in:
372-8493(remember that the hyphen tells NUMBER to treat this as a double-length value). We want to define a word which will format this value back as a phone number. Let's call it .PH# (for "print the phone number") and define it thus:
: .PH# <# # # # # [CHAR] - HOLD #S #> TYPE SPACE ;
Our definition of .PH# has everything that UD. has, and more. The Forth word # (pronounced number) produces a single digit only. A number-formatting definition is reversed from the order in which the number will be printed, so the phrase
# # # #produces the right-most four digits of the phone number.
Now it's time to insert the hyphen. Using [CHAR] we can get the code value of this ASCII character on the stack. The Forth word HOLD takes this ASCII code and inserts it into the formatted number character string.
We now have three digits left. We might use the phrase
# # #but it is easier to simply use the word #S, which will automatically convert the rest of the number for us.
If you are more familiar with ASCII codes represented in hexadecimal form, you can use this definition instead:
HEX : .PH# <# # # # # 02D HOLD #S #> TYPE SPACE ; DECIMAL
Either way, the compiled definition will be exactly the same.
Now let's format an unsigned double-length number as a date, in the following form:
6/15/03
Here is the definition:
: .DATE <# # # [CHAR] / HOLD # # [CHAR] / HOLD #S #> TYPE SPACE ;
Let's follow the above definition, remembering that it is written in reverse order from the output. The phrase
# # [CHAR] / HOLDproduces the right-most two digits (representing the year) and the right-most slash. The next occurence of the same phrase produces the middle two digits (representing the day) and the left-most slash. Finally #S produces the left-most two digits (representing the month).
We could have just as easily defined
# # [CHAR] / HOLDas its own word and used this word twice in the definition of .DATE.
Since you have control over the conversion process, you can actually convert different digits in different number bases, a feature which is useful in formatting such numbers as hours and minutes. For example, let's say that you have the time in seconds on the stack, and you want a word which will print hh:mm:ss. You might define it this way:
: SEXTAL 6 BASE ! ; : :00 # SEXTAL # DECIMAL [CHAR] : HOLD ; : SEC <# :00 :00 #S #> TYPE SPACE ;
We will use the word :00 to format the seconds and minutes. Both seconds and minutes are modulo-60, so the right digit can go as high as nine, but the left digit can only go up to five. Thus in the definition of :00 we convert the first digit (the one on the right) as a decimal number, then go into "sextal" (base 6) and convert the left digit. Finally, we return to decimal and insert the colon character. After :00 converts the seconds and the minutes, #S converts the remaining hours.
For example, if we had 4500 seconds on the stack, we would get
4500. SEC 1:15:00 ok
Table 7-2 summarizes the Forth words that are used in number formatting. (Note the "KEY" at the bottom, which serves as a reminder of the meanings of "n," "d," etc.)
<# | Begins the number conversion process. Expects the unsigned double-length number on the stack. | |
# | Converts one digit and puts it into an output character string. # always produces a digit--if you're out of significant digits, you'll still get a zero for every #. | |
#S | Converts the number until the result is zero. Always produces at least one digit (0 if the value is zero). | |
c HOLD | Inserts, at the current position in the character string being formatted, a character whose ASCII value is on the stack. HOLD (or a word which uses HOLD) must be used between <# and #>. | |
SIGN | Inserts a minus sign in the output string if the top of stack is negative. Usually used with ROT immediately before #> for a leading minus sign. | |
#> | Completes number conversion by leaving the character count and address on the stack (these are the appropriate arguments for TYPE). |
phrase | stack | type of arguments |
---|---|---|
<# ... #> | ( ud -- addr u ) | double-length unsigned |
<# ... ROT SIGN #> | ( n |d| -- addr u ) | double-length signed (where n is the high-order cell of d and |d| is the absolute value of d). |
n, n1, ... | single-length signed |
d, d1, ... | double-length signed |
u, u1, ... | single-length unsigned |
addr | address |
c | ASCII character value |
So far we have formatted only unsigned double-length numbers. The <#...#> form expects only unsigned double-length numbers, but we can use it for other types of numbers by making certain arrangements on the stack.
For instance, let's look at a simplified version of the system definition of D. (which prints a signed double-length number):
: D. TUCK DABS <# #S ROT SIGN #> TYPE SPACE ;
The phrase ROT SIGN inserts a minus string in the character string if the third number on the stack is negative. We have prepared for this test by putting a copy of the high-order cell (the one with the sign bit) at the bottom of the stack, by using the word TUCK.
Because <# expects only unsigned double-length numbers, we must take the absolute value of our double-length signed number, with the word DABS. We now have the proper arrangement of arguments on the stack for the <#...#> phrase. In some cases, such as accounting, we may want a negative number to be written
12345-in which case we would place the phrase ROT SIGN at the left side of our <#...#> phrase, like this:
<# ROT SIGN #S #>
$ |
: .$ TUCK DABS <# # # [CHAR] . HOLD #S ROT SIGN [CHAR] $ HOLD #> TYPE SPACE ;
Let's try it:
2000.00 .$ $2000.00 okor even
2,000.00 .$ $2000.00 ok
We recommend that you save .$, since we'll be using it in some future examples.
You can also write special formats for single-length numbers. For example, if you want to use an unsigned single-length number, simply put a zero on the stack before the word <#. This effectively changes the single-length number into a double-length number which is so small that it has nothing (zero) in the high-order cell.
To format a signed single-length number, again you must supply a zero as a high-order cell. But you must also leave a copy of the signed number in the third stack position for ROT SIGN, and you must leave the absolute value of the number in the second stack position. The phrase to do all this is
DUP ABS 0
Here are the "set-up" phrases that are needed to print various kinds of numbers:
Number to be printed | Precede <# by |
---|---|
double-length, unsigned | (nothing needed) |
63-bit, plus sign | TUCK DABS (to save the sign in the third stack position for ROT SIGN) |
single-length, unsigned | 0 (to give a dummy high-order part) |
31-bit, plus sign | DUP ABS 0 (to save the sign) |
Here is a list of double-length math operators:
D.R | ( d width -- ) | Prints the signed double-length number, right-justified within the field width. | |
D+ | ( d1 d2 -- d-sum ) | Adds two double-length numbers. | |
D- | ( d1 d2 -- d-diff ) | Subtracts two double-length numbers (d1-d2). | |
DNEGATE | ( d -- -d ) | Changes the sign of a double-length number. | |
DMAX | ( d1 d2 -- d-max ) | Returns the maximum of two double-length numbers (d1-d2). | |
DMIN | ( d1 d2 -- d-min ) | Returns the minimum of two double-length numbers (d1-d2). | |
D= | ( d1 d2 -- f ) | Returns true if d1 and d2 are equal. | |
D0= | ( d -- f ) | Returns true if d is zero. | |
D< | ( d1 d2 -- f ) | Returns true if d1 is less than d2. | |
DU< | ( ud1 ud2 -- f ) | Returns true if ud1 is less than ud2. Both numbers are unsigned. |
The initial "D" signifies that these operators may only be used for double-length operations, whereas the initial "2," as in 2SWAP and 2DUP, signifies that these operators may be used either for double-length numbers or for pairs of numbers.
Here's an example using D+:
200,000 300,000 D+ D. 500000 ok
Here's a table of very useful Forth words which operate on a combination of single- and double-length numbers:
M+ | ( d n -- d-sum ) | Adds a double-length number to a single-length number. Returns a double-length result. |
SM/REM | ( d n1 -- n2 n3 ) | Divide d1 by n1, giving the symmetric quotient n3 and the remainder n2. Input and output stack arguments are signed. An ambiguous condition exists if n1 is zero or if the quotient lies outside the range of a single-cell signed integer. |
FM/MOD | ( d n1 -- n2 n3 ) | Divide d1 by n1, giving the floored quotient n3 and the remainder n2. Input and output stack arguments are signed. An ambiguous condition exists if n1 is zero or if the quotient lies outside the range of a single-cell signed integer. |
M* | ( n1 n2 -- d-prod ) | Multiplies two single-length numbers. Returns a double-length result. All values are signed. |
M*/ | ( d +n1 n2 -- d-result ) | Multiplies a double-length number by a single-length number and divides the triple-length result by a single-length number (d*n/n). Returns a double-length result. All values are signed. |
Here's an example using M+:
200,000 7 M+ D. 200007 ok
Or, using M*/, we can redefine our earlier version of % so that it will accept a double-length argument:
: % 100 M*/ ;as in
200.50 15 % D. 3007 ok
If you have loaded the definition of .$ we gave in the last Handy Hint, you can enter
200.50 15 % .$ $30.07 ok
We can redefine our earlier definition of R% to get a rounded double-length result, like this:
: R% 10 M*/ 5 M+ 10 SM/REM NIP ;then
987.65 15 R% .$ $30.08 ok
Notice that M*/ is the only ready-made Forth word which performs multiplication on a double-length argument. To multiply 200,000 by 3, for instance, we must supply a "1" as a dummy denominator:
200,000 3 1 M*/ D. 600000 oksince
3 1is the same as 3.
M*/ is also the only ready-made Forth word that performs division with a double-length result. So to divide 200,000 by 4, for instance, we must supply a "1" as a dummy numerator:
200,000 1 4 M*/ D. 50000 ok
When a definition contains a number, such as
: SCORE-MORE 20 + ;the number is compiled into the dictionary in binary form, just as it looks on the stack.
The number's binary value depends on the number base at the time you compile the definition. For example, if you were to enter
HEX : SCORE-MORE 14 + ; DECIMALthe dictionary definition would contain the hex value 14, which is the same as the decimal value 20 (16+4). Henceforth, SCORE-MORE will always add the equivalent of the decimal 20 to the value on the stack, regardless of the current number base.
If, on the other hand, you were to put the word HEX inside the definition, then you would change the number base when you execute the definition.
For example, if you were to define:
DECIMAL : EXAMPLE HEX 20 . DECIMAL ;the number would be compiled as the binary equivalent of decimal 20, since DECIMAL was current at compilation time.
At execution time, here's what happens:
EXAMPLE 14 ok
The number is output in hexadecimal.
For the record, a number that appears inside a definition is called a "literal." (Unlike the words in the rest of the definition which allude to other definitions, a number must be taken literally.)
Here is a list of the Forth words we've covered in this chapter:
U. | ( u -- ) | Prints the unsigned single-length number, followed by one space. |
UM* | ( u1 u2 -- ud ) | Multiplies two single-length numbers. Returns a double-length result. All values are unsigned. |
UM/MOD | ( ud u1 -- u2 u3 ) | Divides a double-length by a single-length number. Returns a single-length quotient and remainder. All values are unsigned. |
U< | ( u1 u2 -- f ) | Leaves true if u1 < u2, where both are treated as single-length unsigned integers. |
HEX | ( -- ) | Sets the base to sixteen. |
OCTAL | ( -- ) | Sets the base to eight (available on some systems). |
DECIMAL | ( -- ) | Returns the base to ten. |
<# | Begins the number conversion process. Expects the unsigned double-length number on the stack. |
# | Converts one digit and puts it into an output character string. # always produces a digit--if you're out of significant digits, you'll still get a zero for every #. |
#S | Converts the number until the result is zero. Always produces at least one digit (0 if the value is zero). |
c HOLD | Inserts, at the current position in the character string being formatted, a character whose ASCII value is on the stack. HOLD (or a word which uses HOLD) must be used between <# and #>. |
SIGN | Inserts a minus sign in the output string if the top of stack is negative. Usually used with ROT immediately before #> for a leading minus sign. |
#> | Completes number conversion by leaving the character count and address on the stack (these are the appropriate arguments for TYPE). |
phrase | stack | type of arguments |
---|---|---|
<# ... #> | ( d -- addr u ) | double-length unsigned |
<# ... ROT SIGN #> | ( n |d| -- addr u ) | double-length signed (where n is the high-order cell of d and |d| is the absolute value of d). |
D+ | ( d1 d2 -- d-sum ) | Adds two double-length numbers. |
D- | ( d1 d2 -- d-diff ) | Subtracts two double-length numbers (d1-d2). |
DNEGATE | ( d -- -d ) | Changes the sign of a double-length number. |
DMAX | ( d1 d2 -- d-max ) | Returns the maximum of two double-length numbers (d1-d2). |
DMIN | ( d1 d2 -- d-min ) | Returns the minimum of two double-length numbers (d1-d2). |
D= | ( d1 d2 -- f ) | Returns true if d1 and d2 are equal. |
D0= | ( d -- f ) | Returns true if d is zero. |
D< | ( d1 d2 -- f ) | Returns true if d1 is less than d2. |
DU< | ( ud1 ud2 -- f ) | Returns true if ud1 is less than ud2. Both numbers are unsigned. |
D.R | ( d width -- ) | Prints the signed double-length number, right-justified within the field width. |
M+ | ( d n -- d-sum ) | Adds a double-length number to a single-length number. Returns a double-length result. |
SM/REM | ( d n1 -- n2 n3 ) | Divide d1 by n1, giving the symmetric quotient n3 and the remainder n2. Input and output stack arguments are signed. An ambiguous condition exists if n1 is zero or if the quotient lies outside the range of a single-cell signed integer. |
FM/MOD | ( d n1 -- n2 n3 ) | Divide d1 by n1, giving the floored quotient n3 and the remainder n2. Input and output stack arguments are signed. An ambiguous condition exists if n1 is zero or if the quotient lies outside the range of a single-cell signed integer. |
M* | ( n1 n2 -- d-prod ) | Multiplies two single-length numbers. Returns a double-length result. All values are signed. |
M*/ | ( d +n1 n2 -- d-result ) | Multiplies a double-length number by a single-length number and divides the triple-length result by a single-length number (d*n/n). Returns a double-length result. All values are signed. |
n, n1, ... | single-length signed |
d, d1, ... | double-length signed |
u, u1, ... | single-length unsigned |
addr | address |
c | ASCII character value |
| |
---|---|
Arithmetic left and right shift | the process of shifting all bits in a number, except the sign bit, to the left or right, in effect doubling or halving the (assumed signed) number, respectively. |
Logical left and right shift | the process of shifting all bits in a number, including the sign bit, to the left or right, in effect doubling or halving the (assumed unsigned) number, respectively. |
ASCII | a standarized system of representing input/output characters as byte values. Acronym for American Standard Code for Information Interchange. (Pronounced ask-key) |
Binary | number base 2. |
Byte | the standard term for an 8-bit value. |
Cell | the Forth term for a single-cell value. |
Decimal | number base 10. |
Hexadecimal | number base 16. |
Literal | in general, a number of symbol which represents only itself; in Forth, a number that appears inside a definition. |
Mask | a value which can be " superimposed" over another, hiding certain bits and revealing only those bits that we are interested in. |
Number formatting | the process of printing a number, usually in a special form such as 3/13/03 or $47.93. |
Octal | number base 8. |
Sign bit high-order bit | the bit which, for a signed number, indicates whether it is positive or negative and, for an unsigned number, represents the bit of the highest magnitude. |
Two's complement | for any number, the number of equal absolute value but opposite sign. To calculate 10 - 4, the computer first produces the two's complement of 4, (i.e., -4), then computes 10 + (-4). |
Unsigned number | a number which is assumed to be positive. |
Unsigned single-length number | an integer which falls within the range of 0 to 2147483647. |
Word | In Forth, a defined dictionary entry, elsewhere, a term for a 16-bit value. |
Integer division | produces a quotient q and a remainder r by dividing operand a by operand b. Division operations return q, r, or both. The identity b*q + r = a holds for all a and b. |
Floored division | is integer division in which the remainder carries the sign of the divisor or is zero, and the quotient is rounded to its arithmetic floor. |
Symmetric division | is integer division in which the remainder carries the sign of the dividend or is zero and the quotient is the mathematical quotient "rounded towards zero" or "truncated". |
| |
2147483647 ok
What was her definition? [answer]
: MATCH humorous sensitive AND art-loving music-loving OR AND smoking 0= AND IF ." I have someone you should meet " THEN ;
For example:
12.3 .DEG 12.3 ok[answer]
7x2 + 20x + 5given x, and returns a double-length result.
DECIMAL 0 HEX 0 BINARY 0 DECIMAL 1 HEX 1 BINARY 1 DECIMAL 2 HEX 2 BINARY 10 ... DECIMAL 16 HEX 10 BINARY 10000
[answer]
..(two periods not separated by a space) and the system responds "ok," what does this tell you? [answer]
555-1234 .PH# 555-1234 ok 213/372-8493 .PH# 213/372-8493 ok
[answer]