Analysis of Answers for Assembly Language Experiment 10-2

“Assembly Language”, 3rd Edition by Wang Shuang

Chapter 10: CALL and RET Instructions

Experiment 10: Writing Subroutines (Page 206)

————————————

Note: Since Experiment 10 has three 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 2.

———————————–

2. Solving Division Overflow Issues

Problem

As mentioned earlier, the DIV instruction can perform division. When performing 8-bit division, the quotient is stored in AL and the remainder in AH; for 16-bit division, the quotient is stored in AX and the remainder in DX. However, there is a problem: what happens if the quotient exceeds the maximum value that AL or AX can store?

For example, consider the following code segment:

mov bh, 1

mov ax, 1000

div bh

This performs 8-bit division, and the quotient is 1000, which cannot fit in AL.

Another example is the following code segment:

mov ax, 1000H

mov dx, 1

mov bx, 1

div bx

This performs 16-bit division, and the quotient is 11000H, which cannot fit in AX.

When using the DIV instruction for division, it is likely to encounter the above situation: the quotient is too large and exceeds the range that the register can handle. When the CPU executes the DIV instruction and such a situation occurs, it triggers an internal error in the CPU known as: division overflow. We can handle this error with special programs, but we will not discuss error handling here; this will be covered in later courses. For now, let’s look at some phenomena that occur when division overflow happens, as shown in Figure 10.1.

Analysis of Answers for Assembly Language Experiment 10-2

The figure shows the results of executing the relevant code segment using Debug, where the DIV instruction caused a division overflow in the CPU, and the system handled it accordingly.

Now we understand the problem: using the DIV instruction for division can lead to division overflow. Due to this issue, when performing division operations, attention must be paid to the values of the divisor and dividend. For example, 1000000/10 cannot be calculated using the DIV instruction. So what should we do? We can use the following subroutine divdw to solve this.

Subroutine Description

Name: divdw

Function: Performs division operations that do not cause overflow, where the dividend is of dword type, the divisor is of word type, and the result is of dword type.

Parameters: (AX) = low 16 bits of dword data

(DX) = high 16 bits of dword data

(CX) = divisor

Returns: (DX) = high 16 bits of the result

(AX) = low 16 bits of the result

(CX) = remainder

Application Example: Calculate 1000000/10 (F4240H/0AH)

mov ax, 4240H

mov dx, 000FH

mov cx, 0AH

call divdw

Result: (DX) = 0001H, (AX) = 86A0H, (CX) = 0

Note

Here is a formula:

X: Dividend, range: [0, FFFFFFFF]

N: Divisor, range: [0, FFFF]

H: High 16 bits of X, range: [0, FFFF]

L: Low 16 bits of X, range: [0, FFFF]

int(): Descriptive operator for quotient, e.g., int(38/10) = 3

rem(): Descriptive operator for remainder, e.g., rem(38/10) = 8

Formula: X/N = int(H/N)*65536 + [rem(H/N)*65536+L]/N

This formula transforms the potentially overflowing division operation: X/N, into multiple non-overflowing division operations. All division operations on the right side of the equation can be performed using the DIV instruction, which will not cause division overflow.

(For readers interested in the derivation of this formula, please refer to Appendix 5).

==================

Before the programming experiment, let’s analyze and understand the above formula in depth:

(1) The purpose of the formula is to decompose the potentially overflowing division X/N into two non-overflowing divisions H/N and [rem(H/N)*65536+L]/N.

(2) The first division H/N uses the high 16 bits of the original dividend, H, divided by the original divisor N, obtaining the quotient as the high 16 bits of the original expression and the remainder as the high 16 bits of the second division’s dividend.

(3) The second division [rem(H/N)*65536+L]/N uses the remainder of the first division, rem(H/N), as the high 16 bits of the dividend (where 65536=16^4, *65536 is equivalent to shifting the hexadecimal number left by 4 bits, which in binary is a left shift of 16 bits, indicating that it should be treated as the high 16 bits; of course, in the code implementation, it is not necessary to actually multiply by 65536, just placing rem(H/N) in the corresponding register for the high 16 bits is sufficient), and the low 16 bits of the original dividend, L, as the low 16 bits of the dividend. The quotient obtained will be the low 16 bits of the original expression, and the remainder will be the original expression’s remainder.

(4) In summary, the quotient of the first division serves as the high 16 bits, and the quotient of the second division serves as the low 16 bits, together forming the quotient of the original expression; the remainder of the second division serves as the original expression’s remainder.

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); 2. Non-overflowing division operation divdw; Application example: Calculate 1000000/10 (F4240H/0AH); Result: (DX)=0001H, (AX)=86A0H, (CX)=0; Note: The subroutine uses BX and BP, which should be saved on the stack; here I have omitted that. assume cs:codecode segment    start: mov ax, 4240H  ; Parameter, low 16 bits of dividend X, L=rem(F4240H/65536)           mov dx, 000FH  ; Parameter, high 16 bits of dividend X, H=int(F4240H/65536)           mov cx, 0AH    ; Parameter, divisor N           call divdw           mov ax, 4c00H           int 21H    divdw: mov bx, ax ; L temporarily stored in BX           mov ax, dx ; H stored in AX, preparing for the first division H/N           mov dx, 0           div cx     ; H/N is 16-bit division, quotient int(H/N) in AX, remainder rem(H/N) in DX           mov bp, ax ; Quotient of H/N temporarily stored in BP           mov ax, bx ; L reloaded into AX, rem(H/N) is in DX, preparing for the second division           div cx     ; (DX) stores the remainder of the second division; (AX) stores the quotient of the second division, which is the final low 16 bits of the quotient           mov cx, dx ; The remainder of the second division is the final remainder, stored in CX           mov dx, bp ; Final quotient's high 16 bits stored in DX           ret        ; Return after division completioncode endsend start

The code compiles and runs normally, yielding the result: (AX)=86A0H, (DX)=0001H, (CX)=0, as shown in the figure below.

Analysis of Answers for Assembly Language Experiment 10-2

Leave a Comment