Probably not much faster, but definitely
much smaller to:
1) zero the result,
2) note the optional sign,
3) eliminate the strlen and just consume chars left to right up to the first non-digit,
4) do a
result=result*radix+digit for every digit you encounter along the way,
5) apply the sign and you're done, unless you feel like checking for overflows or illegal syntax.
Here's an untested attempt at what I'm describing; it's only about 106 bytes, and needs no table or external support. Comments and corrections are welcome:
Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; atoi: convert ASCII text to a 16-bit signed integer.
; Expects: a buffer pointer in (0,x) pointing to ASCII
; text ... the optional sign is noted (default +),
; then decimal digits accumulate as an unsigned
; integer. Conversion completes upon encountering
; the first non-digit char, then the sign is applied.
; Two bytes of ZP at N and N+1 are used.
; Returns: integer value in (0,x). Overflows: ignored.
;
atoi:
ldy #0 ;
lda 0,x ; move string pointer in TOS
sta N ; to temp ZP pointer
lda 1,x ;
sta N+1 ;
stz 0,x ; zero accumulator in TOS
stz 1,x ;
jsr gettxt ; grab a non-space buffer char
cmp #'-' ; leading minus sign?
beq atoi2 ; yes: set sign bit in C
clc ; no : must be positive
eor #'+' ; leading plus sign?
bne atoi3 ; no : go with an assumed '+'
atoi2:
iny ; skip over the '+' or '-' char
atoi3:
php ; save sgn (CS:'-' CC:'+'|none)
dey ; back up one for first getnxt
atoi4:
jsr getnxt ; grab next char
bcs atoi5 ; not a decimal digit: finish
pha ; save the digit
jsr x10 ; multiply TOS by 10
pla ; retrieve the digit
adc 0,x ; (CC is assumed from context)
sta 0,x ; add it to TOS
bcc atoi4 ;
inc 1,x ;
bra atoi4 ; try another digit
atoi5:
plp ; retrieve sign
bcc atoi6 ;
lda #0 ; negate if applicable
sbc 0,x ;
sta 0,x ;
lda #0 ;
sbc 1,x ;
sta 1,x ;
atoi6:
rts ;
;
x10:
lda 1,x ; unsigned 16-bit multiply x 10
pha ; (multiply by 5 and fall thru)
lda 0,x ;
jsr x2 ;
jsr x2 ;
adc 0,x ; (CC is assumed from context)
sta 0,x ;
pla ;
adc 1,x ;
sta 1,x ;
x2:
asl 0,x ; unsigned 16-bit multiply x 2
rol 1,x ;
rts ;
;
getnxt:
iny ; bump the buffer pointer
gettxt:
lda (N),y ; grab a char from text buffer
cmp #' ' ;
beq getnxt ; skip over any space char(s)
eor #'0' ;
cmp #10 ; ASCII decimal digit?
bcc gottxt ; yes: CC, convert to binary
eor #'0' ; no : CS, return orig. ASCII
gottxt:
rts ;
;
Here's an untested (and definitely buggy) snippet of atof from my floundering WozFloat package. Overkill for your case, I know, but maybe some insights to be gained (and bugs to be found by some industrious viewers)?
Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; atof: convert ASCII text to a floating point number.
; Expects: a buffer at buff containing ASCII text, e.g.
; -18.5132E+12 ... the optional significand sign is
; noted (default +), then decimal digits accumulate
; as an unsigned integer. The optional decimal point
; and optional signed exponent are noted and used to
; scale by the appropriate power of 10, then the sign
; is applied. Conversion completes upon encountering
; the first non-space char not matching the expected
; pattern.
; Returns: the number of chars consumed in Y, and the
; closest available floating point representation of
; the consumed text in facc; the caller should NOT
; assume a valid return value UNLESS the ovfl count
; remains unchanged and Y > 0.
;
atof:
ldy #0 ; point to start of buffer
jsr getlhs ; scan optional sign and LHS
stx cnt ; mark any skipped LHS digits
cmp #'.' ; paused at DP ?
bne atof2 ; no : done with significand
jsr getrhs ; yes: scan the RHS of DP
atof2:
pha ; save the next buffer char
lda cnt ;
sta tmp ; save the DP location in tmp
ldx #$96 ;
lda ext ;
sta facc+3 ; move the unsigned significand
lda ext+1 ; accumulator into facc
sta facc+2 ;
lda ext+2 ;
bpl atof3 ; accumulator > 8388607 ?
inx ;
lsr ; yes: shift it right to
ror facc+2 ; keep it "positive",
ror facc+3 ; at least for now ...
atof3:
stx facc ;
sta facc+1 ;
ldx #facc ;
jsr ulpx ; round "up" if indicated
ldx #0 ; preload a default exp10 of 0
pla ; retrieve the next buffer char
cmp #'E' ; exponential notation?
bne atof5 ; no : done scanning
jsr getlhs ; yes: scan the exp10
txa ;
ldx #99 ; highest allowable |exp10|
ora ext+1 ;
ora ext+2 ; scanned |exp10| > 255 ?
bne atof4 ; yes: keep the 99
lda ext ;
cmp #99 ; scanned |exp10| >= 99 ?
bcs atof4 ; yes: keep the 99
tax ; no : keep scanned value
atof4:
asl sgn ;
bcc atof5 ; exp10 < 0 ?
dex ;
txa ;
eor #$ff ; yes: make it so
tax ;
atof5:
tya ; buffer scan is complete:
pha ; save Y for return
txa ; retrieve exp10
clc ;
adc cnt ; calculate the total power of
sta cnt ; ten needed for adjustment
bpl atofmul ; non-negative so multiply
atofdiv:
jsr div10 ; divide facc by 10.0
beq atofzz ; underflow: exit with 0.0
inc cnt ;
bne atofdiv ; loop until adjusted
atofmul:
beq atofzz ; done adjusting?
jsr mul10 ; no : multiply facc by 10.0
dec cnt ;
bvc atofmul ; no overflow: loop ???? :-/
atofzz:
bit sgn ; negative significand?
bpl finexp ;
ldx #farg ; yes: negate it now
jsr fnegx ;
pla ;
tay ; restore Y
rts ;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; getlhs (get left-hand side of decimal): parse an
; optional '+' or '-' at buff,Y and save it in sgn
; (default +), then convert and accumulate the
; following string of 0 or more ASCII decimal digits
; into unsigned binary in the 24-bit little-endian
; accumulator in ext; if the accumulated value tries
; to top $F9FFFF (16383999) then stop accumulating
; and skip over digits up to the 1st non-digit char;
; on exit, A holds the first non-digit, Y points to
; its buffer position and X holds the skipped-digit
; count.
; 24 bytes
getlhs:
ldx #0 ; zero the skipped digit count
stx ext ;
stx ext+1 ; zero the integer accumulator
stx ext+2 ;
jsr gettxt ; grab a non-space buffer char
cmp #'-' ; leading minus sign?
beq getlh2 ; yes: set sign bit in C
clc ; no : must be positive
eor #'+' ; leading plus sign?
bne getlh3 ; no : go with an assumed '+'
getlh2:
iny ; skip over the '+' or '-' char
getlh3:
ror sgn ; init sgn (CS:'-' CC:'+'|none)
dey ; back up one for first getnxt
;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; getrhs (get right-hand side of decimal): convert and
; continue to accumulate a string of 0 or more ASCII
; decimal digits at buff,Y into unsigned binary in
; the 24-bit little-endian accumulator in ext,
; decrementing cnt for each digit accumulated; if the
; accumulated value tries to top $F9FFFF (16383999)
; then stop accumulating and skip over digits up to
; the 1st non-digit char; on exit, A holds the first
; non-digit char and Y points to its buffer position.
; 36 bytes
getrhs:
jsr getnxt ; grab next char
bcs gottxt ; not a decimal digit: rts
pha ; save the digit
lda ext+2 ;
cmp #$19 ; impending overflow?
bcc getrh2 ; no : multiply and add
inx ; yes: count a skipped digit,
pla ; discard it from the stack
bcs getrhs ; and try for another digit
getrh2:
dec cnt ; update decimal point location
jsr times10 ; multiply accumulator by 10
pla ; retrieve the digit
adc ext ; (CC is assumed from context)
sta ext ; add it to the accumulator
bcc getrhs ;
inc ext+1 ;
bne getrhs ;
inc ext+2 ;
bcs getrhs ; try another digit (always)
;
times10:
pha ; unsigned 24-bit multiply x 10
lda ext+1 ; (multiply by 5 and fall thru
pha ; to times2)
lda ext ;
jsr times2 ;
jsr times2 ;
adc ext ; (CC is assumed from context)
sta ext ;
pla ;
adc ext+1 ;
sta ext+1 ;
pla ;
adc ext+2 ;
sta ext+2 ;
times2:
asl ext ; unsigned 24-bit multiply x 2
rol ext+1 ;
rol ext+2 ;
rts ;
;
getnxt:
iny ; bump the buffer pointer
gettxt:
lda buff,y ; grab a char from text buffer
cmp #' ' ;
beq getnxt ; skip over any space char(s)
eor #'0' ;
cmp #10 ; ASCII decimal digit?
bcc gottxt ; yes: CC, convert to binary
eor #'0' ; no : CS, return orig. ASCII
gottxt:
rts ;
;
_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some
VTL02C on it and see how it grows on you!
Mike B.
(about me) (learning how to github)