I couldn't resist trying an asm version
x86 and x64 using FPU
Code: Select all
Macro M_GetNextChar()
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
CompilerIf #PB_Compiler_Unicode
!movzx eax, word [rdx] ; x64 Unicode
!add rdx, 2
CompilerElse
!movzx eax, byte [rdx] ; x64 Ascii
!add rdx, 1
CompilerEndIf
CompilerElse
CompilerIf #PB_Compiler_Unicode
!movzx eax, word [edx] ; x86 Unicode
!add edx, 2
CompilerElse
!movzx eax, byte [edx] ; x86 Ascii
!add edx, 1
CompilerEndIf
CompilerEndIf
EndMacro
Macro M_AddInt32ToDouble()
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov [rsp - 4], ebx
!fiadd dword [rsp - 4]
CompilerElse
!mov [esp - 4], ebx
!fiadd dword [esp - 4]
CompilerEndIf
EndMacro
Macro M_MulFromTable(Offset)
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!fmul qword [rdi + rax * 8 + Offset]
CompilerElse
!fmul qword [edi + eax * 8 + Offset]
CompilerEndIf
EndMacro
Procedure.d ValueD(*ValueString)
; >> Initialize <<
Protected Result.d
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov rdx, [p.p_ValueString]
!push rbx
!push rsi
!push rdi
!lea rdi, [.vald_lut]
CompilerElse
!mov edx, [p.p_ValueString]
!push ebx
!push esi
!push edi
!lea edi, [.vald_lut]
CompilerEndIf
!fld qword [.vald_lut + 64] ; 100000000
!fldz ; 0
!mov esi, 0x00000001 ; counter add value
!xor ebx, ebx ; integer value
!xor ecx, ecx ; 2 x 16 bit counter
; >> Prescan <<
!.l0:
M_GetNextChar()
!cmp eax, ' ' ; skip leading spaces
!je .l0
!cmp eax, '+' ; '+' ==> .l1
!je .l1
!cmp eax, '-' ; not '-' ==> .l2
!jne .l2
!or ecx, 0x00001000 ; set flag for negative value
; >> Main <<
!.l1:
M_GetNextChar() ; get next character
!.l2:
!sub eax, '0' ; < '0' ==> .l3
!jc .l3
!cmp eax, 9 ; > '9' ==> .l4
!ja .l4
!imul ebx, 10 ; ebx * 10
!add ebx, eax ; ebx + digit
!add ecx, esi ; update step counter
!test ecx, 0x7 ; is step a multiple of 8
!jnz .l1 ; no ==> .l1
!fmul st0, st1 ; double * 100000000
M_AddInt32ToDouble() ; double + ebx
!xor ebx, ebx ; clear ebx
!jmp .l1 ; ==> .l1
!.l3:
!test esi, 0x00010000 ; did a '.' already occur
!jnz .l4 ; yes ==> .l4
!or esi, 0x00010000 ; update counter add value
!cmp eax, '.' -48 ; '.' ==> .l1
!je .l1
; >> Finalize <<
!.l4:
!mov eax, ecx ; add remaining value to ebx
!and eax, 7
M_MulFromTable(0)
M_AddInt32ToDouble()
!test ecx, 0x00001000 ; test negative value flag
!jz .l5
!fchs ; change sign when negative
!.l5:
!shr ecx, 16 ; shift so ecx is counter after '.'
!jz .l7 ; nothing behind a '.' ==> .l7
!mov eax, ecx
!and eax, 15
M_MulFromTable(72) ; correct for dot position
!shr ecx, 4
!jz .l7
!.l6:
!fmul qword [.vald_lut + 200] ; further correction if required
!dec ecx
!jnz .l6
; >> Done <<
!.l7:
!fstp st1
; >> Cleanup <<
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!pop rdi
!pop rsi
!pop rbx
CompilerElse
!pop edi
!pop esi
!pop ebx
CompilerEndIf
!fstp qword [p.v_Result]
ProcedureReturn Result
; >> Lookup table <<
!.vald_lut:
!dq 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8
!dq 1.0e0, 1.0e-1, 1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6, 1.0e-7, 1.0e-8
!dq 1.0e-9, 1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, 1.0e-14, 1.0e-15, 1.0e-16
EndProcedure
There's only a minor difference in accuracy with ValD.
Code: Select all
S.s = "-0.00000000000000000000005"
Debug ValD(S)
Debug ValueD(@S)
;Debug MATH_UnicodeValD(@S)
S.s = "12345678901234567890.12345678901234567890"
Debug ValD(S)
Debug ValueD(@S)
;Debug MATH_UnicodeValD(@S)
On x64, SSE seems to be a bit faster
Code: Select all
; 64 bit version using SSE instead of FPU
Macro M_GetNextChar()
CompilerIf #PB_Compiler_Unicode
!movzx eax, word [r9] ; Unicode
!add r9, 2
CompilerElse
!movzx eax, byte [r9] ; Ascii
!add r9, 1
CompilerEndIf
EndMacro
Procedure.d ValueD(*ValueString)
; >> Initialize <<
Protected Result.d
!lea r8, [.vald_lut]
!mov r9, [p.p_ValueString]
!movsd xmm1, [r8 + 64] ; 100000000
!xorpd xmm0, xmm0
!xorpd xmm3, xmm3 ; sign
!mov r10d, 1 ; counter add value
!xor edx, edx ; integer value
!xor ecx, ecx ; 2 x 16 bit counter
; >> Prescan <<
!.l0:
M_GetNextChar()
!cmp eax, ' ' ; skip leading spaces
!je .l0
!cmp eax, '+' ; '+' ==> .l1
!je .l1
!cmp eax, '-' ; not '-' ==> .l2
!jne .l2
!movsd xmm3, [.vald_sgn]
; >> Main <<
!.l1:
M_GetNextChar() ; get next character
!.l2:
!sub eax, '0' ; < '0' ==> .l3
!jc .l3
!cmp eax, 9 ; > '9' ==> .l4
!ja .l4
!imul edx, 10 ; edx * 10
!add edx, eax ; edx + digit
!add ecx, r10d ; update step counter
!test ecx, 0x7 ; is step a multiple of 8
!jnz .l1 ; no ==> .l1
!cvtsi2sd xmm2, edx
!mulsd xmm0, xmm1 ; double * 100000000
!addsd xmm0, xmm2
!xor edx, edx ; clear edx
!jmp .l1 ; ==> .l1
!.l3:
!test r10d, 0x00010000 ; did a '.' already occur
!jnz .l4 ; yes ==> .l4
!or r10d, 0x00010000 ; update counter add value
!cmp eax, '.' -48 ; '.' ==> .l1
!je .l1
; >> Finalize <<
!.l4:
!cvtsi2sd xmm2, edx
!mov eax, ecx ; add remaining value to edx
!and eax, 7
!mulsd xmm0, [r8 + rax*8]
!addsd xmm0, xmm2
!orpd xmm0, xmm3 ; set sign
!shr ecx, 16 ; shift so ecx is counter after '.'
!jz .l7 ; nothing behind a '.' ==> .l7
!mov eax, ecx
!and eax, 15
!mulsd xmm0, [r8 + rax*8 + 72]; correct for dot position
!shr ecx, 4
!jz .l7
!.l6:
!mulsd xmm0, [r8 + 200] ; further correction if required
!dec ecx
!jnz .l6
; >> Done <<
!.l7:
!movsd [p.v_Result], xmm0
ProcedureReturn Result
; >> Lookup table <<
!.vald_sgn: dq 0x8000000000000000
!.vald_lut:
!dq 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8
!dq 1.0e0, 1.0e-1, 1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6, 1.0e-7, 1.0e-8
!dq 1.0e-9, 1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, 1.0e-14, 1.0e-15, 1.0e-16
EndProcedure