Overview
Convert a binary number to decimal digits for display. Uses repeated subtraction to extract the tens digit (no division instruction on Z80). Print both digits using the character printing routine. Perfect for scores, lives counters, and timers.
Code
; =============================================================================
; NUMERIC DISPLAY - ZX SPECTRUM
; Print 0-99 as two decimal digits
; Taught: Game 1 (Ink War), Unit 4
; CPU: ~250 cycles | Memory: ~30 bytes
; =============================================================================
; Print two-digit number
; Input: A = number (0-99), B = row, C = column
; Output: Advances C by 2
print_two_digits:
push bc
push de
; Extract tens digit by repeated subtraction
ld d, 0 ; D = tens counter
.tens_loop:
cp 10
jr c, .print ; Less than 10? Done counting
sub 10
inc d
jr .tens_loop
.print:
push af ; Save units digit
; Print tens digit
ld a, d
add a, '0' ; Convert to ASCII
call print_char
inc c ; Next column
; Print units digit
pop af
add a, '0'
call print_char
inc c
pop de
pop bc
ret
Three-digit version (0-999):
; Print three-digit number
; Input: HL = number (0-999), B = row, C = column
print_three_digits:
push bc
push de
; Extract hundreds
ld d, 0
.hundreds:
ld a, h
or a
jr nz, .sub_hundred
ld a, l
cp 100
jr c, .print_hundreds
.sub_hundred:
ld a, l
sub 100
ld l, a
ld a, h
sbc a, 0
ld h, a
inc d
jr .hundreds
.print_hundreds:
ld a, d
add a, '0'
call print_char
inc c
; Now L contains 0-99, use two-digit routine
ld a, l
call print_two_digits
pop de
pop bc
ret
With leading zero suppression:
; Print number without leading zeros
; Input: A = number (0-99), B = row, C = column
print_number:
push bc
push de
ld d, 0
.tens:
cp 10
jr c, .check_tens
sub 10
inc d
jr .tens
.check_tens:
push af
; Only print tens if non-zero
ld a, d
or a
jr z, .skip_tens
add a, '0'
call print_char
inc c
.skip_tens:
pop af
add a, '0'
call print_char
pop de
pop bc
ret
Trade-offs
| Aspect | Cost |
|---|---|
| CPU | ~250 cycles |
| Memory | ~30 bytes |
| Limitation | Simple version limited to 0-99 |
When to use: Displaying scores, lives, timers, level numbers.
When to avoid: Very large numbers (use BCD or lookup tables for speed).
How It Works
The Z80 has no division instruction, so we use repeated subtraction:
- Start with tens counter = 0
- While number >= 10: subtract 10, increment counter
- Counter = tens digit, remainder = units digit
- Add
'0'($30) to convert to ASCII
ASCII Digit Reference
| Digit | ASCII Code |
|---|---|
| '0' | $30 (48) |
| '1' | $31 (49) |
| '2' | $32 (50) |
| ... | ... |
| '9' | $39 (57) |
BCD alternative (DAA)
For score counters that increment frequently, BCD (Binary Coded Decimal) avoids the repeated-subtraction loop entirely. Store the score with each digit in 4 bits (so $99 holds 99, $123 spans two bytes), and use the Z80's DAA instruction after every add to keep the BCD encoding intact:
; Add 10 to a two-digit BCD score
add_ten_bcd:
ld a, (score_bcd)
add a, $10 ; Add 1 to the tens digit
daa ; Adjust to keep BCD form
ld (score_bcd), a
ret
; To print, just split the byte and add '0' to each nibble
print_score_bcd:
ld a, (score_bcd)
push af
rrca
rrca
rrca
rrca ; A = high nibble
and $0f
add a, '0'
call print_char
inc c
pop af
and $0f ; A = low nibble
add a, '0'
call print_char
ret
BCD pays for itself when the score updates many times per frame (combos, time bonuses) — no division loop, just one DAA per add. The trade-off is that BCD overflow needs explicit handling for scores beyond $99 / $9999.
Related
Patterns: Character Printing
Vault: Z80 — DAA and arithmetic | ZX Spectrum