flat assembler
Message board for the users of flat assembler.

Index > DOS > shortest way to display decimal number ?

Goto page 1, 2  Next
Author
Thread Post new topic Reply to topic
Slai



Joined: 11 Jan 2006
Posts: 40
Location: NY/Bulgaria
Slai
I am trying to make very short code for displaying unsigned integer. I saw the "Sample code for Outputting integers" in the FAQ, but I am trying to use as less registers as possible (in fact I want to use only eax). In my variant I am using recursion, but I want to use the code in a macro so recursion is no good. Any sugestions ?
Code:
macro OutputDigit {
      add  dl, 30h ; 30h = '0'
      mov  ah, 2
      int  21h }

ShowDec:
  cmp  eax, 10
  jb   skip
    xor  edx, edx ; edx = 0
    mov  ebx, 10 
    div  ebx
    push dx       ; save all the reminders untill eax >= 10
    call ShowDec
    pop  dx         
    OutputDigit   ; displays number from 0 to 9  that is in dl
    ret
    skip:
  mov  dl, al
  OutputDigit
  ret    
Post 22 May 2006, 19:11
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4633
Location: Argentina
LocoDelAssembly
Slai wrote:
In my variant I am using recursion, but I want to use the code in a macro so recursion is no good.


Why not?

Code:
org 100h

macro OutputDigit {
      add  dl, '0'  
      mov  ah, 2  
      int  21h }  

firstShowDec = 1
macro ShowDec {

if firstShowDec
    firstShowDec = 0

    push ..endShowDec
..ShowDec:
    xor  edx, edx ; edx = 0
    mov  ecx, 10
    div  ecx
    test eax, eax
    jz   ..print

    push dx       
    call ..ShowDec
    pop  dx

    ..print:
    OutputDigit   ; displays number from 0 to 9  that is in dl 
    ret 

..endShowDec:
else
  call ..ShowDec
end if
}

mov     eax, 12345678
ShowDec

mov     eax, 87654321
ShowDec

int $20    


Not sure if this is what you want though
Post 22 May 2006, 20:41
View user's profile Send private message Reply with quote
Slai



Joined: 11 Jan 2006
Posts: 40
Location: NY/Bulgaria
Slai
yes this looks better, just I have a plan to use a tempoary variable that contains the number 10 instead of a register, and I thought that the easyest way will be to use a tempoary variable in the stack that will be something like dword [esp - 4] I guess ..
Anyways I came up with a new code that pushes a negative number, than pushes all the reminders from the divisions in the stack, and then it pops them untill the negative value. But something isn't right in my new code because it displays only the last digit from the number
Code:
org 100h

macro ShowDec { local nextDigit, showDigit, done
    push word -1
    nextDigit:
      xor  edx, edx ; edx = 0
      mov  ecx, 10
      div  ecx
      push dx
    test eax, eax
    jz   nextDigit

    pop  dx
    showDigit:
      add  dl, '0'
      mov  ah, 2   
      int  21h
      pop  dx
    jns  showDigit }

  mov     eax, 1234567890
  ShowDec
  ret    
OR is there a way to display it with multiplication instead of division, so eax will rotate left instead of right ?
Post 23 May 2006, 01:18
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4633
Location: Argentina
LocoDelAssembly
POP doesn't update flags so you need to test dx before JNS instruction. And about replacing DIV with MUL there is a trick to do that but I don't remember how to calculate the multiplicator number to simulate division.

[edit] I found it but you can't get the reminder with this method... http://www.agner.org/assem/pentopt.pdf [section 18.7 Division (all processors)][/edit]
Post 23 May 2006, 01:55
View user's profile Send private message Reply with quote
Slai



Joined: 11 Jan 2006
Posts: 40
Location: NY/Bulgaria
Slai
yes, before posting the code I tryed to test it in few different ways like :
pop dx
test dx,dx
jns showDigit
and than
pop dx
cmp dx,-1
jne showDigit
, but they all gave me only the last digit of the number. I am missing something else I guess ..
Post 23 May 2006, 04:28
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4633
Location: Argentina
LocoDelAssembly
you are right, there is another error, replace "jz nextDigit" with "jNz nextDigit". I suggest you replace "push word -1" with just "push -1", it encodes in less bytes and has the same meaning.
Post 23 May 2006, 12:55
View user's profile Send private message Reply with quote
Slai



Joined: 11 Jan 2006
Posts: 40
Location: NY/Bulgaria
Slai
Great it worked ! thanks a lot Very Happy
Post 23 May 2006, 21:29
View user's profile Send private message Reply with quote
Slai



Joined: 11 Jan 2006
Posts: 40
Location: NY/Bulgaria
Slai
and here is the result for now : (33 bytes)
Code:
macro ShowDec { local nextDigit, showDigit, done
    push -1
    mov  ecx, 10
    nextDigit:
      xor  edx, edx ; edx = 0
      div  ecx
      push dx
    test eax, eax
    jnz  nextDigit
    pop  dx
    showDigit:
      add  dl, '0'
      mov  ah, 2   
      int  21h
      pop  dx
    test dx, dx
    jns  showDigit }    
Post 23 May 2006, 21:49
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4633
Location: Argentina
LocoDelAssembly
replace "mov ecx, 10" with "xor ecx, ecx"/"mov cl, 10" to save an extra byte.
Post 23 May 2006, 23:22
View user's profile Send private message Reply with quote
Matrix



Joined: 04 Sep 2004
Posts: 1171
Location: Overflow
Matrix
xor edx, edx ; edx = 0 - 3 bytes
cdq ; - 2 bytes

Wink

, so overall, let me post it:
29 bytes:

Code:
macro ShowDec { local nextDigit, showDigit, done
    xor ebx, ebx
    xor cx,cx
    mov bl, 10
    nextDigit:
      cdq
      div  ebx
      push dx
      inc cx
    test eax, eax
    jnz  nextDigit
    showDigit:
      pop  dx
      add  dl, '0'
      mov  ah, 2
      int  21h
    loopw showDigit }
    
Post 24 May 2006, 01:26
View user's profile Send private message Visit poster's website Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4633
Location: Argentina
LocoDelAssembly
the problem with CDQ is that this macro is intended to work with unsigned numbers so making a CDQ with EAX above to $7FFFFFFF will set EDX to $FFFFFFFF which will overflow the division

[edit] An additional problem is that you use an extra register with respect to the previous code and Slai wants to use the less amount of registers as possible. Destroying EBX I think it's a bad idea because it doesn't follow the common calling conventions which says that EAX, EDX and ECX are the only volatile GPR but that's just my preference, maybe Sai has no problem in destroying it Razz [/edit]
Post 24 May 2006, 02:02
View user's profile Send private message Reply with quote
Matrix



Joined: 04 Sep 2004
Posts: 1171
Location: Overflow
Matrix
whoops, true, cdq will have that overflow bug, with sign extend, so only can be used with word write routine...

30 bytes then.

int21h has some functions that return some undefined register contents too,
maeby you should not use it either then, or save registers to stack.

ebx seems neccesary to use cx as counter.

doing the same using bios interrupt has the adventages of can be used wthout dos, and smaller by 1 byte:
because of add al, '0'

Code:
macro ShowDec { local nextDigit, showDigit, done
    xor ebx, ebx
    xor cx,cx
    mov bl, 10
    nextDigit:
      xor edx,edx
      div  ebx
      push dx
      inc cx
    test eax, eax
    jnz  nextDigit
    showDigit:
      pop  ax
      add  al, '0'
      mov  ah, 0xe
      int  10h
    loopw showDigit }
    


if you whould like to use the least registers you whould have to use fpu for division, - that is not small,
or make it the smallest, and pushad, popad...
Post 24 May 2006, 03:18
View user's profile Send private message Visit poster's website Reply with quote
Borsuc



Joined: 29 Dec 2005
Posts: 2466
Location: Bucharest, Romania
Borsuc
Okay, probably off topic, and I know it's not small (but it's fast).. It's a conversion to base10 in strings. A lot of the stuff here is very off topic, but it might be some inspiration. And by the way, it doesn't use divs at all, only a single mul per each digit! Razz I'm sure it can be optimized and adjusted for your macro. Wink

70 bytes
Code:
    ; esi must point at the string buffer that will receive the base10 digits
    ; eax is the integer
    mov ecx, 01999999Ah
    mov edi, esi

   rep_loop:
    mov ebx, eax
    mul ecx
    mov eax, edx

    lea edx, [edx*4+edx]
    add ebx, '0'
    add edx, edx
    sub ebx, edx

    mov [esi], bl
    inc esi

    test eax, eax
    jne rep_loop

    mov eax, esi
    sub eax, edi


   reverse_digits:
    dec esi
    mov cl, [edi]
    mov dl, [esi]
    mov [esi], cl
    mov [edi], dl
    inc edi
    cmp edi, esi
    jb reverse_digits    

Hope it helps Very Happy
Post 26 May 2006, 20:16
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4633
Location: Argentina
LocoDelAssembly
I'm interested in this Very Happy

Is there any link explaining it?

Regards

PS: By "shortest way" can also mean shortest time Wink
Post 26 May 2006, 22:00
View user's profile Send private message Reply with quote
Borsuc



Joined: 29 Dec 2005
Posts: 2466
Location: Bucharest, Romania
Borsuc
The algo is like this (the "input number" is the number that gets converted):

Code:
1. fill "ecx" with the MAGIC number Smile
2. set "ebx" to the value of the current input number
3. multiply "eax" with "ecx"; "eax" being the current input number
   this will yield "eax/10" in the register "edx"
   because of that MAGIC number's magic Smile
4. now we will set "eax" to "current input number"
   which effectively means we just "divided eax by 10"
5. next, we will multiply "edx" (not "eax") by 10
   this is what those "lea" and "add edx, edx" are all about
6. now we have in "edx" the number truncated to 10 boundary
   this means that, by subtracting we will get the last digit
   I used an "add ebx, '0'" before the subtraction -- it is ok
   it doesn't matter in which order you do it. That addition
   is just for ASCII stuff anyway
7. next we put that digit in "ebx" into the string
8. repeat from 2
9. ...    
The "reverse_digits" part is obvious, isn't it?

Let's see an example:

Code:
input number: 951
eax = 951

1. ecx = 01999999Ah
2. ebx = 951 (eax, in fact)
3. multiply eax with MAGIC ecx, so edx = 951/10 = 95
4. eax = 95
5. edx = 950
6. digit = ebx - edx = 951 - 950 = 1 :wink:
7. ...

repeat

2. ebx = 95 (remember, step 4 above)
3. multiply eax with MAGIC ecx, so edx = 95/10 = 9
4. eax = 9
5. edx = 90
6. digit = ebx - edx = 95 - 90 = 5 :wink:
7. ...

repeat

2. ebx = 9
3. multiply eax with MAGIC ecx, so edx = 9/10 = 0
4. eax = 0
5. edx = 0
6. digit = ebx - edx = 9 - 0 = 9 :wink:
7. ...    
I hope you see it clearly now...

Oh, and that MAGIC number... well, I understood it from "The Art of Assembly Language Programming", it explains it somewhere.. I think here.
PS: 6554 is "199A". Guess what 1999999A is... Wink

Cheers Very Happy
Post 26 May 2006, 22:46
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 7802
Location: Kraków, Poland
Tomasz Grysztar
There was once an interesting discussion about this on the other board - http://www.asmcommunity.net/board/index.php?topic=21308.0
Post 26 May 2006, 23:06
View user's profile Send private message Visit poster's website Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4633
Location: Argentina
LocoDelAssembly
Thanks, great explanation!! Very Happy The part I didn't understand was how to calculate the magic number but well, it's magic Razz and I didn't pay attention that you calculate MOD like "mod = x div y * y - x" (this because I'm very silly in maths and I can't see such simple things fast Sad)

Searching 01999999Ah in Software Optimization Guide for AMD64 Processors I found this:
Quote:

Simpler Code for Restricted Dividend
Integer division by a constant can be made faster if the range of the dividend is limited, which
removes a shift associated with most divisors. For example, for a divide by 10 operation, use the
following code if the dividend is less than 4000_0005h:
mov eax, dividend
mov edx, 01999999Ah
mul edx
mov quotient, edx


I'd tested with values greater than 4000_0005h and I had no problems, unsigned division seems to have no restrictions.

Regards Very Happy

[edit] Complete BS, the code HAS problems with values greater than 4000_0005h and I don't know from where "unsigned division seems to have no restrictions" comes. [/edit]


Last edited by LocoDelAssembly on 06 Feb 2008, 17:38; edited 1 time in total
Post 26 May 2006, 23:28
View user's profile Send private message Reply with quote
Slai



Joined: 11 Jan 2006
Posts: 40
Location: NY/Bulgaria
Slai
I used the windows scientific calculator to divide 1 0000 0000 h by A , and I got 19999999h , so I guess this is an easy way to get the magic number, just I don't know where the extra 1 comes from .. I guess because the result is something like 19999999.9999999 ... h
Post 01 Jun 2006, 04:09
View user's profile Send private message Reply with quote
Slai



Joined: 11 Jan 2006
Posts: 40
Location: NY/Bulgaria
Slai
about the showDec macro, I first started it because I wanted to test an algorithm for finding the sqare root of integer, but than I wanted to modify it for the assembler/compiler that I am planing to do, where "Output eax" will be compiled not as a call to a function, but directly to this code, so thats why I wanted it to use least space and least registers as possible. So I tryed to replace ebx with "dword [esp-4]" but the code came up something like 42 bytes, so I gave up on that idea.
And with the todays high-speed/size tehnologies I am still wondering whitch one is better, optimising for speed, or for space...
Post 01 Jun 2006, 04:58
View user's profile Send private message Reply with quote
Borsuc



Joined: 29 Dec 2005
Posts: 2466
Location: Bucharest, Romania
Borsuc
Slai wrote:
I used the windows scientific calculator to divide 1 0000 0000 h by A , and I got 19999999h , so I guess this is an easy way to get the magic number, just I don't know where the extra 1 comes from .. I guess because the result is something like 19999999.9999999 ... h
The 1 comes from rounding. I don't know how to explain this, cuz I learnt the trick from the link I posted above (to AoA).

Slai wrote:
And with the todays high-speed/size tehnologies I am still wondering whitch one is better, optimising for speed, or for space...
My advice is to choose one which you like. I don't really care how fast CPUs get, I just appreciate their internals (i.e no matter how fast a CPU is, it STILL performs addition steps, etc.). And it's the same with size: Some say a byte isn't noticeable (since they have Gigas of hard space), but no matter how many Gigas are there, the byte is STILL there. That's why I appreciate them. And I decide which version to use (size/speed) based on my opinion, not on the chips, but rather on my design/implementation choices. If I find it suitable somewhere to use the size version (even if it's unsignificant in this capitalistic-business-resource-consuming-crap) I use it, no matter of what others are doing (i.e if everyone's a thief, that doesn't mean you have to be one), just like an artist uses his skills to improve something with value rather than success (i.e design 1 quality thing than 100 bloated-crap).

Sorry to scare you off with my opinions.
Post 01 Jun 2006, 16:34
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  
Goto page 1, 2  Next

< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2020, Tomasz Grysztar. Also on GitHub, YouTube, Twitter.

Website powered by rwasa.