Skip to content

Numeric Display

Convert a number (0-99) to two ASCII digits and print them. Essential for scores and counters.

Taught in Game 1, Unit 4 numbersscoretextconversion

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

AspectCost
CPU~250 cycles
Memory~30 bytes
LimitationSimple 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:

  1. Start with tens counter = 0
  2. While number >= 10: subtract 10, increment counter
  3. Counter = tens digit, remainder = units digit
  4. Add '0' ($30) to convert to ASCII

ASCII Digit Reference

DigitASCII 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.

Patterns: Character Printing

Vault: Z80 — DAA and arithmetic | ZX Spectrum