Skip to content

Numeric Display

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

Code status: maintained Z80 routines, executed in a bounded CPU fixture. The checks compare actual memory or emitted characters with independent expectations. They establish the stated routine contracts in uncontended 48K RAM; ROM startup, display timing and original hardware are separate checks.

Audit execution record · Scope and remaining checks

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.

Calling the routines

Supply print_char: A is an ASCII character, B is the row and C is the column. It must preserve BC, DE and HL. The character printer meets that contract. Include numeric.inc once and allocate one writable score_bcd byte if using its BCD helpers.

print_two_digits accepts A = 0–99 and writes two digits, including a leading zero. print_three_digits accepts HL = 0–999 and writes three. Both return C immediately after their digits and preserve B/DE; the three-digit routine also changes HL. print_number accepts A = 0–99, suppresses the leading zero and advances C by one or two. It preserves B/DE/HL. AF is working state.

Choose a start column that leaves enough room: C ≤ 30 for two digits and C ≤ 29 for three. B is 0–23 for the bitmap printer. Invalid numeric values are outside these contracts. Call once after a value changes; doing the same division and drawing every update is unnecessary.

The BCD helpers accept a valid packed decimal byte, from $00 to $99. Adding ten wraps 90–99 to 00–09 and sets the processor’s carry; inspect that carry before another instruction changes it if the game needs an overflow signal. The BCD printer advances C by two. A change from two visible digits to one also needs the caller to clear the old second cell.

For example, after establishing the documented stack, state and data:

Z80 assembly
    ld a,42
    ld b,10
    ld c,3
    call print_two_digits  ; prints 42; C is now5
    ; Include numeric.inc and characters.inc once; allocate score_bcd if used.

Decimal and BCD text

numeric.inc
; Pasmo syntax. Supply print_char: A=ASCII, B=row, C=column; preserve BC/DE/HL.
; Inputs must fit the display: two digits C<=30, three digits C<=29.
; Callers own coordinates and display; no memory allocation or ROM calls here.
; print_two_digits A=0..99, print_three_digits HL=0..999.
; Both preserve B/DE and return C advanced by their digit count; AF changes.
; print_three_digits also changes HL to the remainder after hundreds.
; print_number A=0..99 advances C by one or two; preserves B/DE/HL.
; print_score_bcd needs a valid packed-BCD score_bcd byte and C<=30; C advances2.
; =============================================================================
; NUMERIC DISPLAY - ZX SPECTRUM
; Print 0-99 as two decimal digits
; Taught: Game 1 (Ink War), Unit 4
; =============================================================================

; Print two-digit number
; Input: A = number (0-99), B = row, C = column
; Output: Advances C by 2
print_two_digits:
    push    de

    ; Extract tens digit by repeated subtraction
    ld      d, 0                ; D = tens counter
numeric_tens_loop:
    cp      10
    jr      c, numeric_print           ; Less than 10? Done counting
    sub     10
    inc     d
    jr      numeric_tens_loop

numeric_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
    ret

; Print three-digit number
; Input: HL = number (0-999), B = row, C = column
print_three_digits:
    push    de

    ; Extract hundreds
    ld      d, 0
numeric_hundreds:
    ld      a, h
    or      a
    jr      nz, numeric_sub_hundred
    ld      a, l
    cp      100
    jr      c, numeric_print_hundreds
numeric_sub_hundred:
    ld      a, l
    sub     100
    ld      l, a
    ld      a, h
    sbc     a, 0
    ld      h, a
    inc     d
    jr      numeric_hundreds

numeric_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
    ret

; Print number without leading zeros
; Input: A = number (0-99), B = row, C = column
print_number:
    push    de

    ld      d, 0
numeric_tens:
    cp      10
    jr      c, numeric_check_tens
    sub     10
    inc     d
    jr      numeric_tens

numeric_check_tens:
    push    af

    ; Only print tens if non-zero
    ld      a, d
    or      a
    jr      z, numeric_skip_tens

    add     a, '0'
    call    print_char
    inc     c

numeric_skip_tens:
    pop     af
    add     a, '0'
    call    print_char
    inc     c

    pop     de
    ret

; 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
    inc     c
    ret

Code

Three-digit version (0-999):

With leading zero suppression:

Trade-offs

Aspect Cost
CPU Not measured for this caller; include the selected input, drawing and scheduling costs
Memory Assemble the selected routines and caller to obtain code and state sizes
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:

  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

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:

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