
Topics covered in this tutorial:

* About this tutorial...
* Running examples
* Decimal number entry
* Numbers and "literals"
* Moving between number sizes
* Useful mixed number size operators
* Useful compound arithmetic operators
* Defining 32-bit (or "double") numbers for numeric input
* The order of bytes in memory
* Bit numbering conventions
* Logical AND, OR and XOR operations
* Signed and unsigned numbers
* Displaying signed and unsigned numbers
* Comparing signed and unsigned numbers
* Logical and Arithmetic number shifting
* Numeric bases
* The useful numeric base control functions: #B, #D, and #H
* ASCII characters
* Number formatting
* Using scaled and fractional integer arithmetic instead of floating point
* Examples and problems


----------------------
About this tutorial...
----------------------

This tutorial is the most advanced so far, and is intended to give you
some important background to general methods of computing using HeliOS,
with particular emphasis on "numbers".

Again the subject matter is miscellaneous, and you may pick and choose what
you want to read: however, we do strongly recommend that you become familiar
with EVERYTHING that is presented here, since all of it is important for
general HeliOS programming.

At the end of this tutorial are a few example problems for you to try.


----------------
Running examples
----------------

This tutorial contains many shorts sections of example code.

Remember that you can easily run these examples from the editor by simply
highlighting them and pressing Left-Amiga-e.

Note that many short example phrases have associated comments to the right
of them, often using a preceding "->" symbol.

These "->" symbols, and the following comments, are merely intended to
indicate what the code fragment is doing, and should not be included in
highlighted sections of code to be run-tested: if you do try to run these
comments, you will merely get an error.


--------------------
Decimal number entry
--------------------

You have seen already in the previous tutorials that double length (32-bit)
numbers are specified by including a "." character within the number as it
is entered.

As we said earlier, this is not specifically meaningful in terms of being a
decimal point: all the "." character does is tell HeliOS to interpret the
number as 32-bit.

However, there is a little trick whereby HeliOS can actually use the "."
character to indicate a decimal point in number entry, because HeliOS does
actually store the offset position of this "." in a special system variable
called DPL.  This simple mechanism is included to allow you to interpret
numbers as decimals within your program if you wish.

Actually, if a "." character is encountered in a numeric entry, the DPL
variable will contain the number of characters AFTER the "decimal point".

Thus:

123.00   DPL @L .  -> Prints "2"
1.2300   DPL @L .  -> Prints "4"
  ^^^^
  DPL stores this character count (= number of characters after ".")

If you look in DPL after entering a 16-bit number you will always find the
value "-1".

Thus:

12300    DPL @L .  -> Prints "-1"

Having registered the decimal point position using DPL, it is a simple
matter for your program to store this information and use it to deal with
the number as a "decimal" if required.

Note that you MUST READ "DPL" IMMEDIATELY after a number has been entered,
otherwise it may no longer contain a value relevant to that number.

See later in this tutorial for more details on how you might handle decimal
numbers using integer arithmetic.


----------------------
Numbers and "literals"
----------------------

You will see the term "literal" used in HeliOS (and FORTH) documentation,
and it is useful to know a little more about what this means.

The term "literal" is used loosely in FORTH to specify that a number (or
even sometimes a text string) should be evaluated and (possibly) compiled
into the dictionary rather than used immediately.

Look at the definition of the HeliOS word "LITERAL":

LITERAL      ( n1 _ _ _ )

             Used within colon definitions to compile the CFA of the word
             "LIT" and then compile n1 as 16-bit literal into dictionary.

             At run time n1 will be pushed onto stack.

             This is a state-sensitive word which does nothing if not
             used within a colon definition.

This word LITERAL is a state-sensitive word, and thus has different actions
depending upon whether the system is in compile-mode or execute-mode (i.e.
whether the system is compiling a colon definition or executing directly).

If the word LITERAL is executed in direct execution mode it will just do
nothing at all.

If the word LITERAL is executed while a colon definition is being compiled
(within the definition) it will cause the current 16-bit top-of-stack number
to be compiled into the body of the word being defined, along with special
code which will place that number on the stack when the word is run later.

This is achieved by compiling the CFA of a special word LIT, followed by
the 16-bit numeric value.  At run time, the action of LIT will be to put
the following number onto the stack and advance the program pointer to the
next position after the number storage space.

When an apparently "ordinary number" is interpreted by HeliOS, either in
the command line or a program, HeliOS always first has to read the actual
numeric expression in the text and "interpret" the number.

If the number is a legal and valid expresion, the process of interpreting
it will leave the number on the HeliOS stack.

HeliOS then has to do something with the number, and in this case it needs
to decide whether to simply carry on, with the number on the stack, or
compile the number into the current word definition for use later.

HeliOS checks to see if it is currently within the process of interpreting
a colon definition (i.e. if it is currently compiling a word) and if so
it will compile the number as a "literal".  If it is not within a colon
definition HeliOS will simply carry on with the number on the stack.


---------------------------
Moving between number sizes
---------------------------

Often you need to change from a 16-bit to a 32-bit number or address, and
it is useful to be able to do this fluently and quickly.

Addresses are very easy to convert:

16-bit to 32-bit ->     Use W>L

32-bit to 16-bit ->     Use L>W

Signed numbers are converted as follows:

16-bit to 32-bit ->     Use S->D

                        or...

                        If the number to convert is known to be positive,
                        you can just place an extra 16-bit "0" cell on
                        the stack, as with unsigned numbers.

                        e.g.

                        7   = 16-bit value 7

                        7 0 = 32-bit value 7

                        or...

                        If the number to convert is known to be negative,
                        you can just place an extra 16-bit "-1" cell on
                        the stack.

                        e.g.

                        -7    = 16-bit value 7

                        -7 -1 = 32-bit value 7


32-bit to 16-bit ->     Use DROP, but note that if the number exceeds
                        16-bit limits, the result will be truncated.

Unsigned numbers are converted as follows:

16-bit to 32-bit ->     Place an extra 16-bit "0" cell on the stack.

                        e.g.

                        7   = 16-bit value 7

                        7 0 = 32-bit value 7

32-bit to 16-bit ->     Use  DROP, but note that if the number exceeds
                        16-bit limits, the result will be truncated.


----------------------------------
Useful mixed number size operators
----------------------------------

Often it is useful to be able to carry out arithmetic operations on 16-bit
and 32-bit numbers without having to convert the numbers to the same size
first.

HeliOS has several mixed size operators for commonly required functions:

     M*            ( n1 n2 _ _ _ d1 )

                   Returns the double number signed product of n1 * n2 = d1.

     M*/           ( d1 n1 n2 _ _ _ d2 )

                   Multiplies d1 by n1 and divides by n2 returning quotient
                   d2.

     M+            ( d1 n1 _ _ _ d2 )

                   Returns the double number sum of a single and a double
                   number.

     M-            ( d1 n1 _ _ _ d2 )

                   Returns the double number difference between a single and
                   a double number.

     M/            ( d1 n1 _ _ _ n2 )

                   Returns the signed single number quotient of the mixed
                   magnitude division of d1 by n1.

     M/MOD         ( d1 n1 _ _ _ n2 n3 )

                   Returns the signed remainder and quotient, n2 and n3, of
                   the division d1 / n1.

                   The remainder n2 takes its sign from d1.


------------------------------------
Useful compound arithmetic operators
------------------------------------

Some of the most often used arithmetic functions have special compound
operators which improve compilation and execution speed as well as being
easier to type into source code: they also take up less memory space.

For example, to add 1 to a number on the stack you can use the following
long or short methods:

1 +            -> Two words

or

1+             -> Single word is quicker and easier

There are a number of these special compound operators, and they are well
worth learning, so we include below a brief listing.

Single length:

     1+            ( n1 _ _ _ n2 )

                   Returns n1 incremented by 1 as n2.

     1-            ( n1 _ _ _ n2 )

                   Returns n1 decremented by 1 as n2.

     2*            ( n1 _ _ _ n2 )

                   Returns n1 * 2 as n2.

     4*            ( n1 _ _ _ n2 )

                   Returns n1 * 4 as n2.

     2+            ( n1 _ _ _ n2 )

                   Returns n1 incremented by 2 as n2.

     2-            ( n1 _ _ _ n2 )

                   Returns n1 decremented by 2 as n2.

     2/            ( n1 _ _ _ n2 )

                   Returns n1 / 2 as n2.

     4/            ( n1 _ _ _ n2 )

                   Returns n1 / 4 as n2.

Double length:


     D2*           ( d1 _ _ _ d2 )

                   Returns d1 * 2 as d2.

     D4*           ( d1 _ _ _ d2 )

                   Returns d1 * 4 as d2.

     D1+           ( d1 _ _ _ d2 )

                   Returns d1 incremented by 1 as d2.

     D1-           ( d1 _ _ _ d2 )

                   Returns d1 decremented by 1 as d2.

     D2+           ( d1 _ _ _ d2 )

                   Returns d1 incremented by 2 as d2.

     D2-           ( d1 _ _ _ d2 )

                   Returns d1 decremented by 2 as d2.

     D2/           ( d1 _ _ _ d2 )

                   Returns d1 / 2 as d2.

     D4/           ( d1 _ _ _ d2 )

                   Returns d1 / 4 as d2.


See the "Dictionary.doc" also for details of some useful and time saving
special variable and memory "storage" words such as +!, 1!, CYCLE+! etc.


-------------------------------------------------------
Defining 32-bit (or "double") numbers for numeric input
-------------------------------------------------------

You have already learned that to specify a number as "double", or 32-bit,
you must include a "." anywhere within the input number representation.

For example:

1     -> 16-bit number 1
1.    -> 32-bit number 1
1000  -> 16-bit number 1000
1.000 -> 32-bit number 1000
10.00 -> 32-bit number 1000
100.0 -> 32-bit number 1000
1000. -> 32-bit number 1000
    ^
    A "." character in any position specifies that a number is 32-bit

This convention of using "." as a 32-bit number specifier was part of the
traditional FORTH language, and has been retained by HeliOS.

However, HeliOS includes two extra choices of "specifier character" when
you want to input a 32-bit number.

These are the characters "-" and ",", so in HeliOS we can have:

100.0 -> 32-bit number 1000    ->  Using a "point" specifier
1,000 -> 32-bit number 1000    ->  Using a "comma" specifier
10-00 -> 32-bit number 1000    ->  Using a "Hyphen" specifier

Note that the position of any of these specifiers, if present, will be
recorded in DPL.

Another interesting point about this is that in traditional FORTH you were
only allowed to have one instance of the double number specifier within any
one number string.

Thus, something like this would produce an error in traditional FORTH:

200.00.00

In HeliOS, however, you can have multiple specifiers, so that all of the
following expressions are legal:

2,000,000.00

20-00-00

20.00-00-1,00

If several of the above characters are encountered in the numeric expression
only the position of the last, or "rightmost", one will be stored in DPL.

Please read the Dictionary.doc section on "GENERAL NOTATION" for more
details on numeric entry.


----------------------------
The order of bytes in memory
----------------------------

It is very important to know that Motorola 680xx CPUs, by convention, store
multiple byte quantities in the following order:

High order byte  -> stored in lowest address
Low order byte   -> stored in highest address

This is not as silly as it seems, because you can easily read numbers in
hex format stored in this way, especially when they are printed out in a
standard memory dump like this:

 ADDRESS  D8 D9 DA DB DC DD DE DF  E0 E1 E2 E3 E4 E5 E6 E7  89ABCDEF01234567

  7A5A560 00 AE BC 7C 06 F8 67 00  00 A6 BC 7C 11 7E 67 00  ...|..g....|.~g.
          ^^       ^^
          ||       ||
      High byte  Low byte     ------------------> High memory
          |||||||||||
          ^^^^^^^^^^^
           Longword

It is important always to be aware of this storage convention, especially
when dealing with numbers on the HeliOS stack.

A "double" 32-bit number on the HeliOS stack always has its low word in the
higher memory address, and its high word in the lower memory address.

However, there is a further twist to this story.....

The HeliOS system and Return stacks are filled from high to low memory, so
the TOP of the stack has the lowest memory address!

The stack grows downwards in terms of memory address, and the stack pointer
is decremented with each new item added to the stack.

This means, in conjunction with the previously mentioned Motorola storage
convention, that the high order word of any double number on the stack is
always nearer to the top of the stack (the last item added) than the low
order word.


Look at this example:


                       Low Memory/Top of stack

                                  |

123 = Top of stack item 16-bit number 123 = Last item added to stack

456 = 2nd on stack item 16-bit number 456 = Next-to-last item added to stack

0   = High word of 3rd on stack item 32-bit number 789. }  Two stack cells
                                                        }- holding 32-bit
789 = Low  word of 3rd on stack item 32-bit number 789. }  number value

                                  |

                     High Memory/Bottom of stack


We put the top of the HeliOS stack uppermost in the above illustration
because we conventionally talk of the "Top" of the stack.

However, if we wanted to display the progression from high to low memory,
we might write:

                     High Memory/Bottom of stack

                                  |

789    <- Low  word of 3rd on stack 32-bit number 789.
0      <- High word of 3rd on stack 32-bit number 789.
456    <- 2nd on stack 16-bit number 456
123    <- Top of stack 16-bit number 123

                                  |

                       Low Memory/Top of stack

Try experimenting with the stack in the interpreter until you are VERY
familiar with all this, because there is no doubt that you will make many
mistakes over stack handling if you do not have an absolutely clear idea
of what is going on.


-------------------------
Bit numbering conventions
-------------------------

All numbers are stored in the computer as binary bit sequences, and there
is a convention which specifies these bits in terms of a "bit number".

Bit-positions within any number are generally specified in a sequence from
right to left, and are numbered from 0 on the right.

The rightmost bit is called the "least significant bit", or "LSB".

The leftmost bit is called the "most significant bit", or "MSB".

(The leftmost bit is also sometimes called the "sign bit". See later.)

For example:

An 8-bit number -> 11111111
                   ^      ^
                  MSB    LSB
                   7      0

A 16-bit number -> 1111111111111111
                   ^              ^
                  MSB            LSB
                  15              0


----------------------------------
Logical AND, OR and XOR operations
----------------------------------

The HeliOS words AND, OR, and XOR use binary bit logic to combine two
numbers.

As you may know, in these operations each bit is treated independently, on
a columnar basis, and there are no carries from one bit-column to the next.

Let us first examine the AND operation.

In an AND operation, for any result-bit to be "1", the corresponding bits
in the two source numbers must BOTH be "1".

Let us see what happens when we AND two binary numbers:

        0000000011111111
        0110010110100010  AND
        ----------------
        0000000010100010

Notice in this example that the top number contains all "0"s in the high
byte and all "1"s in the low byte.  The effect of this on the second number
in this example is that the pattern of the low-order eight bits is retained,
but the high-order eight bits are all set to zero.

Here the top number is being used as a "mask" to mask out the high-order
byte of the second number.


Let us now examine the OR operation.

In an OR operation, a "1" in the same column of EITHER number produces a
"1" in the result.

Let us see what happens when we OR two binary numbers:

        1000100100001001
        0000001111001000  OR
        ----------------
        1000101111001001

Once again, note that each column is treated separately with no carries,
and in this case the "1"s in both numbers have been "logically" combined.


Logical operations can be used to test individual bits of a number, and
this can be useful for many things, such as reading hardware registers or
testing bits in a flag word.

For example, you might have a 16-bit flag word, and you might want to test
the current state of bit 3.

Here is how you could do it.

        1011101010011100  <- The source flag word we wish to test
                    ^
             We want to test bit 3

So we can use a mask with only this bit set:

        0000000000001000  <- Mask
                    ^
         Only the bit we want to test is set

Then we use an AND operation:

        1011101010011100
        0000000000001000   AND
        ----------------
        0000000000001000

Since the bit was "1", the result is non-zero, which can be used as a
"true" value representing the state of the tested bit: had it been "0"
the result would have been "0" or "false".

We can use the OR operator, if we wish, to set a bit of our flag word.

Let us assume we want to set bit 1 of the flag word:

First we make a mask with only bit 1 set:

        0000000000000010
                      ^
               Only bit 1 set

Then we use the OR operation:

        1011101010011100
        0000000000000010   OR
        ----------------
        1011101010011110
                      ^
               Bit 1 is now set

What if we wanted to clear bit 2 of our flag word without changing any of
the other bits?

In this case we would make a mask like this with only bit 2 clear:

        1111111111111011
                     ^
            Only bit 2 is clear

Now we could use the AND operation again:

        1011101010011100
        1111111111111011  AND
        ----------------
        1011101010011000
                     ^
            Bit 2 is now clear

Notice that we used an inverse mask that contains all "1"s except for the
bit which we wanted to set to "0".


Another useful logical bit operator is XOR.

The XOR operator tests corresponding bits and only generates a "1" in the
result when both "source" bits are different.

This has an interesting and useful effect.

Here is an example, using a mask with all bits set to "1":

        1011101010011100
        1111111111111111  XOR
        ----------------
        0100010101100011

Notice that this operation has simply flipped all the bits of the top value.

This is a very common use of XOR, and is well worth remembering:

* Performing an XOR with a mask of all "1"s will flip all the bits of the
  source value.

Look at another example, with a mask of all "0"s:

        1011101010011100
        0000000000000000  XOR
        ----------------
        1011101010011100

* Performing an XOR with a mask of all "0"s will leave all the bits of the
  source value unchanged.


---------------------------
Signed and unsigned numbers
---------------------------

The Amiga, like other computers, stores numbers in a binary format, and
as far as the computer is concerned numbers are collections of binary bits.

However, there are two distinctions which the computer makes in order to
interpret the binary number data in memory.

1. The size of the number storage space in terms of byte length.

   The Amiga CPU recognises all numeric data as being one of 3 sizes:

   Byte  8  bits wide
   Word  16 bits wide
   Long  32 bits wide

2. Whether the number is signed or unsigned

   The Amiga CPU can carry out arithmetic in two ways:

   a. Using twos-complement signed number arithmetic (signed)
   b. Assuming numbers are simple positive integers (unsigned)

It is important to be aware whether you are dealing with signed or unsigned
numbers, but remember that this distinction applies only to the operations
which you carry out on the numbers and the way you interpret them.  As far
as the computer is concerned all numbers are stored in an identical way as
collections of binary bits.

It is important also to be aware of the limits of numeric values within the
"Byte", "Word", and "Long" number storage sizes.

Numeric values lie in the following ranges:

Unsigned number ranges:

   Byte = 8  bits = 0 to 255           in decimal range
   Word = 16 bits = 0 to 65,535        in decimal range
   Long = 32 bits = 0 to 4,294,967,295 in decimal range

Signed number ranges:

   Byte = 8  bits = -128         to  127         in decimal range
   Word = 16 bits = -32,768      to  32,767      in decimal range
   Long = 32 bits = -2147483648  to  2147483647  in decimal range


In HeliOS the standard number size (or "width") for most stack operations
is 16-bit or "Word" length.

Any 16-bit numeric value stored on the stack can be interpreted as either
signed or unsigned: the stored binary representation is identical in each
case.

Assuming that all the binary bits in the 16-bit binary representation are
set (e.g. 1111111111111111), the number will have the maximum possible value
of decimal 65535 if it is interpreted as an unsigned number.

On the other hand, if it is interpreted as a signed number, this maximum
16-bit binary representation will have the signed value of -1.

In case you are not yet fully familiar with the way computer CPUs handle
signed numbers, here is a quick overview.

Let us consider 16-bit numbers.

So that we can denote a positive or negative number within our number
storage space we designate one bit (the top, or highest bit) as a sign
indicator.  This means that we have to sacrifice one bit from our actual
numeric representation and this reduces the maximum stored numeric value
to half of its unsigned value, as represented by the remaining 15 bits.

Since we use the highest bit

1111111111111111
^
sign bit

as a sign indicator, only the remaining bits

1111111111111111
 ^^^^^^^^^^^^^^^
 15 value bits

are available to specify the actual number.

In 15 bits we can only express a number up to decimal 32767.

When the sign bit is set, indicating a negative number, we can have an
equally large number as a negative value.

This means that with a signed 16-bit number we can represent a number from
-32768 decimal to +32767 decimal.

This matter of using a sign bit is slightly more sophisticated than might
at first appear, and it is not simply a case of setting the sign bit to
indicate the sign of a number.

This leads us on to the topic of twos-complement arithmetic.

As an illustration, first imagine that you have before you a simple counter
such as you might see on a tape recorder.

Let us imagine that the counter has three digits.

As the tape winds forward the counter wheels turn and the number increases
up to a maximum value and then starts again from 0.

If the counter set to 0 and then the tape is wound backwards, the first
number to appear on the counter will be 999, followed by 998, 997 etc.

Since the first number to appear as we started backwrds was 999, and since
our counter does not actually display "-" numbers, we could say that 999 is
equivalent, for all intents and purposes, to -1.  In a similar way, we can
say that 998 is equivalent to -2, and so on.

The representation of signed numbers in a computer is very similar.

Let us start with the number

0000000000000000  = 0

Going forward by one from "0" we would get

0000000000000001  = 1

Going backward by one from "0" we would get

1111111111111111  = -1 (signed) = 65535 (unsiged)

Going backward by one more we would get

1111111111111110  = -2 (signed) = 65534 (unsigned)

This may be comprehensible, but it may also seem a little arbitrary, so
let us see how the scheme can be useful when doing signed arithmetic.

Here we have a simple arithmetic operation:

2 - 1 = 2 + (-1) = ?

or

     2
    -1 +
   ___

     ?

(Subtracting one from two is the same as adding two plus negative one.)

In binary notation the two looks like this:

0000000000000010

In binary notation, as we have seen the -1 looks like this:

1111111111111111

The computer can simply add these numbers in the normal way, so that when
the total of any column exceeds one, it carries a one into the next column,
and so on.

Here is the result of the above example operation:

   0000000000000010
 + 1111111111111111
   ----------------
  10000000000000001
  ^
  Final carry out, which is dropped, or "lost"

As you can see the computer has carried a 1 into every column, all the way
across and ended up with a one in the seventeenth place.

Since the stack is only 16 bits wide the result is truncated:

0000000000000001 = 1

This is the right answer, and you can see that this simple convention for
representing negative numbers allows the computer to carry out arithmetic
operations very easily.

If you want to learn more about twos-complement arithmetic there are many
texts on computing topics which discuss the matter in more depth.

For now, you might be interested to see the relationship between positive
and negative signed numbers:

0000000000000001 = 1
1111111111111111 = -1

0000000000000010 = 2
1111111111111110 = -2

0000000000000011 = 3
1111111111111101 = -3

Can you see the relationship between a positive number and its negative
counterpart in twos-complement arithmetic?

Here is the simple rule:

To change a positive number into its twos-complement negative counterpart
you must:

1. Flip all the bits so that all "1"s become "0"s and all "0"s become "1"s.

2. Add 1

3. Ignore any high-order carry

Try it!


--------------------------------------
Displaying signed and unsigned numbers
--------------------------------------

HeliOS has a range of functions for printing numbers to the screen, and
these include both signed and unsigned variants.

In general, unsigned number printing functions contain a "U" to indicate
that they are unsigned operators.

Here are a few examples:

.    Print a signed 16-bit number

D.   Print a signed 32-bit number

U.   Print an unsigned 16-bit number

UD.  Print an unsigned 32-bit number

There are more number output functions, and you can find descriptions of
them in the "Dictionary.doc" file.


-------------------------------------
Comparing signed and unsigned numbers
-------------------------------------

It is well worth while looking in the "Dictionary.doc" section devoted to
"Comparisons and Tests" in order to familiarise yourself with the various
number comparison operators provided by HeliOS.

You must always ensure that you are testing numbers of the same kind, and
you should also always make sure that signed numbers lie within the legal
range for the size of number you are using (Byte, Word, or Long).

Failure to distinguish signed and unsigned numbers is a frequent cause of
problems for beginners when they are testing or comparing numbers which are
potentially negative.

Always remember that, for example, if you are using a 16-bit number storage
space you must be careful once you start using numbers which can possibly
exceed 32,767.  This is because any number larger than this falls within
the range of "potentially" negative numbers, and you may run into trouble
if you have been a little careless over number test operators.

For example, your program may include a numeric size test like this:

APPLES @ ORANGES @ > IF...............

Look at how this works as you increase the values of the variables:

200   APPLES ! 100   ORANGES !        APPLES @ ORANGES @ >     Result = 1
2000  APPLES ! 1000  ORANGES !        APPLES @ ORANGES @ >     Result = 1
20000 APPLES ! 10000 ORANGES !        APPLES @ ORANGES @ >     Result = 1
30000 APPLES ! 10000 ORANGES !        APPLES @ ORANGES @ >     Result = 1
40000 APPLES ! 10000 ORANGES !        APPLES @ ORANGES @ >     Result = 0

Can you see why this goes wrong?

Yes, 40000, as a "signed" value, is actually -25536, and we were using a
signed comparison operator!

Of course, if you use an unsigned comparison, everything is OK:

40000 APPLES ! 10000 ORANGES !        APPLES @ ORANGES @ U>    Result = 1

This is a very important point, and if you cannot quite see why there is a
potential problem here we recommend that you read again the documentation
on signed and unsigned numbers, with particular attention the the ranges of
values available for each size of number (Byte, Word, or Long).

Here again is the important information concerning size ranges:

Unsigned number ranges:

   Byte = 8  bits = 0 to 255           in decimal range
   Word = 16 bits = 0 to 65,535        in decimal range
   Long = 32 bits = 0 to 4,294,967,295 in decimal range

Signed number ranges:

   Byte = 8  bits = -128         to  127         in decimal range
   Word = 16 bits = -32,768      to  32,767      in decimal range
   Long = 32 bits = -2147483648  to  2147483647  in decimal range


--------------------------------------
Logical and Arithmetic number shifting
--------------------------------------

Logical shifting of numbers means that we perform a bitwise shift of
the number like this:

1111111100000000

Logically shifed right by 1 position, becomes

0111111110000000

Logically shifed right by 1 more position, becomes

0011111111000000

etc.

In other words, the bits are shifted along to the right and the empty bit
position on the far left is filled by a 0.

Logical shifting to the left is similar:

1111111100000000

Logically shifed left by 1 position, becomes

1111111000000000

and again....

1111110000000000

This is very straightforward.

Interestingly, and most usefully, the fact is that this logical shift
operation actually performs division and multiplication by powers of 2.

Because a logical shift operation is very fast, and because multiplication
and division are very slow, you can appreciate that whenever you want to do
any multiplication or division by powers of 2, logical shifting is the best
way of doing it.

Look at this:

0000000000000001 = 1

1<-LSL

0000000000000010 = 2

1<-LSL

0000000000000100 = 4

1->LSR

0000000000000010 = 2

1->LSR

0000000000000001 = 1

It really works!

Now look at this

0000000000000001 = 1

1<-LSL = 0000000000000010 = 2  = 1 * (2)
2<-LSL = 0000000000000100 = 4  = 1 * (2*2)
3<-LSL = 0000000000001000 = 8  = 1 * (2*2*2)
4<-LSL = 0000000000010000 = 16 = 1 * (2*2*2*2)

It works in a similar way for division and LSR.

Try it.....

This is a marvellous way of quickly effecting division and multiplication
by factors of 2, and you should use it wherever possible.

The above was really great for unsigned numbers, but look what happens if
we try this on signed numbers:

1111111111111101 = -3

1->LSR

0111111111111110 = 32766

Alas, we have shifted across the sign bit and in so doing we have destroyed
the essential sign information.

Obviously this is no use, but fortunately we have another shift operation
which actually is clever enough to preserve the sign bit.

This is the "arithmetic" shift operation.

In the "arithmetic shift right" operation the sign bit is preserved as well
as being shifted across into the next bit position to the right.

In the "arithmetic shift left" operation the sign bit is not preserved, but
a special flag is set in the MC680xx CPU to indicate the status of the sign
bit before and after the operation.

Look first at how the arithmetic right shift works:

1111111111111100 = -4

1->ASR     (= -4 divided by 2)

1111111111111110 = -2

which is correct.

Arithmetic shifting right on the MC680xx works correctly except for a
peculiar rounding effect:

1111111111111101 = -3

1->ASR     (= -3 divided by 2)

1111111111111110 = -2

You can see that, effectively, numbers not divisible by two are divided as
if pre-decremented to the next negative multiple of two.

This is a quirk which you need to take account of when using ASR for quick
division.

Look also at what happens with -1:

1111111111111111 = -1

1->ASR     (= -1 divided by 2)

1111111111111111 = -1

As you can see, this is another peculiar result of the above mentioned
"quirk", and requires care in use.

In the arithmetic shift left operation on the MC680xx, as we said above,
the sign bit is not preserved, but a special status flag is set.

In order to represent this situation in HeliOS, the ASL function returns a
flag on the stack indicating whether the sign bit changed as a result of the
ASL operation.  A flag value of "1" indicates that the sign bit changed, a
flag value of "0" indicates that the sign bit did not change.

There is one last important thing to note about these shift operations.

The HeliOS "fast multiply and divide by factor of two" operators, namely
2*, D2*, 2/, D2/, 4*, D4*, 4/, D4/ all use simple ASR and ASL operations.

This is fine in most cases, but you must be aware that the quirks mentioned
above all apply to these HeliOS fast operators, and be aware also that the
fast "ASL-based" multipliers do not provide a flag for sign change.


-------------
Numeric bases
-------------

As far as the computer is concerned numbers are stored and manipulated as
simple binary values, and only when they are represented to the "outside
world" do they get translated into nice humanly readable decimal numbers.

For the purposes of easy manipulation of particular numbers by programmers
or program users, some circumstances require that numbers be entered into
the computer, or displayed, in number bases other than decimal.

For example, it is easier to work with hexadecimal numbers in many cases,
because this form of number still retains a visual relationship to the
natural binary used by the computer, but is much more easiliy readable
than a simple string of "0"s and "1"s.

It is 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.

Here is a simple comparison table:

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

Note:

There is a convention that a "$" symbolic prefix is used to indicate that 
a number is in hexadecimal format, and a "%" symbolic prefix is used to 
indicate that a number is in binary format.

e.g.

$FFFE          -> Hexadecimal

%101001010001  -> Binary


Let us now take a single-length (16-bit) binary number:

0111101110100001

To convert this number to hexadecimal we first subdivide it into four
units of four bits each:

0111 1011 1010 0001

then convert each 4-bit unit to its hex equivalent:

7 B A 1

or simply 7BA1.

In HeliOS it is easy to change number bases by simply setting a variable
called BASE to the appropriate value:

2  BASE !L  -> Sets numeric base to Binary
8  BASE !L  -> Sets numeric base to Octal
10 BASE !L  -> Sets numeric base to Decimal
16 BASE !L  -> Sets numeric base to Hexadecimal

Actually, there is an even more convenient set of words which automatically
set the numeric base to commonly required values:

BIN         -> Sets numeric base to 2  = Binary
DECIMAL     -> Sets numeric base to 10 = Decimal
HEX         -> Sets numeric base to 16 = Hexadecimal

This is fine, but there is a subtlety involved here when we consider what
happens to numbers while we are compiling colon definitions.

Because HeliOS is a compiler AND an interpreter, it is necessary to fully
understand the distinction between "run-time" and "compile-time" actions of
certain HeliOS functions.  This applies particularly to the numeric base
operators, and below we will give some examples which you should study
very carefully: this is a VERY important topic.

We will assume, by the way, in all these examples, that the numeric base
is always set to DECIMAL at the start.

Look at this simple line of code, which might be entered at the command
line or in your source code:

." Here is the number 3 expressed in HEX: " 3 HEX . DECIMAL WAITSPACE

Now look at this line:

." Here is the number 3 expressed in BINARY: " 3 BIN . DECIMAL WAITSPACE

No problem at all here.

Now let us make a colon definition:

 : SHOW-3-HEX

 ." Here is the number 3 expressed in HEX: " 3 HEX . DECIMAL WAITSPACE
 ;

Ok, there is still no problem.

Now let us make another colon definition, this time showing a DECIMAL
equivalent of the HEX number "$FF":

 : SHOW-$FF-DECIMAL

 ." Here is the HEX number $FF expressed in DECIMAL: "

 HEX FF DECIMAL .
 WAITSPACE
 ;

This does not work!

Can you see why?

Yes, the problem is that the change to the HEX numeric base is not carried
out as the compilation of the colon definition occurs (at compile-time),
but will only take place when the colon definition executes (at run-time).
However, the number "FF" is INTERPRETED AT COMPILE-TIME, when the numeric
base is still set to DECIMAL.

This means that the number "FF", which we have specified in hexadecimal,
is not recognised because the compiler is trying to interpret it using the
current DECIMAL number base, and it is not a valid decimal number.

The distinction between run-time and compile-time is very important and
needs to be carefully studied.  As we have just seen, it is particularly
important when you need to supply numbers in alternate number systems
within a colon definition.

Without going into detail here (see the dictionary for more information),
there are several points of interest about run-time and compile-time.

1. Most words, when inside a colon definition, will be compiled rather than
   executed: this is the very essence of compilation, of course.

2. There are some words which are IMMEDIATE words, which means that they
   always execute immediately, even within colon definitions.

3. There are some words which are STATE SENSITIVE words, which means that
   they behave "cleverly", acting differently whether thay are in a colon
   definition or not.

4. There are methods of forcing an IMMEDIATE word to be compiled.

5. There are methods of forcing words to be executed rather than compiled
   when inside colon definitions.

All this means that you have a comprehensive toolkit within HeliOS to help
handle any possible run-time/compile-time contingency.

Let us look again at our "failed" example:

 : SHOW-FF-DECIMAL

 ." Here is the HEX number FF expressed in DECIMAL: "

 HEX FF DECIMAL .
 WAITSPACE
 ;

This would work fine if we could force the HEX and DECIMAL commands to be
executed at compile time rather than compiled into the colon definition.

We can do this quite easily using the special HeliOS commands "[" and "]".

These are defined as follows:

     [             ( _ _ _ )

                   Leave compile mode and start interpreting.


     ]             ( _ _ _ )

                   Leave interpret mode and start compiling.

Look at this new definition:

 : SHOW-FF-DECIMAL

 ." Here is the HEX number FF expressed in DECIMAL: "

 [ HEX ] FF [ DECIMAL ] DECIMAL .
 WAITSPACE
 ;

This will now work fine.

Notice that we included an extra "DECIMAL" word in the "compiled" part of
the code, and this will ensure that no matter what the current number base
might be at run-time, the number will always be printed as decimal.

This could be made even safer:

 : SHOW-FF-DECIMAL

 ." Here is the HEX number FF expressed in DECIMAL: "

 BASE @L

 [ HEX ] FF [ DECIMAL ] DECIMAL .

 BASE !L

 WAITSPACE
 ;

Let us look just what is happening here:

1. We read the string and compile it, ready to be printed at RUN-TIME

2. We compile words to get the current BASE at RUN-TIME onto the stack

3. We switch to HEX numeric base at COMPILE-TIME

4. We interpret the number at COMPILE-TIME and compile it as a literal

5. We switch back to DECIMAL numeric base at COMPILE-TIME

6. We compile words to set the current BASE at RUN-TIME to DECIMAL

7. We compile words to print the number at RUN-TIME

8. We compile words to restore the previous BASE at RUN-TIME from the stack


This is all very well, and is quite effective as far as it goes.

In fact, that was as far as traditional FORTH did go!

However, HeliOS includes some clever "state sensitive" words which help
make number entry in different numeric bases very much easier.


---------------------------------------------------------
The useful numeric base control functions: #B, #D, and #H
---------------------------------------------------------

Look at these very useful word definitions:

     #B            ( - - - n1 or d1 )               "hash-b"

                   Converts the next word in the input text stream to a
                   single or double number using binary numeric base.

     #D            ( - - - n1 or d1 )               "hash-d"

                   Converts the next word in the input text stream to a
                   single or double number using decimal numeric base.

     #H            ( - - - n1 or d1 )               "hash-h"

                   Converts the next word in the input text stream to a
                   single or double number using hexadecimal numeric base.

These are all state sensitive words, and perform their function within a
colon definition by compiling the number as a literal, so that at run time
n1 or d1 will be pushed onto the stack.

The size of the value left on the stack at run time or compile time depends
on the size of the number read from the input stream.

Using these new commands, we can now make our example even easier:

 : SHOW-FF-DECIMAL

 ." Here is the HEX number FF expressed in DECIMAL: "

 #H FF . WAITSPACE
 ;


Here is another example, using the new "controlled" number base words in a
command line:

#B 11111111111111111111. D. #H FFFF . #D 12345 .


Or, try executing this section of code:

(You can just highlight the code and press Left-Amiga-e if you wish)

SCRCLR

DECIMAL

CR

."  Decimal      : "

#B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR

HEX

."  Hexadecimal  : "

#B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR

BIN

."  Binary       : "

#B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR

DECIMAL

WAITSPACE


Do you understand what is happening here?


Now put the same code into a colon definition and try it:

: NUMBERS

SCRCLR

DECIMAL

CR

."  Decimal      : "

#B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR

HEX

."  Hexadecimal  : "

#B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR

BIN

."  Binary       : "

#B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR

DECIMAL

WAITSPACE
;

If you compile and then execute this definition you will see that it still
works fine, proving that the words #B, #D, and #H are state sensitive.


----------------
ASCII characters
----------------

If the computer uses binary notation to store numbers, how does it store
alphabetic characters and other symbols?

Of course the computer still uses binary storage, but in this special case
the binary values are interpreted according to a special code, called ASCII,
which was adopted as an industry standard many years ago.

ASCII = The American Standard Code for Information Interchange
            ^        ^        ^        ^           ^
            A        S        C        I           I   = ASCII


There are a few interesting things to know about ASCII codes:

1. ASCII codes are byte sized.

2. The codes below $1F are non-printing special "control" codes.

3. The Amiga convention uses a simple $A (decimal 10) as a text "carriage
   return" control character.

4. Upper and lower case alphabetic characters have a single bit difference
   in their ASCII codes:

   ASCII "M" = 1001101
   ASCII "m" = 1101101
                ^
                Lower case has an extra bit set, but is otherwise identical.

HeliOS has words which interpret and allow you to display ASCII codes:

EMIT  Takes an ASCII code on the stack and prints the alphabetic character.

      e.g.

      75 EMIT would print out "K"

ASCII Used in the form:

      ASCII K

      to read the following character in the input stream and return the
      associated ASCII code.

      e.g.

      ASCII K . would print "75"

      ASCII K EMIT would print out "K"

The "ASCII" command word is state sensitive and works equally in compile or
direct command mode.

Of course, all computer strings consist of ASCII codes, and it is useful
to remember the simple rules mentioned above when manipulating strings,
reading text files, switching the case of strings etc.

Let us look at the problem of switching the case of strings.

We can use the logical AND and OR operators to do this, because in the
ASCII code system one particular bit (bit 5) is always used to specify
the different upper and lower case variants of any character.

As we showed above:

   ASCII "M" = 01001101
   ASCII "m" = 01101101
                 ^
                 Bit 5 is always set for lower case

So, look what we can do with the AND operation:

   ASCII "m" = 01101101
               11011111  AND
               --------
   ASCII "M" = 01001101
                 ^
                 We have cleared bit 5

We used 11011111 as a mask to clear bit 5 of the lower case "m", and this
neatly converted the character to an upper case "M".

So, we can use AND to convert from lower to upper case.

Now let us try using OR to convert from upper to lower case.

This time we will use, 00100000, the inverse of the previous mask.

   ASCII "M" = 01001101
               00100000  OR
               --------
   ASCII "m" = 01101101
                 ^
                 We have set bit 5

So, as you can see, we can use OR to convert from upper to lower case.

One more important point to remember here is that you should always take
care before performing these case conversion operations that the character
ASCII code which you are converting lies between the values 65 (="A") and
122 (="z").  Remember that values outside this range are not convertible
between upper and lower case, because the upper and lower case convention
only applies to the letters of the alphabet.


-----------------
Number formatting
-----------------

In HeliOS, as in FORTH, you can easily design custom number output formats.

Look at these ways of outputting numbers, for example:

$200.00        12/31/86       372-8493        6:32:59      98.6

These all represent the kinds of output you can create by defining your own
number-formatting words.

We will now explain how to do this, but before you read the following please
look in the "Dictionary.doc" and familiarise yourself with the set of words
described in the section "NUMERIC OUTPUT".

In particular, look at the definitions of:

#
#S
<#
#>
HOLD
SIGN

The simplest number-formatting definition we might write would be:

: UD.  ( ud - - - )  <#  #S  #>  TYPE ;

This will print an unsigned double-length number.

Looking within our word definition, the words "<#" and "#>" signify the
beginning and the end of the number conversion process, and the entire
number conversion is being performed by the single word #S.

The word "#S" converts the value on the stack into ASCII characters, and
has the following properties:

1. It will only create as many digits as are necessary to represent the
   number.

2. It will not produce leading zeroes.

3. It always produces at least one digit, which will be zero if the value
   was zero.

Let us use our new word:

12,345  UD.  -> 12345
1-2     UD.  -> 12
0.      UD.  -> 0

Going back to our definition, the word TYPE displays the characters that
represent the number.

You can see, if you use the function on two numbers in succession, that
there is no space generated before or after the numbers:

12,345 12,345  UD. UD. -> 1234512345
                              ^^
                           No space

If you wanted to include a space, or even a carriage return, after the
number display, you could easily do so:

:  UD.  ( ud - - - )  <#  #S  #>  TYPE SPACE CR ;

Now, let us say that you have a telephone number on the stack, expressed as
a 32-bit unsigned integer, and typed in as:

01623-554828

Remember that the hyphen will tell the number input routine that this is
a 32-bit "double" number.

Suppose that we now want to output this number in the same format that it
was input.

We will define a new function to do this, called .PHONE#:

: .PHONE# ( ud - - - ) <# # # # # # # 45 HOLD #S #> TYPE SPACE ;

In the above definition, each use of the word "#" produces one digit, but
note that the number formatting process works from the right of the final
number.  This means that the six initial "#"s will be responsible for the
last six (rightmost) digits of the final output.

Now look at the number 45 in the definition: this is the ASCII code for
the hyphen "-", as you can see if you go into the interpreter and type
"45 EMIT".

The word HOLD takes this ASCII code and inserts it into the formatted number
character string at the current position (i.e. 6 digits from the right).

Of course, if you wish, you can replace the number 45 in our definition
with the more readable expression "ASCII -", like this:

: .PHONE# ( ud - - - ) <# # # # # # # ASCII - HOLD #S #> TYPE SPACE ;

After handling the hyphen we still have 5 more digits to deal with, and
these are simply handled by using "#S", which will automatically convert
the rest of the number for us.

Perhaps you would like to try out our new definition ".PHONE#" now.

The <# ... #> sequence is called a pictured numeric output phrase, because
it forms a picture (from right to left) of how you want the number to be
formatted.

Another use for this type of pictured number output is when we wish to
display dates and times, so we can now try formatting an unsigned double
length number as a date in the following form:

07/15/86

Here is the definition

    :  .DATE  ( ud - - - )

    <#
    # #  ASCII  /  HOLD
    # #  ASCII  /  HOLD
    # #
    #>
    TYPE SPACE
    ;

Let us follow this definition, remembering that it is written in reverse
order from the output.

The first phrase

#  #  ASCII  /  HOLD

produces the rightmost two digits (representing the year) and the rightmost
"/" character.  The next occurrence of the same phrase produces the middle
two digits (representing the day) and the leftmost "/".  Finally  "# #"
produces the leftmost two digits (representing the month).

We could just as easily have defined it like this:

:  /nn  ( ud - - - ud )  # # ASCII  /  HOLD  ;

:  .DATE  ( ud - - - ) <#  /nn  /nn  #  #  #>  TYPE SPACE  ;

Since you have control over the conversion process, you can actually convert
different digits in different number bases, a feature that is very useful
in formatting such numbers as hours and minutes.

For example, let us say that you have the time in seconds on the stack and
you want a word that will print "hh:mm:ss".

You might define it this way:

:  SEXTAL  6 BASE !L  ;
:  :00    ( ud - - - ud )  #  SEXTAL  #  DECIMAL  ASCII  :  HOLD  ;
:  .TIME  ( ud - - - )    <#  :00 :00 #S #>  TYPE SPACE  ;

We use the word ":00" to format the seconds and the 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 we go into "sextal" (base 6) to convert
the left digit.

Finally we return to decimal and insert the colon character.

After ":00" converts the second and the minutes, "#S" converts the remaining
hours.

Now, if we have 4,500 seconds stored on the stack, we would get

4,500  .TIME   ->   1:15:00

Note that there are 86,400 seconds in a day: too many for a 16-bit number.

So far we have formatted only unsigned double-length numbers.

The <# ... #> form expects only unsigned double-length numbers, but we can
use if for other types of numbers by making 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.  ( d - - - )

   DUP>R  DABS  <#  #S  R>  SIGN  #>  TYPE SPACE  ;

The word SIGN, which must be situated within the pictured numeric output
phrase, inserts a minus sign in the character string only if the top number
on the stack is negative.  So, we save a copy of the high-order cell (the
one with the sign bit) on the return stack for later use, using "DUP>R".

The word "<#" expects only unsigned double-length numbers, therefore we must
take the absolute value of our double-length signed number using "DABS".

We now have the proper arguments for the pictured numeric output phrase.

Next, the word "#S" converts the digits, right to left.

Finally, we return the sign-indicator to the data stack using "R>".

If it is negative, SIGN will add a minus sign to the formatted string.

Since we want our minus sign to appear at the left, we include SIGN at the
right of our  <# ... #> phrase, but in some cases, such as accounting, we
may want a negative number to be written like this:

12345-

in which case we would place the word SIGN at the left side of our main
<# ... #> phrase like this:

<#  SIGN  #S  #>

Let us now define a word that will print a signed double-length number with
a decimal point and two decimal places to the right of the decimal: this
could be used for outputting cash in "£"s and pence.

    : £.  ( d - - - )

    DUP>R DABS
    <#
    # #
    ASCII . HOLD
    #S
    R>  SIGN
    ASCII £ HOLD
    #>
    TYPE SPACE
    ;

Let's try it

2000.00 £.     -> £2000.00

or even

2,000.00  £.   -> £2000.00


You can also write special formatting words 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 initial single-length number into an equivalent
double-length number which 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.  However, you must also save a copy of the signed number
for SIGN to use, and you must leave the absolute value of the number in the
second stack position:

( n - - - )  DUP>R  ABS  0  <#  #S  R>  SIGN  #.


The following set-up phrases are needed to print various kinds of numbers:


NUMBER TO BE PRINTED            PRECEDE  <#  by

32-bit number unsigned          (nothing needed)

31-bit number and sign bit      DUP>R DABS
                                ^^^^^
                                To save the sign on the return stack
                                for removal just before SIGN

16-bit number unsigned          0

                                To give a dummy high-order word, thus
                                converting to equivalent double number

15-bit number and sign bit      DUP>R  ABS  0
                                ^^^^^       ^
                                |||||       To give a dummy high-order word,
                                |||||       thus converting to equivalent
                                |||||       double number
                                |||||
                                To save the sign on the return stack
                                for removal just before SIGN


------------------------------------------------------------------------
Using scaled and fractional integer arithmetic instead of floating point
------------------------------------------------------------------------

One of the many idiosyncracies of FORTH (and HeliOS) which make it so
very different from most other languages is the use of scaled integer
arithmetic.  FORTH traditionally did not have dedicated floating point
math functions, and neither does HeliOS: both languages rely on rather
unusual scaled integer techniques which are far faster and more efficient
than using floating point calculation.

Remember again the philosophy of helping the computer to do its work in
the most efficient way possible: floating point arithmetic is a "human
methodology" and the computer has to do extra work to try to simulate
this kind of calculation.

HeliOS and FORTH use methods of calculation which tie in very efficiently
with the way the computer functions internally, thus making arithmetic
processing many times quicker than more "conventional" techniques.

First of all we need to look at just what we mean by floating point and
scaled integer calculation.

Look at the following calculation which you might perform on a desk top
calculator:


YOUR ENTRY                CALCULATOR DISPLAY

 7.60                           7.60
 x                              7.60
 2.00                           2.00
 =                              15.2

The decimal point "floats" in the sense that it will end up in the correct
display position to discriminate between whole and partial elements of the
number.  This is known as "floating point" arithmetic and we are all very
familiar with these methods of expression in everyday calculations.

Floating-point format can also be seen in the way scientific notation keeps
account of large numbers by remembering a numeric element combined with a
number of decimal places.

Thus, in scientific notation, 12 million might be expressed as:

                    6
12 million = 12 x 10   =  12 times 10 to the power 6

Ten to the sixth power equals one million.

In floating point computation 12 million could be stored as the two numbers
12 and 6, where it is understood that 6 is the power of ten to be multiplied
by 12.  In a similar way a number like 1.234 could be stored as 1234 and -3.

The good thing about floating point representation is that the computer can
represent an enormous range of numbers using two relatively small numbers.

On the other hand, scaled integer arithmetic is a method of storing numbers
in memory without storing the positions of each number's decimal point.  In
this system all numbers are stored in terms of multiples of the smallest
unit of definition.  For example, in working with measurements of distance
where the very smallest denomination would be the "centimetre", all values
would be stored as multiples of centimetres.

Here is a comparison between scaled integer and floating point methods of
numeric representations of metric distance values.

REAL WORLD              SCALED-INTEGER          FLOATING-POINT
VALUE                   REPRESENTATION          REPRESENTATION

1.23   (metres)         123  (centimetres)      123(-2)
10.98                   1098                    1098(-2)
100.00                  10000                   1(2)
58.60                   5860                    586(-1)

As you can see, with scaled integers all the values must conform to the
same scale and all numbers are integers.  If you are using scaled integer
arithmetic in a computer program, the program needs to insert a decimal
point in the final on-screen display according to the scale required. Of
course the program "knows" what basic unit all numbers are derived from,
so this is no problem.

With floating point representation the decimal point position is actually
stored with each number, but this is not necessarily an advantage because
the program itself will still need to take account of the final display
requirement for decimal point expression just like the integer version.

Of course, not all numbers are based on decimal systems, and perhaps you
can already see that scaled integer arithemetic is going to ultimately
prove rather more flexible than decimal floating point.

This whole topic is somewhat controversial, because it so happens that the
commonly accepted way of doing things is not the best!  This means that the
two camps argue from differing points of view: one prefers to have superior
real performance, the other prefers easy conventionality of expression.

Most computer languages and most programmers use floating point arithmetic
as an automatic standard, preferring to stick with decimal representations
and allowing the computer to do most of the work in adapting its internal
calculations to human conventional and perceptual requirements.

There is undoubtedly some logic in this, especially when the "man in the
street" first enters the world of computer programming: it just seems so
obvious to carry on using our normal conventions when we write computer
programs....or does it?

Perhaps so, but perhaps not when we consider things more deeply.

Making the programmer's life easier is one thing, but in most cases the
programmer is there to write a computer program to do a particular job,
and a "good" computer programmer presumably wishes to write a computer
program which is as efficient as possible.  In this case, should the
programmer not be prepared to adapt his methods to allow the very best
software control of the computer?

Many programming applications require real-time calculations in non-decimal
number systems: computer games are a good example.

In fact, it would be fair to say that MOST programming applications require
real-time calculations in non-decimal number systems.

Many programs on the Amiga, which are often concerned with the generation
of real time graphics and sound, need to be as fast as possible to get the
most out of the computer.

In these cases (in MOST cases, in fact) a "good" programmer is interested
in maximizing the efficiency of the computer rather than making the task
of programming (possibly) easier by adhering in the source code to an
artificial decimal floating point number representation system.

In many cases, also, memory efficiency is an important criterion, as well as
speed, and again here scaled integer arithmetic comes out on top.  If an
application has to repeatedly perform calculations millions of times every
second in a time critical environment, scaled integer arithmetic will give
superior performance.  Floating point multiplication or division can take
many times as long as its equivalent scaled integer counterpart.

Also, to perform addition or subtraction, the realignment of the values
prior to a floating point operation is at least as time-consuming as the
arithmetic operation itself.  The fact is that the Amiga does not think in
"floating point" terms, and you pay a heavy penalty for trying to make it
act as though it does.

Do not think that integer arithmetic is some "poor relation" only suited to
arcade game programming.  For many years FORTH programmers have been writing
complex applications with scaled integer arithmetic involving solutions of
fancy things such as differential equations, Fast Fourier Transforms, non-
linear least square fitting, linear regression, and so on.

Of course FORTH systems can support floating point arithmetic, and some
do indeed have built-in floating point functions.  HeliOS does not have a
built-in floating point system, but you can easily incorporate one into
the language for yourself if you really do prefer to work in this way.

Calculation ranges and types do also affect which kind of number system
is preferred, and most problems with physical input and outputs require
a dynamic range of no more than a few thousand to one, and thus fit
comfortably in a 16-bit integer word.  Some calculations may actually
require 32-bit intermediate values, but HeliOS accommodates this easily.

The "trick" to eliminating wasteful floating point operations from your
code is to ensure that values are always scaled according to the range of
possible values that you're interested in.

HeliOS, along with any FORTH system, provides a powerful toolkit of high
level commands called "scaling operators" to support techniques such as
rational approximations and fractional arithmetic.  These techniques are
immensely powerful and provide unique possibilities for performing the
kinds of "fraction" calculation which are often more appropriate to real
world applications that decimal floating point: remember, the real world
is NOT built from floating point decimal numbers!

Let us look at the possibilty of performing a HeliOS fractional arithmetic
calculation using the "*/" function.

Here is the definition of "*/" :

 */  ( n1 n2 n3 - - - n4 )

     Multiplies then divides (n1xn2/n3) using a 32-bit intermediate result.

As its name implies */ performs multiplication, then division.

For example, let us assume that the stack contains three numbers:

( 225 32 100 - - - )

If we use "*/" it will first multiply 225 by 32, then divide the result by
100.

The "*/" operator is particularly useful as an integer arithmetic solution
to problems such as percentage calculations.  For example, you could define
the word % (percent) like this:

 :  %  ( n1  %  - - - n2 )

 100 */

 ;

Now, by entering the number 225 and then using

32  %

you would end up with 32% of 225 (which is 72) on the stack.

Note that "*/" is not just a "*" and a "/" operating successively, but a
much more powerful operation which uses a double-length intermediate result.
This means that during its internal calculations "*/" can accept overflow
into 32-bit values without truncation of values.

Suppose you want to compute 34% of 2000.

Remember that single length operators * and / only work with arguments and
results within the range of -32768 to +32767.

So, if you were to enter the phrase

2000  34  *  100 /

you would get an incorrect result because the "intermediate result" (in
this case the result of multiplication) exceeds +32767.

However, "*/" allows a 32-bit intermediate result, so that its range will
be large enough to hold the result of any two single-length numbers which
might be multiplied together.

The phrase

2000 34  100  */

returns the correct answer because the end result falls within the range of
single length numbers.

Another interesting question involved here is that of "rounding".

Let us look at another hypothetical problem involving percentages.

If 32% of the students eating at a school cafeteria usually buy bananas,
how many bananas should be on hand to feed 22 students?

Naturally we are only interested in whole bananas, so we need to round off
any partial remainder.

As our definition of "%" now stands, any fractional percentage value is
simply dropped.  In other words the result is "truncated".

Look at these figures:

32% OF     ACCURATE ANSWER      RESULT OF OUR % FUNCTION

225           72.00             72  = exactly correct
226           72.32             72  = truncated, but correct as if rounded
227           72.64             72  = truncated, but not rounded correctly

Ideally, with any decimal value of 0.5 or higher, we want to round upwards
to the next whole banana.

Let us now define the word R% for "rounded percent", like this:

 :  R%   ( n1 % - - - n2 )

 10 */ 5 + 10 /
 ;

Now, using this new function, the expression

227 32 R%  .

will give you 73, which is correctly rounded up.

Look again at the definition of R%  ->     10 */ 5 + 10 /

Notice that we first divide by 10 rather than 100, and this gives us larger
(by a factor of 10) number to work with as a result.

Next we add the median value of 5, ensuring that we are rounding up fully
to the next value, then we perform the final division by 10.

Look at this:

OPERATION               STACK CONTENTS

                        227  32  10

*/                      726

5  +                    731

10 /                    73

The final division by 10 sets the value to the correct decimal position.

A disadvantage to this method of rounding is that you lose one decimal
place of range in the final result;  that is, it can only go as high as
3276 rather than 32767.  However if that is a problem you can always use
double-length numbers and still be able to round.

Look at this, for example:

Using "M*/" we can redefine our earlier version of % so that it will accept
a double-length argument:

:  %  ( d1 n%  - - -  d2 )  100  M*/  ;

as in

20,050  15 % D.    ->  3007

We can redefine our earlier definition of R% to get a rounded double-length
result like this:

:  R%  ( d1 n% - - - d2 )   10 M*/  5 M+  1 10  M*/  ;

then

20,050  15  R%     -> 3008

Let us look at another example.

Take the simple problem of computing two-thirds of 171.

Basically there are two ways to go about it.

1.  We could compute the value of the fraction 2/3 by dividing 2 by 3
    to obtain the repeating decimal 0.6666666666666666 etc.

    Then we could multiply this value by 171.

    The result would be 113.999999999999 etc.

    This is not quite right, but it could be rounded up to 114.

2.  We could multiply 171 by 2 to get 342.

    Then we could divide this by 3 to get 114.

Notice that the second way is much simpler and more accurate: it is more
of a "real world" practical solution.

Most computer languages support the first way: after all, how many times
have you seen a "C" or BASIC program using a fraction like two-thirds in
its calculations.  Of course, it is assumes "by default" that you must
accept the inefficiency of 0.6666666666666666666666666 etc.

However, HeliOS supports the second way of doing things!

The "*/" function lets you easily have a fraction like two-thirds, as in

171  2   3  */

Now let us take a slightly more complex example.

We want to distribute $150 in proportion to two values:

7105            ?
5145            ?
----            ---
12250           150

Again we could solve the problem this way:

(7105 / 12250)  x  150

and

(5145 / 12250) x 150

but for greater accuracy we should say:

(7105 x 150) / 12250

and

(5145 x 150) / 12250

In HeliOS we could say:

7105 150 12250  */

which would give us 87.

Then we could say:

5145  150  12250  */

which would give us 63.

It can be said here that the values 87 and 63 are "scaled" to 7105 and 5145.

Calculating percentages as we did earlier is also a form of scaling, and
for this reason "*/" is known as a "scaling" operator.

Another, slightly more powerful, scaling operator in HeliOS is "*/MOD":

 */MOD   ( n1, n2, n3 - - - remainer, quotient )

         Multiplies then divides (n1 x n2/n3).

         Returns the remainder and the quotient.

         Uses a 32-bit intermediate result.

As you can see, this function returns a remainder, which can be very useful
in many applications for rounding etc.

So far we have only used scaling operations to work on rational numbers,
but they also can be used on rational approximations of irrational constants
such as "pi" or "the square root of 2".

For example the so called "real" value of "pi" is

3.14159265358 etc.

To multiply a number by "pi" yet stay within the bounds of single-length
arithmetic, we could write

31416  10000  */

and get quite a good approximation.

:  *PI   ( n1  - - - n2 )  31416  10000  */  ;

Now let us write a definition to compute the area of a circle, given its
radius, by using the formula:

pi * r²

The value of the radius will be on the stack, so we DUP it and multiply it
by itself then multiply by "pi":

:  AREA  ( radius - - - area )  DUP  *  *PI ;

Try it with a circle whose radius is 10 inches:

10  AREA

The result is 314.

For even more accuracy we might wonder if there is a pair of integers other
than 31416 and 10000 that is a closer approximation to "pi", and actually
there is.

The fraction

355  113  */

is accurate to more than six places beyond the decimal, as opposed to less
than four places with 31416!

A new and improved definition can now say:

:  PI  ( n1 - - - n2 )  355 113 */  ;

It turns out that you can approximate nearly any constant by many different
pairs of integers, all numbers less than 32768, with an error of less than
10 to the power -8.  This is very interesting: just think about it and you
will appreciate that there is here a great potential method for very fast 
and very accurate computation.

We have just seen how to express fractions as a pair of integers when we
are scaling, but some applications require non integer values in situations
other than scaling.

For instance, how might you add these two fractions

7         23
--   +    --   =
34        99

without using floating point?

We can do it in HeliOS with a simple technique called fractional arithmetic,
which is sometimes also called "fixed-point" arithmetic.

Fixed-point arithmetic involves scaling and an implied decimal point.

Instead of scaling by multiples of ten (which we humans are used to) we
scale by multiples of two (which computers are much better at).

In this technique the implied decimal point might be more aptly called a
'binary point'.

Suppose we define

 +1 CONSTANT 16384

where 16384 is a scaled value equivalent to 1.

In binary 16384 looks like this:

0100000000000000

The 1 is scaled up in binary along with the implied binary point.

Now let us extend HeliOS to define two new math operators - "fractional
multiply" and "fractional divide", based on our new scale:

:  *.  ( n1 n2 - - - n3 )    +1   */ ;

:  /.  ( n1 n2 - - - n3 )    +1   SWAP  */ ;

To demonstrate these new functions, we can start simply.

If we divide 1 by 1 we should get 1:

1  1  /.

which gives a result of 16384, which is our scaled unit value, since 16384
represents positive 1.

If we divide 1 by 2. with:

1  2  /.

we get a result of 8192.

Here 8192 represents the value "one-half", since it is half of 16384.

Let us go back to the problem:

7         23
--   +    --   =
34        99

We can now solve the problem like this:

7  34  /.  23  99  /.  +

Notice that the final operator is simply "+" to add the two fractions.

Of course this is not yet the whole solution, since the answer is not yet
expressed in a useful final form.

To scale the result back to decimal form we now use

10000   *.

to give the result 4381.

Our answer, then, is "4381/10000", better known as "0.4381".

With fractional arithmetic we can add, subtract, multiply and divide using
fast integer operations, even though we are dealing with non-integer values.

In number-crunching applications which do a lot of arithmetic computation
the speed of these internal calculations is often crucial, and the methods
described here can have a huge impact of software performance.

By contrast, the problem of converting numbers to human-readable (decimal)
form is relatively unimportant, because only the final result needs to be
displayed.  In fact, with applications such as graphics and sound the result
never needs to be converted to decimal form but, rather, remains in a form
suitable to the computer.

Nevertheless, we might still want to input and output these fractions using
customary decimal notation.

If you want HeliOS to print the result of the above calculation with the
decimal in the correct place, all you need is a some nice customised number
formatting words such as this:

:  #.####    DUP ABS 0 <#  # # # #  46 HOLD  # ROT SIGN  #>  TYPE SPACE  ;

:  .F   ( fraction - - - )  10000  *.  #.#### ;

Now the complete statement of our problem could be:

7  34  /.  23  99  /.  + .F

This would print out

0.4381

This has not used floating point calculation at all, but it has produced
the same result a very great deal faster.

What if we want to express input arguments as floating point numbers?

Let us say we want to accept, for example

.1250 + .3750 = ?

To do this in HeliOS we first need a word to convert a number with a decimal
point into a scaled fraction:

:  D>F  ( d1 -- fraction )   DROP  10000 /.  ;

(D>F stands for double-number to fraction.)

Now we can enter

.1250  D>F  .3750  D>F  +  .F

This would give the result

0.5000

Notice that we MUST express the inputs complete to four decimal places.

We can multiply two fractions with  *. like this:

.7500  D>F  .5000  D>F   *.  .F

This would give the result

.3750

Interestingly, if we use *. to multiply a fraction by an integer, the result
is an integer.

For example

28  .5000   D>F   *.

gives a result of 14.

With /.  we can divide say  -0.3 by 0.95 like this

-.3000  D>F   .9500  D>F   /.   .F

This would give the result

-0.3160

We can also get a fraction result by using  /. on two integers.

Thus

22  44  /.   .F   

would give the result

.5000

We also get an integer result if we  /.  an integer by a fraction.

To summarise, using the letter "f" for fraction and "i" for integer, the
following input/output combinations of fractional numbers or combinations
of fractions and integers are possible:

f f  +   gives  f
f f  -   gives  f
f i  *   gives  f
i f  *   gives  f
f i  /   gives  f
f f  *.  gives  f
f i  *.  gives  i
i f  *.  gives  i
f f  /.  gives  f
i i  /.  gives  f
i f  /.  gives  i

Using a binary oriented scalar such as 16384 instead of a decimal number
such as 10000 allows greater accuracy within 16 bits (by a ratio of 16 to
10).

Also,  *. and  /.  can be coded in assembler extremely efficiently.

However, the choice of 16384 as the value of "1" is quite arbitrary.

Had we chosen 256, we would get 8 bits (including sign) to the left, and
eight bits to the right of the binary point.

This same technique can be extended of course into 32 bit numbers if that
kind of precision is really needed.

A great use for  *.  and /.  is with trig functions, since an angle can
be represented internally as a fraction of a circle (well-behaved between
0 and 1).

Conversions to and from degrees become easy as well.

Here we have very briefly introduced scaling operators, rounding, rational
approximations and fixed-point technique.

There is nothing to prevent you from adding floating point functions to
HeliOS if you wish, but it would be best first to at least try the virtues
of compactness, high performance, simplicity and elegance afforded by the
existing arithmetic tools.  To do this requires a rigorous and continual
rejection of anything that is not absolutely necessary, but by the judicious
use of scaling and double-length integers where required you can indeed
eliminate the expense of floating point operations from your code.

You might prefer to add floating point functions if:

1.  You want to use your computer like a calculator for 1-shot computations.

2.  You value the initial programming time more highly than the execution
    time spent whenever the calculation is performed.

3.  You need a number to be able to describe a very large dynamic range
    (greater than -2 billion to +2 billion).

How often do you think these criteria will be important?

---------------------
Examples and problems
---------------------

1.  Write a word which uses a BEGIN....UNTIL loop to determine the upper
    (positive) and lower (negative) limit for a signed single-length number.

    Check the results against the values given above.

2.  Define a word which converts a bit number into a mask corresponding to
    that bit.

    For example:

    0 BIT  should give the result 1 (decimal) or 1   (binary)
    1 BIT  should give the result 2 (decimal) or 10  (binary)
    2 BIT  should give the result 8 (decimal) or 100 (binary)
    etc.

3.  Define a word which turns on a specified bit in a number on the stack.

    The stack effect would be ( n1, Bit# - - - n2 ).

    For example:

    If you have binary 1000 on the stack using the new word to set bit 1
    would leave you with binary 1010.

4.  Now define a similar word which turns off a specified bit.

5.  Define a word which puts on the stack a flag representing the status of
    a single specified bit from a 16-bit number n1 on the stack.

    The stack effect would be ( n1, Bit# - - - flag [1 or 0] ).

    Try using the result of this word as a flag to drive an IF...THEN
    statement in another word.

6.  Define a word which toggles (or "flips") a specified bit from a 16-bit
    number on the stack.

7.  Define a word which produces a number consisting of the "bit-difference"
    between two numbers on the stack.

8.  Write a formatted output word to display a 32-bit signed integer as a
    string of digits, a decimal point, and 1 decimal digit.

9.  Write a routine that, given the value of "x" on the stack, evaluates
    the following quadratic equation and returns a double-length result.

    7x² + 20x + 5

10. In the previous problem, how large a value of "x" will work without
    overflowing 32 bits as a signed number?

11. Write a word that prints the numbers 0 to 16 (decimal) in decimal,
    hexadecimal and binary form in three columns.

    For example:

    DECIMAL  0   HEX  0   BINARY  0
    DECIMAL  1   HEX  1   BINARY  1
    DECIMAL  2   HEX  2   BINARY  10
    .....
    DECIMAL  16  HEX  10  BINARY  10000

12. Translate the following algebraic expression into a HeliOS definition:

        -  ab
         -----
           c

    given  ( a b c - - - ) as a stack parameter input.

13. A histogram is a graphic representation of a series of values, where
    each value is shown by the height or length of a bar.

    Define a word called PLOT which will serve as a component to a general
    histogram application.

    Given a value between 0 and 100, PLOT should draw a horizontal row of
    stars on your display screen to represent the value.

    The catch?

    There are only 78 columns on the display screen!

    Thus a value of 100 must be plotted as 78 stars.

    50 must be plotted as 49 stars.

    0 must be plotted as 0 stars.

    etc.

14. In command line "calculator" style convert the given temperatures, using
    the formulas below, and expressing all arguments and results in whole
    degrees:

    C  =  F - 32
          ------
           1.8

    F  =  (C x 1.8) + 32

    K  =  C + 273


    a.  0F   in Centigrade
    b.  212F in Centigrade
    c.  -32F in Centigrade
    d.  16C  in Fahrenheit
    e.  233K in Centigrade

15. Now define actual HeliOS words to perform the temperature conversions.

    You could use names such as:

    F>C  F>K  C>F  C>K  K>F  K>C

    Test them with the values from the previous problem.

************************************************************************
End
************************************************************************
