# 64-bit math on 68000

The 68000 is a 16-bit CPU that can do operations on 32-bit numbers. That
usually should be enough, but in rare cases (e.g. a game with
ultra-inflated scoring) you may want even *larger* and use 64-bit
numbers. Luckily, the 68000 has some provisions to work with numbers of
arbitrary sizes.

Yes, this is a 16-bit CPU with 32-bit instructions working with 64-bit numbers, because why not?

- The Extend flag
- Addition
- Substraction
- Negation (flip sign)
- Comparison
- Boolean operations
- Extend 32-bit to 64-bit
- Even larger values

## The Extend flag

Most 68000 instructions set the condition flags, which are used by branch
instructions and such. One of those flags however is to allow working
with large numbers: the Extend flag holds the carry or borrow of the last
operation (note that this is *not* the same thing as the Carry or
Overflow flags).

There are also a few "extended" instructions to go with it: `addx`

, `subx`

, etc. The way it works is that you start
the operation with the "normal" instruction (e.g. `add`

) on
the lower bits, then execute the "extended" instructions (e.g. `addx`

) on the higher bits. The Extend flag is used to pass carry
from the lower to the higher bits. You can perform operations on numbers
with arbitrary sizes this way.

**
Note that the Extend and Carry flags are not the same, and a
few instructions will affect them differently (e.g. move
will clear Carry but not touch Extend). Extend is used for working
with large numbers, Carry is used for comparisons.
**

## Addition

To add two 64-bit numbers, you need two instructions: first do `add.l`

on the low halves of both operands, then `addx.l`

on the high halves. The `addx`

instruction resumes the last
addition (in particular moving the carry from the low half to the high
half). Flags will be set accordingly after both instructions are
executed.

Since registers are only 32-bit, we need to use two registers to hold a
single 64-bit value. In this case, `d1`

and `d0`

hold the upper and lower halves of the destination operand, while `d3`

and `d2`

hold the source operand:

` `**add.l** d3, d1
**addx.l** d2, d0

Most of the time you'll do the above.

You can also use `addx`

when both operands are in memory, in
this case two address registers hold pointers to the *end* of the
numbers (i.e. first byte after each of them). Numbers must be stored big
endian for this to work. See the example below — note that we need to
reset the flags manually since the `add`

instruction doesn't
support an operand combination like this:

` `**lea** 8(a0), a0
**lea** 8(a1), a1
**and.b** #$00, ccr
**addx.l** -(a1), -(a0)
**addx.l** -(a1), -(a0)

One big problem is that there's no immediate form for the `addx`

instruction, so if you have to add a fixed value you'll
need to store it in registers or memory. Note that the `move`

and `moveq`

instructions don't change the Extend flag, so it's
safe to use between the two additions:

```
; Add 100 to 64-bit value in d1:d0
; Assumes that d2 is free to use
```**moveq** #100, d2
**add.l** d2, d1
**moveq** #0, d2
**addx.l** d2, d0

## Substraction

Substraction is similar to addition, but using
`sub`

and `subx`

instead:

` `**sub.l** d3, d1
**subx.l** d2, d0

Same limitations as `addx`

apply.

## Negation (flip sign)

Negation is also similar to addition, but using
`neg`

and `negx`

instead:

` `**neg.l** d1
**negx.l** d0

For the record, unlike `addx`

and `subx`

, you
*can* actually use `negx`

with just about every valid
operand (except `a0-a7`

).

## Comparison

Here we run into a problem: there is *not* a `cmpx`

instruction, so the obvious approach doesn't work. If you want to do a
comparison, you'll need to waste a couple of registers (or 8 bytes in
memory) to do a substraction and ignore its result:

```
; assume d4-d5 are free
```**move.l** d1, d5
**move.l** d0, d4
**sub.l** d3, d5
**subx.l** d2, d4
; flags will be set here

If you can discard at least one of the operands then you could instead store the result of the substraction there and avoid wasting anything else.

Another thing you can do if you need to jump away when both values are different, you may be able to get away by checking if either pair of halves is different:

` `**cmp.l** d3, d1
**bne** Different
**cmp.l** d2, d0
**bne** Different

You can't do the reverse to check if they're identical (because one pair could be but not the other), but you could instead check if they're different to hop over the actual jump:

` `**cmp.l** d3, d1
**bne.s** Different
**cmp.l** d2, d0
**bne.s** Different
**bra** Identical
Different:

If you can get away with just checking if a 64-bit number is positive or negative, you can test the sign of the upper half:

` `**tst.l** d0
**bmi** Negative

## Boolean operations

Boolean operations (`and`

, `or`

, `eor`

,
`not`

) are not affected across bits, so you can apply them
directly on the individual halves. Just bear into mind that the flags
will only reflect the result of the last half you touch, so you may need
to compare the values manually if that's
relevant.

` `**and.l** d3, d1
**and.l** d2, d0

` `**or.l** d3, d1
**or.l** d2, d0

` `**eor.l** d3, d1
**eor.l** d2, d0

` `**not.l** d1
**not.l** d0

## Extend 32-bit to 64-bit

Even when working with 64-bit values, it's likely that most of the time
you'll be dealing with 32-bit ones. This means that sooner or later
you'll find yourself wanting to use a 32-bit number in a 64-bit operation
which means you'll need to convert it to 64-bit. These examples assume
that the value is in `d0`

and will be extended to the
`d1/d0`

pair.

For unsigned numbers the upper half will be always 0, so:

` `**moveq** #0, d1

For signed numbers it's trickier: you need to make the upper half 0 if
it's positive or -1 if it's negative. One way to do this would be to
check the sign flag, then use the `smi`

instruction (which
sets a register to 0 when sign is positive or -1 when negative), then
extend the value into a longword.

` `**tst.l** d0
**smi.b** d1
**ext.w** d1
**ext.l** d1

To extend from 8-bit or 16-bit to 64-bit, first extend them to 32-bit as usual then do one of the above.

## Even larger values

Do you need *even larger* values? In theory, you can use these
instructions to work with arbitrarily large numbers (though the number
of registers may be a problem, so you may need to work off memory).

The following adds two 128-bit numbers (one in `d0-d3`

and the
other in `d4-d7`

):

` `**add.l** d7, d3
**addx.l** d6, d2
**addx.l** d5, d1
**addx.l** d4, d0

The following adds two 256-bit numbers in memory (pointed by `a0`

and `a1`

, respectively), since there's no way to
fit both of them in registers. Note that we need to clear the flags
manually because the `add`

instruction doesn't take this
operand combination.

` `**lea** 32(a0), a0
**lea** 32(a1), a1
**and.b** #$00, ccr
**addx.l** -(a1), -(a0)
**addx.l** -(a1), -(a0)
**addx.l** -(a1), -(a0)
**addx.l** -(a1), -(a0)
**addx.l** -(a1), -(a0)
**addx.l** -(a1), -(a0)
**addx.l** -(a1), -(a0)
**addx.l** -(a1), -(a0)

Finally, remember that these "extended" instructions come in byte, word and long variants, so you aren't restricted to sizes multiple of 32-bit.