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?
- Extracting the digits
- Adding byte-sized numbers
- Adding larger numbers
- Substraction
- Checking if result fits
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: