Chapter 3: Arithmetic and Logic

Plus and Minus

Integer arithmetic

A whole section about + and -? What can be so special about these two words? Again, it's the fact that StrongForth provides a whole set of overloaded versions of + and -, thus distinguishing between integer arithmetic and address arithmetic and single, double and mixed operations. Let's start with the most obvious overloaded versions of + and -:

+ ( INTEGER INTEGER -- 1ST )
- ( INTEGER INTEGER -- 1ST )

This is really nothing unusual, both words work as expected. But have a closer look at the stack diagrams. The two operands are both of data type INTEGER, which means you can add or subtract two arbitrary, different items of data type INTEGER and it's subtypes, which are UNSIGNED, SIGNED and CHARACTER. For example, you can subtract an item of data type UNSIGNED from an item of data type SIGNED, as in the following example:

-35 7 - .S
SIGNED  OK
.
-42  OK

The data type of the result is identical to the data type of the first operand. This is a general principle for all of StrongForth's arithmetic and logic words. It is usually very handy:

CHAR C 3 + .
F OK
3 CHAR C + .
70  OK

The result of adding 3 to the character C is still a character, while the reverse operation yields an item of data type UNSIGNED, which is the ASCII value of the character C, incremented by 3.

As expected, StrongForth has overloaded versions of + and - for double precision arithmetic, which replace the ANS Forth words D+ and D-. Even for mixed-mode operations, StrongForth provides overloaded versions:

+ ( INTEGER-DOUBLE INTEGER-DOUBLE -- 1ST )
+ ( INTEGER-DOUBLE INTEGER -- 1ST )
+ ( INTEGER-DOUBLE SIGNED -- 1ST )
- ( INTEGER-DOUBLE INTEGER-DOUBLE -- 1ST )
- ( INTEGER-DOUBLE INTEGER -- 1ST )
- ( INTEGER-DOUBLE SIGNED -- 1ST )

Regarding mixed-mode operations, two things are interesting. First, there are two different versions for adding signed and unsigned numbers to a double-precision integer. This is because an unsigned single number has to be zero extended before adding it to a double number, while a signed single number has to be sign extended prior to the addition. Note that items of data type INTEGER are assumed to be unsigned numbers in this context.

125000. 52000 + .
177000  OK
366600. -18 + .
366582  OK

If, for example, sign extension would be applied in the first example, the result would be something like 111464, which is obviously not the expected result.

The second interesting thing about the mixed-mode versions of + and - is the fact that you can not add a double-precision number to a single-precision number.

45 830000. +

45 830000. + ? undefined word
UNSIGNED UNSIGNED-DOUBLE

Actually, this operation makes no sense if the data type of the result should be identical to the data type of the first operand, because the result normally does not fit into the data type. In cases like that, you have to use an explicit type cast to convert the double number into a single number:

45 830000. CAST INTEGER + .
43613  OK

This is not very satisfying, but what else did you expect? Cutting off the most significant part of a double number can't be safe. Generally, using a type cast is always a good indicator for a potentially dangerous operation.

Like in ANS Forth, + and - are accompanied by a small set of shortcut words: 1+, 1- and +!. Again, these work as expected. Note that StrongForth provides overloaded versions of 1+ and 1- for double numbers:

361 1+ .
362  OK
7625599. 1+ .
7625600  OK

More overloaded versions are required for +!, which might also be considered being an arithmetic operation. Here's what StrongForth has to offer for pure integer arithmetic:

+! ( INTEGER DATA -> INTEGER -- )
+! ( INTEGER-DOUBLE DATA -> INTEGER-DOUBLE -- )
+! ( INTEGER DATA -> INTEGER-DOUBLE -- )
+! ( SIGNED DATA -> INTEGER-DOUBLE -- )
+! ( INTEGER CDATA -> INTEGER -- )

The first version is identical to the ANS Forth word +!. Next, you have an overloaded version for double numbers, which would probably be called D+! if it was defined in ANS Forth. The third and fourth version perform mixed-mode operations by adding an unsigned or signed single number to a double number variable, respectively. Zero and sign extension are properly applied. And finally, the last overloaded version adds a single number to a character size number in memory. Here's a simple example of how it might be used:

CHAR A PAD !
 OK
7 PAD .S +!
UNSIGNED CDATA -> CHARACTER  OK
PAD @ .
H OK

Please note that +! can only be applied to numbers stored in the DATA memory area. Since the CODE and CONST memory areas are supposed to be read-only, it makes no sense trying to modify their contents by applying something like +!. Even when writing to these memory areas during program development, it should be sufficient to write only once, and then leave the value unchanged.

In this section, you have seen how +, -, 1+, 1- and +! are applied to integer numbers. But what about addresses? In StrongForth, addresses are not integers. Since address arithmetic has some very special rules, StrongForth provides separate overloaded versions of the above words for addresses. You'll learn more about these words in the next section.

Address Arithmetic

What are the operations, a programmer usually wants to perform on addresses? Well, he or she generates addresses by defining variables, stores data items at addresses and retrieves (fetches) data items from addresses. Working with arrays, it further makes sense to add an index to an address, to subtract an index from an address and to calculate the difference between two addresses. On the other hand, things like multiplying an address with a constant factor, or even adding two addresses do not make sense at all. Arithmetic operations on addresses are usually limited to a rather small subset of what can be done with integers. That's the reason why data type ADDRESS is not a subtype of data type INTEGER.

In StrongForth, the arithmetic operations to be performed on addresses are strictly limited. If addresses were a subtype of INTEGER, all arithmetic operations on integers could also be performed on addresses. In fact, StrongForth provides overloaded versions of +, -, 1+, 1- and +! for exclusive use on addresses. Let's get a first overview:

WORDS +
+ ( CFAR-ADDRESS INTEGER -- 1ST )
+ ( FAR-ADDRESS -> DOUBLE INTEGER -- 1ST )
+ ( FAR-ADDRESS -> SINGLE INTEGER -- 1ST )
+ ( FAR-ADDRESS INTEGER -- 1ST )
+ ( INTEGER-DOUBLE SIGNED -- 1ST )
+ ( INTEGER-DOUBLE UNSIGNED -- 1ST )
+ ( INTEGER-DOUBLE INTEGER-DOUBLE -- 1ST )
+ ( CADDRESS INTEGER -- 1ST )
+ ( ADDRESS -> DOUBLE INTEGER -- 1ST )
+ ( ADDRESS -> SINGLE INTEGER -- 1ST )
+ ( ADDRESS INTEGER -- 1ST )
+ ( INTEGER INTEGER -- 1ST )
 OK

You already know the overloaded versions for data types INTEGER and INTEGER-DOUBLE from the previous section. These words can not be applied to addresses. However, address arithmetic is explicitly supported by the other 8 overloaded versions of +. For example,

+ ( ADDRESS INTEGER -- 1ST )

adds an integer offset to any ADDRESS. Since data types DATA, CONST, CODE and PORT are subtypes of ADDRESS, this version of + also applies to addresses that are bound to a specific memory area. The result of the addition has the same data type as the first operand, i. e., it is still an address. Note that the reverse operation, adding an address to an integer, is not supported. Adding integers to addresses is not a commutative operation. This means, that you sometimes have to explicitly SWAP the operands to make sure the address is the first operand of an addition. The reason is obvious. Because of the general rule for all arithmetic words of returning a result of the same data type like the first operand, adding an address to an integer would yield an integer instead of an address. This is in most cases not the desired result. If you consider the missing commutativity as a deficiency, you can easily define new overloaded versions of + like the following:

: + ( INTEGER ADDRESS -- 2ND )
  SWAP + ;

But why does StrongForth provide the following overloaded version of +?

+ ( ADDRESS -> SINGLE INTEGER -- 1ST )

Isn't this operation already covered by the first one? It is not. In StrongForth, adding an integer offset to an address of a single-cell item is semantically different from adding an integer to just an address. Imagine you have an array of 10 single-cell items, and you want to fetch the one with index 5. In ANS Forth, you'd write

ARRAY 5 CELLS + @

To calculate the correct address, you have to multiply the index 5 with the number of address units per single cell by using the word CELLS. In StrongForth, this explicit multiplication is not required. If StrongForth's compiler knows that ARRAY is an address of a single-cell item, it selects the above version of +, which automatically multiples the address by the number of address units per cell before the actual addition is performed. If, for example, ARRAY returns an address of data type DATA -> UNSIGNED, you can simply write

ARRAY 5 + @

to fetch the item with index 5 from the array. The same mechanism works with addresses of double cell items.

+ ( ADDRESS -> DOUBLE INTEGER -- 1ST )

automatically multiplies the integer offset with two times the number of address units per single cell before it is added to the address. Here's a small demonstration:

HERE .S DUP . 5 + .
ADDRESS 6684 6689  OK
HERE -> SINGLE .S DUP . 5 + .
ADDRESS -> SINGLE 6684 6694  OK
HERE -> DOUBLE .S DUP . 5 + .
ADDRESS -> DOUBLE 6684 6704  OK
HERE CAST CADDRESS -> SINGLE .S DUP . 5 + .
CADDRESS -> SINGLE 6684 6689  OK

HERE is just used to generate a random address for this example. Adding 5 to an unspecific address really increments the address by 5, while adding 5 to an address of a single-cell or a double-cell item increments the address by 10 or 20, respectively. This means, on the system in use the number of address units per single cell is 2. The integer offset is interpreted as an index. The resulting address is the original address plus the size of the specified number of items both addresses point to.

But what about the last case, where 5 is added to the address of a character size item? Although

+ ( ADDRESS -> SINGLE INTEGER -- 1ST )

matches the address, the address is only incremented by 5. And this is correct, because the number of address units per character is 1 on the system in use. Actually,

+ ( CADDRESS INTEGER -- 1ST )

is another overloaded version of +, and this version takes care of the special case of adding integer offsets to character addresses.

If you turn back to the list of all overloaded versions of +, you will notice that the order of the 4 different versions of + is very important.

+ ( CADDRESS INTEGER -- 1ST )

is found first, because it deals with the most special case. Adding an integer offset to a character address is independent of what the address points to, because the items always have character size. Next, the following two overloaded versions are found:

+ ( ADDRESS -> DOUBLE INTEGER -- 1ST )
+ ( ADDRESS -> SINGLE INTEGER -- 1ST )

The compiler knows the size of the items the address points to, and it further knows that it's not character size. Finally,

+ ( ADDRESS INTEGER -- 1ST )

takes care of the most general case, where the compiler has no information at all about the size of the items the address points to. Therefore, the integer offset is just interpreted as address units.

In total, StrongForth provides four versions of + for ordinary addresses, used to increment addresses by units of character size, single-cell size, double-cell size, and by address units. Since the compiler automatically selects the correct overloaded version of +, you usually don't have to apply CELLS or CHARS to adapt the integer offset to the size of the items stored at the address.

The same set of overloaded versions of + exists for addresses of data types FAR-ADDRESS and CFAR-ADDRESS as well. Note that there is no overloaded version of + for adding two addresses, because this operation makes no sense. However, it does make sense to calculate the difference of two addresses, yielding an integer. Therefore, StrongForth provides four additional overloaded versions of -, with are found in the dictionary in this order:

- ( CADDRESS 1ST -- SIGNED )
- ( ADDRESS -> DOUBLE 1ST -- SIGNED )
- ( ADDRESS -> SINGLE 1ST -- SIGNED )
- ( ADDRESS 1ST -- SIGNED )

Subtracting two addresses requires that both have exactly the same data type. Again, StrongForth considers the size of the items pointed to. If both addresses point to items of character size, the result is the number of characters that fit between the two addresses. It is negative if the second address is higher than the first. Otherwise, if both addresses point to either single-cell items or to double-cell items, the result is the number of single-cell or double-cell items that fit between the addresses. Only if none of these three cases apply, the last overloaded version catches the general case by calculating the difference between the two addresses in address units.

You can easily see that subtracting a DATA address from a CODE address is not a meaningful operation. If one address points to a single-cell item and the other one points to a double-cell item, StrongForth wouldn't know how to calculate the difference. That's why both addresses need to have exactly the same data type.

The result of subtracting two addresses is always of data type SIGNED, because the difference is not an address. If you know that the first address is always higher that the second one, you may cast the difference to an UNSIGNED number before further processing.

Of course, + and - are not the only operations for address arithmetic. The following arithmetic operations can be applied to addresses:

Multiplying and dividing two addresses, or even an address and an integer number, is not possible.

What about the last two words? These two words are compiled by LOOP and +LOOP, respectively. If the loop index of a DO loop is an address, address arithmetic is applied to it as well. Here's an example:

DATA-SPACE
 OK
-12 VARIABLE ARRAY -5 , +8 , +3 ,
 OK
: TOTAL ( DATA -> INTEGER UNSIGNED -- 2ND )
  0 CAST INTEGER ROT ROT
  OVER SWAP + SWAP
  ?DO I @ +
  LOOP ;
 OK
ARRAY 4 TOTAL .
-6  OK

In this example, address arithmetic is applied two times. First, + adds the array size to an address pointing to a single-cell item of data type INTEGER. The next occurrence of + is pure integer arithmetic. However, the second usage of address arithmetic is hidden in LOOP, which compiles (LOOP) to increment the loop index by cell size, because the loop index is of data type DATA -> INTEGER.

To emphasise the difference, here's how TOTAL would be implemented in ANS Forth:

: TOTAL ( addr u -- n )
  0 ROT ROT
  OVER SWAP CELLS + SWAP
  ?DO I @ + 1 CELLS
  +LOOP ;

Apart from the additional type cast required in the StrongForth version, address arithmetic makes the difference. In ANS Forth, address arithmetic is explicitly performed by using CELLS to ensure the addresses are incremented with regard to cell size. Since address arithmetic is implicit in StrongForth, words like CELLS, CELL+, CHARS and CHAR+ are rarely used. However, CELLS and CHARS do exist in StrongForth, because they are still required in connection with low-level words like ALLOT. CELL+ and CHAR+ are replaced by overloaded versions of 1+:

1+ ( CADDRESS -- 1ST )            \ replaces CHAR+
1+ ( ADDRESS -> DOUBLE -- 1ST )
1+ ( ADDRESS -> SINGLE -- 1ST )   \ replaces CELL+
1+ ( ADDRESS -- 1ST )

Multiplication and Division

ANS Forth, and also StrongForth, provides a variety of words for multiplication and division. Let's begin with a simple multiplication. Here's what StrongForth has to offer:

WORDS *
* ( SIGNED-DOUBLE SIGNED -- 1ST )
* ( INTEGER-DOUBLE UNSIGNED -- 1ST )
* ( SIGNED SIGNED -- 1ST )
* ( INTEGER UNSIGNED -- 1ST )
 OK

That's not as much as for addition and subtraction, but remember that there's no address arithmetic for multiplication. Multiplying two unsigned or two signed numbers yields an unsigned or signed number, respectively. If you multiply a signed number with an unsigned number, the result is a signed number. But, StrongForth does not allow multiplying an unsigned number with a signed number. The result would have to be a signed number, which violates the general rule for all arithmetic operations in StrongForth, which says that the data type of the result is always identical to the data type of the first operand. You have to swap the operands in order to perform this operation.

Note that StrongForth does not need separate names for words to multiply signed and unsigned numbers. Since the compiler is able to distinguish the data types of the operands, * can be overloaded. Mixed-mode multiplication, i. e., multiplying a (signed or unsigned) double-precision number by a (signed or unsigned) single-precision number, is overloaded as well. Again, the data type of the result is identical to the data type of the first operand. The version of * for signed double-precision numbers is implemented as a colon definition:

: * ( SIGNED-DOUBLE SIGNED -- 1ST )
  DUP 0<
  IF ABS CAST UNSIGNED * NEGATE
  ELSE CAST UNSIGNED *
  THEN ;

But, in some cases you might want the result of multiplying two single-precision numbers to be a double-precision number, thus violating the general rule. For these special cases, StrongForth provides two additional mixed-mode operations:

WORDS M*
M* ( SIGNED SIGNED -- SIGNED-DOUBLE )
M* ( UNSIGNED UNSIGNED -- UNSIGNED-DOUBLE )
 OK

These two words can not be overloaded versions of *, because then the compiler would not be able to distinguish normal and mixed-mode multiplication by the input parameters. Let's try a few examples:

-46 +17 * .S .
SIGNED -782  OK
-460 +170 M* .S .
SIGNED-DOUBLE -78200  OK
-460000. 170 * .S .
SIGNED-DOUBLE -78200000  OK

An interesting detail is the fact that multiplication with an unsigned single-precision number allows the first operand to be either signed or unsigned, while this is not possible for M*. This is because only the most significant word of the result depends on whether the multiplicand is signed or unsigned, while the least significant word is identical in both cases.

Division is a similar case. The result depends on whether the operands are interpreted as signed or unsigned numbers. Therefore, signed and unsigned division are completely different operations. However, all plain division words are overloaded:

WORDS /
/ ( UNSIGNED-DOUBLE UNSIGNED -- 1ST )
/ ( SIGNED SIGNED -- 1ST )
/ ( UNSIGNED UNSIGNED -- 1ST )
 OK

Since / has to be more specific on the data types of its arguments, it can not be applied to items of data types INTEGER, INTEGER-DOUBLE or CHARACTER. The signed/unsigned attribute of these three data types is supposed to be unspecified. But, does it really make sense to perform a division on an ASCII character or any other number without signed/unsigned attribute? Well, normally not. If it's still required in one of your StrongForth applications, just use a type cast:

CHAR x CAST UNSIGNED 3 / CAST CHARACTER . \ does this make sense?
( OK

Note that M/ does not exist in StrongForth. The overloaded version of /, where the dividend is of data type UNSIGNED-DOUBLE and the divisor is of data type UNSIGNED, does the job quite nicely. If you want the result to be a single-precision number, you can use a type cast, as in the following example:

1000000. 50 / CAST UNSIGNED .
20000  OK

Another possibility is to use UM/MOD instead of /. UM/MOD will be presented later in this section.

MOD and /MOD are close relatives to /. They are applied to the same operand combinations and perform a division as well, but they deliver different results:

WORDS MOD
MOD ( UNSIGNED-DOUBLE UNSIGNED -- 2ND )
MOD ( SIGNED SIGNED -- 2ND )
MOD ( UNSIGNED UNSIGNED -- 2ND )
 OK
WORDS /MOD
/MOD ( UNSIGNED-DOUBLE UNSIGNED -- 2ND 1ST )
/MOD ( SIGNED SIGNED -- 2ND 1ST )
/MOD ( UNSIGNED UNSIGNED -- 2ND 1ST )
 OK

Against the rule, the result of MOD as well as the first part of the result of /MOD do not have the same data type as the first operand. Why? The reason is that the remainder is more related to the divisor than to the dividend. It's absolute value is always less than the actual divisor. This is most obvious when considering mixed-mode operations. Although the data type of the first operand, the dividend, is UNSIGNED-DOUBLE, the result will always fit into a single-precision number of data type UNSIGNED, like the divisor.

Next, there's */ and */MOD:

WORDS */
*/ ( UNSIGNED-DOUBLE UNSIGNED UNSIGNED -- 1ST )
*/ ( SIGNED SIGNED SIGNED -- 1ST )
*/ ( UNSIGNED UNSIGNED UNSIGNED -- 1ST )
 OK
WORDS */MOD
*/MOD ( SIGNED-DOUBLE SIGNED SIGNED -- 3RD 1ST )
*/MOD ( UNSIGNED-DOUBLE UNSIGNED UNSIGNED -- 3RD 1ST )
*/MOD ( SIGNED SIGNED SIGNED -- 3RD 1ST )
*/MOD ( UNSIGNED UNSIGNED UNSIGNED -- 3RD 1ST )
 OK

Again, StrongForth provides overloaded versions for signed and unsigned numbers, and for unsigned mixed-mode. For */, an additional overloaded version for signed mixed-mode operands is available. This version is equivalent to the ANS Forth word M*/, which is a signed operation. Note that StrongForth, just like ANS Forth, requires the divisor to be a positive signed number. The definition of */ for signed double-precision numbers is based on the version for unsigned double-precision numbers:

: */ ( SIGNED-DOUBLE SIGNED SIGNED -- 1ST )
  CAST UNSIGNED ROT DUP 0<
  IF ABS CAST UNSIGNED-DOUBLE ROT DUP 0<
     IF ABS CAST UNSIGNED ROT */
     ELSE CAST UNSIGNED ROT */ NEGATE
     THEN
  ELSE CAST UNSIGNED-DOUBLE ROT DUP 0<
     IF ABS CAST UNSIGNED ROT */ NEGATE
     ELSE CAST UNSIGNED ROT */
     THEN
  THEN CAST SIGNED-DOUBLE ;

The mixed-mode versions of */ and */MOD both use triple-precision intermediate results, i. e. they multiply a double-precision number with a single-precision number, giving a triple-precision intermediate result. The intermediate-result is then divided by a single-precision number, giving a double-precision quotient and, in case of */MOD, a single-precision remainder.

Of course, the ANS Forth words FM/MOD, SM/REM and UM/MOD are also available in StrongForth.:

FM/MOD ( SIGNED-DOUBLE SIGNED -- 2ND SIGNED )
SM/REM ( SIGNED-DOUBLE SIGNED -- 2ND SIGNED )
UM/MOD ( UNSIGNED-DOUBLE UNSIGNED -- 2ND UNSIGNED )

Floored and symmetric division are signed operations. UM/MOD is unsigned. The data type of the remainder is identical to the data type of the divisor, but the quotient does not have the same data type as the dividend. This is not possible, because the dividend has to be a double-precision number, while the quotient has to be a single-precision number. Therefore, all three words may be considered being low-level.

Finally, let's see how 2* and 2/ look like in StrongForth:

WORDS 2*
2* ( INTEGER-DOUBLE -- 1ST )
2* ( INTEGER -- 1ST )
 OK
WORDS 2/
2/ ( SIGNED-DOUBLE -- 1ST )
2/ ( UNSIGNED-DOUBLE -- 1ST )
2/ ( SIGNED -- 1ST )
2/ ( UNSIGNED -- 1ST )
 OK

Since the semantics of 2* is the same for signed and unsigned numbers, there's only one overloaded version for both single and double numbers. Note that data types UNSIGNED and SIGNED are both subtypes of data type INTEGER, so 2* for single numbers applies to both signed and unsigned numbers as well as to items of data type CHARACTER. 2* for double numbers replaces the ANS Forth word D2*.

On the other hand, the semantics of 2/ depends on whether the operand is signed or unsigned, because the sign bit has to be preserved for signed numbers and not for unsigned numbers. This means StrongForth has to provide a total of four separate overloaded versions for unsigned single numbers, signed single numbers, unsigned double numbers, and signed double numbers. The ANS Forth word D2/ is replaced by the version of 2/ for signed double numbers.

Other Arithmetical Operations

ABS

The ANS Forth words ABS and DABS fit nicely into the concept of StrongForth. As usual, StrongForth provides the double number version by overloading the single number version:

WORDS ABS
ABS ( SIGNED-DOUBLE -- 1ST )
ABS ( SIGNED -- 1ST )
 OK

Both words are defined for signed numbers only, since calculating the absolute value does not make sense if the operand is unsigned. Following the general rule, the data type of the result is identical to the data type of the (first) operand. Any attempt to apply ABS to an unsigned number or to any other item that is not SIGNED or a subtype of SIGNED; will fail:

-45 ABS .
45  OK
45 ABS .

45 ABS ? undefined word
UNSIGNED

Quite often, the absolute value is calculated with the intention to make a signed number unsigned. In those cases, the result of ABS has to be explicitly casted to the desired unsigned data type. Examples of ABS followed by a type cast to UNSIGNED can be found in the definitions of the words THROW, BLOCK and BUFFER.

NEGATE

NEGATE is closely related to ABS, because both words change the sign of their respective operands. But although both words are typically applied to signed numbers, applying NEGATE to an unsigned number also makes sense. Therefore, NEGATE and it's overloaded version for double-precision arithmetic are defined for items of data type INTEGER.

WORDS NEGATE
NEGATE ( INTEGER-DOUBLE -- 1ST )
NEGATE ( INTEGER -- 1ST )
 OK
+41 NEGATE .
-41  OK
41 NEGATE .
65495  OK

Oops. What happened here? 41 is of data type UNSIGNED, so the data type of the result of NEGATE is also UNSIGNED. As a consequence, StrongForth uses the overloaded version of . for unsigned numbers. To get the correct result, you have to explicitly tell StrongForth, that you want the result of NEGATE to be a signed number:

41 NEGATE CAST SIGNED .
-41  OK

Although this example works as expected, you generally have to be careful with type casts. The value of an unsigned number can not always be represented by a signed number, as a slight modification of the above example shows:

41000 NEGATE CAST SIGNED .
24536  OK

41000 is a valid unsigned number on a 16-bit system, but -41000 is not a valid signed number. Even if the operand is signed, NEGATE (and also ABS) fail to deliver a correct result in a very special case:

-32768 NEGATE .
-32768  OK
-32768 ABS .
-32768  OK

These examples fail to produce the correct result, because the highest positive value that can be represented by an item of data type SIGNED is +32767, while the lowest negative value is -32768.

A final example shows that a type cast is not always required after NEGATE has been applied to an unsigned number:

824 VARIABLE N
 OK
13 NEGATE N +!
 OK
N @ .
811  OK

MIN and MAX

Let's see what StrongForth has to offer for calculating the minimum and the maximum of two values:

WORDS MIN
MIN ( SIGNED-DOUBLE 1ST -- 1ST )
MIN ( INTEGER-DOUBLE 1ST -- 1ST )
MIN ( ADDRESS 1ST -- 1ST )
MIN ( SIGNED 1ST -- 1ST )
MIN ( INTEGER 1ST -- 1ST )
 OK
WORDS MAX
MAX ( SIGNED-DOUBLE 1ST -- 1ST )
MAX ( INTEGER-DOUBLE 1ST -- 1ST )
MAX ( ADDRESS 1ST -- 1ST )
MAX ( SIGNED 1ST -- 1ST )
MAX ( INTEGER 1ST -- 1ST )
 OK

All of these versions have in common, that the two operands and the result have exactly the same data type. It makes no sense to calculate, let's say, the maximum of an address and a character.

Now, what are the operands MIN and MAX can be applied to? The versions for items of data type INTEGER can also be applied to items of data type UNSIGNED and CHARACTER, because these are subtypes of INTEGER. And what about SIGNED? Isn't SIGNED another subtype of INTEGER? It is. But since there are special versions of MIN and MAX for items of data type SIGNED, which are found before the versions for items of data type INTEGER, the latter ones have no chance to get applied to signed single numbers. Actually, it's the signed versions of MIN and MAX that implement the semantics of the respective ANS Forth words. The versions for items of data type INTEGER have an unsigned semantics, as can be seen in the following example:

30000 40000 MAX .
40000  OK
+30000 -25536 MAX .
30000  OK
40000 CAST SIGNED .
-25536  OK

Note that, on a 16-bit system, unsigned number 40000 and signed number -25536share the same bit pattern. Unsigned versions of MAX and MIN only exist in StrongForth, not in ANS Forth.

Since ADDRESS is not a subtype of INTEGER, separate versions of MIN and MAX for items of data type ADDRESS and it's subtypes are defined. However, these versions have the same semantics as the respective versions for unsigned numbers.

As usual, StrongForth provides overloaded versions for double numbers. The versions of MIN and MAX for items of data type SIGNED-DOUBLE are replacements for the ANS Forth words DMIN and DMAX, respectively, while the versions for items of data type INTEGER-DOUBLE would probably be called UDMIN and UDMAX in ANS Forth, if they existed. MIN and MAX can not be applied to items of data type FAR-ADDRESS, because this data type does not define a unique order on it's values.

Comparison Operators

Comparison operators are <, >, =, <> as well as 0<, 0>, 0= and 0<>. Operators like <= and >=, though very common in other programming languages, are neither part of the ANS Forth standard nor of StrongForth. However, they can easily be defined.

The stack diagrams of < and > resemble very much those of the MIN and MAX operators from the preceding section. The obvious difference is that they leave a flag on the stack instead of an item of the same data type as the operands:

WORDS <
< ( SIGNED-DOUBLE 1ST -- FLAG )
< ( INTEGER-DOUBLE 1ST -- FLAG )
< ( ADDRESS 1ST -- FLAG )
< ( SIGNED 1ST -- FLAG )
< ( INTEGER 1ST -- FLAG )>
 OK
WORDS >
> ( SIGNED-DOUBLE 1ST -- FLAG )
> ( INTEGER-DOUBLE 1ST -- FLAG )
> ( ADDRESS 1ST -- FLAG )
> ( SIGNED 1ST -- FLAG )
> ( INTEGER 1ST -- FLAG )
 OK

= and <> are different. These words can be applied to pairs of any data type, as long as the data types of both operands are exactly the same. Being applicable to any data type means in StrongForth, that two overloaded versions are available: one for SINGLE and one for DOUBLE:

WORDS =
= ( DOUBLE 1ST -- FLAG )
= ( SINGLE 1ST -- FLAG )
 OK
WORDS <>
<> ( DOUBLE 1ST -- FLAG )
<> ( SINGLE 1ST -- FLAG )
 OK

Unsigned numbers and addresses can never be negative. Therefore, 0< does only exists for signed single and double numbers:

WORDS 0<
0< ( SIGNED-DOUBLE -- FLAG )
0< ( SIGNED -- FLAG )
 OK
WORDS 0>
0> ( SIGNED-DOUBLE -- FLAG )
0> ( SIGNED -- FLAG )
 OK

As you can see, 0> is also restricted to signed numbers. Overloaded versions for unsigned numbers are obsolete, because they would have the same semantics as 0<>.

Just like = and <>, 0= and 0<> can be applied to items of any data type:

WORDS 0=
0= ( DOUBLE -- FLAG )
0= ( SINGLE -- FLAG )
 OK
WORDS 0<>
0<> ( DOUBLE -- FLAG )
0<> ( SINGLE -- FLAG )
 OK

Are these all comparison operators? Well, almost. One very interesting comparison word is still missing: WITHIN. Since the semantics of WITHIN does not differ for signed and unsigned numbers, one version for items of data type INTEGER and one for items of data type ADDRESS is all you need:

WORDS WITHIN
WITHIN ( ADDRESS 1ST 1ST -- FLAG )
WITHIN ( INTEGER 1ST 1ST -- FLAG )
 OK

Remember that ADDRESS is not a subtype of INTEGER. The data types of the three operands must be identical, because it normally makes no sense to compare items of different data types. If it's still necessary, use a type cast. Note also that an overloaded version for double-precision numbers is not provided. But again, it can easily be added to StrongForth's vocabulary:

: WITHIN ( INTEGER-DOUBLE 1ST 1ST -- FLAG )
  OVER - >R - R> < ;
 OK

In ANS Forth, one would write

: WITHIN ( n lo-limit hi-limit+1 -- f )
  OVER - >R - R> U< ;
 OK

to define WITHIN for single-precision numbers. There are two differences. First, StrongForth uses < instead of U<, because < is overloaded for unsigned numbers. Second, the StrongForth version applies to double-precision integers. A StrongForth version for single-precision integers would look exactly the same except for the stack diagram which would say INTEGER instead of INTEGER-DOUBLE. This is possible because all words used in this definition, even >R and R>, are overloaded for single and double numbers.

Logical Operations

Bitwise Logical Operations

Just like ANS Forth, StrongForth provides the usual binary and unary bitwise logical operations:

AND ( SINGLE LOGICAL -- 1ST )
OR ( SINGLE LOGICAL -- 1ST )
XOR ( SINGLE LOGICAL -- 1ST )
INVERT ( LOGICAL -- 1ST )

Apart from the stack diagrams, these operations bear no surprise. Maybe you expected all parameters to be of data type SINGLE, so it would be possible to apply logical operations to all kinds of operands. Why is the second operand, or the only operand of INVERT, restricted to items of data type LOGICAL?

If arbitrary data types were allowed, even obviously meaningless operations like a logical AND of an address with a character, would be possible. To understand the restriction, consider the typical applications of logical operations.

First of all, logical operations are used to calculate a condition. In this case, both operands are of data type FLAG, which is a subtype of LOGICAL. This causes no problem:

6 VALUE X
 OK
X 4 > STATE @ AND .
FALSE  OK

Second, logical operations are frequently used to set, clear and test specific bits in any kind of operand. Since data type LOGICAL is a bit vector by definition, it's perfectly suited for this purpose. StrongForth even provides a word BIT that delivers an item of data type LOGICAL with one bit set:

: BIT ( UNSIGNED -- LOGICAL )
  1 CAST LOGICAL SWAP LSHIFT ;

With this word and the logical operations, it's easy to set, clear and test individual bits in items of any data type. Here are some examples:

5 BIT .S .
LOGICAL 32  OK
11 BIT .
2048  OK
CHAR A 2 BIT OR .
E OK
HEX ABCD DECIMAL 12 BIT AND 0= .
TRUE  OK

As usual, the data type of the result is identical to the data type of the first operand.

Logical Constants

The logical constants TRUE and FALSE are both of data type FLAG:

-1 CAST FLAG CONSTANT TRUE
0 CAST FLAG CONSTANT FALSE

Since ANS Forth does not support data types, there is no difference between 0 and FALSE, or -1 and TRUE. But this is different in StrongForth. Consider a situation where you want to define a flag variable:

0 VARIABLE RUNNING

defines RUNNING as a constant of data type UNSIGNED. This means, it can not be used in logical expressions like this one:

>IN @ 6 > RUNNING @ AND .

>IN @ 6 > RUNNING @ AND ? undefined word
FLAG UNSIGNED

The interpreter complains, because AND does not accept an item of data type UNSIGNED as the second parameter. If RUNNING is defined as a variable of data type FLAG, the previous example works. Note that only items of data type FLAG can now be stored in RUNNING:

FALSE VARIABLE RUNNING
 OK
>IN @ 6 > RUNNING @ AND .
FALSE  OK
TRUE RUNNING !
 OK
-1 RUNNING !

-1 RUNNING ! ? undefined word
SIGNED DATA -> FLAG

Shift Operations

ANS Forth specifies two shift operations, LSHIFT and RSHIFT, which are defined in StrongForth as well:

LSHIFT ( LOGICAL UNSIGNED -- 1ST )
RSHIFT ( LOGICAL UNSIGNED -- 1ST )

Note that the first operand of these two words is of data type LOGICAL, and that the result has the same data type. Items of data type INTEGER cannot be shifted, because shifting is a logical operation, while operations on integers are arithmetic by definition. StrongForth strongly distinguishes between arithmetic and logic operations. To shift an integer value, you have to use either a multiplication or a type cast:

15 3 LSHIFT .

15 3 LSHIFT ? undefined word
UNSIGNED UNSIGNED
15 8 * .
120  OK
15 CAST LOGICAL 3 LSHIFT .
120  OK

Instead of multiplying by 8, you can also apply three times 2* on the operand. However, 2* and 2/ are arithmetic operations that don't apply to logical values:

15 2* 2* 2* .
120  OK
6 BIT 2* .

6 BIT 2* ? undefined word
LOGICAL

An obvious solutions is to do a logical left shift by 1:

6 BIT 1 LSHIFT .
128  OK

But there's still a better way. StrongForth provides overloaded versions of LSHIFT and RSHIFT that just shift by 1 bit:

WORDS LSHIFT
LSHIFT ( LOGICAL UNSIGNED -- 1ST )
LSHIFT ( LOGICAL -- 1ST )
 OK
WORDS RSHIFT
RSHIFT ( LOGICAL UNSIGNED -- 1ST )
RSHIFT ( LOGICAL -- 1ST )
 OK
6 BIT LSHIFT .
128  OK

The unary versions of LSHIFT and RSHIFT are actually replacements of 2* and 2/ for items of data type LOGICAL. Since UNSIGNED is not a subtype of LOGICAL and vice versa, the decision on which version to compile or interpret will never be ambiguous.


Dr. Stephan Becher - January 4th, 2008