“Assembly Language”, 3rd Edition by Wang Shuang
Chapter 10 CALL and RET Instructions
Experiment 10 Writing Subroutines (Page 206)
————————————
Note: Since Experiment 10 has 3 questions, and each question is more complex than previous experiments with longer code, it is divided into three articles. This is the reference answer for Question 3.
———————————–
3. Value Display
Problem
Write a program to display the data in the data segment in decimal format.
data segment
dw 123, 12666, 1, 8, 3, 38
data ends
These data are stored in memory as binary information, indicating the size of the values. To display them on the screen in a human-readable format, we need to convert the information. For example, the value 12666 is stored in the machine as binary information: 0011000101111010B (317AH), which the computer can understand. To read the understandable value 12666 on the display, we should see a string of characters: “12666”. Since the graphics card follows ASCII encoding, to display this string of characters on the monitor, it should be stored in the machine as ASCII codes: 31H, 32H, 36H, 36H, 36H (the ASCII codes for characters “0” to “9” are 30H to 39H).
From the above analysis, we can see that in the conceptual world, there is an abstract data 12666, which represents a numerical size. In the real world, it can have multiple representations, stored in electronic machines as high and low levels (binary), or written in human language “12666” on paper, blackboard, or screen. Now, the problem we face is to convert the same abstract data from one representation to another.
To display the data in decimal format on the screen, we need to perform two steps:
(1) Convert the data stored in binary format into a string representing decimal format;
(2) Display the string in decimal format;
We have already implemented the second step in the first subroutine of this experiment, so we just need to call show_str. Let’s discuss the first step, as converting binary information into a decimal string is a commonly needed function, and we should write a general subroutine for it.
Subroutine Description
Name: dtoc
Function: Converts word-type data into a string representing a decimal number, with the string ending with 0
Parameters: (ax) = word-type data
ds:si points to the starting address of the string
Returns: None
Example of Application: Write a program to display the data 12666 in decimal format at row 8, column 3 on the screen, in green. When displaying, we call the first subroutine show_str from this experiment.
assume cs:codedata segment db 10 dup(0)data endscode segment start: mov ax, 12666 mov bx, data mov ds, bx mov si, 0 call dtoc mov dh, 8 mov dl, 3 mov cl, 2 call show_str : : :code endsend start
Tip
Next, we will conduct a simple analysis of this problem.
(1) To obtain the string “12666”, we need to get a series of ASCII codes representing that string: 31H, 32H, 36H, 36H, 36H.
The ASCII code corresponding to decimal digit characters = decimal digit value + 30H.
To obtain the string representing the decimal number, we first need to find the value of each digit of the decimal number.
Example: For 12666, we first find the values of each digit: 1, 2, 6, 6, 6. Then we add 30H to each of these numbers to get the ASCII code string representing 12666: 31H, 32H, 36H, 36H, 36H.
(2) So, how do we get the value of each digit? We can use the following method:

As seen, dividing 12666 by 10 gives a remainder 5 times, recording each remainder gives us the value of each digit.
(3) Based on the above analysis, we can derive the processing steps as follows:
Divide 12666 by 10, loop 5 times, record each remainder, and add 30H to each remainder to obtain the ASCII code string representing the decimal number. As follows:

(4) Questioning (3).
Given that the data is 12666, we know that 5 iterations were performed. However, in practical problems, the programmer does not know the value of the data, meaning the program cannot determine the number of iterations in advance.
So, how do we determine that all digit values have been obtained? We can see that as long as the quotient is 0, all digit values have been obtained. We can use the jcxz instruction to implement the related functionality.
===================
Analysis Explanation:
The difficulty and core of Question 3 should be how to extract each digit of a decimal number and then convert these digits into their corresponding ASCII character codes. The implementation idea has been detailed by Professor Wang Shuang in the tips section, so it will not be repeated here. However, there are some special considerations during code implementation, such as the order of operations, register preservation, etc., which need careful thought. For example, in the following sample code, the data is in the data segment, and the string storage is in the code segment. Thus, when the code runs, the ds must point to the data segment to fetch data, and during conversion and display, ds must point to the code segment. It is easy to introduce bugs while writing code, so special attention is needed. It is recommended to thoroughly debug the code after completion to gain a better understanding of the code and logic.
Question 3 is a comprehensive problem, and many functionalities can be implemented in various ways. The following sample code is just one approach; everyone can try different methods to achieve it. For example, when converting numbers to string format, the order is reversed, requiring reordering. I used the head-tail swap method for reordering, but you can also use a stack to implement it. I hope everyone can think and try different methods.
Reference implementation code example is as follows:
;"Assembly Language", 3rd Edition by Wang Shuang; Chapter 10 CALL and RET Instructions; Experiment 10 Writing Subroutines (Page 206); 3. Value Display dtoc show_str; Application Example: dw 123,12666,1,8,3,38
assume cs:code,ds:data,ss:stackdata segment dw 123,12666,1,8,3,38data endsstack segment db 16 dup(0)stack endscode segment db 16 dup(0) ; Buffer to store the converted string start: mov ax, stack mov ss, ax mov sp, 10H ; Initial stack segment mov bp, 0 ; bp points to data mov dh, 8 ; dh stores the starting row for display mov cx, 6 ; Total of 6 data s: push cx ; cl is used for parameters, so save cx first mov ax, data mov ds, ax ; ds points to data segment mov ax, ds:[bp] ; ax as parameter, stores the word data to be converted mov bx, code ; ax cannot be reused for parameters, use bx mov ds, bx ; ds changes to point to string segment address mov si, 0 ; ds:si points to the first byte of the converted string call dtoc ; Call dtoc subroutine to complete conversion mov dl, 3 ; dl as parameter, starting column for display mov cl, 2 ; cl as parameter, display attributes call show_str ; Call show_str subroutine to display on the screen add bp, 2 ; Point to the next data add dh, 1 ; Switch to the next row pop cx loop s
mov ax, 4c00H int 21H
; dtoc subroutine, converts word-type data to string format, ending with 0 ; Parameters: (ax) = a word-type data ; Returns: ds:si points to the starting address of the converted string dtoc: push bx push cx push dx push bp push di ; Save the registers to be used on the stack mov bx, 10 ; As the divisor mov di, 0 ; Record the number of divisions get_rem: mov dx, 0 ; High 16 bits of the dividend, use 16-bit division to prevent overflow div bx ; (ax) = quotient, (dx) = remainder add dl, 30H ; Since the remainder is less than 10, we can use dl to take it, add 30H to convert to ASCII code mov ds:[si], dl ; Temporarily store in ds:si inc si ; Point to the next position inc di ; Increment the number of divisions by 1 mov cx, ax ; Store the quotient in cx jcxz do_sort ; Must save first and then judge, otherwise the last remainder will be missed jmp short get_rem ; Calculate the next remainder do_sort: mov ax, di ; After obtaining the remainders, need to reorder the string mov bl, 2 ; Adjust the sorting loop count to half of the number of divisions div bl ; (al) = quotient, (ah) = remainder mov ah, 0 ; Now ax contains the number of loops to perform mov cx, ax jcxz pre_ret ; If the string length is 1, do not swap, otherwise loop indefinitely mov bx, si mov bp, si sub bx, di ; Let bx point to the start of the string sub bp, 1 ; Let bp point to the end of the string do_swap: mov dl, [bx] ; Swap front and back two by two mov dh, ds:[bp] mov [bx], dh mov ds:[bp], dl inc bx dec bp loop do_swap pre_ret: mov byte ptr [si], 0 ; si now points to the end of the string, just write the end marker 0 sub si, di ; Let si point back to the start of the string pop di pop bp pop dx pop cx pop bx ret
; show_str subroutine, displays a null-terminated string at a specified position on the screen ; Parameters: (dh) = row, (dl) = column, (cl) = attribute, ds:si points to the string ; Returns: None show_str: push es push di push si push ax push bx mov ax, 0B800H mov es, ax mov ax, 160 mul dh mov di, ax mov ax, 2 mul dl add di, ax ; (di) = (dh)*160 + (dl)*2, points to the starting display position mov bl, cl ; Store the attribute in bl do_show: mov cl, ds:[si] mov ch, 0 jcxz show_ok mov es:[di], cl inc di mov es:[di], bl inc di inc si jmp short do_show show_ok: pop bx pop ax pop si pop di pop es retcode endsend start
The code compiled and tested successfully, as shown in the following image.
