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:
``Start:	MOV AL, 	MOV SI, string	CALL ConvertByte2Decimal	MOV DX, string	MOV CX, length	CALL Write	JMP Exit;DX: String Address;CX: LengthWrite:	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 stringsmallNumber:	MOV BYTE , 20H		;Move a space character to position 0	ADD AL, 30H			;Convert AL toto ASCII equivelent	MOV , AL	;Move Quotent to postion 1continueConvert:	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	\$-stringnumber	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.
Posted on 2008-11-05 04:18:41 by Grich
Addon: I've been testing it by putting different byte values into
``number	DB    190``
.
Posted on 2008-11-05 04:20:30 by Grich
You can use my 16-bit function:

``; ------------------------------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 procedureIntToStr ENDP; ------------------------------``

This is coded in TASM.
Posted on 2008-11-05 06:00:46 by XCHG
Thanks XCHG, i'll convert it over to NASM.
Posted on 2008-11-05 16:30:01 by Grich
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?
Posted on 2008-11-05 18:43:30 by roticv

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.:)
Posted on 2008-11-06 06:02:48 by Grich
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:
``;----------------------------------------------------------------------------;   	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 stringsmallNumber:	MOV BYTE , 20H		;Move a space character to position 0	ADD AL, 30H			;Convert AL toto ASCII equivelent	MOV , AL	;Move Quotent to postion 1continueConvert:	ADD AH, 30H			;Convert AH toto ASCII equivelent	MOV , AH	;Move Remainder to position 2	RET					;Return to Caller``
Posted on 2008-11-09 00:23:35 by Grich
It's the law of inverse proportion - the smallest flaw will bring down a mountain, no matter how stable the mountain seems to be.
Posted on 2008-11-09 01:56:57 by Homer

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. :)
Posted on 2008-11-09 03:07:20 by Grich
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:

``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 , DXDIV      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.
Posted on 2008-11-09 08:02:27 by XCHG
Just a few questions:

• 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
Posted on 2008-11-10 01:45:44 by Grich
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:

``do thisdo thatCALL somethingdo 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.
Posted on 2008-11-10 04:02:30 by XCHG

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?
Posted on 2008-11-10 04:38:43 by Grich
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.
Posted on 2008-11-10 06:43:14 by XCHG

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!
Posted on 2008-11-10 17:25:09 by Grich

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.
Posted on 2008-11-10 23:00:32 by SpooK
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.
``                .MODEL    SMALL                .386PUBLIC          CONVER10;************************************* DG *****************************************                .DATANUM10            DB 12        DUP(?);;************************************* CG *****************************************                .CODE;convers bin value in eax to string of dec;import argument: EAX = bin value;export: DX = address of dec stringCONVER10        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``
Posted on 2008-11-16 00:21:38 by BtryKit