[C#] Taschenrechner mit eigenem Parser

03/18/2014 18:02 DeinMud#1
Wollte mal meinen Taschenrechner mit eigenem Parser vorstellen

Beschreibung:

Der Taschenrechner hat derzeit folgende Funktionen:

-Grundrechenarten + ^
-Sinus
- Arkus Sinus
-Cosinus
- Arkus Cosinus
-Tangens
-Arkus Tangens
-Fakultät
-Logharitmus
-Wurzel
-eigene Funktionen
-Modulo
-Exponentialfunktion
-Wechsel zwischen Gradmaß und Bogenmaß
-PI und E Konstanten
-Klammerhervorhebung
-Rechenschritte werden ausführlich angezeigt
-Funktionsgraphen zeichnen lassen (+ nähere Untersuchungen mit Zoomen / skalieren / dynamisch Punkte neu berechnen und Graphen aktualisieren)

Damit sind Ausdrücke wie:
sqrt(PI * (13 / 8,51)^-2) / log(3)
oder
sqrt(6 * 2^-(3! mod (4! mod 5!)) * 11 / (8,5-3,5)^2)
kein Problem.
Desweiteren werden viele Tippfehler ignoriert, bspweise:
+-3 * 3
wird zu +3 * 3
oder -- wird zu + bzw --- zu - usw.
oder 0,51234,156,231 + 12351,1234,2
klappt auch, die weiteren kommas werden einfach ignoriert.
was dann zu 0,51234156231 + 12351,12342 wird.

Der Funktionsgraph kann auserdem verschoben (mit linksklick gedrückt halten und mausbewegen) bzw gezoomed werden (mit Mausrad).

Die Funktionsweise meines Parsers ist so:
Die Operatoren bzw Funktionen haben alle eine Priorität und zu allererst wird der Ausdruck n-mal durchlaufen, wobei n für die anzahl der vorhandenen operatoren steht.
Dannach wird zu allererst der mit der obersten Priorität gesucht (das sind die Funktionen wie sin, cos und Klammern etc), anschliessend wird wenn sin( bzw cos( gefunden wurde, der Inhalt der Klammer erfasst und rekursiv die Funktion erneut aufgerufen mit dem Ausdruck in den Klammern als parameter, das wird solange gemacht bis keine Klammern mehr vorhanden sind (d.h. keine Funktionen mehr sondern nur noch operatoren vorhanden sind), zur Veranschaulichung:

Aufgabe:

13+21*sin(16-2^-(17+3/8))
sin( wird erfasst und der Inhalt bis zur schliessenden sin Klammer wird als parameter an die Funktion übergeben.
Also 16-2^-(17+3/4)
Da sind erneut Klammern, das heisst es wird die Funktion erneut rekursiv aufgerufen und als Parameter wird der Inhalt der Klammern übergeben, also:
17+3/4.
Nun gibt es keine Funktionen und Klammern mehr, d.h. die Operatoren mit der höchsten Priorität sind durch, nun kommen die mit der zweithöchsten Priorität, das wäre ^ davon gibt es hier auch nichts, also geht man zu den Operatoren mit der dritthöchsten Priorität, das wäre * und /
Nun wird der Ausdruck durchgegangen und wenn auf ein * oder auf ein / gestoßen wird, wird erst nach links gezählt bis zuerst min. eine Zahl und dann ein Operator gefunden wurde oder bis der Anfang/das Ende des Ausdrucks erreicht wurde. Das gleiche dann nach rechts hin.
Dannach wird mit Substring der Teilausdruck rausgefiltert, die mittels der beiden Variablen die nach unten und oben gezählt worden sind gefiltert worden und es kommt raus:
3/4
Das wird nun abgefragt ob sich / oder * befindet, dann gesplittet und jenachdem dividiert bzw multipliziert (hier dividiert).
Anschliessend wird der Term durch das Ergebniss ersetzt und man hat 17+0,75 raus.
Nun kommt der letzte Teil mit den Operatoren mit der niedrigsten Priorität: + und -
Es wird nun genau wie bei * / durchgegangen und die grenzen gezählt, anschliessend abgefragt - ausgerechnet - ersetzt und es kommt 17,75 raus.
Das wars eigtl schon, nun ruft die Funktion sich dauernd rekursiv auf, bis der ganze Ausdruck abgearbeitet wurde und das Ergebniss alleine dort stehen bleibt.
Natürlich gibt es einige Ausnahmen, beispielsweise wenn die Zahl zu groß wird, kommt sowas wie 2981283e-3 was soviel heisst wie 2981283*10^-3
das muss man dann explizit rausfiltern.

Screenshot:

Taschenrechner:
[Only registered and activated users can see links. Click Here To Register...]

Funktionsgraph:
[Only registered and activated users can see links. Click Here To Register...]

Download:

[Only registered and activated users can see links. Click Here To Register...]
03/18/2014 19:58 Tasiro#2
Quote:
Originally Posted by DeinMud View Post
Die Funktionsweise meines Parsers ist so:
Die Operatoren bzw Funktionen haben alle eine Priorität und zu allererst wird der Ausdruck n-mal durchlaufen, wobei n für die anzahl der vorhandenen operatoren steht.
Dannach wird zu allererst der mit der obersten Priorität gesucht (das sind die Funktionen wie sin, cos und Klammern etc), anschliessend wird wenn sin( bzw cos( gefunden wurde, der Inhalt der Klammer erfasst und rekursiv die Funktion erneut aufgerufen mit dem Ausdruck in den Klammern als parameter, das wird solange gemacht bis keine Klammern mehr vorhanden sind (d.h. keine Funktionen mehr sondern nur noch operatoren vorhanden sind)
Für einen eigenen Entwurf ist das schon ziemlich weit. Als nächstes könntest du die von deiner Grammatik beschriebenen Sätze (die Ausdrücke) formal beschreiben. Das könnte etwa so aussehen:
Code:
E = E + T | T
T = T * F | F
F = ( E ) | number
Dabei steht E für expression, T für term und F für factor. Das Verkettungszeichen (der senkrechte Strich) steht für eine Alternative.
Wenn eine Grammatik so vorliegt, kann man ganz einfach für jedes Nichtterminal (das auf der linken Seite vom "=", also E, T und F) eine eigene Funktion schreiben. Für die Terminale (number, +, *, ( und )) könnte das auch hilfreich sein.
Wenn du dein Programm erweiterst, könntest du so auch einen einfachen Interpreter erstellen.
Sollte dich dieses Thema interessieren, kannst du dich im Internet darüber informieren oder ein Buch zulegen.
03/18/2014 20:56 DeinMud#3
Ja, mein Konzept ist auch problemlos erweiterbar,
probiere grade das Ausdrücke wie 5(3+2)
wie 5*(3+2) gewertet werden.

Ich versteh nicht genau was du meinst, kannst du ein Beispiel zeigen was du meinst?
03/18/2014 23:01 Tasiro#4
Es ist nur ein Beispiel, nicht mehr.
Der Text wird nur ein einziges Mal durchlaufen.

Oft wird vor dem Parser noch ein Lexer genutzt. Dieser gibt Token wie einzelne Zeichen, ganze Zahlen oder Symbole wie sin an den Parser weiter und lässt zugleich den Leerraum aus. Dadurch muss sich der Parser damit nicht mehr beschäftigen. In obigem Beispiel übernähme er die Aufgabe von expression.Replace(" ","") und von EvalNumber.

Es wurden zahllose Werkzeuge geschaffen, um Lexer und Parser automatisiert zu erschaffen, sodass nur noch die Regeln festgelegt werden müssen.
03/19/2014 20:38 DeinMud#5
Update:

-Ausdrücke wie 5(3-2)
werden jetzt wie 5*(3-2) gewertet.
-Sin^-1;cos^-1;tan^-1 hinzugefügt
-Klammerhervorhebung hinzugefügt (im Bild nicht sichtbar)
-Design geändert
03/19/2014 20:40 Rullx3#6
Könntest du den Sourcecode Posten? Würde mir das gerne näher betrachten
03/19/2014 20:47 DeinMud#7
Kannst es meinetwegen decompilen.
03/19/2014 22:27 alpines#8
Falls du noch Sachen suchst was du hinzufügen kannst, werf mal einen Blick auf NCalc.
Du kannst als Operator noch % hinzufügen (statt mod) und \ für Ganzzahldivision, so das der Rest nicht returned wird. Beispiel:

15 \ 4 = 3

Außerdem sieht die Umkehrfunktion vom Sinus/Cosinus/Tangens hässlich aus, verwende doch lieber arcsin / arccos / arctan.
03/19/2014 23:24 Schlüsselbein#9
Wenn jetzt noch den (arc)cotangens, die Exponentialfunktion, die hyperbolischen und Areafunktionen hinzufügst, solltest du die elementaren beisammen haben (falls ich grad nicht noch was vergessen hab).
03/19/2014 23:50 Tasiro#10
Quote:
Originally Posted by Schlüsselbein View Post
Wenn jetzt noch den (arc)cotangens, die Exponentialfunktion, die hyperbolischen und Areafunktionen hinzufügst, solltest du die elementaren beisammen haben (falls ich grad nicht noch was vergessen hab).
Sekans und Kosekans.

Die Möglichkeit, rekursive Funktionen zu definieren, fehlt. Dazu wäre natürlich eine bedingte Berechnung notwendig.
Und wenn du schon einmal dabei bist: Beträge, mehrere Funktionsdefinitionen mit mehreren Variablen und Terme wären hilfreich, etwa (x+1)|(x=2) statt drei (die Syntax ist der Mathematik entnommen).
03/20/2014 19:31 DeinMud#11
Es können nun Funktionsgraphen automatisch erstellt werden (siehe Bilder)

Mod werd ich später noch mit % ersetzen.
03/20/2014 19:37 AoXis#12
Gerade mal ausprobiert, wenn ich x^2 mache kommt zeigt der mir ein falsches Ergebnis an, wenn ich dann 1x^2 mache kommt wieder was komplett anderes raus.
03/20/2014 19:43 DeinMud#13
1x geht auch nicht, du musst 1*x eingeben.
Bei 1x^2 kommt nämlich 1 und x^2, heisst bei z.B. x = 2
2^2 = 4
14 raus. Deswegen das komische Ergebniss, das werd ich noch ändern.
x^2 kommt nichts falsches raus. (zumindest bei mir)
03/20/2014 19:49 AoXis#14
Quote:
Originally Posted by DeinMud View Post
1x geht auch nicht, du musst 1*x eingeben.
x^2 kommt nichts falsches raus.
hmm komisch da sagt Geogebra was anderes? Was mach ich falsch? Ich glaub irgendwas hab ich nicht beachtet :( also ich denk mal er Probiert die Werte einzusetzen dann würde die Zeichnung auch stimmen :) dachte er Zeichnet die Funktion x^2 auf.

[Only registered and activated users can see links. Click Here To Register...]

[Only registered and activated users can see links. Click Here To Register...]
03/20/2014 19:57 DeinMud#15
Ah, das ist aber nicht der Fehler im Graphen zeichnen, sondern das ist nen Rechenfehler, hatte den bislang nicht bemerkt, werde das beheben.

Bei - Vorzeichen scheint da iwas schief zu laufen..

€ Habe den Fehler nun gefunden, werde die neue Version bald hochladen wenn Ich geprüft habe ob sich jetzt irgendein neuer Bug eingeschlichen hat.

Habs geupdated, sollte jetzt klappen!