NASM - The Netwide Assembler

NASM Forum => Example Code => Topic started by: TightCoderEx on May 18, 2012, 12:08:05 PM

Title: Unsigned -> BCD with optional ASCII output
Post by: TightCoderEx on May 18, 2012, 12:08:05 PM
Kinda didn't name this right, as the primary purpose will be to return an ASCII decimal representation with optional commas.  Not really sure why I threw the BCD component in other than to stretch the grey matter and come up with an algo that at least worked with what I originally conceptualized.

Not really sure if I created something totally unnecessary, as just having skimmed through the 92 some odd pages of Intel's 4100 page manual on IA-32 64 bit architecture that relates to floating point and BCD, but after all, that's what my hobby is all about.  Dream something up and then see if I can make it work.

Code: [Select]

; =========================================================================================================

; ENTRY: Arg 1: RBP + 16 = [Q] 64 bit integer to be converted
;     2:       24 = [Q] Optional ASCII buffer, NULL if not required
;        3:       32 = [W] Bit 0 ON -> Insert comma separator in string

; LEAVE: RAX = Packed BCD <Byte> digits
;             B4 = 180 Bytes
; ---------------------------------------------------------------------------------------------------------

  I2BCD enter 48, 0 ; Room for ASCII string and packed BCD
  mov al, ' '
  call InitFrame ; Set frame to spaces
  push rdi
  push rsi ; In most routines, these are not to be modified
  push rbx

; Probably should check DF flag, but as this will only be called from apps of my design and
; I never leave a routine without resetting DF, I'm probably pretty safe

std ; Set direction flag

; Setup registers for primary or only loop.
; RAX = 64 bit unsigned integer value to be converted
; BH = Bit 0 (RBX bit 8) write commas in string
; RCX = 10 Divisor
; RDX = NULL, especially 63 - 4 need be cleared
; RSI = Point to end of ASCII string (modified in loop)
; RDI = Points to 64 bit BCD value (could easily be modifed to 80 bit)
; R8 = Position index for when commas should be posted.

xor rax, rax
push rax ; Will become RCX
push rax ;   "     "   RDX
push rax ;   "     "   RBX
lea rsi, [rbp - 8] ; Point to BCD Value
mov [rsi], rax ; and start with null
mov rdi, rsi
dec rdi ; Bump to last position of ASCII string
stosb ; and write terminating character
pop rbx
mov bh, [rbp + 32] ; Get condition bits from caller, Arg #3
pop rdx ; = NULL
pop rcx ; Zero bits 63 - 4
mov cl, 10 ; Divisor
mov rax, [rbp + 16] ; Get Arg #1 from caller
push rsi ; Were going to need at end
mov r8b, 0

; Cycle RAX out to zero

.L0 or rax, rax ; Is RAX zero
jz .Done

mov dl, 0 ; Erase any previous characters
div rcx ; Determine next digit 0 - 9

; I suppose I could have set a condition to bounce around this if Arg #2 was NULL, but
; creating an ASCII string will probably be the primary purpose for this procedure.

push rax
bt rbx, 8 ; Are we to write commas
jnc .NoCommas
inc r8b
bt r8, 2 ; Are we ready to write a commad
jnc .NoCommas
mov al, ','
stosb ; Insert comma in string
mov r8b, 1

; Convert digit to its ASCII equivalent and post to next position in string.
; NOTE: DF = 1 so RDI will be decremented

    .NoCommas mov al, dl
    or al, 48
    stosb ; Write character 0 - 9
pop rax

; This is where the packed BCD is created. Procedure could easily be converted to unpacked
; version

inc bl ; Bump position toggle
bt rbx, 0 ; NULL if we are at 10's position
jnc .Tens
mov [rsi], dl ; Write units digit to desintaion
jmp .L0

.Tens shl rdx, 4 ; Shift
or [rsi], dl
inc rsi ; Point to next position
jmp .L0

; Determine length of output string, move to destination if required and then return
; BCD in RAX

.Done cld ; Restore direction flag
pop rsi ; Get pointer to beginning of BCD again
mov rcx, rsi ; Used to calculate size of ASCII
inc rdi
sub rcx, rdi ; Get length of output string

lodsq ; Get BCD digits
  mov rsi, [rbp + 24] ; Get destination address
  or rsi, rsi ; Was a pointer defined
  jz .Exit
  ; Didn't think checking for overflow here is necessary as output will be 28 characters max.
xchg rsi, rdi ; Swap to proper positions
rep movsb ; and copy string

  .Exit pop rbx
  pop rsi ; Restore
  pop rdi
  leave ; Kill procedure frame
  ret 18 ; Waste callers arguments

After debating with myself for a bit, even in Linux, I've decided to stick with STDCALL.  Mostly due to I've adopted the habit of EBX pointing to arrays of data or structures. ESI source data. EDI destination data all of which are to be non volatile.  Playing around a little with FASTCALL, I discovered I was pushing a lot onto stack regardless, so why not do it as arguments to the procedure.