Reputation: 1150
This code is taken from an online example. Let's say I have the variable I want to print in my DL.
DISPLAY_HEX PROC NEAR
MOV BL,DL
MOV BH,0
MOV CL,4
SHL BX,CL
MOV DL,BH
CALL ONE_DIGIT
MOV CL,4
SHR BL,CL
MOV DL,BL
CALL ONE_DIGIT
RET
DISPLAY_HEX ENDP
ONE_DIGIT PROC NEAR
CMP DL,9
JA LETTER
ADD DL,48
JMP NEXT
LETTER: ADD DL, 'A'-10
NEXT: MOV AH,02H
INT 21H
END: RET
ONE_DIGIT ENDP
Why the shifts? Can't it be printed like a decimal? Also, why is both SHR and SHL being used here?
Upvotes: 2
Views: 2673
Reputation: 364160
Your code is massively overcomplicated, no wonder it was confusing. It's getting the high nibble of DL by left-shifting BX, so the two nibbles are split into BH and BL, but the nibble in BL is in the top 4 bytes.
You need one shift to get the high 4 bits down to the bottom of a register.
Using AND to keep only the low 4 bits would be easier, and much faster on a real 8086 (where each count of a shift took a clock cycle, unlike modern CPUs with barrel-shifter ALUs that can do arbitrary shifts in 1 clock cycle).
This is a simpler and easier-to-understand implementation, which is also more compact and thus faster and better on a real 8086.
; Input in DL
; clobbers AX, CL, DX
DISPLAY_HEX PROC NEAR
mov dh, dl ; save a copy for later
mov cl, 4
shr dl, cl ; extract the high nibble into an 8-bit integer
CALL ONE_DIGIT
and dh, 0Fh ; clear the high nibble, keeping only the low nibble
mov dl, dh ; and pass it to our function
CALL ONE_DIGIT
RET
DISPLAY_HEX ENDP
To save code size, mov dl, 0Fh
/ and dl, dh
would be only 2 bytes per instruction, instead of 3 for and dh, 0Fh
, and it takes the mov
off the critical path for out-of-order execution.
The implementation of ONE_DIGIT
also has needlessly-complex branching. (Often you'd implement nibble -> ASCII with a lookup table, but it only needs one branch, not two. Running an extra add
is cheaper than an extra jmp
.)
; input: DL = 0..15 integer
; output: AL = ASCII hex char written
; clobbers = AH
ONE_DIGIT PROC NEAR
CMP DL,9
JBE DIGIT
ADD DL, 'A'-10 - '0'
DIGIT:
ADD DL, '0'
MOV AH,02H
INT 21H ; write char to stdout, return in AL. Checks for ^c/break
RET
ONE_DIGIT ENDP
We could have done ADD dx, '00'
(and changed the cmp
) to operate on both nibbles at once (after isolating them each in DH and DL).
We could also hoist the MOV AH,02H
, because int 21h
/ah=2
doesn't modify AH..
If we cared about performance, we'd create a 2-character string and use one int 21h
system call to print both digits at once, though.
Upvotes: 1
Reputation: 58437
In base16 (hex) you've got 16 possible digits (0..F
), so it takes exactly 4 bits to represent one hexadecimal digit (log2(16) == 4). Here I'm talking about digits in the sense of the values (0..F
, or 0..15
in base10), not the ASCII characters.
So one byte can hold two hexadecimal digits. Let's say that DL
holds the following bits: XXXXYYYY
(where each X
and Y
is either a binary 0 or 1).
First the 16-bit register BX
is shifted 4 bits to the left. BX
consists of BL
(least significant byte) and BH
(most significant byte). BH
has been set to 0, and BL
contains the input, so prior to the shift BX
will contain the bits 00000000XXXXYYYY
. And after the shift it will contain 0000XXXXYYYY0000
.
Then the most significant byte of BX
(i.e. BH
, which now contains 0000XXXX
) is moved to DL
, converted to a character, and printed.
For the second part BL
, which now contains YYYY0000
is shifted 4 bits to the right, resulting in 0000YYYY
. And then that value is converted to a character and printed.
Upvotes: 5