Komplexer Taschenrechner

  • VB.NET

Es gibt 38 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Komplexer Taschenrechner

    Hallo,
    ich brauche Hilfe. Da ich kein Praktikum habe, sollten wir uns ein Projekt ausdenken. Mein Projekt ist daher ein Taschenrechner. Da ich aber schon mal einen
    einfachen Taschenrechner vor ca. 6 Jahren programmiert habe, ist das keine Herausforderung für mich mehr. Daher will ich jetzt einen Komplexen Taschenrechner
    programmieren (habe noch 4 Wochen zeit, Ziel ist: mit Funktionen inklusive Graphen). Das Grundprinzip habe (denke ich mal) verstanden von VB. Damals habe ich
    den Taschenrechner per Variablen rechnen lassen und pro Rechenzeichen eine Methode erstellt. Das ist einfach und kann ich reproduzieren. Aber ich will jetzt erst die
    Eingabe machen lassen durch eine Textbox (bzw. durch Buttons in die Textbox). Kein Problem. Habe ich soweit fertig. Mein Problem ist jetzt, dass ich die Rechenzeichen
    nicht in die Rechnung ein bauen lassen kann. Da die Textbox ein String ist kann man halt nicht so einfach damit rechnen. Ich habe es erst versucht (für Addition) über
    .split und diese dann in Variablen einzuspeichern. Das hat funktioniert. Aber nur für 1+1 aber nicht für 1+1+1. D.h. ich habe ein Array eingebaut gehabt für mehr "Variablen",
    quasi unendlich 1+1+1+1+.... usw. Durch ein Zähler für "+" im String habe ich dann halt die benötigte Anzahl an Arrays erstellt und den String an den entsprechenden Stellen
    Splitten lassen. Ich hätte weiter machen können und das so programmieren, dass es funktioniert, aber es wäre nicht zielführend, da ich dann das zwar für
    andere Rechenarten übernehmen könnte, aber für keine Mischen formen (z.B. 1+1/2). Also habe ich es anders versucht und habe das Internet durchforstet aber leider kein Ergebnis gefunden.
    Meine nächste Idee wäre jetzt gewesen, dass ich durch die Eingabe (oder Eingabe durch Buttons) die Zahlenketten direkt Variablen (Arrays) zugewiesen werden. D.h. ich müsste durch ein
    Rechenzeichen ein "neues" Array (neue Variable) generieren lassen für mindestens eine zweite Zahlenkette. Gleichzeitig müsste irgendwo dieses Rechenzeichen zwischen gespeichert
    werden (mit richtiger Reihenfolge bei mehr als einem Rechenzeichen) und später in die Rechnung eingefügt werden. (Soweit ich weiß, kennt VB die Punkt vor Strichregel). Für mich klingt
    diese Idee eher realisierbar, aber ich weiß halt nicht, wie ich diese Rechenzeichen speichern kann bzw. später dann anwenden kann.
    Kann mir jemand helfen bzw. eine Idee liefern? Vielleicht kennt jemand auch noch eine andere Idee, wie ich dieses Problem lösen kann.
    Viele Grüße
    Moritz

    Moritz Zander schrieb:

    War sehr hilfreich
    Dafür gibt es den Hilfreich-Button unter dem Post von @petaod ;)
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @Moritz Zander Falls Du hier noch reinschaust...

    Ich hab folgendes mal gefunden, ohne mich damit beschäftigt zu haben, aber es sieht so aus, als ob man mit einem "Einzeiler" einen MatheExpressionParser bekommen kann.

    C#-Quellcode

    1. using System;
    2. using System.Data;
    3. class Program
    4. {
    5. static void Main(string[] args)
    6. {
    7. Console.Write(Evaluate("10+12*4").ToString()); // 58
    8. Console.ReadLine();
    9. }
    10. public static double Evaluate(string expression)
    11. {
    12. return Math.Round((Convert.ToDouble(new DataTable().Compute(expression, null))), 6);
    13. }
    14. }
    codewars.com Rank: 4 kyu
    spätestens bei 2^2 scheitert es leider: System.Data.EvaluateException: "Der Ausdruck enthält den nicht unterstützten Operator '^'."
    siehe MSDN
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ich hab mal ein kleines Testprogramm gemacht, Form, Button, TextBox, 2 Label:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    3. Dim expression = Me.TextBox1.Text
    4. Me.Label2.Text = String.Empty
    5. Me.Label1.Text = Me.Evaluate(expression).ToString()
    6. End Sub
    7. Public Function Evaluate(expression As String) As Double
    8. Try
    9. Return (Convert.ToDouble(New DataTable().Compute(expression, Nothing)))
    10. Catch ex As Exception
    11. Me.Label2.Text = ex.Message
    12. Return 0
    13. End Try
    14. End Function
    15. End Class
    da stößt man sehr schnell an die Grenzen dieses Verfahrens.
    Ich denke, die CodeDom-Methode vom @ErfinderDesRades ist da zielführend.
    Aber Achtung:
    Da gibt für Operatoren unterschiedliche Syntax in VB und C#:
    C#: a ^ b => VB: a Xor B
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Danke erstmal an alle die noch geantwortet haben :thumbsup:
    Ich habe erst den Formelparser und ExpressionTree versucht in mein Programm einzubauen, bin leider glorreich dran gescheitert. Habe mich dann nochmal selbst auf die Suche gemacht und bin auch zufällig über den DataTable().Compute() Befehl gestoßen:


    VB.NET-Quellcode

    1. Private Sub btnCalculate_Click(sender As Object, e As EventArgs) Handles btnCalculate.Click
    2. Dim Ausdruck As String = txtRechnen.Text.Replace(",", ".")
    3. Dim Ergebnis As String = ""
    4. Try
    5. Ergebnis = New DataTable().Compute(Ausdruck, Nothing)
    6. Catch ex As Exception
    7. Ergebnis = "Error"
    8. End Try
    9. txtRechnen.Text = Ergebnis
    10. End Sub


    So habe ich es erstmal gelöst, ist aber noch nicht Zielführend wegen 2^2 z.B. . @ErfinderDesRades : Was ist ein Wrapper? So tief im Fachjargon bin ich nun nicht. ^^

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „Moritz Zander“ ()

    Moritz Zander schrieb:

    Was ist ein Wrapper?
    Eine Klasse, die die Methoden einer i.A. komplexen oder nativen (C, C++) Klasse einfach zugreifbar kapselt.
    Hier nicht relevant.
    Du müsstest Dich mit nem "richtigen" Formalparser auseinandersetzen.
    Oder
    Du spaltest die Elemente sofort beim Eingeben auf und hast dann Operatoren und Operanden einzeln in einer List(Of String) vorliegen, die Du dann abarbeiten kannst.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    (naja - mein TaschenRechner-Wrapper wäre keine Klasse, sondern ein Modul.
    Bzw eine Klasse mit nur Shared Membern.)
    Wrapper in dem Sinne verallgemeinert ist ein Dingens, was ein mächtiges, kompiliziertes anderes Dingens mit viel Funktionalität enthält.
    Der Wrapper "verpackt" dieses Dingens also, und gibt nach aussen nur ganz spezifische Funktionalität - alles andere kapselt er ab.

    Hier sinnvoll, weil DataTable ist eine mächtige Klasse, und die DataTable.Compute()-Funktion ist eigentlich auch ganz anders gedacht.
    Ein Wrapper würde nun eine DataTable enthalten, und nur die TaschenRechner-Funktion nach aussen bereitstellen.
    Intern täte er eigentlich nix, sondern leitet von aussen kommende TaschenRechner-Aufrufe einfach in richtiger Weise an die DataTable weiter.

    Oder beispielhafter erklärt: Gegeben die berühmte eierlegende WollmilchSau.
    Du willst aber nur die Milch, und zwar auf einfache Weise.
    Ein MilchWrapper würde nun eine eierlegende WollmilchSau enthalten, und intern alle Operationen ausführen, mit denen man an die Milch kommt.
    Und dir die Milch auf einfache Weise bereitstellen.

    Quasi ein Bauer ist ein Wrapper um seinen Kuhstall.
    Deswegen kann man so einfach Milch bei ihm kaufen.
    Danke für die Erklärung.

    RodFromGermany schrieb:

    Du spaltest die Elemente sofort beim Eingeben auf und hast dann Operatoren und Operanden einzeln in einer List(Of String) vorliegen, die Du dann abarbeiten kannst.

    Das dachte ich mir schon. Meine Idee war/ist den Wert z.B. für Potenzieren raus zu filtern und erst extra berechnen lassen und dann zurück geben lassen in die Rechenzeile, klassisch, erst Potenzieren (später auch Wurzel ziehen) vor den anderen Rechenarten.
    Quasi:
    Button für Potenz fügt zwei Platzhalter ein: Zahl ^ Exponent . Sobald der Exponent eingegeben wurde wird es berechnet und die Platzhalter durch das Ergebnis ersetzt. Das sollte das Problem theoretisch lösen.
    Meine Lösung:

    VB.NET-Quellcode

    1. Private Sub btnPotenz_Click(sender As Object, e As EventArgs) Handles btnPotenz.Click
    2. Dim Ausdruck1 As String = txtZahl.Text.Replace(",", ".")
    3. Try
    4. Zahl = New DataTable().Compute(Ausdruck1, Nothing)
    5. Catch ex As Exception
    6. Zahl = "Error"
    7. End Try
    8. Dim Ausdruck2 As String = txtExponent.Text.Replace(",", ".")
    9. Try
    10. Exponent = New DataTable().Compute(Ausdruck2, Nothing)
    11. Catch ex As Exception
    12. Exponent = "Error"
    13. End Try
    14. Potenzieren = Zahl ^ Exponent
    15. Form1.txtRechnen.Text += Potenzieren
    16. Me.Close()
    17. End Sub

    Bin zwar nicht 100%ig zu frieden, aber es funktioniert. Das stellt eine zweites Fenster da, das durch Knoprdruck auf den Button "^" sich öffnet. Man kann dann die Zahl und den Exponenten eingegeben, auch als Rechnung und wird dann zurück in die Rechenzeile gegeben.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Moritz Zander“ ()

    @Moritz Zander Dim Zahl As String :?:
    Zwei Mal New DataTable() in einer Prozedur :?:
    Du liest lediglich einen numerischen Wert aus, da kannst Du das DataTable()-Gedöns sein lassen und den String per Double.TryParse() in einen numerischen Wert konvertieren.
    Also:
    Schmeiß Deine Lösung vollständig weg und befasse Dich zunächst etwas mit den Grundlagen der .NET-Programmierung, bis Du ein Gefühl für Deine Problematik bekommst:
    [Sammelthread] Programmieren, aber was? (Programmideen)
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    @Moritz Zander Dim Zahl As String


    Steht bei mir ganz oben als Variable, um von überall drauf zugreifen zukönnen, genauso wie Dim Exponent As String und Dim Potenzieren As String (Hier zwar eher unötig, aber eine Angewohnheit). Die Datatable() habe ich geschrieben, damit man z.B. (3+4) ^ (4-2) lösen kann, wie mich @ErfinderDesRades drauf hingewiesen hat. Ich könnte es hier vorher als Funktion schreiben, dass ich diesen Befehl nicht immer wieder verwende, sondern die Funktion.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Moritz Zander“ ()

    Es geht nicht um die Sichtbarkeit der Variable, sondern um den Datentyp.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    RodFromGermany schrieb:

    bis Du ein Gefühl für Deine Problematik bekommst

    Mein Problem bleibt immer noch dasselbe (Ich umgehe es praktisch nur die ganze Zeit, löse aber es nicht): Ich möchte erst die Rechnung eingeben bevor ich rechne. Die normale Methode mit zwei Variablen, die dann die verschiedenen Rechenarten durchlaufen würde, kenne ich bereits bzw. habe ich schonmal gemacht. Aber ich möchte jetzt halt die ganze Rechnung eingeben können, z.B. 4+2*8. Das wäre ja 20, aber wenn ich das so in das Programm mit zwei Variablen eingeben würde, dann würde es erst 4+2=6 rechnen und das Ergebnis direkt wieder in die 1. Variable einsetzen und dann 6*8 rechnen, was dann ja 48 wäre. Der Befehl Datatable().Compute hat mir weiter geholfen, ist aber nicht die Lösung, weil halt die sowas wie Potenzen, Wurzeln, sin, cos, tan fehlt. Oder ich hätte mehr Variablen erstellen müssen, aber ich weiß halt vorher nicht wie viele Rechenoperationen eingegeben werden, welches dann ein neues Problem hervorrufen würde, weil ich dann nicht wüsste, wie man die einzelnen Rechenschritte mit einander verbindet, da es ja einmal 2*5-7 sein könnte und dann 2-5*7 oder -7+2*5, wobei das dasselbe Ergebnis hat wie 2*5-7, aber hier Addiert wird. D.h. ich kann keine Methode erstellen, die nur einen Fall abhandelt, sondern alle beinhaltet bzw. diese einzeln abarbeitet (Klammern -> Potenzen/Wurzeln(in a*a^-1) -> Punkt vor Strich), wie du es schon geschrieben hast. Und da liegt bei mir das eigentliche Problem: Wie bekomme ich ein Rechenzeichen in eine Rechnung, wenn man es keiner Variable zuordnen kann? Oder: Wie erstelle ich eine (mehrere) Funktion(en), die den Wert vor und/oder nach dem Rechenzeichen in Variablen speichern und dann priorisiert berechnen? -> -7+2*5 -> Wert1 = -7, Wert2 = 2, Wert3 = 5 -> "*" zuerst Wert2 * Wert3 = Ergebnis -> dann Ergebnis + Wert1 = Ergebnis -> Rechenzeile = Ergebnis.
    bzw. direkt bei der Eingabe die Zahlen Variablen zuordnen (ich weiß nicht, wie ich dann den Rechenoperator hinzufügen kann):
    -7+2*5
    -7 = Wert1, Wert2 = 2, Wert3 = 5 (bei Button-Rechenzeichen: neue Variable anlegen, bei Ergebnis Rechenzeichen hinzufügen)
    Ergebnis = Wert1 + Wert2 * Wert3
    Da liegt mein Problem. Ich weiß nicht wie ich diese Rechenzeichen in die Rechnung hinzufügen soll.