home *** CD-ROM | disk | FTP | other *** search
- title BCDASM -- Copyright 1997, Morten Elling
- subttl Format a packed signed BCD for Ascii output
-
- include model.inc
- include modelt.inc
- include bcd.ash
-
- @CODESEG
-
- ;//////////////////////////////////////////////////////////////////////
- ;// Name bcdFmt
- ;// Desc Format a packed signed BCD for Ascii output.
- ;// (Write BCD to string.)
- ;//
- ;//
- ;// Entry Passed args
- ;// - pass (-1) to use default value (see below)
- ;// - width defaults to string length (may expand
- ;// to strsz-1)
- ;// - numDec indicates how many decimals the BCD has
- ;// (decimal dot moves left by numDec positions)
- ;// - prec indicates how many decimals to output
- ;// - for positive numbers/zero, signCh can be
- ;// ' ' to replace '+', or 00h to suppress '+'
- ;// - fillCh can be in range 21h..7Fh to replace
- ;// ' ' as string fill character
- ;// - sepMCh can be 00h to suppress grouping by 1000's
- ;//
- ;// Exit Zero-ended Ascii string returned to destination buffer.
- ;// Acc = string length (0 if bad args or string exceeds
- ;// destination's size)
- ;//
- ;// Note Here's the number -123456789012 in various formats:
- ;// ;0----+----1----+----2 (w=width, nD=numDec)
- ;// " 123,456,789,012-" w 20
- ;// " 1,234,567,890.12-" w 20, nD 2
- ;// "1,234,567,890.12-" w -1, nD 2
- ;// "1234567890.12-" w -1, nD 2, sepMCh 0
- ;// "1234.56789012- " w 17, nD 8, sepMCh 0, rtJust 0
- ;// " 1,234.57-" w 17, nD 8, prec 2
- ;// " 0.001235-" w 17, nD 14, prec 6
- ;// "12.3456789012000-" w 17, nD 10, prec 13
- ;// " 0.0123456789012000-" w 20, nD 13, prec 16
-
- bcdFmt proc
- arg pStr :dataptr, \ ; Addr of Ascii result buffer
- strsz :@uint, \ ; Byte size of buffer (min. width+1)
- pBCD :dataptr, \ ; Addr of packed signed BCD
- bcdsz :@uint, \ ; Byte size of BCD
- width :@uint, \ ; Field width (default: length)
- numDec :@uint, \ ; Number of decimals (default: zero)
- prec :@uint, \ ; Decimals to output (default: all)
- rtJust :byte, \ ; Right justify (default: yes)
- signCh :byte, \ ; Sign character (default: '+' or '-')
- fillCh :byte, \ ; Fill character (default: ' ')
- sepCh :byte, \ ; Decimal separator (default: '.')
- sepMCh :byte ; Thousands separator (default: ',')
- local @@numDigits :@uint, \ ; (Local variables)
- @@intDigits :@uint, \
- @@decDig :@uint, \
- @@decLtZ :@uint, \
- @@decRtZ :@uint, \
- @@fillLen :@uint, \
- @@strLen :@uint, \
- @@rndNib :byte
- @uses ds,es,rsi,rdi,rbx,rcx,rdx
- ;.
- ; ----- Count significant bytes in BCD
- @LES rdi, [pBCD]
- if @isDataFar
- @LDSEGM ds, es
- endif
- mov rcx, [bcdsz]
- dec rcx
- add rdi, rcx
- mov dh, [rdi] ; Sign byte
- dec rdi
- sub rax, rax
- std ; Set direction flag
- repz scasb
- adc rcx, rax ; Byte count
- mov dl, [rdi+1] ; Non-zero byte (if count > 0)
-
- ; ----- Fill in default values
- ; (don't know width yet)
- dec rax ; Acc = -1
- cmp [numDec], rax
- jne sh @@d1
- inc [numDec] ; Default numDec = 0
- @@d1: cmp [prec], rax
- jne sh @@d2
- push [numDec]
- pop [prec] ; Default precision = all
- @@d2: inc [fillCh]
- jz sh @@d2a
- dec [fillCh]
- jnz sh @@d3
- @@d2a: mov [fillCh], ' '
- @@d3: inc [sepCh]
- jz sh @@d3a
- dec [sepCh]
- jnz sh @@d4
- @@d3a: mov [sepCh], '.'
- @@d4: cmp [sepMCh], al
- jne sh @@d5
- mov [sepMCh], ','
- @@d5: ; signCh
- test dh, 80h ; Force '-' for signed numbers
- mov dh, '-'
- jnz sh @@d6
- mov dh, [signCh]
- test dh, dh
- jz sh @@d6
- cmp dh, ' '
- jz sh @@d6
- mov dh, '+'
- @@d6: mov [signCh], dh
-
- ; ----- Compute no. of integers
- add rcx, rcx ; 2 BCD digits per byte
- jc sh @@err ; Do away with biggies
- jz sh @@sl1
- cmp dl, 10h ; Decrement if no high digit
- sbb rcx, 0
- @@sl1: ; integers = max(1, digits - numDec)
- mov [@@numDigits], rcx
- sub rcx, [numDec]
- ja sh @@sl2
- mov rcx, 1
- @@sl2: mov [@@intDigits], rcx
-
- ; ----- Compute no. of 1000's separators
- sub rax, rax ; None
- cmp [sepMCh], al ; if suppressed
- je sh @@sl3
- mov rax, rcx ; Else (integers - 1) / 3
- dec rax
- sub rdx, rdx
- mov rbx, 3
- div BX
-
- ; ----- Compute total string length
- ; less blank-fill
- @@sl3: mov rcx, rax ; 1000's separators
- sub rax, rax
- cmp al, [signCh]
- adc rcx, rax ; + sign char (if non-zero)
- cmp rax, [prec]
- adc rcx, [prec] ; + dot + decimals (if non-zero)
- jc sh @@err
- add rcx, [@@intDigits]; + integers
- jc sh @@err
-
- ; ----- Check against boundaries
- cmp rcx, [strsz]
- jae sh @@err
- mov rax, [width]
- cmp rax, -1
- jne sh @@sl4
- mov [width], rcx ; This is the default width
- mov rax, rcx
- @@sl4: cmp rax, [strsz]
- jae sh @@err
-
- ; ----- Get size of blank-fill
- sub rax, rcx
- jnc sh @@sl5
- sub rax, rax
- @@sl5: mov [@@fillLen], rax
- add rcx, rax
- mov [@@strLen], rcx
- jmp sh @@nd0
-
-
- ; ----- Error: return empty string
- @@err: sub rax, rax
- @LES rdi, [pStr] ; df=1
- stosb ; Make AsciiZ
- jmp @@ret ; Return 0
-
-
- ; //////////////////////////////////////////////////////////////
- ; Before building the string, let's compute a few more variables
- ; to make things easier later on, figure out where to get the
- ; first BCD digit (with 2 digits per byte, it could be at an odd
- ; position), and get the digit needed to do rounding.
-
-
- ; ----- The digits in the fractional part may consist
- ; of up to 3 parts: zero-fill on right, zero-fill
- ; on left, and the decimal digits.
- ; The length of the fractional part = [prec]
- ;
- @@nd0: ; Rightmost zerofill = max(0, prec-numDec)
- mov rax, [prec]
- mov rdx, rax
- sub rax, [numDec]
- ja sh @@nd1
- sub rax, rax
- @@nd1: mov [@@decRtZ], rax
- ; Leftmost zerofill = min(prec, max(0,numDec-digits))
- mov rax, [numDec]
- sub rax, [@@numDigits]
- ja sh @@nd2
- sub rax, rax
- @@nd2: cmp rax, rdx
- jb sh @@nd3
- mov rax, rdx
- @@nd3: mov [@@decLtZ], rax
- ; Decimal digits = prec - rightfill - leftfill
- sub rdx, rax
- sub rdx, [@@decRtZ]
- mov [@@decDig], rdx
-
- ; ----- Get nibble required for rounding
- mov rsi, @uiptr [pBCD]
- sub al, al ; Default to no rounding
- sub dh, dh
- mov rbx, [numDec]
- sub rbx, [prec]
- jbe sh @@fr2
- mov ah, bl
- dec rbx
- shr rbx, 1 ; Two BCD digits per byte
- cmp rbx, [bcdsz]
- jae sh @@fr2
- mov al, [rsi+rbx]
- inc dh
- test ah, 1 ; If (numDec - prec) odd,
- jnz sh @@fr1 ; get low nibble
- @shr al, 4 ; else get high nibble
- inc rbx
- dec dh
- @@fr1: and al, 0fh
- add rsi, rbx
- @@fr2: mov [@@rndNib], al
- ; dh = odd nibble flag, rsi digit ptr
-
-
- ; //////////////////////////////////
- ; ----- Build string in destination
- ; from right (hi) to left (lo)
- ; (direction flag is set)
- @LES rdi, [pStr]
- add rdi, [@@strLen]
- sub al, al ; Zero-
- stosb ; terminate the string
-
- ; ----- Left justify if appr.
- test [rtJust], -1
- jnz sh @@s1
- mov rcx, [@@fillLen]
- mov al, [fillCh]
- rep stosb
-
- ; ----- Sign character (if non-zero)
- @@s1: mov al, [signCh]
- test al, al
- jz sh @@s2
- stosb
-
- ; ----- Rightmost '0' fill (if any)
- @@s2: mov rcx, [@@decRtZ]
- mov al, '0'
- rep stosb
-
- ; ----- Decimal digits
- mov rbx, rdi ; Remember start of digits
- mov ah, [rsi]
- mov rcx, [prec]
- test rcx, rcx
- jz sh @@s5 ; Branch if no fraction part
- mov rcx, [@@decDig]
- test rcx, rcx
- jz sh @@s4
- test dh, 1
- jnz sh @@s3a ; Start at odd nibble
- @@s3: mov al, [rsi]
- mov ah, al
- and al, 0fh
- or al, '0'
- stosb
- inc dh ; Next nibble is high
- dec rcx
- jz sh @@s4
- @@s3a: mov al, ah
- @shr al, 4
- or al, '0'
- inc rsi
- stosb
- dec dh ; Next nibble is low
- dec rcx
- jnz @@s3
-
- ; ----- Leftmost '0' fill (if any)
- @@s4: mov rcx, [@@decLtZ]
- mov al, '0'
- rep stosb
-
- ; ----- Decimal dot
- mov al, [sepCh]
- stosb
-
- ; ----- Integers + 1000's separators
- @@s5: mov rcx, [@@intDigits]
- test rcx, rcx
- jz sh @@sz
- test dh, 1
- jnz sh @@s8
- @@s6: mov dl, 3
- @@s7: mov al, [rsi]
- mov ah, al
- and al, 0fh
- or al, '0'
- stosb
- dec rcx
- jz sh @@sz
- dec dl
- jnz sh @@s9
- mov al, [sepMCh]
- test al, al
- jz sh @@s8
- stosb
- @@s8: mov dl, 3
- @@s9: mov al, ah
- @shr al, 4
- or al, '0'
- stosb
- inc rsi
- dec rcx
- jz sh @@sz
- dec dl
- jnz @@s7
- mov al, [sepMCh]
- test al, al
- jz @@s6
- stosb
- jmp @@s6
-
- ; ----- Right justify if appr.
- @@sz: test [rtJust], -1
- jz sh @@rd1
- mov rcx, [@@fillLen]
- mov al, [fillCh]
- rep stosb
-
- ; //////////////////////////////
- ; ----- String done, now adjust
- ; if we're to round up
- @@rd1: cmp [@@rndNib], 5
- jb sh @@end
- mov rdi, rbx ; Recall start of digits
- mov rsi, rbx
- if @isDataFar
- @LDSEGM ds, es
- endif
- mov rcx, [@@decDig]
- add rcx, [@@decLtZ]
- jz sh @@rd3
- @@rd2: lodsb
- inc al
- cmp al, '9'
- stosb
- jbe sh @@end
- sub @bptr [rsi+1], 0Ah
- dec rcx
- jnz @@rd2
- cmpsb ; Skip decimal dot
- @@rd3: mov rcx, [@@intDigits]
- test rcx, rcx
- jz sh @@rd6
- mov ah, [sepMCh]
- jmp sh @@rd5
- @@rd4: cmpsb ; Skip thousands separator
- @@rd5: cmp ah, [rsi]
- je @@rd4
- lodsb
- inc al
- cmp al, '9'
- stosb
- jbe sh @@end
- sub @bptr [rsi+1], 0Ah
- dec rcx
- jnz @@rd5
-
- ; ----- Special case: The rounding carry has
- ; snowballed past the leftmost digit.
- @@rd6: mov rsi, @uiptr [pStr]
- cmp rdi, rsi
- jae sh @@rd9 ; OK to overlay left-side blank-fill
- mov rdi, rsi
- mov rcx, [@@strLen]
- mov rax, [@@fillLen]
- sub rcx, rax
- add rdi, rcx
- test rax, rax
- jnz sh @@rd8 ; OK to overlay right-side blank-fill
- inc rcx ; Include trailing zero
- jz sh @@rde
- cmp rcx, [strsz] ; Ok to expand?
- jb sh @@rd7 ; Yes
- @@rde: jmp @@err ; No, must return error
- @@rd7: inc rdi
- inc [@@strLen]
- @@rd8: lea rsi, [rdi-1]
- rep movsb ; Move string right (up mem)
- @@rd9: mov al, '1'
- stosb ; Store the rounding carry
- ; ToDo: Insert sepMCh if carry
- ; begins a new 1000's group
-
- ; ----- Return string length
- @@end: mov rax, [@@strLen]
- @@ret: cld ; Clear direction flag
- RET
- bcdFmt endp
-
- END