Hi guys, this is my first post here. I've been programming in assembly for about six months now as a hobby and enjoying it.
I've got a problem: I have made a routine called ConvertByte2Decimal that converts a byte in memory to an ASCII decimal string at a specified address. It works fine until it reaches the number 160 and above. It seems to go back to 001. I think if someone with fresh eyes may spot the problem.
Here's the code:
This has caused me many sleepless nights. :sad: Any help will be geatly appreciated.
NOTE: It's coded in 16 BIT NASM assembly.
I've got a problem: I have made a routine called ConvertByte2Decimal that converts a byte in memory to an ASCII decimal string at a specified address. It works fine until it reaches the number 160 and above. It seems to go back to 001. I think if someone with fresh eyes may spot the problem.
Here's the code:
Start:
MOV AL,
MOV SI, string
CALL ConvertByte2Decimal
MOV DX, string
MOV CX, length
CALL Write
JMP Exit
;DX: String Address
;CX: Length
Write:
MOV BX, 1
MOV AH, 40H
INT 21H
RET
;----------------------------------------------------------------------------
; Routine Name : ConvertByte2Decimal
; Version : 1.0
; Created : 24/10/2008
; Last Update : 2/11/2008
; Author : Grich
; Description : Converts a byte in memory to a decimal string
; Prerequisites : AL: Byte to be converted
; DS: Segment of destination string
; SI: Offset of destination string
;---------------------------------------------------------------------------
ConvertByte2Decimal:
MOV BL, 0AH
MOV AH, 0H ;Clears out Higher Bytes in AX for the Divide
DIV BL ;Divide AX by 0AH (10D)
CMP AL, 0AH ;COMPARE AL to 0AH (10D)
JNAE smallNumber ;IF AL !>= 0AH GOTO smallNumber
MOV DX, AX ;Saves the answer to DX
AND AX, 0FH ;Mask out Higher level Byte in AX
DIV BL ;Divide AX by 0AH (10D)
ADD AL, 30H ;Convert AL to ASCII equivelent
ADD AH, 30H ;Convert AH to ASCII equivelent
MOV , AL ;Move Quotent to postion 0
MOV , AH ;Move Remainder to position 1
MOV AX, DX ;Move DX back to AX
JMP continueConvert ;Continue on to put character three down in the string
smallNumber:
MOV BYTE , 20H ;Move a space character to position 0
ADD AL, 30H ;Convert AL toto ASCII equivelent
MOV , AL ;Move Quotent to postion 1
continueConvert:
ADD AH, 30H ;Convert AH toto ASCII equivelent
MOV , AH ;Move Remainder to position 2
RET ;Return to Caller
Exit:
MOV AH, 4CH
MOV AL, 0
INT 21H
string DB " "
length EQU $-string
number DB 190
This has caused me many sleepless nights. :sad: Any help will be geatly appreciated.
NOTE: It's coded in 16 BIT NASM assembly.
Addon: I've been testing it by putting different byte values into
number DB 190
.You can use my 16-bit function:
This is coded in TASM.
; ------------------------------
IntToStr PROC
COMMENT *
Description : Converts an unsigned integer value in the AX register
to its equivalent text format.
Calling Convention : Push from left to right.
Parameter(s):
WORD Param1 = The Buffer's Offset.
Stack Usage : Up to 22 Bytes.
Note : 1) The caller must clean up the pushed parameter(s)
from the stack.
2) The buffer should be equal to the length of the number + 1
for the null-terminating character.
3) The value of the AX register will remain unchanged after
the execution of the procedure.
Example: Converts the number 65535 in the AX register to a
null-terminating string buffer.
.DATA?
String1 DB 10 DUP(?)
.CODE
MOV AX , 0FFFFh ; The number
PUSH OFFSET String1 ; Push the buffer's offset
CALL IntToStr ; Call the proc
ADD SP , 0002h ; Remove the parameter from the stack
; String1 = '65535', 0
*
PUSH AX ; Push the AX register onto the stack
PUSH BX ; Push the BX Register onto the stack
PUSH CX ; Push the CX Register onto the stack
PUSH DX ; Push the DX Register onto the stack
PUSH BP ; Push the BP Register onto the stack
PUSH OFFSET @@__IntToStrEP ; Offset of the end of the procedure
MOV BP , SP ; Move the content of the SP to BP
MOV BX , WORD PTR ; BX = Buffer OFFSET
MOV CX , 0Ah ; Divide by 10 each time
@@__InnerIntToStrLoop: ; Inner loop
XOR DX , DX ; Clear out the remainder
DIV CX ; Divide by 10
PUSH DX ; Push the remainder onto the stack
TEST AX , AX ; See if the number is zero
JE @@__EOInnerIntToStrLoop ; Jump to ... if zero
CALL @@__InnerIntToStrLoop ; Keep dividing if the number's not zero yet
@@__EOInnerIntToStrLoop: ; End of the loop
POP DX ; Pop the last pushed number from the stack
OR DL , 30h ; Add 48d to the number to make it a character
MOV BYTE PTR , DL ; Put the character into the buffer
INC BX ; Move the buffer to the next position
RET ; Do it again until we pop all the digits
@@__IntToStrEP: ; End of the procedure routine
MOV BYTE PTR , 0 ; Null-terminating string
POP BP ; Pop the BP register
POP DX ; Pop the DX register
POP CX ; Pop the CX register
POP BX ; Pop the BX register
POP AX ; Pop the AX register
RET ; Return to the calling procedure
IntToStr ENDP
; ------------------------------
This is coded in TASM.
Thanks XCHG, i'll convert it over to NASM.
Grich, I lost you in your code when you compared al with 0ah (which is 10 in decimal). al is the quotient, why would you want to compare with 10? Should it be that you check the original number in ax and see if it is less than 10 and handle it as one case, else the rest assume that it be 2 digits long?
Should it be that you check the original number in ax and see if it is less than 10 and handle it as one case, else the rest assume that it be 2 digits long?
Mmmm ... I'll try it and report back. Thanks roticv.:)
I found the problem! Something simple and silly. At AND AX, 0FH, if you change it to MOV AH, 0H, it works!
I think I was being a bit too ambitious using the AND instruction to mask out bytes! :lol:
Anyway, here is the routine that works:
I think I was being a bit too ambitious using the AND instruction to mask out bytes! :lol:
Anyway, here is the routine that works:
;----------------------------------------------------------------------------
; Routine Name : ConvertByte2Decimal
; Version : 1.0
; Created : 24/10/2008
; Last Update : 2/11/2008
; Author : Grich
; Description : Converts a byte in memory to a decimal string
; Prerequisites : AL: Byte to be converted
; DS: Segment of destination string
; SI: Offset of destination string
;---------------------------------------------------------------------------
ConvertByte2Decimal:
MOV BL, 0AH
MOV AH, 0H ;Clears out Higher Bytes in AX for the Divide
DIV BL ;Divide AX by 0AH (10D)
CMP AL, 0AH ;COMPARE AL to 0AH (10D)
JNAE smallNumber ;IF AL !>= 0AH GOTO smallNumber
MOV DX, AX ;Saves the answer to DX
MOV AH, 0H ;Mask out Higher level Byte in AX
DIV BL ;Divide AX by 0AH (10D)
ADD AL, 30H ;Convert AL to ASCII equivelent
ADD AH, 30H ;Convert AH to ASCII equivelent
MOV , AL ;Move Quotent to postion 0
MOV , AH ;Move Remainder to position 1
MOV AX, DX ;Move DX back to AX
JMP continueConvert ;Continue on to put character three down in the string
smallNumber:
MOV BYTE , 20H ;Move a space character to position 0
ADD AL, 30H ;Convert AL toto ASCII equivelent
MOV , AL ;Move Quotent to postion 1
continueConvert:
ADD AH, 30H ;Convert AH toto ASCII equivelent
MOV , AH ;Move Remainder to position 2
RET ;Return to Caller
It's the law of inverse proportion - the smallest flaw will bring down a mountain, no matter how stable the mountain seems to be.
It's the law of inverse proportion - the smallest flaw will bring down a mountain, no matter how stable the mountain seems to be.
Wise words. Really relevant to Assembly! Thanks guys, I really appreciate the effort you put into this problem. I'll be back for some more questions later. :)
Grich, I don't know what you are doing in the code but consider rewriting it. It might work but looking at the instructions, I would say it is not the best way of implementing that algorithm especially with the CMP instruction in there.
I would do it like this:
1) Put the byte in a WORD register. For example, if AL is the byte value to be converted, do this:
So that AH will be zero. Now AX is the value that you want to convert to string.
2) Then create two labels namely @Divide and @EndLoop.
3) Then Push the OFFSET of @EndLoop into the stack.
4) Now in the @Divide loop, start by dividing your AX value by 10. For this, you have to XOR DX , DX because we are going to perform a DIV with a 16-bit divisor which takes in a 32-bit DX:AX register. So clear DX in order to have DX:AX equal to your value:
5) Then push the remainder into the stack (Remainder will be in DX):
6) Now see if the quotient is zero.
7) If not, use the CALL isntruction to CALL the @Divide label again. This way, the 16-bit Instruction Pointer of the next line of the code which will be our @EndLoop label will be pushed onto the stack.
8) If yes, then do nothing and let the code roll to the @EndLoop label.
So basically what we are doing is that we are taking a number (say 120) and dividing it by 10 constantly until the result is 0.
1) Take 120.
2) Divide it by 10. Quotient will be 12 and remainder will be 0. Push the remainder (0) into the stack.
3) See if the quotient is 0 (12 is not zero). So now take 12 and divide it by 10. The quotient will be 1 and the remainder will be 2. Push the remainder (2) into the stack.
4) Now take the quotient of the previous step (1) and try to divide it by 10. The quotient will be 0 and the remainder will be 1. Push the remainder (1) into the stack.
5) Now check if the quotient of the previous step (0) is 0. Yes it is. Exit your loop.
Now look at the values you have pushed onto the stack from top to bottom: 021. That is the reverse order of 120 which was your original value. Now start a loop and POP the values you had pushed onto the stack. So you will pop 1, 2 and 0 which is 120. You got the value. Do something with it now.
There is one catch though that you will find out for yourself later.
I would do it like this:
1) Put the byte in a WORD register. For example, if AL is the byte value to be converted, do this:
AND AX , 0x00FF
So that AH will be zero. Now AX is the value that you want to convert to string.
2) Then create two labels namely @Divide and @EndLoop.
3) Then Push the OFFSET of @EndLoop into the stack.
4) Now in the @Divide loop, start by dividing your AX value by 10. For this, you have to XOR DX , DX because we are going to perform a DIV with a 16-bit divisor which takes in a 32-bit DX:AX register. So clear DX in order to have DX:AX equal to your value:
MOV CX , 0x000A
@Divide:
XOR DX , DX
DIV CX
5) Then push the remainder into the stack (Remainder will be in DX):
PUSH DX
6) Now see if the quotient is zero.
7) If not, use the CALL isntruction to CALL the @Divide label again. This way, the 16-bit Instruction Pointer of the next line of the code which will be our @EndLoop label will be pushed onto the stack.
8) If yes, then do nothing and let the code roll to the @EndLoop label.
So basically what we are doing is that we are taking a number (say 120) and dividing it by 10 constantly until the result is 0.
1) Take 120.
2) Divide it by 10. Quotient will be 12 and remainder will be 0. Push the remainder (0) into the stack.
3) See if the quotient is 0 (12 is not zero). So now take 12 and divide it by 10. The quotient will be 1 and the remainder will be 2. Push the remainder (2) into the stack.
4) Now take the quotient of the previous step (1) and try to divide it by 10. The quotient will be 0 and the remainder will be 1. Push the remainder (1) into the stack.
5) Now check if the quotient of the previous step (0) is 0. Yes it is. Exit your loop.
Now look at the values you have pushed onto the stack from top to bottom: 021. That is the reverse order of 120 which was your original value. Now start a loop and POP the values you had pushed onto the stack. So you will pop 1, 2 and 0 which is 120. You got the value. Do something with it now.
There is one catch though that you will find out for yourself later.
Just a few questions:
Sorry if the questions sound Noob, but I'm still learning :D
- Why is using the CMP instruction not the best way to implement the algorithm?
- Why would using the stack help? (I haven't used a stack yet in my assembly projects yet)
- Why use the CALL instruction to go to labels, I would think that the JMP instruction would be easier.
Sorry if the questions sound Noob, but I'm still learning :D
Q: Why is using the CMP instruction not the best way to implement the algorithm?
A: It is just not required while you can use the TEST instruction. And also I remember CMP instruction used on 8-bit registers is an expensive operation.
Q: Why would using the stack help?
Because the results you will get from the algorithm will be in reverse so if you push something into the stack in reverse, you can pop them in the correct way. For example, we push 1, 2, 3 into the stack. Then when you POP, you get 3 first because the rule of stack is last in, first out or (LIFO). You can look that up in Wikipedia I believe.
Q: Why use the CALL instruction to go to labels, I would think that the JMP instruction would be easier.
A: You can use both CALL and JMP but the difference is that CALL will PUSH the IP of the next instruction, into the stack so the next RET will return to that instruction.
So let's say you have:
That CALL instruction will push the IP of "do other thing" into the stack so when you are in the "something" procedure and you say RET, the program will go back to "do other thing". The next instruction after CALL.
I hope this helps.
A: It is just not required while you can use the TEST instruction. And also I remember CMP instruction used on 8-bit registers is an expensive operation.
Q: Why would using the stack help?
Because the results you will get from the algorithm will be in reverse so if you push something into the stack in reverse, you can pop them in the correct way. For example, we push 1, 2, 3 into the stack. Then when you POP, you get 3 first because the rule of stack is last in, first out or (LIFO). You can look that up in Wikipedia I believe.
Q: Why use the CALL instruction to go to labels, I would think that the JMP instruction would be easier.
A: You can use both CALL and JMP but the difference is that CALL will PUSH the IP of the next instruction, into the stack so the next RET will return to that instruction.
So let's say you have:
do this
do that
CALL something
do other thing
That CALL instruction will push the IP of "do other thing" into the stack so when you are in the "something" procedure and you say RET, the program will go back to "do other thing". The next instruction after CALL.
I hope this helps.
It is just not required while you can use the TEST instruction. And also I remember CMP instruction used on 8-bit registers is an expensive operation.
I heard about that as well, I'll use that.
Because the results you will get from the algorithm will be in reverse so if you push something into the stack in reverse, you can pop them in the correct way. For example, we push 1, 2, 3 into the stack. Then when you POP, you get 3 first because the rule of stack is last in, first out or (LIFO). You can look that up in Wikipedia I believe.
I'm very familiar with stacks, but haven't had any use for them yet (I'm an applications programmer generally).
A: You can use both CALL and JMP but the difference is that CALL will PUSH the IP of the next instruction, into the stack so the next RET will return to that instruction.
I understand CALL and RET pretty well for making procedures but if you keep calling up procedures within procedures wouldn't your program crash when it runs out of stack space? Wouldn't JMP be sufficient for jumping around in procedures?
Well suppose you are converting a 16-bit integral value to its equivalent ASCII representation. You can have values in the range of 0-(2^16)-1 which is 0...65535. Your biggest value has 5 digits in it. So you will use CALL the maximum number of 5 times. You will be pushing 16-bit IP values in each CALL which will be 5*16 = 80 bits. I think your stack can handle that 8)
Apart from that, using CALL is the recommended method for the algorithm I proposed. It is not necessarily the best method in other algorithms.
Apart from that, using CALL is the recommended method for the algorithm I proposed. It is not necessarily the best method in other algorithms.
I think your stack can handle that 8)
lol :) I think it could as well.
Apart from that, using CALL is the recommended method for the algorithm I proposed. It is not necessarily the best method in other algorithms.
So, using CALL or JMP would depend on the algorithm. Well, my thoughts on Assembly has been turned ccompletely upside down!
So, using CALL or JMP would depend on the algorithm. Well, my thoughts on Assembly has been turned ccompletely upside down!
Well, I think XCHG's point is that if you can eliminate any branches in code execution, do so.
While newer CPU's are becoming faster with more adaptive branch predictions, CALL/JMP can still be considerably more expensive than other logic approaches.
i wrote a function like yours X years ago, this function convers a bin value(unsigned) in EAX to a string of dec in address of "fixed".
and i also wrote a function with signed value, that first convers "complemental code" to "true code", and have a little trick as dealing with
value -(2^(x-1)).(because it can't represent the value of -(2^(x-1)) by "true code".
following is unsigend function.
and i also wrote a function with signed value, that first convers "complemental code" to "true code", and have a little trick as dealing with
value -(2^(x-1)).(because it can't represent the value of -(2^(x-1)) by "true code".
following is unsigend function.
.MODEL SMALL
.386
PUBLIC CONVER10
;************************************* DG *****************************************
.DATA
NUM10 DB 12 DUP(?);
;************************************* CG *****************************************
.CODE
;convers bin value in eax to string of dec
;import argument: EAX = bin value
;export: DX = address of dec string
CONVER10 PROC NEAR
PUSHF;
PUSH EAX;
PUSH EBX;
PUSH ECX;
PUSH ESI;
LEA SI, NUM10
CMP EAX, 0;
JNZ EAX_NOT_0;
MOV DL, '0' ;whether eax == 0,?
MOV , DL;
INC SI;
JMP CONVER_END_10;
EAX_NOT_0:
MOV EBX, 10D;
XOR CL, CL;
DIV10:
CMP EAX, 0;
JZ zCONVER_END_10;
XOR EDX, EDX;
DIV EBX;
ADD DL, 30H;
MOV , DL;
INC SI;
INC CL;
JMP DIV10;
CONVER_END_10:
MOV , '$';
CMP CL, 1;
JBE END10;
SHR CL, 1;
XOR CH, CH;
DEC SI;
LEA BX, NUM10;
REVER10:
MOV AL, ;
XCHG , AL;
MOV , AL;
INC BX;
DEC SI;
LOOP REVER10;
END10:
LEA DX, NUM10;
POP ESI;
POP ECX;
POP EBX;
POP EAX;
POPF;
RET;
CONVER10 ENDP
;************************************** end *********************************************
END