BCD math

Ever need to display a number on screen but you don't want to deal with slow divisions? (especially with large numbers) Then you should look into BCD, which stores numbers in a way that makes it easy to extract the digits.

BCD is ideal for anything that'd be on screen like score, lives, ammo, seconds, etc.

What's BCD?

BCD (Binary Coded Decimal) is a way to store decimal numbers that uses four bits per digit (10 out of the 16 possible combinations are used). The advantage is that you can split away the digits with only bit shifting (instead of slow divisions), making it ideal for values that need to be displayed on screen (like score).

To figure out the BCD equivalent of a number, just take the number in decimal and write it as hexadecimal. For example, 1234 in BCD would be $1234.

Extracting the digits

The idea behind BCD is that each digit is exactly four bits. You can extract each digit by shifting (or rotating) four bits, then doing an and with $0F (which will mask out the bottom four bits). This example goes through all digits of a 32-bit BCD number (which can hold 8 digits):

; d0.l = BCD number

    moveq   #8-1, d7
Loop:
    ; Push the next digit into the
    ; bottom bits and mask them out
    rol.l   #4, d0
    move.w  d0, d1
    and.w   #$0F, d1
    
    ; Now d1.w contains the digit
    ; Do whatever you need to do
    
    dbf     d7, Loop

Adding byte-sized numbers

68000 can do BCD operations! Albeit on a per-byte basis.

The abcd instruction lets you add two BCD bytes. This is good enough if you only need two digits. First you need to clear the flags (later you'll understand why). Then use the abcd instruction on two data registers (e.g. d0 and d1) much like you'd do with normal additions:

    and     #0, ccr     ; Clear flags
    abcd    d1, d0      ; Add numbers

Sadly, the abcd instruction is rather limited, so you can't add fixed values as-is... you'll need to store the value into a register then do the above:

    moveq   #1, d1      ; Number to add
    and     #0, ccr     ; Clear flags
    abcd    d1, d0      ; Add numbers

Adding larger numbers

The above won't help with larger numbers (like score). For that, there's another variant of abcd that uses memory instead. Of course you need the numbers in RAM for this.

Take two address registers (e.g. a0 and a1) and make them point to the first byte after the numbers. Then clear the flags, and use abcd for as many bytes as needed, like in the example below (which is for 32-bit numbers):

    lea     (Number1+4), a0
    lea     (Number2+4), a1
    
    and     #0, ccr         ; Clear flags
    abcd    -(a1), -(a0)    ; Add first two digits
    abcd    -(a1), -(a0)    ; Add next two digits
    abcd    -(a1), -(a0)    ; Add next two digits
    abcd    -(a1), -(a0)    ; Add last two digits

(now you can see why you need to clear the flags: the carry is also added)

Substraction

Substraction works the same way but you use sbcd instead of abcd. That's all.

Checking if result fits

If you need to check if the result is too large to fit (or too small, if substracting), all you need to do is check the carry flag after you're done with it (with bcc or bcs). If carry is set, it means the result doesn't fit and you should do something.

Simple example on how to prevent score from going back to zero:

    lea     (Score+4), a0
    lea     (Points+4), a1
    
    ; Add points to the score
    and     #0, ccr
    abcd    -(a1), -(a0)
    abcd    -(a1), -(a0)
    abcd    -(a1), -(a0)
    abcd    -(a1), -(a0)
    
    ; Check if score went beyond the limit
    ; If so, force it back to the maximum
    bcc     ScoreOk
    move.l  #$99999999, (Score)
ScoreOk: