Print a number in any base without help
The same program in M68K, MIPS, RISC-V, x86.
This program prints the number 48879 in hexadecimal, decimal, and binary. Open it in the editor, choose Build, then Run. The console shows:
BEEF
48879
1011111011101111
In Read two numbers and print their sum, a number output port converted a value into decimal
characters. Here the program does the conversion itself. It sends only characters to port 0x10:
each out (P_CHAR), a prints the character whose code is in a.
P_CHAR equ 0x10
.org 0x8000
ld hl, 48879 ; n = 48879
ld c, 16 ; in hexadecimal
call print_in_base
ld hl, 48879
ld c, 10 ; in decimal
call print_in_base
ld hl, 48879
ld c, 2 ; in binary
call print_in_base
halt
; print an unsigned 16-bit number in base 2 to 36: number in hl, base in c
print_in_base:
ld de, buffer_end
dec de
xor a
ld (de), a ; put the zero terminator at the end
digit:
xor a ; the remainder, which becomes the digit
ld b, 16 ; sixteen bits of n
divide:
add hl, hl ; shift the next dividend bit into the C flag
rla ; bring that bit into the remainder
cp c
jr c, no_sub
sub c ; the divisor goes in once
inc l ; set the new quotient bit to 1
no_sub:
djnz divide
cp 10
jr c, is_digit
add a, 'A' - 10 ; 10 and up become 'A' and up
jr store
is_digit:
add a, '0' ; 0 to 9 become '0' to '9'
store:
dec de
ld (de), a ; in front of the digits we have already
ld a, h
or l ; is anything left of n?
jr nz, digit
ex de, hl ; the first character of the answer
call print
ld a, 10
out (P_CHAR), a
ret
; print(p): the zero terminated string at hl
print:
ld a, (hl)
or a
ret z
out (P_CHAR), a
inc hl
jr print
.org 0x9000
buffer: .ds 18 ; sixteen binary digits, the terminator and a spare byte
buffer_end:
Dividing by the base leaves one digit as the remainder, and it is the rightmost digit. For
example, 48879 divided by 16 is 3054 with 15 left over; 15 becomes the F at the right end of
BEEF. The next division works on 3054 and finds the digit to its left. Rather than reversing the
digits afterwards, the program fills the buffer backwards. It starts with a zero terminator near
buffer_end, then dec de places each new character in front of those already stored.
The divide loop uses the shift and subtract method from Multiply and divide, with the remainder.
Each division takes sixteen passes because hl holds a 16-bit number. Think of hl as unread
dividend bits followed by quotient bits being built. add hl, hl shifts the next dividend bit out
of the top of hl into the C flag and leaves a zero at the bottom. rla shifts that bit into the
remainder in a. If the remainder reaches the base in c, sub c removes one base and inc l
changes the new bottom bit of hl from zero to one. Otherwise it stays zero. After sixteen passes,
hl holds the quotient and a holds the remainder. This division works here because bases 2
through 36 keep the intermediate remainder within one byte.
The remainder becomes a character. Adding '0' converts values 0 through 9 to '0' through
'9'. For 10 through 35, adding 'A' - 10 converts them to 'A' through 'Z'; the assembler
calculates that constant. The routine expects a base from 2 to 36 in c and does not check it.
After storing a character, ld a, h and or l check whether the quotient in hl is zero: the
result is zero only when both bytes are zero. If it is not, the routine divides that quotient for
the next digit. It runs the division at least once, so an input of zero prints 0. The code then
passes the completed zero-terminated string to print and sends character code 10 for a newline.
The buffer has 18 bytes: the longest answer is sixteen binary digits, followed by its zero
terminator, with one spare byte. In the binary run, the first digit is at 0x9001 and the
terminator is at 0x9011.
Try changing all three ld hl, 48879 lines to ld hl, 31. Predict the three lines before you
build and run. Hexadecimal uses two digits, but decimal and binary need different lengths; the
same routine handles all three.
Check your answer
The lines are 1F, 31, and 11111. In base 16, 31 divided by 16 leaves 15 (F) and a
quotient of 1, so the next digit (1) is stored in front of it.