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: 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.
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 procedure
IntToStr 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 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
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 , 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.
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 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.
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
                .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
Posted on 2008-11-16 00:21:38 by BtryKit