Page 1 of 1

Faster StrF()

Posted: Fri Aug 24, 2007 7:01 pm
by Trond
With some simple tagliatelle it's possible to create a faster StrF() than Str() by exploiting the speed of Str():

Code: Select all

Global Dim PrecisionLookup(9)
PrecisionLookup(0) = 0
PrecisionLookup(1) = 10
PrecisionLookup(2) = 100
PrecisionLookup(3) = 1000
PrecisionLookup(4) = 10000
PrecisionLookup(5) = 100000
PrecisionLookup(6) = 1000000
PrecisionLookup(7) = 10000000
PrecisionLookup(8) = 100000000
PrecisionLookup(9) = 1000000000

Procedure.s StrF2(A.f, Precision.l = 4)
  Protected I.l
  Protected Temp.s
  Protected Minus.l
  Precision = PrecisionLookup(Precision)
  If A < 0
    Minus = 1
    A = -A
  EndIf
  I = A
  If A < I
    I - 1
  EndIf
  Temp = Str((A - I) * Precision + Precision)
  If Minus
    If Precision > 0
      ProcedureReturn "-" + Str(I) + "." + Right(Temp, Len(Temp)-1)
    Else
      ProcedureReturn "-" + Str(I)
    EndIf
  Else
    If Precision > 0
      ProcedureReturn Str(I) + "." + Right(Temp, Len(Temp)-1)
    Else
      ProcedureReturn Str(I)
    EndIf
  EndIf
EndProcedure

#Tries = 500000

time = GetTickCount_()
For I = 0 To #Tries
  StrF(1234.5678)
Next
MessageRequester("", Str(GetTickCount_()-time))

time = GetTickCount_()
For I = 0 To #Tries
  StrF2(1234.5678)
Next
MessageRequester("", Str(GetTickCount_()-time))
Of course, speed test without debugger.

Posted: Sat Aug 25, 2007 11:09 am
by schic
thanks for sharing.
Very usefull if one has to change a large number of floats into strings.

But a wrong value is given if the float is null (-1.00)
If code is changed to:

Code: Select all

  If a <= i     And a > 0 ; !!!!!!!
    i - 1
  EndIf
it works correct.

Posted: Sat Aug 25, 2007 12:50 pm
by Trond
Yes, you're correct, I did an error when I tried to work around a PB bug. It should actually be like this:

Code: Select all

If I < A
...
EndIF
But the comparison is not typecasted correctly then, so I had to turn it around and then I made a mistake. The correct one should look like this:

Code: Select all

If A > I
...
EndIF

Posted: Wed Aug 29, 2007 9:36 am
by Rescator
Nice one, but if trying to use a precision higher than 6 it crashes.
See my implementation, using Pow slows things down a bit but not too much and you get rid of the array.

StrF2 has a issue with rounding though.

StrF3 behaves very closely to the original StrF but only up to around a precision of 6.

StrF can easily be tweaked to handle high numbers and very high precision (like 10 decimals after the . ) if you simply change all .l to .q
However any speed gains are lost as the original StrD is just as fast,
actually it seems that StrF and StrD are similar in speed, so if accuracy is wanted then StrD would be my advice.

If you know more or less what the input will be then it's not that hard to make speedier variants of PB's routines, but if you do not know what the input can be then you will find that PB's routines are much safer to use.
Apparently Fred knows his stuff :P

Code: Select all

EnableExplicit
DisableDebugger

Procedure.s StrD3(a.d,precision.l=6) ;Accurate up to a max precision of ?
 Protected i.l,temp$,precmul.q,result$,n.q
 If precision
  precmul=Pow(10,precision)
  If a=0.0
   result$=RSet("0.",precmul,"0")
  Else
   n=a*precmul
   temp$=StrQ(n)
   result$=Left(temp$,Len(temp$)-precision)+"."+Right(temp$,precision)
  EndIf
 Else
  If Not a
   result$="0"
  Else
   result$=Str(a)
  EndIf
 EndIf
 ProcedureReturn result$
EndProcedure

Procedure.s StrF3(a.f,precision.l=6) ;Accurate up to a max precision of 6
 Protected i.l,temp$,precmul.l,result$,n.l
 If precision
  precmul=Pow(10,precision)
  If a=0.0
   result$=RSet("0.",precmul,"0")
  Else
   n=a*precmul
   temp$=Str(n)
   result$=Left(temp$,Len(temp$)-precision)+"."+Right(temp$,precision)
  EndIf
 Else
  If Not a
   result$="0"
  Else
   result$=Str(a)
  EndIf
 EndIf
 ProcedureReturn result$
EndProcedure

Procedure.s StrF2(a.f,precision.l=6) ;Fast but has additional rounding errors compared to original?
  Protected i.l,temp$,minus.l,result.l
  precision=Pow(10,precision) ;Pow is used to avoid crash issue with higher precisions
  If a<0
    minus=1
    a=-a
  EndIf
  i=a
  If a<i
    i-1
  EndIf
  temp$=Str(((a - i)*precision)+precision)
  If minus
    If precision>0
      ProcedureReturn "-"+Str(i)+"."+Right(temp$,Len(temp$)-1)
    Else
      ProcedureReturn "-"+Str(i)
    EndIf
  Else
    If precision>0
      ProcedureReturn Str(i)+"."+Right(temp$,Len(temp$)-1)
    Else
      ProcedureReturn Str(i)
    EndIf
  EndIf
EndProcedure

#Tries = 500000
Define test$,i.l,time.l

time = GetTickCount_()
For i = 0 To #Tries
  test$=StrF(-1234.5678910,6)
Next
MessageRequester("", Str(GetTickCount_()-time)+" "+test$)

time = GetTickCount_()
For i = 0 To #Tries
  test$=StrF2(-1234.5678910,6)
Next
MessageRequester("", Str(GetTickCount_()-time)+" "+test$)

time = GetTickCount_()
For i = 0 To #Tries
  test$=StrF3(-1234.5678910,6)
Next
MessageRequester("", Str(GetTickCount_()-time)+" "+test$)

time = GetTickCount_()
For i = 0 To #Tries
  test$=StrD3(-1234.5678910,6)
Next
MessageRequester("", Str(GetTickCount_()-time)+" "+test$)

time = GetTickCount_()
For i = 0 To #Tries
  test$=StrD(-1234.5678910,6)
Next
MessageRequester("", Str(GetTickCount_()-time)+" "+test$)

Posted: Wed Aug 29, 2007 9:47 am
by Rescator
PS! Try with (-12340000.5678910,10) as the input values for each test. All but the D variants fall apart obviously.

But is it just me or does StrD3 look faster than the original StrD ?

Posted: Wed Aug 29, 2007 3:30 pm
by Trond
Rescator wrote:Nice one, but if trying to use a precision higher than 6 it crashes.
I can't confirm. Only if the precision is higher than 9 (which is "intented"). But the .f type has a precision of max 8 digits anyways, so 10 decimals is totally useless.
StrF2 has a issue with rounding though.
I can't confirm that either. It seems to work perfectly:

Code: Select all

For i = -10000 To 100000
  A.f = i
  If StrF2(a, 6) <> StrF(a, 6)
    Debug StrF2(a, 6)
    Debug StrF(a, 6)
    CallDebugger
  EndIf
  a = i/3.25
  If StrF2(a, 6) <> StrF(a, 6)
    Debug StrF2(a, 6)
    Debug StrF(a, 6)
    CallDebugger
  EndIf
Next
PS! Try with (-12340000.5678910,10) as the input values for each test.
No rounding error occurs in the string to float conversion here:

Code: Select all

a.f = -12340000.5678910
Debug StrF(a, 7)
Debug strf2(a, 7)
If you know more or less what the input will be then it's not that hard to make speedier variants of PB's routines, but if you do not know what the input can be then you will find that PB's routines are much safer to use.
Try this: StrF(1, 1000)

Posted: Sun Sep 02, 2007 11:56 am
by Rescator
Trond wrote:
Rescator wrote:Nice one, but if trying to use a precision higher than 6 it crashes.
I can't confirm. Only if the precision is higher than 9 (which is "intented"). But the .f type has a precision of max 8 digits anyways, so 10 decimals is totally useless.
Oops! I meant to type 9 actually.
And yeah true. Putting in a If to "roof" and "floor" the precision values might be the quickest way to make the input safer though?
Trond wrote:
Rescator wrote:StrF2 has a issue with rounding though.
I can't confirm that either. It seems to work perfectly:

Code: Select all

For i = -10000 To 100000
  A.f = i
  If StrF2(a, 6) <> StrF(a, 6)
    Debug StrF2(a, 6)
    Debug StrF(a, 6)
    CallDebugger
  EndIf
  a = i/3.25
  If StrF2(a, 6) <> StrF(a, 6)
    Debug StrF2(a, 6)
    Debug StrF(a, 6)
    CallDebugger
  EndIf
Next
Oops, now that's creepy:
i got two hits.
16385.539062
16385.539063
Trond wrote:
Rescator wrote:PS! Try with (-12340000.5678910,10) as the input values for each test.
No rounding error occurs in the string to float conversion here:

Code: Select all

a.f = -12340000.5678910
Debug StrF(a, 7)
Debug strf2(a, 7)
Odd!
Check out

Code: Select all

Debug StrF(-12340000.5678910,9)
Debug StrF2(-12340000.5678910,9)
Debug StrF3(-12340000.5678910,9)
Debug StrD3(-12340000.5678910,9)
Here only D3 is correct, the others have rounding errors. (0'ing the fraction.
Result:
-12340001.000000000 (PureBasic)
-12340001.000000000 (Trond's)
-2.147483648 (oops, I suck)
-12340000.567891000 (correct)

Also check out

Code: Select all

Debug StrF(1234.567891,6)
Debug StrF2(1234.567891,6)
Debug StrF3(1234.567891,6)
Debug StrD3(1234.567891,6)
Result:
1234.567871
1234.567871 (Trond's)
1234.567871
1234.567891 (correct)

Although, this only shows that doubles are really advised over singles when you want accuracy. So I didn't really prove much at all.
Hmm, hey Trond, if you modify your routine for doubles,
and whether you use a array or Pow(), are you able to do faster code than StrD() ?
Trond wrote:
Rescator wrote:If you know more or less what the input will be then it's not that hard to make speedier variants of PB's routines, but if you do not know what the input can be then you will find that PB's routines are much safer to use.
Try this: StrF(1, 1000)
Um! I did and got one helluva long number. Was something else supposed to happen? :wink:

Posted: Sun Sep 02, 2007 4:27 pm
by Trond
Rescator wrote:
Trond wrote:
Rescator wrote:If you know more or less what the input will be then it's not that hard to make speedier variants of PB's routines, but if you do not know what the input can be then you will find that PB's routines are much safer to use.
Try this: StrF(1, 1000)
Um! I did and got one helluva long number. Was something else supposed to happen? :wink:
It hangs without using any cpu at all here.

Posted: Sun Sep 02, 2007 8:57 pm
by Trond
Sorry, but my StrF2() is fundamentally broken beyond repair. Will someone please bury this topic?

Posted: Sun Sep 02, 2007 11:30 pm
by Demivec
What's broken. Doesn't it return the same result as StrF()?