Need a fast infix math evaluator

Just starting out? Need help? Post your questions and find answers here.
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Need a fast infix math evaluator

Post by dracflamloc »

I'm creating a scripting language in pb to be released LGPL. I need a fast infix math evaluator. It doesn't need to handle string concatenation or functions. It just needs to take an expression as a string and return the result as a string.

So far I've tried 2 evaluators I've found on the forums. After stripping down Xombies as much as I could, and finding srod's ifix->rpn->solution code, here is what i've discovered so far.

100,000 evaluations of "100+1" takes 700ms on my computer using Xombies. Using srods, it takes 1200ms.

However once you start adding more operations, Xombies falls behind srod's after just 2 more operations and starts to REALLY lag behind with 5+ operations. Does anyone have any suggestions?

The dillemma is that when evaluating loop conditions, typically its an expression like "i+1" which would mean Xombies is a faster algo to use for looping. However once you add a couple more operations, it becomes much much slower (about 60% slower than srods).

I was considering adding a special case to srod's to detect if its a 1-operation expression and handle it in a faster way.

Once I get the math evaluator speed faster the language will be very nearly done. It's pretty quick, though not as fast as lua yet ;)

Also I don't think either of these has a modulus % operator so if anyone can recommend one that does it'd be handy.

I could of course write my own but with all the heads here at pb I figured some of have probably made quite a few already =)
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

Did you try mine? (Did I even release it?)
Edit: Did I even make one? :lol: I'll have one ready in a few seconds. Just a normal +-*/() expression?

Edit:
Could you test the speed of this?

Code: Select all

snip
Faster code below
Last edited by Trond on Mon Mar 06, 2006 6:49 pm, edited 2 times in total.
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Post by dracflamloc »

Oops. Should have specified. PB 3.94. No API either.

I did convert yours (removed the match macro)
Seems to be pretty good. Yours is a good bit faster than Xombies except in the i+1 type of expressions. You are a bit faster than srod on i+1 but the same for most of the other cases.

I modified srod's to handle single operator cases more efficiently, and it put it on par with yours trond. But still falls behind Xombies by a bit.

500ms on this machine here at work for 100,000 "100+1" operations using Xombie, 700ms for the modified srods or yours Trond.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

dracflamloc wrote:Oops. Should have specified. PB 3.94. No API either.

I did convert yours (removed the match macro)
Seems to be pretty good. Yours is a good bit faster than Xombies except in the i+1 type of expressions. You are a bit faster than srod on i+1 but the same for most of the other cases.

I modified srod's to handle single operator cases more efficiently, and it put it on par with yours trond. But still falls behind Xombies by a bit.

500ms on this machine here at work for 100,000 "100+1" operations using Xombie, 700ms for the modified srods or yours Trond.
Arrrgrgrgrrr time for some optimization then. Try if this is faster:
http://pastebin.com/587298


Edit: This should blow the doors and windows and everything off everything:

Code: Select all

OpenConsole()

Global Look.s
Look.s = " " ; Yes, that space is IMPORTANT!!!! Remove and DIE!!!
Global Stream.s
Global StreamPos.l        ; Read position in stream

Procedure.s VisibleToken(s.s)
  Select s.s
    Case #CR$
      s.s = "newline"
    Case #LF$
      s.s = "newline"
    Case ""
      s.s = "nothing"
  EndSelect
  ProcedureReturn s.s
EndProcedure

;Cleanup then end
Procedure Finish()
  PrintN("Ending.")
  Input()
  End
EndProcedure

;Report an error
Procedure Error(s.s)
  PrintN("Error: " + s + ".")
EndProcedure

;Report an error and abort
Procedure Abort(s.s)
  Error(s.s)
  Finish()
EndProcedure

;Report what was expected
Procedure Expected(expected.s)
  Abort("Expected: " + expected + ", got '" + VisibleToken(Look) + "'")
EndProcedure

;Read a character into Look
Goto GetChar_End
GetChar:
  PokeB(@Look, PeekB(@Stream + StreamPos))
  StreamPos + 1
  Return
GetChar_End:

;Eat whitespace
Procedure EatWhite()
  While (Look = " " Or Look = #TAB$)
    !CALL l_getchar
  Wend
EndProcedure

;Match a specific input character
Procedure Match(*s.s)
  If Look <> *s.s
    Expected("'"+*s.s+"'")
  Else
    !CALL l_getchar
    EatWhite()
  EndIf
EndProcedure

;Recognize a mulop
Procedure.l IsMulop(*s.s)
  ProcedureReturn (*s.s = "*" Or *s.s = "/")
EndProcedure

;Get a number
Procedure.l GetNum()
  Protected Value.l
  If (PeekB(Look) < 48 And PeekB(Look) > 57)
    Expected("Integer")
  EndIf
  While (PeekB(Look) > 47 And PeekB(Look) < 58)
    Value = Value * 10 + PeekB(@Look) - '0'
    !CALL l_getchar
  Wend
  ProcedureReturn Value
EndProcedure

;-----

Declare.l Expression()

Procedure.l Factor()
  Protected Value.l
  If (PeekB(Look) > 47 And PeekB(Look) < 58)
    ProcedureReturn GetNum()
  Else
    Match("(")
    Value = Expression()
    Match(")")
  EndIf
  ProcedureReturn Value
EndProcedure

Procedure.l Term() 
  Protected Value.l
  Value.l = Factor()
  While (Look = "*" Or Look = "/")
    If Look = "*"
      !CALL l_getchar
      Value * Factor()
    Else
      !CALL l_getchar
      Value / Factor()
    EndIf
  Wend
  ProcedureReturn Value
EndProcedure

Procedure.l Expression()
  Protected Value.l
  If (Look = "+" Or Look = "-")
    Value = 0
  Else
    Value = Term()
    While (Look = "+" Or Look = "-")
      If Look = "+"
        !CALL l_getchar
        Value + Term()
      Else
        !CALL l_getchar
        Value - Term()
      EndIf
    Wend
  EndIf
  ProcedureReturn Value
EndProcedure

Procedure.l Solve(*s.s)
  Stream = *s.s
  StreamPos = 0
  !CALL l_getchar
  ProcedureReturn Expression()
EndProcedure

#Tries = 100000

time = GetTickCount_()
For I = 0 To #Tries
  Solve("100+1")
Next
MessageRequester("", Str(GetTickCount_()-time))

;Input()
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Post by dracflamloc »

609 ms =)

Thats pretty good. And yours is WAY faster once you add a couple more operations.

Only problem is when I try and plug it into my scripting language it doesnt seem to function
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Post by dracflamloc »

In my scripting language loop test I loop through 3 while-wend's for a total of 100000 loops, which means 100000 decrements.

The math evaluation takes approximately 500ms using xombies, and the overall time taken for the script to execute is 1700ms.

So thats 1200 ms of logic to chip off. 500ms of arithmetic.

I'm thinking I might add a specific increment and decrement operator like C++ has (i++, i--) to speed up the single operations used in looping even more.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

It seems like I optimized a bit too much because it doesn't give the correct result any more. :cry: Don't rely on it.

Edit: I just calculated wrongly in my head! I was wrong, my program is still right! :x
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Post by dracflamloc »

I think the problem is my script stores the values as floats like "2.000000".

Your algo doesnt correctly compute something like "5.000000+1"

In fact now that I look at it srod's algo has this problem too. Guess I'll stick with Xombies unless I find one that can handle floats and such correctly.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

You need to process things like 5.4+1.2 = 6.6? If would be something like this, but for some reason I cannot understand, I get an invalid memory access on ProcedureReturn ValF(Temp). :shock:

Edit: I'm in the process of solving the mystery, it seems to be a combination of a PB bug in ValF and one with the boolean evaluation.
Edit: No, that was not the case, it seems to be a serious bug in the debugger. It works correctly without it enabled.
Edit: No, it's with boolean evalution.

Code: Select all

OpenConsole() 

Global Look.s 
Look.s = " " ; Yes, that space is IMPORTANT!!!! Remove and DIE!!! 
Global Stream.s 
Global StreamPos.l        ; Read position in stream 

Procedure.s VisibleToken(s.s) 
  Select s.s 
    Case #CR$ 
      s.s = "newline" 
    Case #LF$ 
      s.s = "newline" 
    Case "" 
      s.s = "nothing" 
  EndSelect 
  ProcedureReturn s.s 
EndProcedure 

;Cleanup then end 
Procedure Finish() 
  PrintN("Ending.") 
  Input() 
  End 
EndProcedure 

;Report an error 
Procedure Error(s.s) 
  PrintN("Error: " + s + ".") 
EndProcedure 

;Report an error and abort 
Procedure Abort(s.s) 
  Error(s.s) 
  Finish() 
EndProcedure 

;Report what was expected 
Procedure Expected(expected.s) 
  Abort("Expected: " + expected + ", got '" + VisibleToken(Look) + "'") 
EndProcedure 

;Read a character into Look 
Goto GetChar_End 
GetChar: 
  PokeB(@Look, PeekB(@Stream + StreamPos)) 
  StreamPos + 1 
  Return 
GetChar_End: 

;Eat whitespace 
Procedure EatWhite() 
  While (Look = " " Or Look = #TAB$) 
    !CALL l_getchar 
  Wend 
EndProcedure 

;Match a specific input character 
Procedure Match(*s.s) 
  If Look <> *s.s 
    Expected("'"+*s.s+"'") 
  Else 
    !CALL l_getchar 
    EatWhite() 
  EndIf 
EndProcedure 

;Get a number 
Procedure.f GetNum() 
  Protected Temp.s
  If (PeekB(@Look) < 48 And PeekB(@Look) > 57) 
    Expected("Integer") 
  EndIf 
  While ((PeekB(@Look) > 47 And PeekB(@Look) < 58) Or PeekB(@Look) = '.') 
    Temp + Look
    !CALL l_getchar 
  Wend 
  ProcedureReturn ValF(Temp)
EndProcedure 

;----- 

Declare.f Expression() 

Procedure.f Factor() 
  Protected Value.f
  If (PeekB(@Look) > 47 And PeekB(@Look) < 58) 
    ProcedureReturn GetNum() 
  Else 
    Match("(") 
    Value = Expression() 
    Match(")") 
  EndIf 
  ProcedureReturn Value 
EndProcedure 

Procedure.f Term() 
  Protected Value.f
  Value = Factor() 
  While (Look = "*" Or Look = "/") 
    If Look = "*" 
      !CALL l_getchar 
      Value * Factor() 
    Else 
      !CALL l_getchar 
      Value / Factor() 
    EndIf 
  Wend 
  ProcedureReturn Value 
EndProcedure 

Procedure.f Expression() 
  Protected Value.f
  If (Look = "+" Or Look = "-") 
    Value = 0 
  Else 
    Value = Term() 
    While (Look = "+" Or Look = "-") 
      If Look = "+" 
        !CALL l_getchar 
        Value + Term() 
      Else 
        !CALL l_getchar 
        Value - Term() 
      EndIf 
    Wend 
  EndIf 
  ProcedureReturn Value 
EndProcedure 

Procedure.f Solve(*s.s) 
  Stream = *s.s 
  StreamPos = 0 
  !CALL l_getchar 
  ProcedureReturn Expression() 
EndProcedure 

#Tries = 100000 

time = GetTickCount_() 
For I = 0 To #Tries 
  Solve("100+1") 
Next 
MessageRequester("", Str(GetTickCount_()-time)) 

;Input()
Last edited by Trond on Mon Mar 06, 2006 8:40 pm, edited 4 times in total.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

dracflamloc wrote:I think the problem is my script stores the values as floats like "2.000000".

Your algo doesnt correctly compute something like "5.000000+1"

In fact now that I look at it srod's algo has this problem too. Guess I'll stick with Xombies unless I find one that can handle floats and such correctly.
Uhm, works okay here! Am I missing something?

Although I agree with the general thrust of this thread in that a reverse polish method of expression evaluation is probably always going to lose out, in speed terms, to a more direct method. It would suit a compiler more than a scripting language.
I may look like a mule, but I'm not a complete ass.
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Post by dracflamloc »

Code: Select all

Procedure.s DS_XS_Evaluate(*s.s)
  exp.s=PeekS(*s)
  If FindString(Exp,"'",1)>0
    ProcedureReturn DS_XS_Concatenate(Exp)
  EndIf 
 
  Stream = *s.s
  StreamPos = 0
  !CALL l_getchar
  ProcedureReturn StrF(Expression())
EndProcedure 

MessageRequester("",DS_XS_Evaluate("5.000000+1"))
This pops up a messagebox with "5.000000" in it

However with

Code: Select all

MessageRequester("",DS_XS_Evaluate("100+1"))
It pops up 101.000000
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

dracflamloc wrote:

Code: Select all

Procedure.s DS_XS_Evaluate(*s.s)
  exp.s=PeekS(*s)
  If FindString(Exp,"'",1)>0
    ProcedureReturn DS_XS_Concatenate(Exp)
  EndIf 
 
  Stream = *s.s
  StreamPos = 0
  !CALL l_getchar
  ProcedureReturn StrF(Expression())
EndProcedure 

MessageRequester("",DS_XS_Evaluate("5.000000+1"))
This pops up a messagebox with "5.000000" in it

However with

Code: Select all

MessageRequester("",DS_XS_Evaluate("100+1"))
It pops up 101.000000
The new mine seems to always work, have you tested it?
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Post by dracflamloc »

I wonder how hard it would be to port lua's code for this over to pb...
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Post by dracflamloc »

ProcedureReturn ValF(Temp) gives me invalid memory access with your updated code
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

dracflamloc wrote:ProcedureReturn ValF(Temp) gives me invalid memory access with your updated code
I forgot something :oops: and so did Fred :twisted: . Please try again now, i added some brackets.
Post Reply