FORTH and the numbers

published: 14 May 2019 / updated 24 June 2020

Lire cette page en français

 

Stacking and unstacking integers numbers

The FORTH language stores numbers on a stack called data stack or parametric stack. Stacking a number is very simple:

55

stacks the number 55. There are several methods to unstack this number. The simplest is to display the contents of the last stacked item. The word . (dot) displays 55.

55  ok 
. 55  ok 

If you put multiple numbers on the data stack, this is what happens:

11 45 6543

The numbers are put on the data stack in typing order. The last number entered is always at the top of the stack:

stacked value top -- stack -- bottom
11 11
45 45 11
6543 6543 45 11

The successive unstacking of the numbers displays these in the reverse order of their stacking:

11 45 6543  ok
. 6543  ok
. 45  ok
. 11  ok

To better visualize the mechanism of stacking and unstacking numbers, think of a stack of plates: the last plate placed on the stack will be the first recovery.

At any time you can read the contents of the data stack without having to cause the values stored there to be unstacked using the word .S:

  1 2 3 .S display 1 2 3

This storage principle is also called LIFO stack (Last In, First Out) in some works written in English to designate a stack whose mechanism is: "last in, first out".

With the majority of FORTH language versions, the amount of stackable numbers is quite high, but remains limited. If you stack too many numbers, you will overload the data stack. Likewise, any attempt to unstack a number while the data stack is empty, will display an error message.

Each stacked number can only be an integer in simple precision format. Depending on the case, this number can be considered signed or unsigned:

In signed single precision format, the most significant bit indicates the sign of the number.

Specific gForth

The gForth version of the FORTH language uses a double precision data stack. So, all the manipulations explained here mentioning double precision numbers these numbers are 64-bit values with gForth.

The value -1, deposited on the stack as an integer value has the following binary representation:
  1111111111111111 (simple precision).

This same value can be displayed in absolute value using the word U. instead of the word .:

-1 U.   \ display 65535 (simple precision)
        \ display 4294967295 (gForth)

Depending on whether we consider single precision integers signed or unsigned, their definition interval is:

The use of any number outside this range will have no meaning in a single precision stack environment:

100000 .     \ or
-100000 U.   \ will display a value with no apparent connection to 100000.

This residual value corresponds to the least significant part on simple precision of the number 100,000 and did not overflow when stacked on the data stack.

This has no practical use and can be compared to the hosting attempt a combine harvester in an enclosure reserved for bicycles, with for you less risk of not being sued by bicycle owners damaged by this more than doubtful move. This limitation may seem restrictive at first, but FORTH's performance comes at this price.

Processing whole numbers is much faster than processing numbers expressed in different formats. You will find, in use, that the numbers integers are sufficient in most cases. We will see later, that FORTH is not limited to the treatment of whole numbers simple precision: it suffices to express complex data on more than one cell in the stack.

If you are a complete beginner in FORTH, take the time necessary to properly understand what the data stack is, its mechanism and its evolution: the concept of pile is certainly the most important of the FORTH language.

Elementary arithmetic operations

The arithmetic operators + - * and / act on both values at the top of the data stack. The processed values are always signed single precision integers.

Sum of two integers numbers

To add two numbers, first put them on the data stack:
  22 44 + . display 66

Once stacked 22 and 44, the word + adds these two values and the word . displays the result:

55 1 + 3 + .   \ display 59 and can also be written
55 1 3 + + .

The addition is commutative: the values can be deposited on the stack of data in any order:

55 22 +
22 55 +

This calculation principle is called REVERSE POLISH NOTATION. We can also add two numbers unsigned integers, provided you display the result using the word U. instead of .:

35000 10 + U.

It is also possible to add two different sign numbers:

10 -5 + .    \ display       5
-5 10 + .    \ also display  5

Depending on whether we treat values considered to be signed or not, the result definition intervals must be respectively in [- 32768..32767] or [0..65535]. Any result outside of these intervals would make no sense.

FORTH also has the word 1+ which increments the value located at the top of the 1 unit data stack:

10 1+    \ is equivalent to
10 1 +

Subtract two integers numbers

Let two numbers a and b. The difference of two numbers will be written in FORTH in the form:
  a b - for a-b

Subtraction is not commutative:

10 3 - .       \ display      7
3 10 - .       \ display      -7

The word 1- decrements the value at the top of the stack data of 1 unit:

10 1-    \ is equivalent to
10 1 -

Product of two integer numbers

Let two numbers a and b. The product of two numbers will be written in FORTH in the form:
  a b * for a*b

La multiplication est commutative:

7 5 * .    \ or
5 7 * .    \ display    35

The word 2* multiplies the value at the top of the data stack by two:

5 2*        \ is equivalent to
5 2 *

Quotient of two integer numbers

For the division, only the whole quotient is kept on the data stack:

22 7 / .   \  display    3

The division is not commutative:

15 5 / .    \ display    3
5 15 / .    \ display    0

The rest of the division can be obtained by applying the modulo function:

22 7 MOD .    \ display    1

The modulo function can be used to determine the divisibility of one number by another:

: DIV? ( n1 n2 ---)
    OVER OVER MOD CR
    IF
        SWAP . ." is not "
    ELSE
        SWAP . ." is "
    THEN
    ." divisible by " . ;

The word /MOD combines the actions of / and MOD:

22 7 /MOD . .    \ display    3 1

Product and quotient of three numbers

valid only for single precision stack

If we try an operation of the following type:

30000 3 * 10 / . 

we may be somewhat surprised by the result. But everything can be explained, because the product calculated first delivers a value whose capacity is higher than that accepted by the signed single precision values. To treat these special cases, we will preferably use the word */ which combines multiplication and division operations, but deals with the transient result of the multiplication in double precision format.

Example, either to calculate the price including tax of a good (VAT at 20%), we will define the word ATI as follows:

: ATI ( n1 --- n2) 
    DUP 200 1000 */ + ; 
442 ATI .   \ display    530 

The processed values are expressed in cents.

The word */MOD has the same properties as */, but delivers the quotient and the rest of the operation.

For example, to give immediate and practical application of the concepts already expressed, is the conversion of degrees Fahrenheit and Celsius:

: C>F ( °C --- °F) 
    9 5 */ 32 + ; 
: F>C ( °F --- °C) 
    32 - 5 9 */ ; 
37 C>F .    \ display    98 (results are rounded) 

This example works on all versions of the FORTH language.

Processing of algebraic expressions

Operations can be chained, but an operation in algebraic notation with parentheses must be converted to RPN notation taking into account the order of priority of operations. FORTH does not use parentheses in arithmetic operations:

let's see the algebraic expression ( 2 + 5 ) * ( 7 - 2 )

it is written in FORTH2 5 + 7 2 - *

During an algebraic notation conversion operation infixed to notation reverse polish, always start with the highest parenthesis level nested and from the left. Write the transcription in Polish notation reverse of each operation on separate lines, successively from top to bottom, putting them in the extension of the expression algebraic expressed in the initial formula:

By taking each level in order, we rewrite the formula:<
  2 5 + 7 2 - * 5 2 + 3 * /

306/5000 It's shocking? But all the interpreters / compilers work like this when they have to evaluate an algebraic formula. In algebraic notation, parentheses only serve to isolate an expression as a subexpression which becomes a member of a more general expression.

In IT as in arithmetic, an operator is always working on two operands and only two operands simultaneously. The result of an operation relating to two operands delivers a value which can in turn become the operand of another operator. The order of operands and operators is fundamental:

algebraic notation reverse polish
(2+3)*5 2 3 + 5 *
2+(3*5) 2 3 5 * + or 3 5 * 2 +

All arithmetic problems can be solved in this way, is just a matter of habit. The example given above illustrates perfectly the rigor which the Forth programmers must demonstrate. This rigor guarantees unambiguous operation of the programs, whatever their level of complexity.

The postfixed notation (RPN) used by the FORTH language can quickly become a real headache when dealing with somewhat complex arithmetic expressions:

Infixed to postfixed notation converter

Handling data on the stack

The data stack is the fundamental element of the FORTH language for processing of data. Its operation is identical to that of the stack managed by the microprocessor. In certain situations, the data processed by different definitions must be reordered or duplicated.

The word DUP duplicates the contents of the top of the data stack:

10 DUP . .   \ display   10 10
5 DUP * .    \ display   25

The word OVER duplicates the second element of the data stack:

5 15 OVER . . .   \ display   5 15 5

The word SWAP reverses the two elements at the top of the data stack:

1 3 SWAP . .   \ display   1 3
Starting Forth, Copyright FORTH, Inc.
Used with permission
www.forth.com/starting-forth/

The word ROT rotates on the three elements at the top of the data stack:

1 2 3 ROT . . .   \ display   1 3 2

The word -ROT performs a reverse rotation on three elements. His behavior is similar to executing two successive ROT.

The word PICK places the nth element on the stack at the top of the data stack data, n not included. The starting point for counting the items to be processed is 0 and not 1, element number zero being located immediately after the parameter processed by PICK. The sequence 0 PICK is similar to DUP, 1 PICK to OVER. There is no error handling if n is greater than the number of items dropped on the data stack. Example:

1 2 3 4 5 6    4 PICK    \ stacks 2 because 6 is element n ° 0, 5 is element n ° 1, etc ...

The word ROLL rotates on the first n elements of the data stack, n not included. As for PICK, the base starting point for counting the elements to be treated is 0:

1 2 3 4 5 6 4 ROLL . . . . . .   \ display    2 6 5 4 3 1

Here are some examples of how to use these data stack manipulators:

: SQUARED ( n --- n2) 
    DUP * ; 

The word SQUARED raises any integer squared:

2 SQUARED .   display   4 
3 SQUARED .   display   9 
4 SQUARED .   display  16 
 
: TO-CUBE ( n --- n3) 
   DUP DUP * * ; 

Le mot AU-CUBE élève un nombre entier quelconque au cube:

    2 TO-CUBE .   display   8 
    3 TO-CUBE .   display  27 

332/5000 Attention, do not use too high values, because a result higher than 32767, for a 16-bit data stack, becomes false. Always keep in mind that data processed are simple precision integers, therefore of limited capacity. We will see later how to deal with larger numbers.

Passing parameters through the return stack

Next to the parameter stack, there is a second stack in FORTH, called return stack because it is used by the internal interpreter to find the address back on each call to a procedure.

There are sometimes extreme cases where we may have to store one or more parameters elsewhere than on the data stack, this to simplify some few scabrous manipulations. the most convenient solution, among others, is the return stack. This stack is accessible by the words >R and R> with some precautions so as not to compromise operation of this internal stack.

The word >R transfers an integer from the data stack to the return stack.

The word R> transfers an integer from the return stack to the data stack.

An >R R> operation is null. At the end of the definition, there must be as many >R as R> under penalty of disturbing some little the normal course of your definition. Example of use:

: SQUARED ( n --- n^2)              \ squared elevation 
    DUP >R                          \ transfer of copy to return stack 
    CR ." Le carré de " .           \ initial value display 
    ." est " R> DUP * .  ;          \ recovery value deposited on return stack 
                                    \ and display of its square 
////

Control of the display of integer numbers

In FORTH, the single precision integers deposited on the data stack can be displayed by executing the word . (dot). But other words allow to execute a more presentable display.

The word .R displays a signed single precision number framed on the right in a field of n characters. Example:

  3 10 .R  display  3 (3 preceded by 9 spaces)
  101 10 .R  display  101 (101 preceded by 7 spaces)

And here is a very practical application:

: C>F ( °C --- °F)       \ Convert Celsius to Fahrenheit 
    9 5 */ 32 + ; 
: .HARDENING ( °C ---)      \ °C and °F formatted displays 
    DUP 6 .R ." °C " 
    C>F 6 .R ." °F" ; 
: STEEL-HARDENING ( ---)       \ Tempering temperature table 
    CR ." STEEL HARDENING COLORS:" CR 
    CR ." dark red............ "   680 .HARDENING 
    CR ." dark cherry red..... "   740 .HARDENING 
    CR ." cherry red.......... "   770 .HARDENING 
    CR ." light cherry red.... "   800 .HARDENING 
    CR ." light red........... "   850 .HARDENING 
    CR ." very light red...... "   900 .HARDENING 
    CR ." red yellow.......... "   950 .HARDENING 
    CR ." yellow.............. "  1000 .HARDENING 
    CR ." yellow light........ "  1100 .HARDENING 
    CR ." yellow white........ "  1200 .HARDENING 
    CR ." white............... "  1300 .HARDENING 
    CR CR ; 

We execute STEEL-HARDENING:

steel-hardening
STEEL HARDENING COLORS:
 
dark red............ "   680°C   1256°F
dark cherry red..... "   740°C   1364°F
cherry red.......... "   770°C   1418°F
light cherry red.... "   800°C   1472°F
light red........... "   850°C   1562°F
very light red...... "   900°C   1652°F
red yellow.......... "   950°C   1742°F
yellow.............. "  1000°C   1832°F
yellow light........ "  1100°C   2012°F
yellow white........ "  1200°C   2192°F
white............... "  1300°C   2372°F

The word U.R proceeds in the same way as .R, but displays the unsigned whole number:

1 10 U.R     \ display         1
-1 10 U.R    \ display     65535

Example, either to display a sequence of addresses in hexadecimal and the equivalent in decimal in parentheses:

: .H(D) ( n ---) 
    BASE @ >R           \ current digital database backup 
    DUP HEX 4 U.R       \ display in hexadecimal in a 4 char field. 
    SPACE 40 EMIT       \ display of a space and open parenthesis 
    DECIMAL 5 U.R       \ display in decimal in a 5 character field 
    ." )"               \ display of closed parenthesis 
    R> BASE ! ;         \ current digital base restoration 
DECIMAL 
CR 256 .H(D)      \ display    100    (  256) 
CR 65535  .H(D)   \ display    FFFF   (65535) 
HEX 
CR FF .H(D)       \ display    FF     (  255) 
CR FFFF .H(D)     \ display    FFFF   (65535) 

Double precision integers

Integers with gForth

With the gForth version of the FORTH language, single integers are 32 bits.

With gForth, the following explanations mentioning double precision numbers relate to 64-bit values.

For versions on Arduino, double precision numbers are 32-bit.

FORTH does not natively know floating point numbers. However, routines can be compiled to perform calculations on this type of numbers, but it will be at the expense of the processing speed. If you want to process large quantities, such as your bank or the price of your next car you can use the double numbers precision also called double precision numbers. These are distinguished from whole numbers simple precision by introducing a point:

135246. D.    \ display    135246 

The point can be placed elsewhere than at the end of the number:

135.246 D.    \ display    135246 

The elementary operations executable on double precision numbers are addition and subtraction:

3. 5. D+    \ similar as    3 + 5 
3. 5. D-    \ similar as    3 - 5 

Double precision numbers at the top of the stack can be displayed by the following words:

Exemples:

5. D.           \ display    5 
-1. UD.         \ display    4294967295 (18446744073709551615 in gForth) 
10. 12 D.R      \ display    10 
35.3 12 UD.R    \ display    353 

A signed single precision integer can be transformed into a signed double precision number by running the word S>D. Examples:

10 S>D D.       \ display    10 
-5 S>D D.       \ display    -5 

To transform an unsigned single precision whole number into a double number unsigned precision, simply stack the simple precision value 0 on the stack after stacking the unsigned simple precision integer. Example:

10 0 D.         \ display    10 
-5 0 D.         \ display    65531 The reverse operation is possible if the  
                \ double precision value is between 0 and 65535: 
10. DROP .      \ display    10 

When a double precision number is deposited on the data stack, the DPL variable keeps the position of the decimal point; if it's a single precision number which is deposited on the data stack, the DPL variable contains the value -1. We can use this value to define a word ensuring a conditional conversion:

: ?S>D ( d ou n --- d) 
    DPL @ 0< 
    IF 
        S>D 
    THEN ; 
10 ?S>D D.      \ display        10 
10. ?S>D D.     \ display    10 

It is interesting to compare the values of DPL for different positions from the decimal point when processing a double precision number:

3314. DPL @ .    \ display    0 
331.4 DPL @ .    \ display    1 
33.14 DPL @ .    \ display    2 
3.1.4 DPL @ .    \ display    1 

Only the position of the second point is kept in DPL .

Double precision multiplication and division operations are not defined, with the exception of the words D2* 0 and D2/:

10. D2* D.      \ display    20 
10. D2/ D.      \ display    5 

But with FORTH primitives, there is no problem in defining words to multiply a double precision number by three, four, five or more if applicable:

: D3* ( d --- d*3) 
  2DUP 2DUP D+ D+ ; 
: D4* ( d --- d*3) 
  D2* D2* ; 
: D5* ( d --- d*5) 
  2DUP D2* 2SWAP D3* D+ ;    \ etc... 

Simple / double precision mixed operations

Double precision numbers are generally too large to be interesting to apply a treatment of the product or quotient type to them. However, some operators mixed arithmetic allow these operations to be performed.

The word MU/MOD divides an unsigned double precision number by a simple precision number signed; leave the double precision quotient on the stack unsigned and the rest signed. Example:

11. 2 MU/MOD D. .   \ display    5 1 

The word M/MOD divides a double precision number signed by a number simple precision signed; leave the quotient on the stack and the rest in format simple precision signed. Example:

-12. 2 M/MOD . .    \ display    -6 0 

The word U*D multiplies two unsigned single precision numbers and leaves the result in unsigned double precision format on the stack. Example:

10 30 U*D D.        \ display     300 

The word *D multiplies two signed single precision numbers and leaves on the stack the result in double precision format signed. Example:

-10 30 *D D.        \ display    -300