# 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

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.

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, `d0` and `d1` hold the upper and lower halves of the destination operand, while `d2` and `d3` hold the source operand:

``````    add.l   d3, d1

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

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
moveq   #0, d2

## 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
bpl     Positive``````
``````    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 `d1` and will be extended to the `d0/d1` pair.

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

``    moveq   #0, d0``

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   d1
smi.b   d0
ext.w   d0
ext.l   d0``````

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

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