Msx Assembly 101: 06 Printd
This is the next installment in my
MSX Assembly 101
series. We are going to develop here printd
routine, which will print the
decimal value of the byte is the accumulator.
Comparing to printh
(hexadecimal representation) there is an additional
complication: hexadecimal representation always contains two digits (for
example, the byte 0x01
is printed as 01
, with leading zero); the number
of digits in the decimal representation depends on the value of the number:
Value | Digits | Example |
---|---|---|
below ten | 1 | 0x0 is printed as 0 |
at least ten, but less than hundred | 2 | 0x1F is printed as 31 |
at least hundred | 3 | 0xD1 is printed as 209 |
“Testing”
To “test” the routine, we are going to print border values: 0
, 9
, 10
,
99
, 100
, 199
, 200
, 255
. This makes eight test cases. To be able to
see the result better, we are going to print each result on its own line,
outputting 0xD
and then 0xA
after each number. Of course, it will create
an additional new line after the last number, but we do not care.
Traditionally, we start with printd-driver.asm
:
INCLUDE "prelude.asm"
INCLUDE "routines.asm"
ld de, @@data
ld b, 8
@@testcase:
ld a, [de]
inc de
call printd
ld a, 0dh
PutAcc
ld a, 0ah
PutAcc
djnz @@testcase
Stop
@@data:
db 0, 9, 10, 99, 100, 199, 200, 255
INCLUDE "printd.asm"
Here we will use the same prelude.asm
and routines.asm
as before, and we
will use the same Makefile
for compilation. Nevertheless, the code will not
compile yet as printd
routine itself is not written.
Notice that the code will loop (@@testcase
- djnz @@testcase
) eight times
(ld b, 8
) through defined (defined in @@data
, the address of the first
testcase is initially loaded into de
with ld de, @@data
) testcases, loading
them one by one into the accumulator (ld a, [de]
and then inc de
to move to
the next testcase). After calling printd
for each testcase, we use PutAcc
to move to the beginning of the line (printing 0xD
) and then to the next line
(printing 0xA
).
“Meat”: developing printd itself
Now it is the time to develop printd
routine itself.
Values below ten
The simplest is to print the decimal digit, corresponding to the accumulator,
if its value is below ten. The ASCII code of the character, corresponding to
the digit, can be obtained by adding 0x30
to the value (so the value 5
becomes 0x35
, which is the ASCII code of character 5
):
add 030h
PutAcc
Upper digit if value is below hundred, but is at least ten
The master plan:
- upper digit is zero
- check if ten can be subtracted from the accumulator, if it can, subtract it, increment upper digit, repeat (as the value is at least ten, this happens at least once; as the value is below hundred, it does not happen more than nine times)
- print upper digit
The corresponding code:
ld b, 0
@@loop:
cp a, 10
jr c, @@found
sub 10
inc b
jr @@loop
@@found:
ld a, b
add 030h
PutAcc
Here b
stores the upper digit; the first two lines after @@loop
check if
a value in the accumulator is over 10
, and if it is not, pass control to
@@found
label, where the upper digit from b
is printed (as discussed above,
0x30
is added to receive the corresponding ASCII code).
Upper digit if value is at least hundred
If a value is at least 200
, print 2
; otherwise, print 1
:
cp a, 200
jr c, @@lessthan200
ld a, 032h
PutAcc
jr @@end
@@lessthan200:
ld a, 031h
PutAcc
@@end:
Putting it all together
We are going to proceed as follows:
- Check if number is less than
10
. If not, go to 3. - Print a digit corresponding to the number of ones.
- Check if number is less than
100
. If not, go to 5. - Store what is left in the accumulator after removing all tens we could remove, print the number of tens, restore stored value and go to 2.
- Check if number is less than
200
. If not, go to 7. - If we are here, our number is at least
100
and at most199
. Print1
, decrease number by100
, and go to 4. - If we are here, our number is at least
200
(and at most255
as it is represented by one byte). Print2
, decrease number by200
, and go to 4.
The resulting code:
printd:
cp a, 10
jr c, @@first_digit
cp a, 100
jr c, @@second_digit
cp a, 200
jr c, @@lessthan200
push af
ld a, 032h
PutAcc
pop af
sub 200
jr @@second_digit
@@lessthan200:
push af
ld a, 031h
PutAcc
pop af
sub 100
@@second_digit:
push bc
ld b, 0
@@loop:
cp a, 10
jr c, @@found
sub 10
inc b
jr @@loop
@@found:
push af
ld a, b
add 030h
PutAcc
pop af
pop bc
@@first_digit:
add 030h
PutAcc
ret
Notice that to use PutAcc
we need to destroy a value in the accumulator,
hence push af
/pop af
pairs. Also the code (in @@second_digit11
) uses b
register, which we need in printd-driver.asm
, so it is saved on the stack. It
is possible to use another register and avoid preserving it (but do not forget
that “driver” code also uses de
).
