Entwickler-Ecke
Open Source Units - Parser für mathematische Ausdrücke (inkl. Var. und Fkt.)
delfiphan - So 20.02.05 14:29
Titel: Parser für mathematische Ausdrücke (inkl. Var. und Fkt.)
tyParser.pas update:
* Ich hab das ganze noch weiter optimiert, sodass der
tyParser Compiler in fast jedem Fall den schnelleren Code erzeugt als der Delphicompiler!! :D
* Funktionen arccot,abs,round,trunc,frac,heaviside,sign hinzugefügt
* Detaillierte Fehlermeldung bei Syntaxfehlern
* Optional: Interface wrapper (siehe Source für Info)
* Bugfix im Cotangens
* Der tyParser Compiler kann neu 3 verschiedene Funktionstypen erzeugen:
Delphi-Quelltext
1: 2: 3:
| ExprFuncR = function(var Args : array of Extended) : Extended; ExprFunc1V = function(const x : Extended) : Extended; ExprFunc2V = function(const x,y : Extended) : Extended; |
Downloads:
Downloadlink:
tyParser.pas [
http://www.tyberis.com/download/tyParser.pas] (35kb)
FarbMix Parser-Demo:
FarbMix.exe [
http://www.tyberis.com/download/FarbMix.exe] (199kb)
Beispielanwendung:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| Var SinC : IExpr1V; begin SinC := compileStr1V('sin(x)/x',['x']);
if SinC.compiled then ShowMessage(FloatToStr( SinC.Eval(1.2) ));
end;
|
PS: Hab das Freeware Programm
TyMathPlotter [
http://www.delphi-forum.de/viewtopic.php?p=227018#227018] gestern auf die Beine gestellt: Als weiteres Demo des Parsers und als Tool um mathematische Plots zu zeichnen. Man findet zwar viele solche Programme im Netz; hier aber eine "fast and easy" Variante. Man kann damit sehr schnell, sehr einfach, schöne Plots generieren und diese dann als Vektorgraphik direkt ins Word kopieren.
========================================
========================================
//Edit: Hier noch der ursprüngliche Post mit anderen Infos
Mathe-Parser mit vollständigem Syntaxcheck. Bei einem Syntaxfehler wird "NaN" zurückgegeben.
Genaue Erklärung des (ein bisschen vereinfachten) Parsers im Forum unter
http://www.delphi-forum.de/topic_Taschenrechner+Problem_36933.html
Diese Version wertet auch Funktionen (sqrt, sin, cos, ...) und Konstanten wie e und pi aus. Ausserdem können userdefinierte Variablen benutzt werden.
Beispiele:
Delphi-Quelltext
1: 2: 3:
| evalMath('1+2*3'); evalMath('1+a^2*sin(x)', ['x',10, 'a',2]); if isNaN(evalMath('(1+2')) then ShowMessage('Syntaxfehler'); |
mathparser.pas
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473:
| {$B-} unit umath;
interface uses Math, SysUtils;
Function evalMath(const S : String) : Extended; overload; Function evalMath(const S : String; const Variables : array of const) : Extended; overload;
implementation
Function evalMath(const S : String) : Extended; begin Result := evalMath(S, []); end;
Function evalMath(const S : String; const Variables : array of const) : Extended; type OutputType = record Length : Integer; Wert : Extended; end; Type cTyp = (cSTRICHOP,cPUNKTOP,cHOCHOP,cKLAMMERAUF,cKLAMMERZU,cUNDEFINIERT,cPUNKT,cEXP); parseType = function(start : Integer; var Output : OutputType) : Boolean; function parseVARFUNC(start : Integer; var Output : OutputType) : Boolean; forward; function parseGANZZAHL(start : Integer; var Output : OutputType) : Boolean; forward; function parseZIFFER(start : Integer; var Output : OutputType) : Boolean; forward; function parseFAKTOR(start : Integer; var Output : OutputType) : Boolean; forward; function parseKLAMMER(start : Integer; var Output : OutputType) : Boolean; forward; function parseMULTIPLIKATION(start : Integer; var Output : OutputType) : Boolean; forward; function parseADDITION(start : Integer; var Output : OutputType) : Boolean; forward; function parseEXPONENT(start : Integer; var Output : OutputType) : Boolean; forward; function parseREALZAHL(start : Integer; var Output : OutputType) : Boolean; forward;
function returnVariable(const aVarName : Char): Extended; var I: Integer; Count : Integer; VarName : Char; begin Count := High(Variables)+1; if Odd(Count) then raise Exception.Create('Argument count must be even.'); for I := 0 to Count div 2-1 do begin with Variables[I*2] do case VType of vtChar: VarName := VChar else raise Exception.Create('Invalid variable type: Must be single character. '); end; if VarName = aVarName then begin with Variables[I*2+1] do case VType of vtExtended: Result := VExtended^; vtInteger: Result := VInteger; vtInt64: Result := VInt64^ else raise Exception.Create('Invalid variable format.'); end; exit; end; end; Result := NaN; end;
function parseCHARTYP(start : Integer; Typ : cTyp; var Operation : Char) : Boolean; Var sTyp : cTyp; begin sTyp := cUNDEFINIERT; if (start <= length(S)) and (start >= 1) then case S[start] of '^': sTyp := cHOCHOP; '.',',': sTyp := cPUNKT; '+','-': sTyp := cSTRICHOP; '*','/': sTyp := cPUNKTOP; '(': sTyp := cKLAMMERAUF; ')': sTyp := cKLAMMERZU; 'e','E': sTyp := cEXP; end; if sTyp = Typ then begin Operation := S[start]; Result := True; exit; end; Result := False; end; function parseZIFFER(start : Integer; var Output : OutputType) : Boolean; begin if not ((start <= length(S)) and (start >= 1)) then begin result := False; exit; end; Case S[start] of '0'..'9': begin Output.Length := 1; Output.Wert := ord(S[start])-ord('0'); Result := True; exit; end; end; Result := False; end; function parseALPHANUMERIC(start : Integer; var Output : Char) : Boolean; begin if not ((start <= length(S)) and (start >= 1)) then begin result := False; exit; end; Case S[start] of '0'..'9','A'..'Z','a'..'z': begin Output := S[start]; Result := True; exit; end; end; Result := False; end; function parseGANZZAHL(start : Integer; var Output : OutputType) : Boolean; Var Ziffer : OutputType; Count : Integer; begin Output.Length := 0; Count := 0; while parseZiffer(start, Ziffer) do begin inc(count); if count = 1 then Output.Wert := Ziffer.Wert else Output.Wert := Output.Wert * 10 + Ziffer.Wert; inc(Output.Length, Ziffer.Length); inc(start,Ziffer.Length); end; Result := Count > 0; end; function evalFunction(const S : String; const Argument : Extended) : Extended; begin if S = 'sin' then Result := sin(Argument) else if S = 'cos' then Result := cos(Argument) else if S = 'tan' then Result := tan(Argument) else if S = 'cot' then Result := 1/tan(Argument) else if S = 'arcsin' then Result := arcsin(Argument) else if S = 'arccos' then Result := arccos(Argument) else if S = 'arctan' then Result := arctan(Argument) else if S = 'ln' then Result := ln(Argument) else if S = 'log' then Result := ln(Argument)/ln(10) else if S = 'exp' then Result := exp(Argument) else if S = 'sqrt' then Result := sqrt(Argument) else Result := NaN; end;
function parseVARFUNC(start : Integer; var Output : OutputType) : Boolean; Var C : Char; S : String; Klammer : OutputType; begin S := ''; while parseALPHANUMERIC(start, C) do begin S := S + C; inc(start); end; Output.Wert := NaN; if S <> '' then begin Output.Length := length(S); if parseKLAMMER(start, Klammer) then begin inc(Output.Length, Klammer.Length); Output.Wert := evalFunction(S, Klammer.Wert); end else begin if length(S) = 1 then case S[1] of 'e': Output.Wert := exp(1) else Output.Wert := returnVariable(S[1]); end else begin if S = 'pi' then Output.Wert := pi else if S = 'inf' then Output.Wert := infinity; end; end; end; Result := not isNaN(Output.Wert); end; function parseREALZAHL(start : Integer; var Output : OutputType) : Boolean; Var GanzZahl, NachKomma, Exp : OutputType; ignore, Vorzeichen : Char; begin if parseGANZZAHL(start, GanzZahl) then begin inc(start, GanzZahl.Length); if parseCHARTYP(start, cPUNKT,ignore) then begin inc(start); if parseGANZZAHL(start, NachKomma) then begin inc(start); Output.Length := GanzZahl.Length + 1 + NachKomma.Length; Output.Wert := GanzZahl.Wert + NachKomma.Wert / Power(10, NachKomma.Length); end else begin Result := False; exit; end; end else Output := GanzZahl;
if parseCHARTYP(start, cEXP,ignore) then begin inc(start); inc(Output.length); if parseCHARTYP(start, cSTRICHOP,Vorzeichen) then begin inc(start); inc(Output.Length); end else Vorzeichen := '+'; if parseGANZZAHL(start, Exp) then begin inc(Output.Length, Exp.Length); if Vorzeichen = '-' then Exp.Wert := -Exp.Wert; Output.Wert := Output.Wert*Power(10,Exp.Wert); Result := True; exit; end; end else begin Result := True; exit; end; end; Result := False; end; function parseKLAMMER(start : Integer; var Output : OutputType) : Boolean; var ignore : Char; Ausdruck : OutputType; begin if parseCHARTYP(start, cKLAMMERAUF, ignore) then begin inc(start); if parseADDITION(start, Ausdruck) then begin inc(start, Ausdruck.Length); if parseCHARTYP(start, cKLAMMERZU, ignore) then begin Output.Length := 1 + Ausdruck.Length + 1; Output.Wert := Ausdruck.Wert; Result := True; exit; end; end; end; Result := False; end; function parseFAKTOR(start : Integer; var Output : OutputType) : Boolean; var Operation : Char; Faktor, Exponent : OutputType; Pending : Boolean; Count : Integer; begin if parseCHARTYP(start, cSTRICHOP, Operation) then if parseFAKTOR(start+1, Faktor) then begin Output.Length := 1 + Faktor.Length; Case Operation of '-': Output.Wert := -Faktor.Wert; '+': Output.Wert := +Faktor.Wert; end; Result := True; exit; end; Output.Length := 0; Count := 0; Pending := False; while parseEXPONENT(start, Exponent) do begin Pending := False; inc(Output.Length, Exponent.Length); inc(start, Exponent.Length); if Count = 0 then Output.Wert := Exponent.Wert else Case Operation of '^': Output.Wert := Power(Output.Wert, Exponent.Wert); end; inc(Count); if parseCHARTYP(start, cHOCHOP, Operation) then begin Pending := True; inc(start); inc(Output.Length); end else break; end; Result := (Count>=1) and not Pending; end; function parseEXPONENT(start : Integer; var Output : OutputType) : Boolean; var Exponent : OutputType; Operation : Char; begin if parseCHARTYP(start, cSTRICHOP, Operation) then if parseEXPONENT(start+1, Exponent) then begin Output.Length := 1 + Exponent.Length; Case Operation of '-': Output.Wert := -Exponent.Wert; '+': Output.Wert := +Exponent.Wert; end; Result := True; exit; end;
Result := parseREALZAHL(start, Output) or parseVARFUNC(start, Output) or parseKLAMMER(start, Output); end; function parseMULTIPLIKATION(start : Integer; var Output : OutputType) : Boolean; var Faktor : OutputType; Operation : Char; Pending : Boolean; Count : Integer; begin Output.Length := 0; Count := 0; Pending := False; while parseFAKTOR(start, Faktor) do begin Pending := False; inc(Output.Length, Faktor.Length); inc(start, Faktor.Length); if Count = 0 then Output.Wert := Faktor.Wert else Case Operation of '*': Output.Wert := Output.Wert * Faktor.Wert; '/': Output.Wert := Output.Wert / Faktor.Wert; end; inc(Count); if parseCHARTYP(start, cPUNKTOP, Operation) then begin Pending := True; inc(start); inc(Output.Length); end else break; end; Result := (Count>=1) and not Pending; end; function parseADDITION(start : Integer; var Output : OutputType) : Boolean; var Term : OutputType; Operation : Char; Pending : Boolean; Count : Integer; begin Output.Length := 0; Count := 0; Pending := False; while parseMULTIPLIKATION(start, Term) do begin Pending := False; inc(Output.Length, Term.Length); inc(start, Term.Length); if Count = 0 then Output.Wert := Term.Wert else Case Operation of '+': Output.Wert := Output.Wert + Term.Wert; '-': Output.Wert := Output.Wert - Term.Wert; end; inc(Count); if parseCHARTYP(start, cSTRICHOP, Operation) then begin Pending := True; inc(start); inc(Output.Length); end else break; end; Result := (Count>=1) and not Pending; end; Var Output : OutputType; begin if parseADDITION(1, Output) and (Output.Length = length(S)) then Result := Output.Wert else Result := NaN; end; end. |
Beispielanwendung: (es befindet sich ein Image1 und ein Button1 mit onClick Event auf Form1)
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:
| uses umath, math;
const Zoom = 50; Var X0, Y0 : Integer; Procedure Plot(const S : String; const Canvas : TCanvas); Var X, L : Integer; begin with Canvas do begin L := Canvas.ClipRect.Left; MoveTo(L, -trunc(evalMath(S,['x',((L-X0)/Zoom)])*Zoom)+Y0); For X := L+1 to Canvas.ClipRect.Right do LineTo(X, -trunc(evalMath(S,['x',((X-X0)/Zoom)])*Zoom)+Y0); end; end;
procedure TForm1.Button1Click(Sender: TObject); Var S : String; begin S := 'sin(x)'; if isNaN(evalMath(S,['x',0])) then begin ShowMessage('Syntaxfehler'); exit; end; with Image1 do begin X0 := Width div 2; Y0 := Height div 2; with Canvas do begin Pen.Width := 1; MoveTo(0,Y0);LineTo(Width,y0); MoveTo(X0,0);LineTo(X0,Height); Pen.Width := 2; Plot(s,Canvas); end; end; end; |
// Edit: Hab noch einige Funktionen dazugetan... (arctan, log, ln)
// Edit: Vollständige Unterstützung von float-Zahlen (z.B. 1e+10), Konstante "inf"
retnyg - Di 01.03.05 13:45
schade dass dein parser z.b. (3+4) * 100 mit NaN quittiert ^^
edit: WTF ist eigentlich Nan ? :twisted: - aha laut math.pas 0.0
edit2: aha es liegt an den leerzeichen
edit3: ein einfaches trim() wirkt manchmal wunder....
ich mach das jetzt so:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| function xtrim(s:string):string; var i : integer; begin result := ''; for i := 1 to length(s) do if s[i] <> ' ' then result := result + s[i]; end;
procedure TForm1.Button1Click(Sender: TObject); begin edit1.text := floattostr(evalMath(xtrim(edit1.text))); end; |
ansonsten super unit, bravo...
delfiphan - Di 01.03.05 14:46
Genau, Leerschläge verträgt er nicht :) NaN = "Not A Number". sin, cos, pi und alle Konstanten sind übrigens auch Case-sensitive.
Hab die Unit erst vorhin erweitert und raufgeladen (jetzt sind auch Variablen und Funktionen möglich). Evtl. hat's drum noch Bugs drin, ist noch nicht eingehend getestet.
retnyg - Di 01.03.05 15:17
Nan ist übrigens auch das indische fladenbrot, welches im unterschied zu chapati mit joghurt angerührt wird...
retnyg - Mi 02.03.05 15:56
nochn vorschlag: deine unit akzeptiert nur . aber keinen ,
dieser wird allerdings von der funktion floattostr zurückgegeben, wenn ich also mit dem resultat gleich weiterrechnen will führt das mal wieder zu fladenbrot....
also, baue das bitte noch ein dass auch beistriche akzeptiert werden.
delfiphan - Mi 02.03.05 16:14
Ein Komma? Floattostr? Hmm sowas ist mir nicht bekannt. Das muss eine Windows-weite Systemeinstellung sein. Das müsste ich dann noch einbauen, dass die jeweilig lokale Einstellung benützt wird. Übrigens: Ein "1e10" wird auch nicht unterstützt, genauso die Konstanten "Infinity" und "NaN". Wäre aber eigentlich nicht so schwer zu implementieren. Mach ich eventuell morgen, heute reichts von der Zeit her nicht mehr.
Danke für den Verbesserungsvorschlag! :)
delfiphan - Do 03.03.05 14:33
Update: "1e-10", "inf", und "1,2" werden jetzt auch erkannt (siehe Post von retnyg). Es sind weiterhin keine Whitespaces erlaubt.
Test:
evalMath('-a^2+3*((-1)+x)*(-1+x^2-2^-3^-1)--2e+2', ['a',1,'x',2]);
Vergleich evalMath vs. andere Open-Source Parser im Delphi-Forum
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| +-------------+--------+-----------+-------+-------------------+---------+-------+--------------+ | Name | Speed | Syntx.chk | Fkt? | selbstdef. Konst? | Klasse? | Lines | Autor | +-------------+--------+-----------+-------+-------------------+---------+-------+--------------+ | evalMath | 100.0% | ja | ja | ja | nein | 485 | delfiphan | | modparser | 32.3% | ja | ja | ja (max. 3) | nein | 207 | Sven | | TMathParser | 14.7% | nein | ja | ja | ja | 158 | .Chef | | TParser | 10.6% | nein | ja | nein | ja | 489 | Christian S. | | TMath | 7.4% | nein | nein | nein | ja | 486 | deepgreen | | TMathParser²| 6.8% | nein | nein | ja | ja | 452 | Tyr | +-------------+--------+-----------+-------+-------------------+---------+-------+--------------+ |
Welche Ausdrücke wurden verglichen?
Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| +-------------+------------------------------+ | modparser | sqrt(0.5*exp(x)-0.5*sin(y)) | | TMathParser | sin(4*x)*exp(-x/4) | | TParser | sqrt(0.5*exp(1)-0.5*sin(1)) | | TMath | 13/(21*12)+3*7.3+2/7 | | TMathParser²| (4*x)*(-x/4) | +-------------+------------------------------+ y=1, x=1 |
Alle diese Ausdrücke wurden vom angegebenen Parser und von evalMath ausgewertet. Die relative Geschwindigkeit ist dann jeweils in der oberen Tabelle eingetragen (2. Spalte).
Kommentar:
- An dieser Stelle möchte ich betonen, dass ein direkter Vergleich von Parsern nicht gut möglich ist. Einige Parser haben Features, welche evalMath nicht unterstützt (beispielsweise Unterstützung von verschiedenen Typen wie Boolean und Integer).
- Ein String wurde jeweils nur ein Mal ausgewertet. Es gibt Parser, die dafür optimiert sind, Ausdrücke mehrere Male hintereinander zu parsen.
Fazit:
- evalMath ist in dieser Testreihe mit Abstand am schnellsten (3 bis 14 Mal schneller).
- Nur evalMath und modparser führen eine Syntaxüberprüfung durch
- Die meisten Parser sind als Klassen implementiert (evalMath ist eine Stand-Alone Function)
- 2 Parser unterstützen keine Funktionen wie sin und cos
- Die meisten Parser unterstützen externe Konstanten wie x und y
- evalMath besteht aus relativ viel Sourcecode (485 Zeilen), andere sind kleiner.
- evalMath behandelt als einziger verschachtelte Hoch-Operatoren korrekt: -2^-3^-4 = -4096
-> modparser: Unterstützt keine negativen Exponenten (Exception)
-> TMathParser: Gibt "-6" zurück
-> TParser: Endlosschleife bzw. Exception
-> TMath: Kein Hoch-Operator möglich
-> TMathParser²: Exception
Nachteile von evalMath:
- Unterstützt keine Typen (nur Extended)
- Mehrfachauswertungen können nicht optimiert werden (es wird weder ausführbarer Code erzeugt noch eine Baumstruktur)
Vorteile von evalMath:
- Scheint recht schnell zu sein
- Einfach und unkompliziert in der Anwendung. Es muss keine Klasse instanziert werden; Die Angabe von Konstanten via Parameter ist sehr einfach und intuitiv.
- Unterstützt komplizierte Ausdrücke wie doppelte (dreifache) Negationen, u.a.
Mögliche Verbesserungen:
- evalMath verwendet viele Rekursionen, welche sich fast vollständig vermeiden liesse. Dies würde die Auswertung weiter beschleunigen und den Sourcecode verkleinern.
(Für eventuelle Fehler in der Auswertungstabelle oder an anderen Stellen entschuldige ich mich... :))
retnyg - Mo 07.03.05 02:51
delfiphan := mathegott :flehan: :mrgreen:
wundert mich nicht dass der parser der schnellste ist.
Zitat: |
evalMath verwendet viele Rekursionen, welche sich fast vollständig vermeiden liesse. Dies würde die Auswertung weiter beschleunigen und den Sourcecode verkleinern. |
das wäre der hammer wenn du die rekursionen noch beseitigen würdest, käme auch der dateigrösse des kompilates zugute.
hatte ich nicht bei dir mal eine prozedur gesehen welche eine dynamische mathfunc per pointer zuwies ? :gruebel:
delfiphan - Mo 07.03.05 03:18
Naja, es gibt ganz bestimmt noch schnellere, aber unter allen Freewareparsern auf dieser Seite ist meiner offenbar der schnellste :D
Wenn es aber darum geht, den gleichen Ausdruck mehrere Male hintereinander auszuwerten, dann schneidet mein Parser leider nicht als bester ab. Das wird sich aber hoffentlich ändern, denn ich erweitere grad den Parser. Der neue generiert einen Bytecode, welcher dann ausgeführt werden kann. Ich hab mir überlegt, einen Compiler zu schreiben, also einen Parser der direkt Maschinencode erzeugt, jedoch ist der Stack der Floatingpointunit nur 8 floats tief. Und es ist ziemlich mühsam das zu programmieren. Es gibt auch keinen Maschinencode für "Hoch", usw..
Deswegen gibt's jetzt halt "nur" den Bytecode.
Dominique - Mo 07.03.05 13:42
retnyg hat folgendes geschrieben: |
Nan ist übrigens auch das indische fladenbrot, welches im unterschied zu chapati mit joghurt angerührt wird... |
...schmeckt aber auch nur, wenn's stilecht im kleinen tandoori-ofen gebacken wurde...
jasocul - Mo 07.03.05 13:56
@retnyg:
Ließe sich für dein xtrim nicht besser ReplaceStr verwenden?
@delphifan:
Auf den ersten Blick ein schönes Ding. Schau ich mir bei Gelegenheit mal genauer an.
delfiphan - Mo 07.03.05 14:04
Die neue Version kommt aber erst noch, vemutlich heute noch. Dort werden die Whitespaces auch korrekt behandelt (d.h. ignoriert ;)). Das xtrim ist nicht in allen Situationen geeignet, z.B. "x y" wird dann zu "xy".
So wie's momentan aussieht ist der neue Parser etwa gleich schnell wie der alte, vielleicht sogar etwas langsamer, jedoch wird ein Bytecode generiert, welcher dann sehr schnell beliebig oft ausgeführt werden kann. Der Code ist aber jetzt schon 700 Zeilen lang...
retnyg - Mo 07.03.05 14:24
jasocul hat folgendes geschrieben: |
@retnyg:
Ließe sich für dein xtrim nicht besser ReplaceStr verwenden? |
liesse sich schon, aber so ne mini-funktion ist schneller selbst geschrieben als lang in der hilfe rumzulesen wie da nochmal die richtigen parameter hiessen... ausserdem ists so sicher schneller ;) und ich muss nicht in meiner uses drauf achte dass die unit welche den befehl beinhaltet dabei ist... die grösse der exe datei wirds danken.
@delfiphan: hast du es nun wieder mit dem pointer auf eine parse-funktion gelöst ? und die redundazen beseitigt ? :mrgreen:
delfiphan - Mo 07.03.05 14:37
Wollte ich, geht aber nicht. Scheint wie n verzwicktes, unlösbares Delphiproblem zu sein:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
| Procedure Funktion(const S : String; const A : array of const); Type TProcedure = Procedure; Procedure InternalFunction1; Var I : Integer; begin ShowMessage(Integer(A[0].VInteger)+' '+S[0]); end; Procedure InternalFunction2(AnyProcedure : TProcedure); begin AnyProcedure; end; begin InternalFunction2(InternalFunction1); InternalFunction2(@InternalFunction1); end; |
Lösung der Delphihilfe: Interne Funktion einfach rausnehmen, sodass die Funktionen nicht verschachtelt sind. Kann ich aber nicht, da eine der internen Funktionen auf ein "array of const" zugreift, was sich nicht auslagern lässt. Und das array of const die ganze Zeit von Prozedur zu Prozedur weiterzugeben scheint mir unsinnig.
Nein, ich müsste wennschon den ganzen Parser neu schreiben, ohne Rekursionen, bzw. nur dann, wenn es Klammern gibt.
Tja, so läufts, bin jetzt auf Zeile 720 ;)
delfiphan - Mo 07.03.05 21:46
Titel: Parser reloaded
So, hier ist er nun, der neue Parser!
Die neue Unit tyParser hat 4 Funktionen:
-
function ParseExpr(S : String; Vars : Array of Const) : Expression;: Parst einen Stringausdruck und gibt einen Bytecode zurück (Typ "Expression")
-
function EvalExpr(ByteCode : Expression; Args : Array of Const) : Extended;: Wertet den Bytecode mit den gegebenen Argumenten aus
-
function CompileExpr(Bytecode : Expression) : ExprFunc;: Compiliert den Bytecode in Maschinencode und gibt eine Funktion zurück (Typ "ExprFunc")
-
procedure FreeFunc(E : ExprFunc);: Gibt die Funktion (Maschinencode) wieder frei
Anwendungsbeispiele:
Beispiel 1 (Bytecode erzeugen und auswerten)
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7:
| Var Expr : Expression; Resultat : Extended; begin Expr := ParseExpr('1 + x + y',['x','y']); Resultat := EvalExpr(Expr,[2,3]); end; |
Beispiel 2 (Bytecode erzeugen und compilieren (Maschinencode generieren))
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| Var Vars : array[0..1] of Extended; Expr : Expression; MyFunc : ExprFunc; Resultat : Extended; begin Expr := ParseExpr('1 + x + y',['x','y']); MyFunc := CompileExpr(Expr); Vars[0] := 2; Vars[1] := 3; Resultat := MyFunc(Vars); end; |
Es können natürlich beliebig komplizierte Ausdrücke wie "-a*x^2+y^2-sin(-pi*(x-1)*(y+1))+x^-y" ausgewertet werden.
Bugs/Probleme/Bemerkungen
Wenn der Ausdruck in Maschinencode umgewandelt werden soll, muss man darauf achten, dass der floatingpoint-Stack des Prozessors nur 8 Zahlen tief ist! Es darf in dem Fall nicht beliebig tief geklammert werden. Der compilierte Code darf maximal 4kb gross sein.
Falls nur
ein Ausdruck ausgewertet werden soll, dann reicht schon die Vorgängerversion. Dort wird der String evtl. sogar ein bisschen schneller geparst. Wenn aber der gleiche Ausdruck mehrere Male ausgewertet werden soll (aber beispielsweise einfach mit anderen Werten für x), ist diese Unit um einiges schneller!
In dieser Version sind Whitespaces erlaubt.
Die Unit ist noch nicht fertig optimiert, es handelt sich um die erste Version.
Downloads:
Downloadlink:
tyParser.pas [
http://www.tyberis.com/download/tyParser.pas] (27kb)
FarbMix Parser-Demo:
FarbMix.exe [
http://www.tyberis.com/download/FarbMix.exe] (197kb)
//Edit:
Performance
Natürlich ist ein in Delphi hardgecodeter Ausdruck im allgemeinen schneller, da kein Funktionsaufruf nötig ist. Vergleicht man aber Delphi-compilierte Funktionen mit tyParser-compilierten Funktionen, so stellt man fest, dass tyParser nicht wesentlich langsamer ist. Es gibt sogar Fälle, wo tyParser-Funktionen schneller sind als Delphifunktionen! Unten ist ein Beispiel dafür:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| Function MyFunc1(const x,y : extended) : Extended; begin Result := arccos(cos(x))+sin(y); end; begin For I := 1 to 10000000 do begin x := 2; y := 3; Resultat := MyFunc1(x,y); end; Expr := ParseExpr('arccos(cos(x))+sin(y)',['x','y']); MyFunc2 := CompileExpr(Expr); For I := 1 to 10000000 do begin Vars[1] := 2; Vars[2] := 3; Resultat := MyFunc2(Vars); end; |
//Edits: 2 Bugfixes: Vorzeichenwechsel war noch nicht implementiert "(-1)". Bugfix bei der Konstante "e". Demoprogramm und Bild.
retnyg - Mo 07.03.05 21:56
Beeindruckend ^^
werde das morgen mal in Ruhe testen...
delfiphan - Do 10.03.05 22:30
tyParser.pas update:
* Ich hab das ganze noch weiter optimiert, sodass der
tyParser Compiler in fast jedem Fall den schnelleren Code erzeugt als der Delphicompiler!! :D
* Funktionen arccot,abs,round,trunc,frac,heaviside,sign hinzugefügt
* Detaillierte Fehlermeldung bei Syntaxfehlern
* Optional: Interface wrapper (siehe Source für Info)
* Bugfix im Cotangens
* Der tyParser Compiler kann neu 3 verschiedene Funktionstypen erzeugen:
Delphi-Quelltext
1: 2: 3:
| ExprFuncR = function(var Args : array of Extended) : Extended; ExprFunc1V = function(const x : Extended) : Extended; ExprFunc2V = function(const x,y : Extended) : Extended; |
Downloads:
Downloadlink:
tyParser.pas [
http://www.tyberis.com/download/tyParser.pas] (35kb)
FarbMix Parser-Demo:
FarbMix.exe [
http://www.tyberis.com/download/FarbMix.exe] (199kb)
Beispielanwendung:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| Var SinC : IExpr1V; begin SinC := compileStr1V('sin(x)/x',['x']);
if SinC.compiled then ShowMessage(FloatToStr( SinC.Eval(1.2) ));
end;
|
PS: Hab das Freeware Programm
TyMathPlotter [
http://www.delphi-forum.de/viewtopic.php?p=227018#227018] gestern auf die Beine gestellt: Als weiteres Demo des Parsers und als Tool um mathematische Plots zu zeichnen. Man findet zwar viele solche Programme im Netz; hier aber eine "fast and easy" Variante. Man kann damit sehr schnell, sehr einfach, schöne Plots generieren und diese dann als Vektorgraphik direkt ins Word kopieren.
AXMD - Mo 14.03.05 19:43
Ich hab nur ein Wort für deinen Parser: endgenial. Was noch super wäre, wäre die Implementation einer Variablen i oder j (wahlweise), die die imaginäre Einheit darstellt - damit der Parser alle Funktionen auch auf imaginäre Zahlen anwenden kann. Falls du die entsprechenden Implementationen für die komplexen Pendants der Funktionen brauchst kann ich sie dir gerne schicken oder hier posten ;)
Spitze :)
AXMD
delfiphan - Di 15.03.05 00:28
retnyg hat folgendes geschrieben: |
deine funktion liesse sich vielleicht noch schneller machen wenn du bei der hoch-berechnung statt virtualalloc stackalloc benutzt... |
Ich versteh nicht genau, was du meinst. Ich benütz nirgends ein VirtualAlloc für das "Hoch".. (??)
Der Compiler benützt VirtualAlloc um Speicher für die compilierte Funktion zu allozieren. Ich kann da meines Wissens nichts anderes nehmen als VirtualAlloc, da ich für den Speicherblock die Rechte PAGE_EXECUTE_READWRITE vergeben können muss.
Information: Aktueller Download 3 Posts weiter oben [http://www.delphi-forum.de/topic_Parser+fuer+mathematische+Ausdruecke+inkl+Var+und+Fkt_36946.html#227715].
prote - Do 07.04.05 21:52
super, sieht sehr interessant aus, werde ich mir mal bei Gelegenheit zu Gemüte führen :)
Allerdings hast Du'nen Fehler drin:
delfiphan hat folgendes geschrieben: |
- evalMath behandelt als einziger verschachtelte Hoch-Operatoren korrekt: -2^-3^-4 = -4096 |
das wäre nach den allseits bekannten Regeln 4096 und zwar ohne "-" und selbst das wäre nicht korrekt, weil man bei der Schreibweise erst -3^(-4) rechnen müßte und danach erst das Ergebnis als Potenz von -2 ... siehe hierzu auch ein interessanter Wachstumsalgo mit "Hyper-Potenzen":
http://de.wikipedia.org/wiki/Ackermannfunktion
uall@ogc - Do 07.04.05 22:53
@delfiphan
um dein problem zu lösen mussu alle funktionen mit parameter einfach als stdcall; markieren, dadurch werden die parameter auf den stack gepushed und die funktion bekommt die richtigen
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| Procedure Funktion(const S : String); type AnyProcedureX = procedure; Procedure InternalFunction1; begin ShowMessage(S); end; Procedure InternalFunction2(AnyProcedure: AnyProcedureX); stdcall; begin AnyProcedure; end; begin InternalFunction2(@InternalFunction1); end; |
funktioniert einwandfrei wärend hingegen
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| procedure Funktion(const S : String); type AnyProcedureX = procedure; Procedure InternalFunction1; begin ShowMessage(S); end; Procedure InternalFunction2(AnyProcedure: AnyProcedureX); begin AnyProcedure; end; begin InternalFunction2(@InternalFunction1); end; |
crashed, wo du meintest es gibt probleme :)
delfiphan - Do 07.04.05 22:57
@prote: Der Text "-2^-3^-4 = -4096" stammt auch noch von der uralten Vor-Version ;) So falsch ist es aber nicht: Es wurde dort einfach standardmässig links geklammert (das Minus-Zeichen stimmt übrigens: -x^2 = -(x^2)!). In der jetzigen Version wird die Hochoperation aber mathematisch korrekt rechts geklammert. (PS: Ob man jetzt "^" links oder rechts klammert, da scheinen sich auch kommerzielle Softwarefirmen nicht einig zu sein (vgl. Texas Instruments, Maple, Mathematica))
@uall: Danke :) ich werd den Parser aber mal so lassen. Wegen den paar Zeilen, die ich so sparen würde, programmier ich das jetzt nicht mehr um. :\ Aber danke für die Info :)
Information: Aktueller Download siehe weiter oben [http://www.delphi-forum.de/topic_Parser+fuer+mathematische+Ausdruecke+inkl+Var+und+Fkt_36946.html#227715].
Tilo - Sa 28.05.05 19:55
Mal als Vergleich: Mein Taschenrechner rechnet -2^-3^-4=-2^(-3^-4)=-0,991...
.Chef - Sa 28.05.05 22:41
delfiphan hat folgendes geschrieben: |
- evalMath behandelt als einziger verschachtelte Hoch-Operatoren korrekt: -2^-3^-4 = -4096
-> TMathParser: Gibt "-6" zurück
|
Weil ich das gerade sehe: Mein Parser behandelt das sehr wohl, setzt allerdings die mathematisch korrekte Schreibweise -2^(-3)^(-4) voraus, also die Vorzeichen in Klammern. Diese "Ausnahmen" hab ich nicht mit berücksichigt.
Außerdem, wenn ich mich recht erinnere :gruebel:, beinhaltet mein Parser einen Syntaxcheck, d.h. fehlerhafte Stellen werden erkannt und die Ausführung abgebrochen. Es fehlt lediglich die Ausgabe. Es wäre aber ein leichtes - ich hatte nur keine Lust - den Quelltext an den entsprechenden Stellen um eine Exception und Fehlerstelle zu erweitern.
delfiphan - Sa 04.06.05 22:32
Ganz interessant. Werd ich gleich mal ausprobieren!
Also so wie's aussieht ist die Anwendung bei mir recht viel einfacher. Die Geschwindigkeit kann ich nicht direkt vergleichen; Variablen werden bei ihm glaub ich nicht direkt reinkompiliert; oder doch? Auf jeden Fall scheint meiner so (siehe unten) ca. 10x schneller zu sein. Und das gegen meinen Interface-Wrapper, welcher ja langsamer ist, als meine nackte Variante. Aber vermutlich geht's bei ihm auch noch schneller, ich weiss nur nicht wie.
tyParser:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| var sinc: IExpr1V; begin sinc := compileStr1V('x+2*x',['x']); D := 0; repeat sinc.Eval(D); D := D + 1 / 1000000; until D > 1; end; |
Konkurrenz:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| var Parser: TExCQParser; begin Parser := TExCQParser.Create; Parser.RegisterVariable('x',false); Parser.Parse('x+2*x'); D := 0; repeat Parser.SetVariable('x',[D]); Parser.Solve; D := D + 1 / 1000000; until D > 1; Parser.Free; |
Er hat vor allem ca. 7 Units und ich hab eine. Bei ihm ist es aber auch möglich, externe Funktionen zu definieren. Bei mir kann man nur externe Variablen definieren.
Mein Parser reiht schlussendlich einfach ein FPU-Befehl nach dem nächsten hin, von dem her gesehen kann es keinen Parser/Evaluator geben, der um Faktoren schneller ist. Was mein Parser nicht tut ist, einen Ausdruck zuerst zu vereinfachen. Er wird genau so übersetzt wie er eingegeben wird. Da ist also noch Verbesserungspotential.
Ich finde aber vor allem, dass die einfache Anwendung bei mir vor allem von grossem Nutzen ist. Allfällige Syntaxfehler werden bei mir nicht per Exceptions zurückgegeben, was ich irgendwie auch sympathischer finde (aber das ist Geschmackssache).
:)
Und: Bei ihm ergibt "-2^2" halt 4 (statt -4). Und "-(1/2)" oder auch "2*(-1)" ergibt Access Violation. Und solche Details. Bei mir kann es fast gar keine Access Violations geben. Ist nicht OOP und es wird auch fast nirgends irgendwelchen Speicher alloziert..
F34r0fTh3D4rk - Do 15.09.05 15:53
schaffst du es, deinen parser delphi3 konform zu machen ? dynamische arrays, overloads etc bereiten momentan probleme 8)
vielleicht auch n einfaches beispiel, wenn ich zb nur sowas hier ausrechnen möchte:
ok habs so gemacht:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| procedure TForm1.Button1Click(Sender: TObject);
Var sinc: IExpr1V; begin sinc := compileStr1V(edit1.text); edit2.text := floattostr(sinc.Eval(1.0)); end; |
schöner parser, gefällt mir ;)
delfiphan - Do 15.09.05 19:47
Hmm der Parser auf Delphi3? Ich hab nicht mal Delphi3 ...
[info]Ach ja, irgendwo hatte es noch einen Bug oder zwei. Ich glaub wenn ein Variablenname mit klein e beginnt oder sowas gibt's manchmal Probleme. Der Bug ist im
SimpleCalculator [
http://www.delphi-forum.de/viewtopic.php?t=46415] korrigiert; jedoch ist dort der Parser verändert worden, damit er mit komplexen Zahlen arbeitet. Der Compiler ist dort auch nicht mehr dabei. [/info]
F34r0fTh3D4rk - Do 15.09.05 20:15
ja die dynamischen arrays lassen sich ja abändern und die overloads umbenennen, wenn ich lust habe, bringe ich den mal bei delphi3 zum laufen.
Midori - Do 20.10.05 17:27
Wau das klingt ja mal sehr interessant und genau das was ich grad suche.
Wäre es dir möglich das CBuilder5 kompatibel zu machen?
Er meldet leider:
[Pascal Fehler] tyParser.pas(1175): Undefinierter Bezeichner: 'NaN'
[Pascal Fehler] tyParser.pas(1271): Undefinierter Bezeichner: 'sign'
[Pascal Fehler] tyParser.pas(1272): Undefinierter Bezeichner: 'cot'
[Pascal Fehler] tyParser.pas(1276): Undefinierter Bezeichner: 'arccot'
*traurig*
Sind scheinbar nur Konstanten und einfache Funktionen.
Wäre super wenn es so umschreiben könntest, das diese in deiner Klasse
integriert sind ;)
BenBE - Do 20.10.05 18:09
Sind ETWA so definiert:
const NaN = 0 / 0; ;-)
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| function Cot (X): Datentyp begin result := cos(X) / Sin(X); end;
function ArcCot (X): Datentyp begin Result := Pi / 2 - ArcTan(X); end; |
HTH.
Midori - Do 20.10.05 20:50
Ok habs hinbekommen, aber wie das ganze unter CBuilder zum laufen zu bekommen ist habe ich noch net verstanden :)
SinC := CompileExpr(ParseExpr('sin(x)/x',['x']),tyPass1V);
ExprFunc1V SinC = CompileExpr(ParseExpr("sin(x)/x",OPENARRAY(TVarRec,("x"))),tyPass1V);
ExprFunc1V SinC = CompileExpr(ParseExpr("sin(x)/x",&TVarRec("x")),tyPass1V);
Mit dem ['x'] habe ich meine Probleme. Was ist das und wie muss man es in CBuilder angeben? Falls jemand ein kleines komplettes Beispiel hat wäre echt nett :)
FinalFantasy - Fr 04.11.05 14:22
Wie kriege ich die das ganze in eine DLL um sie aus C# aufrufen zu können?
Klassen sind ja in DLLs nicht erlaubt.
(Delphi 5)
delfiphan - Fr 04.11.05 22:52
Unter den Beispielen hat's einige, die ohne Klassen funktionieren. Die kannst du exportieren, jedoch weiss ich nicht, ob das mit dem array of const unter C# funktioniert...
Jakob Schöttl - Do 22.06.06 17:17
Wahnsinn! Die genaue Fehlerbeschreibung...
Bloß mit zu großen Zahln hat er noch ein problem: "Invalid Float point operation."
eine englische fehlermeldung. Vllt hast du das vergessen.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!