Печать десятичного числа в эмуляторе 8086

Я реализовал умножение двух больших целых чисел в emu8086 с кодом, как показано ниже:

; MULTIPLY N1 * N2 = RES
MULTIPLY PROC
    MOV BH, 0H        

    MOV CH, 0H
    MOV CL, L1; initial counter of first loop ( L1 -> length of N1 )
    DEC CX

    MUL_1:
        MOV COUNTER, CL ; store counter of first loop
        MOV CL, L2 ; initial counter of second loop ( L2 -> length of N2 )

        MUL_2:
            MOV BH, 0H
            MOV BL, COUNTER
            DEC BL
            MOV AL, N1[BX] ; get BX th byte of N1

            MOV BL, CL
            DEC BL

            MUL N2[BX] ; multiple N1 and N2 's bytes

            MOV BH, 0H
            MOV BL, COUNTER
            ADD BX, CX
            DEC BX

            ADD RES[BX], AL ; AL should be add into RES[loop1_counter + loop2_counter - 1]
            ADC RES[BX+1], AH; AH and carry should be add into RES[loop1_counter + loop2_counter ]
            ADC RES[BX+2], 0H; carry of above addition should be place here.
        LOOP MUL_2     

        MOV CL, COUNTER; retrieve loop 1 counter 
    LOOP MUL_1   

    RET ; end function 
MULTIPLY ENDP

Итак, я хочу напечатать его в Decimal Mode, я знаю, как напечатать результат в HexaDecimal:

PRINT_TABLE PROC
    MOV CX, 16D

    CASE:
        MOV BX, 16D
        SUB BX, CX

        MOV AL, RES[BX]

        CMP AL, 10D
        JB LBL1
        JAE LBL2


        LBL1:
            ADD AL, '0'
            JMP CONTINUE

        LBL2:
            ADD AL, 55D

        CONTINUE:

        MOV DL, AL
        MOV AH, 02H
        INT 21H        
    LOOP CASE
    RET
PRINT_TABLE ENDP

Может ли кто-нибудь помочь мне распечатать мой результат в десятичном режиме?

Спасибо за ваш аванс :)


person Hossein Mobasher    schedule 02.02.2012    source источник


Ответы (3)


К сожалению, преобразовать значение в десятичное число не так просто, как преобразовать его в шестнадцатеричное. Это связано с тем, что основание 10 не связано с основанием 2 (т.е. 10 не является степенью числа 2). Нам нужно использовать модуль и деление для достижения преобразования. Общий алгоритм на C будет выглядеть примерно так:

unsigned int val = 58932; // assume int is 32-bit
char buf[11] = { 0 }, *chr = buf+9; // 11 characters is enough because log10(2^32) = 9,63, +1 for \0
do
{
    *chr = (val % 10) + '0'; // to ascii
    --chr;
} while((val /= 10) != 0);
++chr;

По завершении chr будет указывать на массив char* с нулевым завершением, который будет содержать ASCII-представление значения val по основанию 10.

Вы можете добиться этого в сборке с помощью инструкции DIV. Большинство оптимизирующих компиляторов C оптимизируют его до операции умножения, которая намного быстрее, чем деление (однако это можно сделать, только если делитель постоянный).

person Daniel Kamil Kozar    schedule 02.02.2012
comment
Поскольку это вопрос 8086 (16-битный), обработка 32-битных значений с помощью метода DIV нетривиальна в ассемблере из-за потенциального переполнения деления. Если вы поместите в DX:AX 32-битное значение, равное ›= 655360, и используете DIV для деления на 10, частное не может быть представлено в 16-битном формате, и возникнет исключение. - person Michael Petch; 12.07.2016

Я решил свою проблему с изменением кода, как показано ниже:

NORMALIZE PROC
    MOV CH, 0H
    MOV CL, L1
    ADD CL, L2
    DEC CX

    NOMRALIZE_LOOP:
        MOV BX, CX
        DEC BX
        MOV AL, RES[BX]
        MOV AH, 1H
        MUL AH
        AAM            
        MOV RES[BX], AL
        ADD RES[BX-1], AH
    LOOP NOMRALIZE_LOOP
    RET
NORMALIZE ENDP

; MULTIPLY N1 * N2 = RES
MULTIPLY PROC
    MOV CH, 0H
    MOV CL, L1

    MOV AL, '0'

    MOV BH, 0H

    SUB_N1:
        MOV BL, CL 
        DEC BL
        SUB N1[BX], AL
    LOOP SUB_N1

    MOV CL, L2

    SUB_N2:
        MOV BL, CL
        DEC BL
        SUB N2[BX], AL
    LOOP SUB_N2

    MOV CH, 0H
    MOV CL, L1

    MUL_1:
        MOV COUNTER, CL
        MOV CL, L2  

        MUL_2:
            MOV BH, 0H
            MOV BL, COUNTER
            DEC BL
            MOV AL, N1[BX]

            MOV BL, CL
            DEC BL

            MUL N2[BX]

            AAM

            MOV BH, 0H
            MOV BL, COUNTER
            ADD BX, CX
            DEC BX
            DEC BX

            ADD RES[BX], AL
            ADC RES[BX-1], AH
            ADC RES[BX-2], 0H
        LOOP MUL_2     

        MOV CL, COUNTER
    LOOP MUL_1

    RET
MULTIPLY ENDP

Я изменил умножение и сохранение чисел с помощью функции AAM. В конце я добавляю функцию NORMALIZE для преобразования результата в десятичный вид. :)

Надеюсь, другие смогут использовать его :)

person Hossein Mobasher    schedule 02.02.2012
comment
AAM — это инструкция, используемая для десятичной арифметики и операций над числами, хранящимися в BCD. Он недоступен в 64-битном режиме. Но пока вас интересует только то, как это работает в emu8086, тогда — отлично! :) - person Daniel Kamil Kozar; 02.02.2012

ОБНОВЛЕНИЕ: ранее я опубликовал способ печати только 16-битного числа, но теперь я нашел способ также печатать 32-битное число, поэтому я решил удалить предыдущее решение. Вот общая идея: - Проверьте, является ли число отрицательным или положительным - Если оно отрицательное, инвертируйте его, используя дополнение до двух. Но мы должны позаботиться об одном крайнем случае: 32-битное число со знаком изменяется от -2^31 до 2^31-1, так что мы можем видеть, что положительного эквивалента -2^31 не существует. Таким образом, мы должны выделить этот случай и сохранить соответствующее число (в строковом формате) и напечатать его. Если оно положительное, то мы многократно делим на 10, берем остаток, помещаем его в стек, а затем выталкиваем обратно и печатаем каждое число последовательно (это несложно, т.к. в сборке x8086 предусмотрены соответствующие подпрограммы) Кстати, в своем коде я также проверял, если 16-битная старшая часть равна нулю, то переходим к подпрограмме _16bits_routine, основанной на том же принципе. Однако деление на 10 не является тривиальным в 32-разрядных системах, поскольку функция div не поддерживает его, поэтому я делаю следующее:

Let A be the number in question, we now divide by 10:
A = q*10 + r (0 <= r <= 9)
now separate A into the high and low parts:
A_high * 2^16 + A_low = q*10 + r (0 <= r <= 9)
our task is to find q and r. To do that we first divide the high part:
A_high = q_high * 10 + r_high (0<= r_high <= 9)
=> A_high * 2^16 = (q_high*2^16)*10 + r_high * 2^16 . Note that r_high is from 0 to 9, so to divide r_high * 2^16 by 10, we simply need to perform the calculations and then store the results in a lookup table! The result:
r_high * 2^16 = q_high_redundant * 10 + r_high_redundant (0 <= r_high_redundant <= 9) (found by using a lookup table) (an interesting note: q_high_redundant is only 16 bits!)
Now divide the low part:
A_low = q_low * 10 + r_low
=> A = A_high * 2^16 + A_low = (q_high*2^16 + q_low + q_high_redundant)*10 + r_low + r_high_redundant
Now you just have to divide r_low + r_high_redundant and add in to the quotient, then you get the results.

Вот код, он не очень быстрый, но и не слишком медленный, поэтому я надеюсь, что вы найдете его полезным:

;Written by Dang Manh Truong
.stack      100h
.data 
base_10     dw      10     
var_32bits_high     dw      0
var_32bits_low     dw      0
quotidient_32bits_high      dw      0
quotidient_32bits_low       dw      0
negate_mask         equ      0FFFFh  
lowest_signed_32bits_high        dw     8000h
lowest_signed_32bits_low         dw     0000h
lowest_signed_32bits_string      dw     "-2147483648$"
qhigh       dw      0
rhigh       dw      0
qlow        dw      0
rlow        dw      0
qhigh_redundant     dw      0
rhigh_redundant     dw      0
q_0         dw      0     
qhigh0      equ     0h
rhigh0      equ     0h
qhigh1      equ     1999h
rhigh1      equ     6h
qhigh2      equ     3333h
rhigh2      equ     2h
qhigh3      equ     4CCCh
rhigh3      equ     8h
qhigh4      equ     6666h
rhigh4      equ     4h
qhigh5      equ     8000h
rhigh5      equ     0h
qhigh6      equ     9999h
rhigh6      equ     6h
qhigh7      equ     0B333h
rhigh7      equ     2h
qhigh8      equ     0CCCCh
rhigh8      equ     8h
qhigh9      equ     0E666h
rhigh9      equ     4h   

.code
main        proc
;Initialization  
    mov     ax,@data
    mov     ds,ax     
;example: 7654321 = 0074CBB1h
;    mov     ax,74h
;    mov     var_32bits_high,ax
;    mov     ax,0CBB1h  
;    mov     var_32bits_low,ax  

;example: 10223803 = 009C0BBh
;    mov     ax,9Ch
;    mov     var_32bits_high,ax
;    mov     ax,0BBh
;    mov     var_32bits_low,ax

;example: 32763    
;    mov     ax,0h
;    mov     var_32bits_high,ax
;    mov     ax,32763
;    mov     var_32bits_low,ax   

;example: 86420 = 00015194h
;    mov     ax,1h
;    mov     var_32bits_high,ax
;    mov     ax,5194h
;    mov     var_32bits_low,ax   

;example: 2147483647 (2^31 - 1) = 7FFFFFFFh
;    mov     ax,7FFFh
;    mov     var_32bits_high,ax
;    mov     ax,0FFFFh
;    mov     var_32bits_low,ax   

;example: -2147483648 (-2^31)= 80000000h
;    mov     ax,8000h
;    mov     var_32bits_high,ax
;    mov     ax,0000h
;    mov     var_32bits_low,ax

;example: -1 = FFFF FFFFh
    mov     ax,0FFFFh
    mov     var_32bits_high,ax
    mov     ax,0FFFFh
    mov     var_32bits_low,ax    

    mov     ax,0
    mov     bx,0        ;bx: quotidient_32bits_high    
    mov     dx,0        ;dx: quotidient_32bits_low  
    mov     cx,0        ;counter = 0  
;16bits or 32bits ?
    mov     ax,var_32bits_high
    cmp     ax,0
    jne     _32bits_routine
    jmp     _16bits_routine

;;;        
_32bits_routine:
    mov     cx,0
;if == -2147483648 (-2^31)   
    mov     ax,var_32bits_high
    cmp     ax,lowest_signed_32bits_high
    jne     check_if_neg
    mov     ax,var_32bits_low
    cmp     ax,lowest_signed_32bits_low
    jne     check_if_neg
;then 
    lea     dx,lowest_signed_32bits_string 
    mov     ah,9
    int     21h
    jmp     return_to_dos
;if < 0
check_if_neg:
    mov     ax,var_32bits_high
    cmp     ax,0
    jnl      preparations
;then print "-" ...
    mov     ah,2
    mov     dl,'-'
    int     21h 
;... and negate number
    mov     ax,var_32bits_high 
    xor     ax,negate_mask
    mov     var_32bits_high,ax
    mov     ax,var_32bits_low
    xor     ax,negate_mask
    inc     ax  
    mov     var_32bits_low,ax
    jnc     preparations
    mov     ax,var_32bits_high
    inc     ax
    mov     var_32bits_high,ax           
preparations:    
    mov     ax,var_32bits_high
    mov     quotidient_32bits_high,ax
    mov     ax,var_32bits_low
    mov     quotidient_32bits_low,ax
while_32bits:
; while >0 do
    mov     ax,quotidient_32bits_high
    cmp     ax,0
    jne     div_high_part
    mov     ax,quotidient_32bits_low
    cmp     ax,0
    jne     div_high_part
    jmp     print_char    
div_high_part:           
;divide high part
    mov     dx,0
    mov     ax,quotidient_32bits_high
    div     base_10
    mov     qhigh,ax
    mov     rhigh,dx
;case rhigh
    mov     ax,rhigh
    cmp     ax,0
    je      _rhigh0
    cmp     ax,1
    je      _rhigh1
    cmp     ax,2
    je      _rhigh2
    cmp     ax,3
    je      _rhigh3
    cmp     ax,4
    je      _rhigh4
    cmp     ax,5
    je      _rhigh5
    cmp     ax,6
    je      _rhigh6
    cmp     ax,7
    je      _rhigh7
    cmp     ax,8
    je      _rhigh8
    cmp     ax,9
    je      _rhigh9
_rhigh0:
    mov     ax,qhigh0
    mov     qhigh_redundant,ax
    mov     ax,rhigh0
    mov     rhigh_redundant,ax
    jmp     _aftercase
_rhigh1:
    mov     ax,qhigh1
    mov     qhigh_redundant,ax
    mov     ax,rhigh1
    mov     rhigh_redundant,ax
    jmp     _aftercase
_rhigh2:
    mov     ax,qhigh2
    mov     qhigh_redundant,ax
    mov     ax,rhigh2
    mov     rhigh_redundant,ax    
    jmp     _aftercase
_rhigh3:
    mov     ax,qhigh3
    mov     qhigh_redundant,ax
    mov     ax,rhigh3
    mov     rhigh_redundant,ax
    jmp     _aftercase
_rhigh4:
    mov     ax,qhigh4
    mov     qhigh_redundant,ax
    mov     ax,rhigh4
    mov     rhigh_redundant,ax
    jmp     _aftercase
_rhigh5:
    mov     ax,qhigh5
    mov     qhigh_redundant,ax
    mov     ax,rhigh5
    mov     rhigh_redundant,ax
    jmp     _aftercase
_rhigh6:
    mov     ax,qhigh6
    mov     qhigh_redundant,ax
    mov     ax,rhigh6
    mov     rhigh_redundant,ax
    jmp     _aftercase
_rhigh7:
    mov     ax,qhigh7
    mov     qhigh_redundant,ax
    mov     ax,rhigh7
    mov     rhigh_redundant,ax
    jmp     _aftercase
_rhigh8:
    mov     ax,qhigh8
    mov     qhigh_redundant,ax
    mov     ax,rhigh8
    mov     rhigh_redundant,ax
    jmp     _aftercase
_rhigh9:    
    mov     ax,qhigh9
    mov     qhigh_redundant,ax
    mov     ax,rhigh9
    mov     rhigh_redundant,ax
    jmp     _aftercase
_aftercase:
;divide low part
    mov     ax,0
    mov     q_0,ax
    mov     dx,0
    mov     ax,quotidient_32bits_low
    div     base_10
    mov     qlow,ax
    mov     rlow,dx
    mov     ax,rlow
    add     ax,rhigh_redundant          
;if remainder >= 10 
    cmp     ax,base_10
    jl      after_if
    sub     ax,base_10
    mov     dx,1 
    mov     q_0,dx     
after_if:
    mov     rlow,ax
    mov     ax,q_0
    add     ax,qlow
    mov     qlow,ax
    jnc     label1
    mov     ax,qhigh
    inc     ax
    mov     qhigh,ax     
label1:    
    mov     ax,qlow
    add     ax,qhigh_redundant
    mov     qlow,ax
    jnc     label2
    mov     ax,qhigh
    inc     ax
    mov     qhigh,ax
label2:    
;push remainder to stack    
    mov     ax,rlow
    push    ax     
    inc     cx
    mov     ax,qhigh
    mov     quotidient_32bits_high,ax
    mov     ax,qlow
    mov     quotidient_32bits_low,ax
    jmp     while_32bits

;;;        
_16bits_routine:
    mov     ax,var_32bits_low
    mov     bx,0   ;bx: quotient 
    mov     cx,0   
while_loop:
    cmp     ax,0
    je      print_char    
    mov     dx,0
    div     base_10
    mov     bx,ax ;ax stores quotidient  
    mov     ax,dx ;dx stores remainder
;push remainder
    push    ax  
;counter = counter + 1
    inc     cx       
;numerator = quotidient
    mov     ax,bx
    jmp     while_loop 
print_char:
    cmp     cx,0
    je      return_to_dos
    pop     ax
;because at this point 0 <= ax <= 9, setting ah = 2 does not change the results
    mov     ah,2
    mov     dl,al
    add     dl,30h   ;0-> '0',1->'1',....
    int     21h
    dec     cx
    jmp     print_char

return_to_dos:
    mov     ah,4ch
    int     21h
main        endp
    end     main
person Dang Manh Truong    schedule 11.03.2017
comment
Деление с повышенной точностью намного проще, чем то, как вы это делаете. Основная причина, по которой div принимает входные данные в DX:AX, состоит в том, чтобы поддерживать это, используя остаток старшей половины div в качестве старшей половины делимого для младшей половины div. Это один из немногих случаев, когда безопасно использовать div с DX != 0. См. Отображение чисел в DOS. - person Peter Cordes; 27.04.2020