在汇编中将 bin 转换为十六进制

Converting bin to hex in assembly

我是初学者,需要帮助将 16 位二进制数转换为十六进制数。我已经完成了大部分代码,但我需要一些帮助。

  1. 如何让它只接受0和1输入而忽略其余的数字和字母?
  2. 转换过程后,我得到了错误的十六进制数字。我做错了什么?

示例输入:

1010101111001101

预期输出:

ABCD

当前输出:

AAAC

这是我的代码:

.MODEL SMALL
.STACK 1000h

.DATA
  title db 'Convert BIN to HEX:.',13,10,'$'
  HEX_Map   DB  '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
  HEX_Out   DB  "00", 13, 10, '$'   ; string with line feed and '$'-terminator

.CODE

main PROC
    mov ax, @DATA                   ; Initialize DS
    mov ds, ax

    mov ah, 0                                
    mov al, 3                ;clearing                                                 
    int 10h                                                                  

    mov ah, 9                                                                 
    lea dx, title                                                          
    int 21h     ;displays title

    mov dx, 0

loop16:                                                                   
    mov cx, 16  ;loop goes 16 Times because I need 16 bit binary input
    mov bx, 0 

;here I'm checking if input numer is 0 or 1, but it doesn't work as I want      
read:                                                                       
    mov ah, 10h                                                                 
    int 16h                          

    cmp al, '0'                                                                 
    jb read                                                                         

    cmp al, '1'                                                               
    ja read10   



read10:                                                                       
    mov ah, 0eh                                                                 
    int 10h                                                                     
    sub al, 48  ;conversion, sub 48 from ascii since 0 is on 48th place in ascii, but I'm not sure if this part is must to be or not                    

    jmp end_loop 

end_loop:                                                                 
    mov ah, 0       ;ah=0 so we can add ax to bx        
    add bx, ax              

    loop read           
    push bx                         ;here I push bx on stack, bx is as my input number                                          

    mov al, 13
    mov ah, 0eh
    int 10h

    mov al, 10
    mov ah, 0eh
    int 10h 



    mov di, OFFSET HEX_Out          ; First argument: pointer
    pop bx                          ;Here I take input number from stack
    mov ax, bx
    call IntegerToHexFromMap        ; Call with arguments
    mov ah, 09h                     ; Int 21h / 09h: Write string to STDOUT
    mov dx, OFFSET HEX_Out          ; Pointer to '$'-terminated string
    int 21h                         ; Call MS-DOS

    mov ah, 10h                                                                 
    int 16h 

    mov ax, 4C00h                   ; Int 21h / 4Ch: Terminate program (Exit code = 00h)
    int 21h                         ; Call MS-DOS
main ENDP

IntegerToHexFromMap PROC
    mov si, OFFSET Hex_Map          ; Pointer to hex-character table

    mov bx, ax                      ; BX = argument AX
    and bx, 00FFh                   ; Clear BH (just to be on the safe side)
    shr bx, 1
    shr bx, 1
    shr bx, 1
    shr bx, 1                       ; Isolate high nibble (i.e. 4 bits)
    mov dl, [si+bx]                 ; Read hex-character from the table
    mov [di+0], dl                  ; Store character at the first place in the output string

    mov bx, ax                      ; BX = argument AX
    and bx, 00FFh                   ; Clear BH (just to be on the safe side)
    shr bx, 1
    shr bx, 1
    shr bx, 1
    shr bx, 1                       ; Isolate high nibble (i.e. 4 bits)
    mov dl, [si+bx]                 ; Read hex-character from the table
    mov [di+1], dl                  ; Store character at the first place in the output string

    mov bx, ax                      ; BX = argument AX
    and bx, 00FFh                   ; Clear BH (just to be on the safe side)
    shr bx, 1
    shr bx, 1
    shr bx, 1
    shr bx, 1                       ; Isolate high nibble (i.e. 4 bits)
    mov dl, [si+bx]                 ; Read hex-character from the table
    mov [di+2], dl                  ; Store character at the first place in the output string

    mov bx, ax                      ; BX = argument AX (just to be on the safe side)
    and bx, 00FFh                   ; Clear BH (just to be on the safe side)
    and bl, 0Fh                     ; Isolate low nibble (i.e. 4 bits)
    mov dl, [si+bx]                 ; Read hex-character from the table
    mov [di+3], dl                  ; Store character at the second place in the output string

    ret
IntegerToHexFromMap ENDP

IntegerToHexCalculated PROC
    mov si, OFFSET Hex_Map          ; Pointer to hex-character table

    mov bx, ax                      ; BX = argument AX
    shr bl, 1
    shr bl, 1
    shr bl, 1
    shr bl, 1                       ; Isolate high nibble (i.e. 4 bits)
    cmp bl, 10                      ; Hex 'A'-'F'?
    jl .1                           ; No: skip next line
    add bl, 7                       ; Yes: adjust number for ASCII conversion
    .1:
    add bl, 30h                     ; Convert to ASCII character
    mov [di+0], bl                  ; Store character at the first place in the output string

    mov bx, ax                      ; BX = argument AX
    shr bl, 1
    shr bl, 1
    shr bl, 1
    shr bl, 1                       ; Isolate high nibble (i.e. 4 bits)
    cmp bl, 10                      ; Hex 'A'-'F'?
    jl .2                           ; No: skip next line
    add bl, 7                       ; Yes: adjust number for ASCII conversion
    .2:
    add bl, 30h                     ; Convert to ASCII character
    mov [di+1], bl                  ; Store character at the first place in the output string

    mov bx, ax                      ; BX = argument AX
    shr bl, 1
    shr bl, 1
    shr bl, 1
    shr bl, 1                       ; Isolate high nibble (i.e. 4 bits)
    cmp bl, 10                      ; Hex 'A'-'F'?
    jl .3                           ; No: skip next line
    add bl, 7                       ; Yes: adjust number for ASCII conversion
    .3:
    add bl, 30h                     ; Convert to ASCII character
    mov [di+2], bl                  ; Store character at the first place in the output string

    mov bx, ax                      ; BX = argument AX (just to be on the safe side)
    and bl, 0Fh                     ; Isolate low nibble (i.e. 4 bits)
    cmp bl, 10                      ; Hex 'A'-'F'?
    jl .4                           ; No: skip next line
    add bl, 7                       ; Yes: adjust number for ASCII conversion
    .4:
    add bl, 30h                     ; Convert to ASCII character
    mov [di+3], bl                  ; Store character at the second place in the output string

    ret
IntegerToHexCalculated ENDP

END main                            ; End of assembly with entry-procedure

当您将位收集到 bx 时,您不能使用 int 10h (0e) 进行字符输出。该 int 调用需要 bl 设置为文本的前景色并且 bh 指向文本页面。

同样在 bx 中,您将计算 1 的数量,而不是输入的数字。在调试器(你的原始代码)中尝试,在 loop 之后放置断点并输入(盲目地,如果它没有显示)例如“1100110011001100”,bx 将是 8(如果我可能是错的有些 int 调用 destroy bx,我没有 运行 它,只是在我的脑海中)。

所以为了修复你的输入部分,我会选择 int 21h, 2 而不是显示字符,就像这样(也修复了 bx 中结果的累积):

    ; read 16 bits from keyboard ('0'/'1' characters accepted only)
    mov cx, 16  ; loop goes 16 Times because I need 16 bit binary input
    xor bx, bx  ; result number (initialized to zero)

read:
    mov ah, 10h
    int 16h     ; read character from keyboard

    cmp al, '0'
    jb read     ; ASCII character below '0' -> re-read it

    cmp al, '1'
    ja read     ; ASCII character above '1' -> re-read it

    mov dl,al   ; keep ASCII for output in DL

    shr al,1    ; turn ASCII '0'(0x30)/'1'(0x31) into CF=0/1 (Carry Flag)
    rcl bx,1    ; enrol that CF into result from right (and shift previous bits up)

    mov ah,2    ; output character in DL on screen
    int 21h

    loop read   ; read 16 bits

我没有检查其余的代码,因为如果我检查的话,我会很想完全重写它,所以暂时只关注输入部分。


调试器应该允许你每次执行一条指令(或者在任何行上放置断点,运行直到它)。

因此您可以在每一步之后检查寄存器和内存中的值。

例如,如果您在原始代码中将断点放在 add bx,ax 之前,您应该能够在调试器中读取(在按下“1”键并且调试器在 add 上中断后)那:

ax 为 1(根据按下的键),bx 从 0 到“1”次按键的计数(在进一步迭代中)。

在按了四个“1”键后,您应该很明显,bx 等于 40100 二进制)与 [=36 相去甚远=],因此有些东西无法按您的意愿工作,您必须从 "what I wanted to wrote there" 重新调整为 "what I really wrote",再次阅读您的代码并了解需要更改哪些内容才能获得预期结果。

在你的情况下,例如在 add 之前添加指令 shl bx,1 将解决这种情况(将旧位移动一个位置 "up",将最低有效位设置为零,即."ready for add ax").

继续努力尝试调试器,如果不弄清楚调试器,几乎不可能在 Assembly 中做任何事情。或者一直在这里问,看到什么,不明白什么。它对于汇编编程来说真的是绝对必要的。

其他选项只是 "emulate" CPU 在你的脑海中 运行 屏幕上的说明和帮助说明(我强烈建议纸质,PC 不知何故不能正常工作为了我)。这比使用调试器要困难和乏味得多。在开始 "emulate" 之前可能会先 weeks/months 而不会出现太多错误,因此您通常会在第一次尝试时发现错误。从好的方面来说,这会让您深入了解 CPU 的工作原理。


关于第二部分(数字到十六进制字符串的转换)。

我会尽力帮助您理解手头的内容,并从原始代码中找出一些错误来演示如何使用它。

所以你有 16 位数,例如:

1010 1011 1100 1101  (unsigned decimal 43981)

我在每组 4 位之间放置了空格(很少称为 "nibble"),因为有一个有趣的事实。每个半字节准确地形成一个十六进制数字。所以上面的数字是十六进制的:

A    B    C    D     (10, 11, 12, 13)

检查每个十六进制数字如何与上面的 4 位对应。

所以你想要的是将原始 16b 值分成四个 4 位数字,从最高有效位到最低有效位(b12-b15、b8-b11、b4-b7、b0-b3 => 16 中的特定位位数:"b15 b14 b13 ... b2 b1 b0").

每个这样的数字的值为 0-15(因为它们是 4 位,并且使用所有可能的组合),所以你想把它变成 ASCII 字符 '0'-'9'对于值 0-9,'A'-'F' 对于值 10-15.

并且每个转换后的值都存储在内存缓冲区中,在下一个字节位置,所以最后它们形成字符串 "ABCD"。

这听起来可能 "obvious",但它是第 2 部分内部计算的完整描述,因此请确保您真正理解每个步骤,以便随时对照此检查您的代码并查找差异。


现在我将向您展示我在第二部分中看到的一些错误,尝试将其与上面的 "theory" 联系起来。

首先是数据和结构:

HEX_Out   DB  "00", 13, 10, '$'

这编译为字节:'0', '0', 13, 10, '$'(或 30 30 0D 0A 24,当被视为十六进制字节时)。

如果你在上面写'A', 'B', 'C', 'D',你能发现问题吗?

你只为数字保留了两个字节(按“00”),但你写了四个字节,所以 1310 也会被覆盖。


现在关于 IntegerToHexFromMap,从代码看来您不明白 andshr 的作用(搜索 bitwise operations explanation)。

您从 bx (copy of ax) 中为前三个字符提取相同的 b4-b7 位,然后为第四个字母提取位 b0-b3。所以这是你尝试将 8 位转换代码扩展到 16 位,但你没有提取正确的位。

我会尝试对它的第一部分进行广泛的评论,让你知道你做了什么。

; bx = 16 bit value, mark each bit as "a#" from a0 to a15
    and bx, 00FFh
; the original: a15 a14 a13 ...  a2  a1  a0  bits get
; AND-ed by:      0   0   0 ...   1   1   1
; resulting into bx = "a7 to a0 remains, rest is cleared to 0"
    shr bx, 1
; shifts bx to right by one bit, inserting 0 into top bit
; bx = 0 0 0 0  0 0 0 0  0 a7 a6 a5  a4 a3 a2 a1  (a0 is in CF)
    shr bx, 1
; shifts it further
; bx = 0 0 0 0  0 0 0 0  0 0 a7 a6  a5 a4 a3 a2  (a1 is in CF)
    shr bx, 1
; bx = 0 0 0 0  0 0 0 0  0 0 0 a7  a6 a5 a4 a3 (a2 ...)
    shr bx, 1
; bx = 0 0 0 0  0 0 0 0  0 0 0 0  a7 a6 a5 a4

; so if bx was value 0x1234 at the beginning, now bx = 0x0003

; conversion to ASCII and write is OK.

因此您使用位 b4-b7 作为第一个字符,但您需要位 b12-b15。我希望你能完全理解这个,我知道一开始可能会混淆哪个位是哪个以及为什么有时右边然后左边有东西。

位通常从最低位(值 20 = 1,所以我称之为 "b0")到最高位(值 2 15 = 32768 在16位数字的情况下,我称之为"b15").

但是由于数字原因,位是从最高有效位到最低有效位(二进制数)写入的,因此 "left" 上的位以 b15 开头,"right" 上的位以 b0 结尾。

向右移动意味着将 b_i 移动到 b_(i-1),这实际上将其值减半,所以 shr value,1 也可以被视为无符号除以二。

左移是从b_ib_(i+1),有效地将值乘以二(指令 shlsal,两者都产生相同的结果,因为 b0 都设置为零)。

sar是"arithmetic"右移,保持最高有效位的值不变(符号位),所以对于-1(所有位都是1)它会再次产生-1,对于所有其他数字,它作为有符号除以二来工作。

顺便说一句,自 80286 CPU 以来,您可以使用 shr bx,4(也可以看作除以 16 = 2*2*2*2)。您真的被迫编码 8086 吗?那么可能值得用 4 加载 cl 并执行 shr bx,cl,而不是四个 shr bx,1。这让我很恼火,四行相同的行。

此外,如果您已经了解 and 的作用,那么现在您一定觉得这很荒谬:

    and bx, 00FFh  ; why not 0Fh already here???
    and bl, 0Fh

现在考虑一下如何提取第一个字符的 b12-b15 位以及如何修复 IntegerToHexFromMap


最后我将向您展示我将如何重写它以使代码非常短,我指的是源代码,但也是二进制大小。 (为了性能我会写不同的代码,而不是 8086,但是这个应该在 8086 上工作):

警告 - 尝试按照上述建议自行修复您的版本。只有当您拥有固定版本时,才可以查看我的代码,作为对 30 年前编写的一些东西的新想法的灵感。此外,如果你正在做学校作业,请确保你可以从头说出关于 XLAT 指令的所有内容,因为作为一名讲师,我会高度怀疑任何使用这个指令的学生,这是完整的历史,因为编译器不使用它,很明显代码是人写的,而且可能有经验。

IntegerToHexFromMap PROC
  ; ax = number to convert, di = string buffer to write to
  ; modifies: ax, bx, cx, dx, di

  ; copy of number to convert (AX will be used for calculation)
    mov dx, ax
  ; initialize other helpful values before loop
    mov bx, OFFSET HEX_Map  ; Pointer to hex-character table
    mov cx, 00404h          ; for rotation of bits and loop counter
      ; cl = 4, ch = 4  (!) Hexadecimal format allows me
      ; to position the two "4" easily in single 16b value.

FourDigitLoop: ; I will do every digit with same code, in a loop
  ; move next nibble (= hexa digit) in DX into b0-b3 position
    rol dx, cl
  ; copy DX b0-b3 into AL, clear other bits (AL = value 0-15)
    mov al, dl
    and al, 0Fh
  ; convert 0-15 in AL into ASCII char by special 8086 instruction
  ; designed to do exactly this task (ignored by C/C++ compilers :))
    xlat
  ; write it into string, and move string pointer to next char
    mov [di],al
    inc di
  ; loop trough 4 digits (16 bits)
    dec ch
    jnz FourDigitLoop

    ret
IntegerToHexFromMap ENDP

如果你只是使用这段代码而不了解它是如何工作的,上帝会杀了一只小猫......你不想那样,对吧?

最后免责声明:我没有任何 16 位 x86 环境,所以我没有测试就编写了所有代码(我只是偶尔尝试编译它,但语法必须是 NASM-like,所以我不要对这个 MASM/TASM/emu8086 来源这样做)。因此可能存在一些语法错误(甚至可能是功能错误?:-O),如果您无法使其正常工作,请发表评论。