Updated 28 June 2007 with "E+" notation, and now returns Error code
This does the same job as number=VAL(numbertext) but about 2.5 times faster, (In Freebasic anyway)
It may look long and complicated but most of the opcodes used are very cheap in clock cycles. There is also a degree of parallel processing with the CPU doing things while the FPU crunches its numbers.
This supports extended precision floating point and E notation. Some tests are provided. It reads up to eighteen decimal digits, ignoring the rest, which should match the resolution of the x87 FPU.
'
' DVAL
' text to floating point number conversion in Assembler
' Charles E V Pegge
' 28 June 2007
' FreeBasic ver 0.16b
' using inline assembler.
function dval(byval p as byte ptr, byval le as long, byref v as double) as long
dim as long m10=10
dim as long m1e4=1e4
dim as long m1e8=1e8
dim as long tm1,tm2
dim as long sgg=0
dim as long sgx=0
'
asm
'======================'
xor eax,eax ' clear eax
fldz ' load zero to fpu
mov ebx,[le] ' length
mov ecx,[p] ' text pointer
'======================'
a0: ' loop for reaching the sign or first digit
cmp ebx,0 ' is it zero or less?
jle a2 ' then terminate now with zero
mov al,[ecx] ' first char
cmp al,44 ' is it
jl aa ' if less then .. aa treat as leading space
jz a2 ' terminate now with zero
cmp al,45 ' is it '-' sign?
jnz a1 ' then
dec ebx ' is it last char?
jle a2 ' then terminate now with zero
inc ecx ' next char
mov dword ptr [sgg],1 ' set neg flag
jmp a1 ' procede to a1
'======================'
aa: ' skip space chars
dec ebx ' down count chars to go
inc ecx ' next char
jmp a0 ' loop back to a0
'======================'
a1: ' begin collecting digits
mov al,[ecx] ' load next char
dec ebx ' down count
jl a2 ' no more digits so goto E section
inc ecx ' ready next char
cmp al,46 ' is this a dot?
jz bb ' if so then goto decimals section
cmp al,48 ' check lower limit
jl a2 ' below '0' char so goto E section
cmp al,57 ' check upper limit
jg a2 ' above '9' so goto E section
fimul dword ptr [m10] ' premultiply the accummulator st
sub al,48 ' base to 0
mov [tm1],eax ' store in temp
fiadd dword ptr [tm1] ' add from temp
jmp a1 ' repeat for next digit
'======================'
a2: ' relay to E section
mov esi,ecx ' transfer index to esi
mov cl,al ' transfer char to cl
jmp c2 ' go on to E section
'======================'
bb: ' decimal point
cmp ebx,0 ' is this the last char?
jle c2 ' then finish now
mov esi,ecx
xor edx,edx ' clear edx
mov eax,1 ' set eax to 10
xor ecx,ecx ' clear to act as char reg
xor edx,edx ' while edx:eax is used as a digit multiplier
'======================'
xor edi,edi ' zero the decimal places tally
b1: ' do loop
' start of decimals loop
dec ebx ' any more chars?
jl c1 ' finish now
mov cl,[esi] ' get next char
inc esi ' ready for next char
cmp cl,48 ' range check lower
jl c1 ' finish
cmp cl,57 ' range check upper
jg c1 ' check for E numbers
cmp edi,18 ' limit of decimal places
jge b1 ' too many decimal places error so ignore rest
fimul dword ptr [m10] ' premultiply accummulator
mul dword ptr[m10] ' multiply eax ready for next loop
inc edi ' tally decimal places
cmp edi,9 ' is this 9 dps done ?
jnz b2 ' skip if not
mov [tm2],eax ' move the multiplier to a second stage
mov eax,1 ' reset the multiplier
b2: ' continue
sub cl,48 ' base to zero
mov [tm1],ecx ' store to tm1
fiadd dword ptr [tm1] ' add from tm1
jmp b1 ' repeat for next digit
'======================'
'======================'
cc: '
c1: ' decimal place scaling
mov [tm1],eax ' save eax to tm1
fidiv dword ptr [tm1] ' divide by this factor (up to 9 decimal places)
cmp dword ptr [tm2],0 ' is there a second stage?
jz c2 ' skip if not
fidiv dword ptr [tm2] ' divide by this factor (up to 9 more decimal places)
'======================'
c2: ' inserted E number checking procedure here:
'======================'
ee: ' E & e numbers
cmp cl,&h45 ' is it E?
jz e1 ' okay
cmp cl,&h65 ' is it e?
jz e1 ' okay
jmp c3 ' else skip E numbers
'----------------------'
e1: ' ready to procede with E number
xor eax,eax ' clear eax
mov ch,10 ' x10 multiplier
dec ebx ' check if end
jl c3 ' finish if at end
mov cl,[esi] ' next char
inc esi ' increment char pointer
cmp cl,43 ' is it a '+' sign?
jz e1a ' then ignore and get next char
cmp cl,45 ' it it a '-' sign
jnz e2 ' skip if not
mov dword ptr [sgx],1 ' set negation flag
'----------------------'
e1a: ' get the next digit
dec ebx ' check if at end
jl c3 ' then finish now
mov cl,[esi] ' get a digit
inc esi ' increment char pointer
'----------------------'
e2: ' first digit is ready
cmp cl,48 ' is it less then 0?
jz e1a ' ignore leading zeros, get next
jl c3 ' then finish
cmp cl,57 ' is it greater than 9?
jg c3 ' then finish
sub cl,48 ' base to zero
mov al,cl ' move to accummulator
dec ebx ' check at end
jl e3 ' if so then procede to process
mov cl,[esi] ' next digit
inc esi ' next digit pointer
cmp cl,48 ' check below 0
jl e3 ' if so then procede to process
cmp cl,57 ' check above 9
jg e3 ' if so then procede to process
mul ch ' multiply accum by 10
sub cl,48 ' base digit to zero
add al,cl ' add it to accum
dec ebx ' check if at end
jl e3 ' then process
mov cl,[esi] ' get the final digit
inc esi ' ready for next xhar
cmp cl,48 ' is it below 0
jl e3 ' then process
cmp cl,57 ' is it above 9
jg e3 ' then process
mul ch ' multiply accum by 10
sub cl,48 ' base digit to zero
add al,cl ' add to accum
adc ah,0 ' carry thru to ah
'----------------------'
' ' ERROR CHECKS
'cmp eax,307 ' E too great
'jle ee1 '
'mov eax,1 ' error code: E out of range
'jmp xx '
ee1: '
dec ebx ' Excess E digits
jl e3 '
mov cl,[esi] '
inc esi
cmp cl,48 '
jl e3 '
cmp cl,57 '
jg e3 '
mov eax,2 ' error code: E overflow
jmp xx '
'----------------------'
e3: ' E is now in binary form in eax
cmp dword ptr [sgx],1 ' is it negative
jz e4m ' if so then go to divider section
'----------------------'
e4: ' multiply by 10 for each E number
cmp eax,8 ' step of 10^8
jl e5 ' try smaller step
fimul dword ptr [m1e8]'
sub eax,8 '
jmp e4 ' repeat
e5: '
cmp eax,4 ' step of 10^4
jl e6 ' try smaller step
sub eax,4 '
fimul dword ptr [m1e4]'
jmp e5 ' repeat
e6: '
cmp eax,0 ' step of 10^1
jle c3 ' done
dec eax '
fimul dword ptr [m10] '
jmp e6 ' repeat till ecx=0
'======================'
'======================'
e4m: ' divide by 10 for each negative E number
cmp eax,8 ' step of 10^8
jl e5m ' try smaller step
fidiv dword ptr [m1e8]'
sub eax,8 '
jmp e4m ' repeat
e5m: '
cmp eax,4 ' step of 10^4
jl e6m ' try smaller step
sub eax,4 '
fidiv dword ptr [m1e4]'
jmp e5m ' repeat
e6m: '
cmp eax,0 ' step of 10^1
jle c3 ' done
dec eax '
fidiv dword ptr [m10] '
jmp e6m ' repeat till ecx=0
'======================'
'======================'
c3: '
cmp dword ptr [sgg],1 ' check if sign '-'
jnz c4 ' skip if not.
fchs ' otherwise change the accummulator sign
'======================'
c4: '
mov eax,[v] ' get pointer to result
fstp qword ptr [eax] ' store the result and pop the FPU stack '
xor eax,eax ' no error
'======================'
xx:
mov [function],eax ' return error code
'======================'
end asm
end function
'--------'
' TEST '
'--------'
dim as string s="-123456.6789e5"
dim v as double
dim erc as long
erc=dval( strptr(s),len(s),v )
print "input: ";s
print "output: ";v
print
print "Error code: ";erc
'-----------------'
' EXTREME TESTS '
'-----------------'
print
print "TESTS AND INTERPRETATIONS"
s=" 0": dval( strptr(s),len(s),v ): print s;" ",v
s=" ": dval( strptr(s),len(s),v ): print s;" ",v
s=" ,": dval( strptr(s),len(s),v ): print s;" ",v
s=" -": dval( strptr(s),len(s),v ): print s;" ",v
s=" .": dval( strptr(s),len(s),v ): print s;" ",v
s=" 0,1":dval( strptr(s),len(s),v ): print s;" ",v
s=" 1,2":dval( strptr(s),len(s),v ): print s;" ",v
s=" 0.1,2":dval( strptr(s),len(s),v ): print s;" ",v
s=" !3":dval( strptr(s),len(s),v ): print s;" ",v
s=" $3":dval( strptr(s),len(s),v ): print s;" ",v
s=" $3$4":dval( strptr(s),len(s),v ): print s;" ",v
s=" 3-4":dval( strptr(s),len(s),v ): print s;" ",v
s=" 3.4":dval( strptr(s),len(s),v ): print s;" ",v
s=" .000456":dval( strptr(s),len(s),v ): print s;" ",v
s=" 3.4.5":dval( strptr(s),len(s),v ): print s;" ",v
s=" 3e2":dval( strptr(s),len(s),v ): print s;" ",v
s=" 3.4e2":dval( strptr(s),len(s),v ): print s;" ",v
s=" -.4e2":dval( strptr(s),len(s),v ): print s;" ",v
s=" .04e2":dval( strptr(s),len(s),v ): print s;" ",v
s=" .04e-2":dval( strptr(s),len(s),v ): print s;" ",v
s=" -.04e-2":dval( strptr(s),len(s),v ): print s;" ",v
s=" -.0012345E-10":erc=dval( strptr(s),len(s),v ): print s;" ",v
print
s=" .123456789012345678E-10":erc=dval( strptr(s),len(s),v ): print s;" ",v
s=" 123456789012345678E+10":erc=dval( strptr(s),len(s),v ): print s;" ",v
s=" 12345678.9012345678E100!?*":erc=dval( strptr(s),len(s),v ): print s;" ",v
's=" 123456789012345678E999":erc=dval( strptr(s),len(s),v ): print s;" ",v
s=" 123456.789012345678E000300":erc=dval( strptr(s),len(s),v ): print s;" ",v
's=" 1234567890.12345678E300":erc=dval( strptr(s),len(s),v ): print s;" ",v
print
print "Error Code: ";erc
'-------------'
' SPEED TEST '
'-------------'
dim i as long
dim t as double
dim td as double
dim tv as double
t=timer
for i=1 to 100000
dval(strptr(s),len(s),v)
next
td=timer-t
t=timer
for i=1 to 100000
v=val(s)
next
tv=timer-t
print
print "for 100000 repeats: "
print "dval time: ";td
print " val time: ";tv
print
print "Speed factor: ";tv/td
Here is the PowerBasic equivalent.
However it performs equally as well as Powerbasic's VAL(). So as it stands, there is no advantage in using dval() in a PB program.
'
' DVAL
' text to floating point number conversion in Assembler
' Charles E V Pegge
' 28 June 2007
' PowerBasic PBWin Ver 8x
' using inline assembler.
#COMPILE EXE
#DIM ALL
#REGISTER NONE
FUNCTION dval(BYVAL p AS BYTE PTR, BYVAL le AS LONG, BYREF v AS DOUBLE) AS LONG
DIM m10 AS LONG: m10=10
DIM m1e4 AS LONG: m1e4=1e4
DIM m1e8 AS LONG: m1e8=1e8
DIM tm1 AS LONG
DIM tm2 AS LONG
DIM sgg AS LONG
DIM sgx AS LONG
'
'======================'
!xor eax,eax ' clear eax
!fldz ' load zero to fpu
!mov ebx,le ' length
!mov ecx,p ' text pointer
'======================'
a0: ' loop for reaching the sign or first digit
!cmp ebx,0 ' is it zero or less?
!jle a2 ' then terminate now with zero
!mov al,[ecx] ' first char
!cmp al,44 ' is it
!jl aa ' if less then .. aa treat as leading space
!jz a2 ' terminate now with zero
!cmp al,45 ' is it '-' sign?
!jnz a1 ' then
!dec ebx ' is it last char?
!jle a2 ' then terminate now with zero
!inc ecx ' next char
!mov dword ptr sgg,1 ' set neg flag
!jmp a1 ' procede to a1
'======================'
aa: ' skip space chars
!dec ebx ' down count chars to go
!inc ecx ' next char
!jmp a0 ' loop back to a0
'======================'
a1: ' begin collecting digits
!mov al,[ecx] ' load next char
!dec ebx ' down count
!jl a2 ' no more digits so goto E section
!inc ecx ' ready next char
!cmp al,46 ' is this a dot?
!jz bb ' if so then goto decimals section
!cmp al,48 ' check lower limit
!jl a2 ' below '0' char so goto E section
!cmp al,57 ' check upper limit
!jg a2 ' above '9' so goto E section
!fimul dword ptr m10 ' premultiply the accummulator st
!sub al,48 ' base to 0
!mov tm1,eax ' store in temp
!fiadd dword ptr tm1 ' add from temp
!jmp a1 ' repeat for next digit
'======================'
a2: ' relay to E section
!mov esi,ecx ' transfer index to esi
!mov cl,al ' transfer char to cl
!jmp c2 ' go on to E section
'======================'
bb: ' decimal point
!cmp ebx,0 ' is this the last char?
!jle c2 ' then finish now
!mov esi,ecx
!xor edx,edx ' clear edx
!mov eax,1 ' set eax to 10
!xor ecx,ecx ' clear to act as char reg
!xor edx,edx ' while edx:eax is used as a digit multiplier
'======================'
!xor edi,edi ' zero the decimal places tally
b1: ' do loop
' start of decimals loop
!dec ebx ' any more chars?
!jl c1 ' finish now
!mov cl,[esi] ' get next char
!inc esi ' ready for next char
!cmp cl,48 ' range check lower
!jl c1 ' finish
!cmp cl,57 ' range check upper
!jg c1 ' check for E numbers
!cmp edi,18 ' limit of decimal places
!jge b1 ' too many decimal places error so ignore rest
!fimul dword ptr m10 ' premultiply accummulator
!mul dword ptr m10 ' multiply eax ready for next loop
!inc edi ' tally decimal places
!cmp edi,9 ' is this 9 dps done ?
!jnz b2 ' skip if not
!mov tm2,eax ' move the multiplier to a second stage
!mov eax,1 ' reset the multiplier
b2: ' continue
!sub cl,48 ' base to zero
!mov tm1,ecx ' store to tm1
!fiadd dword ptr tm1 ' add from tm1
!jmp b1 ' repeat for next digit
'======================'
'======================'
cc: '
c1: ' decimal place scaling
!mov tm1,eax ' save eax to tm1
!fidiv dword ptr tm1 ' divide by this factor (up to 9 decimal places)
!cmp dword ptr tm2,0 ' is there a second stage?
!jz c2 ' skip if not
!fidiv dword ptr tm2 ' divide by this factor (up to 9 more decimal places)
'======================'
c2: ' inserted E number checking procedure here:
'======================'
ee: ' E & e numbers
!cmp cl,&h45 ' is it E?
!jz e1 ' okay
!cmp cl,&h65 ' is it e?
!jz e1 ' okay
!jmp c3 ' else skip E numbers
'----------------------'
e1: ' ready to procede with E number
!xor eax,eax ' clear eax
!mov ch,10 ' x10 multiplier
!dec ebx ' check if end
!jl c3 ' finish if at end
!mov cl,[esi] ' next char
!inc esi ' increment char pointer
!cmp cl,43 ' is it a '+' sign?
!jz e1a ' then ignore and get next char
!cmp cl,45 ' it it a '-' sign
!jnz e2 ' skip if not
!mov dword ptr sgx,1 ' set negation flag
'----------------------'
e1a: ' get the next digit
!dec ebx ' check if at end
!jl c3 ' then finish now
!mov cl,[esi] ' get a digit
!inc esi ' increment char pointer
'----------------------'
e2: ' first digit is ready
!cmp cl,48 ' is it less then 0?
!jz e1a ' ignore leading zeros, get next
!jl c3 ' then finish
!cmp cl,57 ' is it greater than 9?
!jg c3 ' then finish
!sub cl,48 ' base to zero
!mov al,cl ' move to accummulator
!dec ebx ' check at end
!jl e3 ' if so then procede to process
!mov cl,[esi] ' next digit
!inc esi ' next digit pointer
!cmp cl,48 ' check below 0
!jl e3 ' if so then procede to process
!cmp cl,57 ' check above 9
!jg e3 ' if so then procede to process
!mul ch ' multiply accum by 10
!sub cl,48 ' base digit to zero
!add al,cl ' add it to accum
!dec ebx ' check if at end
!jl e3 ' then process
!mov cl,[esi] ' get the final digit
!inc esi ' ready for next xhar
!cmp cl,48 ' is it below 0
!jl e3 ' then process
!cmp cl,57 ' is it above 9
!jg e3 ' then process
!mul ch ' multiply accum by 10
!sub cl,48 ' base digit to zero
!add al,cl ' add to accum
!adc ah,0 ' carry thru to ah
'----------------------'
' ' ERROR CHECKS
'cmp eax,307 ' E too great
'jle ee1 '
'mov eax,1 ' error code: E out of range
'jmp xx '
ee1: '
!dec ebx ' Excess E digits
!jl e3 '
!mov cl,[esi] '
!inc esi
!cmp cl,48 '
!jl e3 '
!cmp cl,57 '
!jg e3 '
!mov eax,2 ' error code: E overflow
!jmp xx '
'----------------------'
e3: ' E is now in binary form in eax
!cmp dword ptr sgx,1 ' is it negative
!jz e4m ' if so then go to divider section
'----------------------'
e4: ' multiply by 10 for each E number
!cmp eax,8 ' step of 10^8
!jl e5 ' try smaller step
!fimul dword ptr m1e8 '
!sub eax,8 '
!jmp e4 ' repeat
e5: '
!cmp eax,4 ' step of 10^4
!jl e6 ' try smaller step
!sub eax,4 '
!fimul dword ptr m1e4 '
!jmp e5 ' repeat
e6: '
!cmp eax,0 ' step of 10^1
!jle c3 ' done
!dec eax '
!fimul dword ptr m10 '
!jmp e6 ' repeat till ecx=0
'======================'
'======================'
e4m: ' divide by 10 for each negative E number
!cmp eax,8 ' step of 10^8
!jl e5m ' try smaller step
!fidiv dword ptr m1e8 '
!sub eax,8 '
!jmp e4m ' repeat
e5m: '
!cmp eax,4 ' step of 10^4
!jl e6m ' try smaller step
!sub eax,4 '
!fidiv dword ptr m1e4 '
!jmp e5m ' repeat
e6m: '
!cmp eax,0 ' step of 10^1
!jle c3 ' done
!dec eax '
!fidiv dword ptr m10 '
!jmp e6m ' repeat till ecx=0
'======================'
'======================'
c3: '
!cmp dword ptr sgg,1 ' check if sign '-'
!jnz c4 ' skip if not.
!fchs ' otherwise change the accummulator sign
'======================'
c4: '
!mov eax,v ' get pointer to result
!fstp qword ptr [eax] ' store the result and pop the FPU stack '
!xor eax,eax ' no error
'======================'
xx:
!mov function, eax ' return error code
'======================'
END FUNCTION
'--------'
' TEST '
'--------'
GLOBAL ps AS STRING
SUB sprint (s AS STRING)
ps=ps+s+$CR
END SUB
SUB tprint (s AS STRING,d AS STRING)
ps=ps+s+CHR$(8)+CHR$(9)+CHR$(9)+d+$CR
END SUB
FUNCTION PBMAIN() AS LONG
DIM s AS STRING: s="-123456.6789e5"
DIM v AS DOUBLE
DIM erc AS LONG
erc=dval( STRPTR(s),LEN(s),v )
sprint ("input: "+s)
sprint ("output: "+STR$(v))
sprint ("")
sprint ("Error code: "+STR$(erc))
'-----------------'
' EXTREME TESTS '
'-----------------'
sprint("")
sprint ("TESTS AND INTERPRETATIONS")
s=" 0": dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" ": dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" ,": dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" -": dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" .": dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 0,1":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 1,2":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 0.1,2":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" !3":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" $3":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" $3$4":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 3-4":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 3.4":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" .000456":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 3.4.5":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 3e2":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 3.4e2":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" -.4e2":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" .04e2":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" .04e-2":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" -.04e-2":dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" -.0012345E-10":erc=dval( STRPTR(s),LEN(s),v ): tprint( s,STR$(v))
sprint("")
s=" .123456789012345678E-10":erc=dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 123456789012345678E+10":erc=dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
s=" 12345678.9012345678E100!?*":erc=dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
's=" 123456789012345678E999":erc=dval( strptr(s),len(s),v ): tprint (s,str$(v))
s=" 123456.789012345678E000300":erc=dval( STRPTR(s),LEN(s),v ): tprint (s,STR$(v))
's=" 1234567890.12345678E300":erc=dval( strptr(s),len(s),v ): tprint (s,str$(v))
sprint("")
sprint ("Error Code: "+STR$(erc))
'-------------'
' SPEED TEST '
'-------------'
DIM i AS LONG
DIM t AS DOUBLE
DIM td AS DOUBLE
DIM tv AS DOUBLE
t=TIMER
FOR i=1 TO 100000
dval(STRPTR(s),LEN(s),v)
NEXT
td=TIMER-t
t=TIMER
FOR i=1 TO 100000
v=VAL(s)
NEXT
tv=TIMER-t
sprint("")
sprint( "for 100000 repeats: ")
tprint( "dval time:",STR$(td))
tprint( " val time:",STR$(tv))
sprint("")
tprint( "Speed factor:",STR$(tv/td))
MSGBOX ps
ps=""
END FUNCTION