Computer Solutions Ltd
Whats New | Advanced Search
Downloads | Exchange Rates

Celebrating 38 years
supplying all your CAN and Embedded Development Tool needs

 


Excerpts from Forth Programmer's Handbook #2


2.2 ARITHMETIC AND LOGICAL OPERATIONS

Forth offers a comprehensive set of commands for performing arithmetic and logical functions. The functions in a standard system are optimised for integer arithmetic, because not all processors have hardware floating-point capability and software floating point is too slow for most real-time applications. All Forth systems provide words to perform fast, precise, scaled-integer computations; many provide fixed-point fraction computations, as well. On systems with hardware floating-point capability, many implementations include an optional, complete set of floating-point operations, including an assembler. See Section 3.11 in this manual and the product documentation for these systems for details.

Programmers who are new to Forth should review Starting Forth's comprehensive discussion of Forth's handling of numbers and arithmetic.


2.2.1 Arithmetic and Shift Operators

In order to achieve maximum performance, each version of Forth implements most arithmetic primitives to use the internal behaviour of that particular processor's hardware multiply and divide instructions. Therefore, to find out at the bit level what these primitives do, you should consult either the manufacturer's hardware description or the implementation's detailed description of these functions.

In particular, signed integer division -- where only one operand (either dividend or divisor) is negative and there is a remainder -- may produce different, but equally valid, results on different implementations. The two possibilities are floored and symmetric division. In floored division, the remainder carries the sign of the divisor and the quotient is rounded to its arithmetic floor (towards negative infinity). In symmetric division, the remainder carries the sign of the dividend and the quotient is rounded towards zero, or truncated. For example, dividing -10 by 7 can give a quotient of -2 and remainder of 4 (floored), or a quotient of -1 and remainder of -3 (symmetric).

Most hardware multiply and divide instructions are symmetric, so floored division operations are likely to be slower. However, some applications (such as graphics) require floored division in order to get a continuous function through zero. Consult your system's documentation to learn its behaviour.

The following general guidelines may help you use these arithmetic operators:

  • The order of arguments to order-dependent operators (e.g., - and /) is such that, if the operator were moved to an infix position, it would algebraically describe the result. Some examples:
     Forth  Algebraic
     a b -  a - b
     a b /  a / b
     a b c */  a * b / c
  • All arithmetic words starting with the letter U are unsigned; others are normally signed. The exception to this rule is that, on most systems, M*/ requires a positive divisor.
  • When executing operations involving address calculations, use the words CELL+, CELLS, CHAR+, and CHARS as appropriate to convert logical values to bytes, rather than to absolute numbers. For example, to increment an address by three cells on a 32-bit system, use 3 CELLS +, not 12 +; this makes the code portable to systems that may have different cell widths.

These operators perform arithmetic and logical functions on numbers that are on the stack. In general, the operands are removed (popped) from the stack and the results are left on the stack.

 Glossary Single-Precision Operations
  * ( n1 n2 -- n3 )
Multiply n1 by n2 leaving the product n3. "star"
  */ ( n1 n2 n3 -- n4 )
Multiply n1 by n2, producing an intermediate double-cell result d. Divide d by n3, giving the single-cell quotient n4. "star-slash"
  */MOD ( n1 n2 n3 -- n4 n5 )
Multiply n1 by n2, producing intermediate double-cell result d. Divide d by n3, giving single-cell remainder n4 and single-cell quotient n5. "star-slash-mod"
  + ( n1 n2 -- n3 )
Add n1 to n2, leaving the sum n3. "plus"
  - ( n1 n2 -- n3 )
Subtract n2 from n1, leaving the difference n3. "minus"
  / ( n1 n2 -- n3 )
Divide n1 by n2, leaving the quotient n3. See the discussion at the beginning of this section about floored and symmetric division. "slash"
  /MOD ( n1 n2 -- n3 n4 )
Divide n1 by n2, leaving the remainder n3 and the quotient n4. "slash-mod"
  1+ ( n1 -- n2 )
Add one to n1, leaving n2. "one-plus"
  1- ( n1 -- n2 )
Subtract one from n1, leaving n2. "one-minus"
  2+ ( n1 -- n2 )
Add two to n1, leaving n2. "two-plus"
  2- ( n1 -- n2 )
Subtract two from n1, leaving n2. "two-minus"
  2* ( x1 -- x2 )
Return x2, the result of shifting x1 one bit toward the most-significant bit, filling the least-significant bit with zero (same as 1 LSHIFT). "two-star"
  2/ ( x1 -- x2 )
Return x2, the result of shifting x1 one bit towards the least-significant bit, leaving the most-significant bit unchanged. "two-slash"
  CELL+ ( a-addr1 -- a-addr2 )
Add the size in bytes of a cell to a-addr1, giving a-addr2. Equivalent to 2+ on a 16-bit system and to 4 + on a 32-bit system. "cell-plus"
  CELLS ( n1 -- n2 )
Return n2, the size in bytes of n1 cells.
  CHAR+ ( c-addr1 -- c-addr2 )
Add the size in bytes of a character to c-addr1, giving c-addr2. "care-plus"
  CHARS ( n1 -- n2 )
Return n2, the size in bytes of n1 characters. On many systems, this word is a no-op. "cares"
  LSHIFT ( x1 u -- x2 )
Perform a logical left shift of u places on x1, giving x2. Fill the vacated least-significant bits with zeroes. "L-shift"
  MOD ( n1 n2 -- n3 )
Divide n1 by n2, giving the remainder n3.
  RSHIFT ( x1 u -- x2 )
Perform a logical right shift of u places on x1, giving x2. Fill the vacated most-significant bits with zeroes. "R-shift"
  Double-Precision Operations
  D+ ( d1 d2 -- d3 )
Add d1 to d2, leaving the sum d3. "D-plus"
  D- ( d1 d2 -- d3 )
Subtract d2 from d1, leaving the difference d3. "D-minus"
  D2* ( xd1 -- xd2 )
Return xd2, the result of shifting xd1 one bit toward the most-significant bit and filling the least-significant bit with zero. "D-two-star"
  D2/ ( xd1 -- xd2 )
Return xd2, the result of shifting xd1 one bit towards the least-significant bit and leaving the most-significant bit unchanged. "D-two-slash"
  Mixed-Precision Operations
  D>S ( d -- n )
Convert double-precision number d to the single-precision equivalent n. Results are undefined if d is outside the range of a signed single-cell number. "D-to-S"
  FM/MOD ( d n1 -- n2 n3 )
Divide d by n1, using floored division, giving quotient n3 and remainder n2. All arguments are signed. This word and SM/REM will produce different results on the same data when exactly one argument is negative and there is a remainder. "F-M-slash-mod"
  M* ( n1 n2 -- d )
Multiply n1 by n2, leaving the double-precision result d. "M-star"
  M*/ ( d1 n1 +n2 -- d2 )
Multiply d1 by n1, producing a triple-cell intermediate result t. Divide t by the positive number n2 giving the double-cell quotient d2. If double-precision multiplication or division only is needed, this word may be used with either n1 or n2 set equal to 1. "M-star-slash"
  M+ ( d1 n -- d2 )
Add n to d1, leaving the sum d2. "M-plus"
  M- ( d1 n -- d2 )
Subtract n from d1, leaving the difference d2. "M-minus"
  M/ ( d n1 -- n2 )
Divide d by n1, leaving the single-precision quotient n2. This word does not perform an overflow check. "M-slash"
  S>D ( n -- d )
Convert a single-precision number n to its double-precision equivalent d with the same numerical value. "S-to-D"
  SM/REM ( d n1 -- n2 n3 )
Divide d by n1, using symmetric division, giving quotient n3 and remainder n2. All arguments are signed. This word and FM/MOD will produce different results on the same data when exactly one argument is negative and there is a remainder. "S-M-slash-rem"
  T* ( d n -- t )
Multiply d by n, yielding a triple-precision result t. Used in M*/. "T-star"
  T/ ( t +n -- d )
Divide a triple-precision number t by the positive number +n, leaving a double-precision result d. Used in M*/. "T-slash"
  UM/MOD ( ud u1 -- u2 u3 )
Divide ud by u1, leaving remainder u2 and quotient u3. This operation is called UM/MOD because it assumes the arguments are unsigned, and it produces unsigned results. Compare with SM/REM and FM/MOD. "U-M-slash-mod"
  UM* ( u1 u2 -- ud )
Multiply u1 by u2, leaving the double-precision result ud. All values and arithmetic are unsigned. "U-M-star"


2.2.2 Logical and Relational Operations

As in the case of arithmetic operations, Forth's implementation of logical and relational operations optimises speed and simplicity. This does imply some limitation on generality in 16-bit systems, although this limitation rarely is an issue in real-time applications.

In order to fully understand the issues, we can represent the entire set of 16-bit integers in three ways, as shown in Figure 7.

A relational which treats a given 16-bit integer as a point on the full signed number line (a) is needed for true arithmetic or algebraic numbers in which the application has carefully determined that there will never be overflow or underflow. For example, this type of relational is needed to test whether -20,000 is less than +20,000. A relational which treats all values as unsigned (b) is also needed, primarily to test locations of given addresses.

Figure 7.

Figure 7. Three ways of representing 16-bit binary numbers

The number line, or rather number circle, shown in (c) probably needs more explanation than the other two. Relational operators which treat numbers in this way have the advantage of being able to act like signed tests around zero and like unsigned tests around 32K. Thus the relation between two numbers is totally independent of their absolute position on the number circle.

It may seem that the third case is the rarest and least useful. In fact, though, it turns out that it handles the vast majority of signed comparisons in real applications and, better still, it is much faster in execution and easier to implement than a relational that assumes number line (a).

Consider three points on the number circle, as shown in Figure 8. It doesn't matter where these points lie in relation to true zero on the circle. Since numbers increase in a clockwise direction, point A is considered to be greater than point X. Point B is considered to be less than point X. Notice that the maximum range of comparisons in the circle is 32K. Further away than that, a value will appear to lie in the opposite semicircle and will produce the opposite result.

Figure 8.

Figure 8. A circular representation of the range of 16-bit numbers

Thus, a relational which uses this number circle is limited to a 32K range for any single test. Since most comparisons use an extremely small fraction of the total 64K range of the circle, this limitation is generally safe.

The number circle relational is easy to implement because it can be defined in terms of subtract, e.g.,

: < - 0< ;

When A is subtracted from X, the arc of the difference is greater than 180 degrees. That is to say, the result of subtraction will appear to be negative in sign. When B is subtracted from X, the arc of the difference is less than 180 degrees; this difference will appear positive. In the first case, the 0< test produces true, in the second case false.

16-bit versions of Forth use the fully signed model (option a in Figure 7) to implement most relationals, as well as MAX and MIN. 32-bit versions of Forth use the circular model. The operator U< is provided for unsigned comparisons, particularly for 16-bit memory addresses that can extend over the full 0 - 64K range.

 Glossary Single-Precision Logical Operations
  ABS ( n -- +n )
Replace the top stack item with its absolute value.
  AND ( x1 x2 -- x3 )
Return x3, the bit-by-bit logical and of x1 with x2.
  INVERT ( x1 -- x2 )
Invert all bits of x1, giving its logical inverse x2.
  MAX ( n1 n2 -- n3 )
Return n3, the greater of n1 and n2.
  MIN ( n1 n2 -- n3 )
Return n3, the lesser of n1 and n2.
  NEGATE ( n -- -n )
Change the sign of the top stack value; if the value was negative, it becomes positive. The phrase NEGATE 1- is equivalent to INVERT (1's complement of the input value).
  OR ( x1 x2 -- x3 )
Return x3, the bit-by-bit inclusive or of x1 with x2.
  XOR ( x1 x2 -- x3 )
Return x3, the bit-by-bit exclusive or of x1 with x2. The phrase -1 XOR is equivalent to INVERT (1's complement of the input value).
  Double-Precision Logical Operations
  DABS ( d -- +d )
Return the absolute value of a double-precision stack value.
  DMAX ( d1 d2 -- d3 )
Return d3, the larger of d1 and d2.
  DMIN ( d1 d2 -- d3 )
Return d3, the lesser of d1 and d2.
  DNEGATE ( d -- -d )
Change the sign of a double-precision stack value. Analogous to NEGATE.


2.2.3 Comparison and Testing Operations

These operations leave on the stack a number that is based upon a test of the contents of one or more items on top of the stack. In general, the test is destructive, in that it replaces the item(s) tested with the numerical results of the test. All numbers in Forth may be interpreted as true or false values; zero equals false, and any non-zero value equals true. The words below, which perform explicit tests, return -1 for true. Comparison and testing operations generally precede an IF, WHILE, or UNTIL construct.

You may also use - (minus) or D- as a not-equal test, because they return a non-zero difference if the two single- or double-precision numbers are unequal.

 Glossary 0< ( n -- flag )
Return flag, which is true if and only if n is less than zero. "zero-less-than"
  0<> ( n -- flag )
Return flag, which is true if and only if n is not equal to zero. "zero-not-equal"
  0= ( n -- flag )
Return flag, which is true if and only if n is equal to zero. "zero-equal"
  0> ( n -- flag )
Return flag, which is true if and only if n is greater than zero. "zero-greater-than"
  < ( n1 n2 -- flag )
Return flag, which is true if and only if n1 is less than n2. "less-than"
  <> ( n1 n2 -- flag )
Return flag, which is true if and only if n1 is not equal to n2. "not-equal"
  = ( n1 n2 -- flag )
Return flag, which is true if and only if n1 is equal to n2. "equal"
  > ( n1 n2 -- flag )
Return flag, which is true if and only if n1 is greater than n2. "greater-than"
  D0< ( d -- flag )
Return flag, which is true if and only if the double-precision value d is less than zero. "d-zero-less"
  D0= ( d -- flag )
Return flag, which is true if and only if the double-precision value d is equal to zero. "d-zero-equal"
  D< ( d1 d2 -- flag )
Return flag, which is true if and only if d1 is less than d2. "d-less-than"
  D= ( d1 d2 -- flag )
Return flag, which is true if and only if d1 is equal to d2. "d-equals"
  DU< ( ud1 ud2 -- flag )
Return flag, which is true if and only if ud1 is less than ud2. "d-u-less"
  FALSE ( -- flag )
Return a flag that is false (binary zero).
  NOT ( flag1 -- flag2 )
Identical to 0=, used for program clarity to reverse the results of a previous test. For example, the following code would test for a value greater than or equal to zero: 0< NOT
  TRUE ( -- flag )
Return a flag that is true (single-cell value with all bits set).
  U< ( u1 u2 -- flag )
Return flag, which is true if and only if u1 is less than u2. "u-less-than"
  U> ( u1 u2 -- flag )
Return flag, which is true if and only if u1 is greater than u2. "u-greater-than"
 References  Conditionals, Section 2.5.3
   MAX and MIN, Section 2.2.2
   Post-testing loops, Section 2.5.2
   Pre-testing loops, Section 2.5.1
   String comparisons, Section 2.3.4

Back to Forth Programming Handbook Index
Back to COMSOL's Forth products page


(On-line excerpt from broader discussion of Defining Words)

2.7.6 Custom Defining Words

One of the most powerful capabilities in Forth is the ability to define new defining words. Thus, the programmer may create new data types with characteristics peculiar to the application, new generic types of words, and even new classes of words with a specified behavior that is common to each class.

In creating a custom defining word, the programmer must specify two separate behaviours:

  • The compile-time behavior of the defining word (creating the dictionary entry, compiling parameters, etc.).
  • The run-time behavior (the action to be performed by words created by the new defining word).

In the cases discussed in the next two sections, compile-time behavior is described in high-level Forth. Several methods for specifying run-time behavior are also discussed.


2.7.6.1 Basic Principles of Defining Words

There are two ways to create new defining words in Forth. In the one case (using DOES>), the run-time behavior is described in high-level Forth; in the other (using ;CODE), the run-time behavior is described in assembler code. The basic principles are the same.

In Forth, a defining word will create a new dictionary entry when executed. All words defined by the same defining word share a common compile-time and run-time behavior. For example, VARIABLE is a defining word; all words defined by VARIABLE share two common characteristics:

  • Each has one cell allotted in which a value may be stored. These bytes may be initialised to zero in some systems.
  • When executed, each of these words will push onto the stack the address of this one-cell reserved area.

On the other hand, all words defined by CONSTANT, which is another defining word, share two other behaviours:

  • Each has compiled into its parameter field a single-precision value, which was on the stack when CONSTANT was executed.
  • When a word defined by CONSTANT executes, it puts its value on the stack.

In each of these examples, the first behavior (the compile-time action) relates to the physical construction of the word, which is determined when the word is compiled. The second behavior describes what all defined words of that type do when executed. All defining words must have a compile-time behavior and a run-time behavior.

The general definition of a defining word looks like:

:<name> <compile-time behavior> <transition word>
  <run-time behavior> ;

The transition word ends the specification of compile-time behavior and begins the specification of run-time behavior. There are two such transition words: ;CODE, which begins run-time behavior described in code (assembler); and DOES>, which begins run-time behavior described in high-level Forth.

The exact behavior of these two words is discussed in the following sections. The description of compile-time behavior is the same, regardless of which transition word is used. In fact, if you change the transition word and run-time behavior from DOES> plus high-level to ;CODE plus equivalent code, no change to the compile-time behavior is necessary.

The compile-time portion of a defining word must contain an existing defining word to create the dictionary entry. If one or more parameters are to be compiled, or if space for variable data is to be allocated, it is convenient to use a defining word which takes care of that for you. The most common defining words, all of which have been described in previous sections, are: CREATE, CONSTANT, 2CONSTANT, VARIABLE, and 2VARIABLE. CREATE is used when there are no compile-time parameters or when space is to be allocated with ALLOT. CONSTANT and VARIABLE reserve space for one parameter; 2CONSTANT and 2VARIABLE reserve space for a double-cell number or two single-cell parameters.

Every defining word must provide space for data or code belonging to each instance of the new class of words. For example, when a variable is defined, space is allotted for its parameter field. If more space is needed than a standard word (e.g., VARIABLE or 2VARIABLE) allots, the usual approach is to use CREATE followed by ALLOT but, if the application is to be target-compiled for ROM, VARIABLE and ALLOT must be used (see Section 2.7.1).

After a new defining word has been created, it can be used to create specific instances of its class, with the syntax:

<parameters> <defining word> <instance1>
<parameters> <defining word> <instance2>

and so forth. The instance1 and instance2 are names that would be specified in an application. The parameters are optional, depending on the defining word, and are specific to each instance.

When a defining word is executed, it may be followed by any number of words -- such as , (to compile a single-precision value) or C, (to compile an 8-bit value) to fill the allotted storage area with explicit values.

 Glossary ;CODE ( -- )
Begin run-time behavior, specified in assembly code. "semi-colon-code"
  DOES> ( -- )
Begin run-time behavior, specified in high-level Forth. At run time, the address of the parameter field of the particular instance of the defining word is pushed onto the stack before the run-time words are executed. "does"
 References  , and C,, Section 2.9.2
   ;CODE, Section 4.2
   ALLOT, Section 2.9.1
   CONSTANT, Section 2.7.3
   CREATE, Section 2.7.1
   Defining words, Starting Forth
   DOES>, Section 2.7.6.2
   VARIABLE, Section 2.7.2

Back to Forth Programming Handbook Index
Back to COMSOL's Forth products page

Home Shop Products Supported Chips Information Zone Contact Site Map
 
Computer Solutions Ltd
3 b Claremont Road,   West Byfleet,  Surrey  KT14 6DY
 
Telephone: +44 (0) 1932 355630    
Email: sales@computer-solutions.co.uk      Web: www.computer-solutions.co.uk
 
Copyright 2017 Computer Solutions Ltd