Converting bin to hex in assembly
Asked Answered
G

1

1

I'm beginner and I need help with converting 16-bit binary number to hex. I have done most of the code, but I need help with a couple of things.

  1. How to make it only accept 0 and 1 in input and ignore the rest of numbers and letters?
  2. After conversion process I'm getting wrong number in hex. What did I do wrong?

Example input:

1010101111001101

Expected output:

ABCD

Current output:

AAAC

Here's my code:

.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
Grissel answered 26/11, 2016 at 11:58 Comment(10)
I see you are already check for 0 and 1 - doesn't that work, then? Also, please edit your question and provide an example of input, expected output, and the current output.Negligence
@RadLexus I just edited. And no, it's not working. When I'm writing letters and other numbers as input it allows it, and I have no idea how to make it onlly allow 0 and 1.Grissel
Can you add a comment next to each interrupt call, detailing what it does? It's been a while since I knew them by heart. The bottom half of your code is very well commented, by the way.Negligence
@RadLexus Just added more comments, I hope it's better now. I'm almost done with this code, but I have no idea how to repair it so it works properly.Grissel
Have you attempted to use a debugger to step through your code to see why it doesn't work?Stevie
Only allowing zero and one would be much more involved. Ignoring anything other than zeroes and ones is much easier.Faceplate
At a glance, this ja read10 seems wrong – you always end up at the next instruction at read10. Perhaps ja read makes that part work.Negligence
@MichaelPetch Yes, but as I said I'm beginner and what debugger says doesn't really help me :(Grissel
@DavidHoelzer Yes, but can you tell me how it should look like, because I have no idea how to code it.Grissel
@RadLexus I deleted read10 part and changed for cmp al, '1' ja read10 But now I cant write anything in inputGrissel
P
5

You can't use int 10h (0e) for char output when you collect bits into bx. That int call requires bl set to foreground colour of text and bh to point to text page.

Also in bx you will count number of ones, not the input number. Try it in debugger (your original code), put breakpoint after loop and enter (blindly, if it doesn't show) for example "1100110011001100", bx will be 8 (I may be wrong if some int call destroy bx, I didn't run it, just in my head).

So to fix your input part I would go for int 21h, 2 instead for displaying the chars, like this (also fixes the accumulation of result in 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

I didn't check the rest of the code, because if I would, I would have strong itch to rewrite it completely, so let stick with the input part only for the moment.


The debugger should allow you to step one instruction per time (or to put breakpoints on any line, and run up till it).

So you can examine values in registers and memory after each step.

If you will for example put breakpoint ahead of your add bx,ax in original code, you should be able to read in debugger (after hitting "1" key and debugger breaking on the add) that:

ax is 1 (according to key pressed), and bx goes from 0 to the count of "1" key presses (in further iterations).

After doing like four "1" key presses it should be obvious to you, that bx equal to 4 (0100 in binary) is far off from 1111, thus something doesn't work as you wanted and you have to readjust from "what I wanted to wrote there" to "what I really wrote", read your code again and understand what needs to be changed to get expected result.

In your case for example adding instruction shl bx,1 ahead of add would fix the situation (moving old bits by one position "up", leaving least significant bit set to zero, ie. "ready for add ax").

Keep trying the debugger stuff hard, it's almost impossible to do anything in Assembly without figuring out debugger. Or keep asking here, what you see and what you don't understand. It's really absolutely essential for Assembly programming.

Other option is just to "emulate" CPU in your head and run the instructions from the screen with help notes (I suggest strongly paper, PC somehow doesn't work well for me). This is much more difficult and tedious, than using debugger. May take weeks/months before you start to "emulate" without too many mistakes, so you will spot bugs usually on first try. On the bright side this would give you deep understanding of how CPU works.


About second part (number to hexadecimal string conversion).

I will try to help you understand what you have at hand, and pick up some mistakes from original code to demonstrate how to work with it.

So you have 16 bit number, like:

1010 1011 1100 1101  (unsigned decimal 43981)

I put spaces between each group of 4 bits (rarely called as "nibble"), because there's a funny fact. Each nibble forms single hexadecimal digit, exactly. So the number above is in hexadecimal:

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

Check how each hexa digit corresponds with the 4 bits above.

So what you want is to break the original 16b value into four 4 bit numbers, from most significant to least significant (b12-b15, b8-b11, b4-b7, b0-b3 => particular bits from 16 bit number: "b15 b14 b13 ... b2 b1 b0").

Each such number will be of value 0-15 (because they are 4 bits, and using all possible combinations), so then you want to turn that into ASCII character '0'-'9' for values 0-9, and 'A'-'F' for values 10-15.

And each converted value is stored into memory buffer, on next byte position, so in the end they form string "ABCD".

This may sound "obvious", but it's complete description of inner-calculation of part 2, so make sure you really understand each step, so you can check your code against this any time and search for differences.


Now I will show you some of the bugs I see in second part, trying to connect it to the "theory" above.

Data and structures first:

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

This compiles to bytes: '0', '0', 13, 10, '$' (or 30 30 0D 0A 24 when viewed as hexadecimal bytes).

If you write 'A', 'B', 'C', 'D' over it, can you spot the problem?

You reserved only two bytes (by "00") for number, but you write four bytes, so also 13 and 10 will be overwritten.


Now about IntegerToHexFromMap, from the code it looks like you don't understand what and and shr does (search for the bitwise operations explanation).

You extract for first three characters the same b4-b7 bits from bx (copy of ax), then for the fourth letter you extract bits b0-b3. So this is your try to extend 8 bit conversion code to 16 bit, but you don't extract the correct bits.

I will try to extensively comment the first part of it, to give you idea what you did.

; 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.

So you take bits b4-b7 for first character, but you need bits b12-b15. I hope you fully get this one, I know it can be confusing at start which bit is which and why sometimes there is something on right and then left.

Bits are usually named from least significant (value 20 = 1, so I call it "b0") to most significant (value 215 = 32768 in case of 16 bit number, I call it "b15").

But for numeric reasons bits are written from most significant to least significant (in binary numbers), so bits on "left" starts with b15, and bits on "right" end with b0.

Shifting to right means to move b_i to b_(i-1), which effectively halves it's value, so shr value,1 can be viewed also as unsigned division by two.

Shifting to left is from b_i to b_(i+1), effectively multiplies the value by two (instructions shl and sal, both producing same result, as b0 is set to zero with both).

sar is "arithmetic" shift right, keeping value of most significant bit intact (sign bit), so for -1 (all bits are 1) it will produce again -1, for all other numbers it works as signed division by two.

BTW since 80286 CPU you can use shr bx,4 (which can be also seen as divide by 16 = 2*2*2*2). Are you really forced to code for 8086? Then it may be worth to load cl with 4 and do shr bx,cl, instead of four shr bx,1. That annoys hell out of me, four identical lines.

Also if you already understand what and does, this must look ridiculous to you now:

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

Now contemplate for a while how to extract bits b12-b15 for first character and how to fix your IntegerToHexFromMap.


And ultimately I will show you how I would rewrite it to have the code very short, I mean source, but also binary size. (for performance I would write different code, and not for 8086, but this one should work on 8086):

WARNING - try to fix your version on your own by above advices. Only when you will have fixed version, then look at my code, as an inspiration for new ideas how some things were written 30 years ago. Also if you are doing school assigment, make sure you can say everything about XLAT instruction from head, because as a lector I would be highly suspicious about any student using this one, it's total history and as compilers don't use it, it's obvious the code was written by human, and probably experienced one.

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

If you will just use this code without understanding how it works, god will kill a kitten... you don't want that, right?

Final disclaimer: I don't have any 16bit x86 environment, so I wrote all the code without testing (I only try to compile it sometimes, but the syntax must be NASM-like, so I don't do that for this MASM/TASM/emu8086 sources). Thus some syntax bugs may be there (maybe even functional bug? :-O ), in case you will be unable to make it work, comment.

Pyrosis answered 26/11, 2016 at 15:44 Comment(4)
Thank you so much! It really helped me, now I understand more how it works. I know you said the second part is to rewrite, but could you still help mi with it? I wanted to do conversion in loop so it would repeat 4 Times since binary numer is 16bit, but I don't know how to start.Grissel
@Grissel answer extended.. about "how to start". You write in human language first, what you want to do. Then try to chip it bit by bit into simpler and simpler steps, until they will start to resemble simple calculation steps. Loop 4 times? "1) counter = 4, 2) label: 3) decrement counter, and jump to label when not zero" -> ready to write code from this. First part of "part 2" answer shows how you can treat "16 bit into 4x 4 bit" in English. ... also please keep trying to run everything in debugger and exploring it's parts, so you can watch CPU, what instruction does change what.Pyrosis
@Grissel and start to use instruction reference guide to read about each instruction, what it does. It's the easiest way how to find out, why "that instruction did produce different result than I expected, WTF?". (I use google with "x86 <instruction name> instruction" search string, then look for sites with familiar domain, like "renejeschke" and few others).Pyrosis
Thanks to you I understood my mistakes and it was easier to fix tchem. Your explanation of how it all works really enlighted it for me. I'm very thankful for your help and involvement and I admire you for your knowledge.Grissel

© 2022 - 2024 — McMap. All rights reserved.