Autor |
Beitrag |
delfiphan
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: So 20.02.05 14:29
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!!
* 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 (35kb)
FarbMix Parser-Demo: 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 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
www.delphi-forum.de/...r+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
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)
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"
Zuletzt bearbeitet von delfiphan am So 19.11.06 18:28, insgesamt 4-mal bearbeitet
Für diesen Beitrag haben gedankt: MagicRain
|
|
retnyg
Beiträge: 2754
SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
|
Verfasst: Di 01.03.05 13:45
schade dass dein parser z.b. (3+4) * 100 mit NaN quittiert ^^
edit: WTF ist eigentlich Nan ? - 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
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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
Beiträge: 2754
SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
|
Verfasst: Di 01.03.05 15:17
Nan ist übrigens auch das indische fladenbrot, welches im unterschied zu chapati mit joghurt angerührt wird...
|
|
retnyg
Beiträge: 2754
SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
|
Verfasst: 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
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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... )
Für diesen Beitrag haben gedankt: MagicRain
|
|
retnyg
Beiträge: 2754
SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
|
Verfasst: Mo 07.03.05 02:51
delfiphan := mathegott
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 ?
_________________ es gibt leute, die sind genetisch nicht zum programmieren geschaffen.
in der regel haben diese leute die regel...
|
|
delfiphan
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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
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
Beiträge: 92
|
Verfasst: 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
Beiträge: 6388
Erhaltene Danke: 146
Windows 7 + Windows 10
Sydney Prof + CE
|
Verfasst: 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
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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
Beiträge: 2754
SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
|
Verfasst: 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 ?
_________________ es gibt leute, die sind genetisch nicht zum programmieren geschaffen.
in der regel haben diese leute die regel...
|
|
delfiphan
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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 (27kb)
FarbMix Parser-Demo: 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.
Zuletzt bearbeitet von delfiphan am Di 08.03.05 13:37, insgesamt 4-mal bearbeitet
|
|
retnyg
Beiträge: 2754
SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
|
Verfasst: Mo 07.03.05 21:56
Beeindruckend ^^
werde das morgen mal in Ruhe testen...
_________________ es gibt leute, die sind genetisch nicht zum programmieren geschaffen.
in der regel haben diese leute die regel...
|
|
delfiphan
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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!!
* 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 (35kb)
FarbMix Parser-Demo: 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 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.
Für diesen Beitrag haben gedankt: MagicRain
|
|
AXMD
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: 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
|
|
retnyg
Beiträge: 2754
SNES, GB, GBA, CPC, A500, 486/66, P4/3.0HT: NintendOS, AmigaOS, DoS
Delphi 5, Delphi 7
|
Verfasst: Mo 14.03.05 23:59
deine funktion liesse sich vielleicht noch schneller machen wenn du bei der hoch-berechnung statt virtualalloc stackalloc benutzt...siehe hier delphi.blue-aura.co....read.php?p=65#post65
_________________ es gibt leute, die sind genetisch nicht zum programmieren geschaffen.
in der regel haben diese leute die regel...
|
|
delfiphan
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: 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.
Zuletzt bearbeitet von delfiphan am So 04.12.05 15:22, insgesamt 1-mal bearbeitet
|
|
|