Arithmetic

Here is a compendium of arithmetic routines implemented in the instruction set for GreenArrays' C18 computer. Most have been tested, but you should verify that they're suitable for your use. In particular, exactly how to code them depends upon what other words exist. And any restriction as to the range of numbers can simplify code.

I hope the stack effect of these simple words is obvious. If not, I'll include a stack comment. But multiply and divide leave the addend on the stack. It is often unnecessary and always a nuisance to remove. Call it an abandoned number, which I'll indicate by . (and multiple such by ..) in comments.

Integer Functions

Arithmetic uses + which can often be optimized

The C18 has no subtract instruction, but there are several ways to accomplish subtraction. With a and b on the stack (b on top):

These are simple functions, often useful:

Double-precision Add

In this section, a double-precision (36-bit) number has its low-order 18 bits on top of the stack. Such numbers range from -34,359,738,368 to 34,359,738,367.

The ALU must be in add-with-carry mode, which means the code must be compiled at an address with the 200 bit set. The words +cy and -cy set and reset this bit in the current code address.

Carry must usually be clear to start adding. It will remain clear if the last + doesn't overflow.

Clear carry (carry mode) (This code is usually in a node's ROM, address 2d3.):

Add an unsigned 18-bit number to a 36-bit number (carry mode, carry clear):

Negate a 36-bit number:

If -d falls into +u, it must end with a .

Shift left a 36-bit number:

Shift right a 36-bit number:

Sign-extend an 18-bit number to 36 bits:

Add 2 36-bit numbers (carry mode, carry clear): Add a signed 18-bit number to a 36-bit number: +s can fall into +d.

Integer Multiply

Multiply is implemented with the +* instruction, with the multiplier in register A and multiplicand in S. It adds S to T if A0 is 1, then does a 36-bit shift of T and A right.

The multiplicand is often left on the stack. The carry bit is not used or changed by the +* instruction.

Unsigned multiplier, signed multiplicand, 36-bit signed product:

If the multiplier is signed, its high-order bit indicates a subtract:

Unsigned multiply, necessary for multiple-precision arithmetic. From Michael:

Multiply and add x:

A faster 18-bit product can be computed from 2 numbers whose bits add up to 18; say 9 and 9. the multiplier (top of stack) is a normal right-justified unsigned integer. The signed multiplicand is expected to be left-shifted the number of bits in the multiplier. The product will be an 18-bit signed integer.

Integer Divide

This code is expects a positive double-precision dividend and a positive divisor that's been set negative, to produce a positive remainder and quotient.

Divide is slower than multiply, since there is no divide step instruction. In carry mode with carry undefined:

(This code is usually in a node's ROM, address 2d5.) The loop starts with a 2*d. The carry of a . + will be used by the following dup + , thus building the quotient in T. Carry is clear upon exit if the numbers were such as produce a reasonable result.

If the dividend is signed:

For a single-precision divide with a small quotient, this is shorter and faster, without carry:

The quotient is counted by next. No ; since next never stops (unless you divide by 0).

The quotient is on top, so:

*/ is a classic Forth word. It multiplies a number by a ratio. With 3 numbers on the stack, the top is the divisor. The 36-bit intermediate product preserves precision:

Integer Square-Root

An algorithm similar to divide produces a 16-bit square-root from a 32-bit number:

Fraction Multiply

The simplest fractions have 17 fraction bits and a sign bit. Thus fractions from .1ffff to -1.00000 can be represented. The addend can be either fraction or integer.

For fractional arithmetic it's useful to define 1. In this context:

Then to convert fractions to/from decimal numbers:

If the multiplier is positive:

If the multiplier is signed, its high-order bit indicates a subtract:

The most convenient fractions have 16 fraction bits, an integer bit and a sign bit. This allows 1.0000 to be represented. However, the product of 2 fractions must be less than 1.ffff.

16-bit code requires an additional left shift to the 17-bit code:

Fraction Divide

Dividing an 18-bit number by a 16-bit fraction requires expanding the dividend to 36-bits with a low-order 0:

Fraction Square-Root

  • sqrt. 1. * sqrt ;

    Multiple-precision Add

    Multiple-precision numbers are stored as an array in RAM, low-order at low address. The largest number is 32 words, since 2 such will fit in 64-word RAM. 2^18^32 is a large number, roughly 10^173. Set a number to 0:

    With b a on the stack, b is added into a. Both addresses are discarded. n is the number-1 of 18-bit words in such numbers. In carry mode:

    Defining the word +c to add with carry, allows using ordinary + to increment an address, without changing the carry: