Formel automatisch auswerten

  • Hallo,


    ich möchte einer Function von außen 3 Eingänge geben: A (float), B (float) und Formula (String).
    Formula soll dann die mathematische Formel sein, die vorgibt wie A und B verrechnet wird.
    Also z.B. "(A+B)*(0.25-A)*B" ... naja irgendso was halt mit Operationen und Klammern.
    Es reichen ein paar Grundrechenarten wie + - / * % und natürlich die Klammern.
    Dabei soll die Länge der Formel keine Rolle spielen.


    Ich könnte jetzt natürlich alle möglichen Strings auseinandernehmen , auswerten und mir dann eine schier unendliche Reihe an Kombinationen switchen wie ich A und B verrechne.
    Evtl. auch mit vorbereiteten Enum-Auswahlen.
    Aber das wäre eine Lebensaufgabe. ;)


    Also bin ich froher Hoffnung auf eine einfachere Lösung, die das automatisch rechnet.
    Wer hat eine Idee?

  • zu allererst die frage:
    wofür brauchst du das?


    ich glaub den string zu zerlegen und auszuwerten ist die einzigste möglichkeit, die es gibt. mir fällt da so auch nix anderes ein, wie man so eine formel auswerten könnte...
    wobei ich die einzelteile des strings in ein array speichern würde. da die grundrechenarten ja begrenzt sind hättest du ja nicht viel zu prüfen, wenn du das array mit einer schleife durchläufst. wie du den string zerlegst ist schon vorgegeben.


    sonst würde ich, wenn die formeln schon bekannt sind, für die jede formel eine funktion anlegen. auch hättest du hier kein problem mit den zahlen in der formel, da du diese einfach als lokale variable speichern kannst.

  • Da es eine Eingabe des Users werden soll sind die Formeln natürlich nicht bekannt.
    Sonst hätte das Ganze ja auch recht wenig Sinn. ;)


    Im Grunde kann die Engine die Auflösung aus einer Formel in ein BP ganz hervorragend.
    Dazu kann man die Math-Expression nutzen.
    Die Benennung des Nodes ist gleichzeitig auch die Formel.
    Nach Enter wird die Formel dann automatisch im Node angelegt.
    Schade, dass es hierzu keine dynamische Lösung gibt. :/


    Evtl. muss ich das doch in C++ lösen, denn das sollte nicht so das Problem sein, denn im Grunde ist die Formel ja auch die direkte Anweisung im Script.
    Ich dachte eben, dass es evtl. schon so etwas vorbereitetes in der Engine gibt, was mir bisher verborgen blieb.
    Oder kennt jemand ein gutes Plugin?

  • Wenn es im math node nicht funktioniert (wahrscheinlich funktioniert es nicht, aber der code könnte nett sein als referenz für dich), dann musst du deinen eigenen interpreter basteln, der aus dem String etwas mathematisches macht.


    Zitat

    Evtl. muss ich das doch in C++ lösen, denn das sollte nicht so das Problem sein, denn im Grunde ist die Formel ja auch die direkte Anweisung im Script.

    Dies ist nur der Fall, da C++ ja zu maschinen Code compiliert wird. Wenn die Formel also erst zur Laufzeit bekannt ist und nicht beim compilieren, gehts nicht. (Außer ist verstehe dich falsch.)
    Also muss ein interpreter her. Über Iteration oder Rekursion sollte man aber ziemlich einfach etwas hinbekommen.

  • Also muss ein interpreter her. Über Iteration oder Rekursion sollte man aber ziemlich einfach etwas hinbekommen.

    Ich glaube du unterschätzt das etwas.
    Die Formeln können recht lang sein mit einer Fülle an Klammern.


    Zuerst muss ich den String validieren und auseinandernehmen.
    Dessen Klammer-Felder in Multidimensionale Arrays schieben und dann Stück für Stück auswerten und durchlaufen lassen.
    Ich sehe darin eine sehr tiefe und geschachtelte Arbeit, die am Ende auch noch völlig inperformant sein dürfte.
    Da habe ich gedacht es gibt evtl. schon Lösungen, denn ich kann mir kaum vorstellen, dass ich der Erste bin, der auf diese "Idee" der Funktion gekommen ist.


    Mit dem C++ und compileren hast du absolut recht - daran dachte ich noch gar nicht.
    Nach dem Compiliertem C++ Code kann man die mathematische Formel natürlich nicht als C++ Anweisung auswerten. :/

  • Die länge der Formeln ist ziemlich egal, solange der Algorithmus gut geschrieben ist. An sich müsstest du das ganze natürlich so schreiben, dass es beim Auswerten auch eine Fehlermeldung zurück gibt.


    Nehmen wir eine Formel:
    (3+6*(2*7+3)-5)*5*(4-5/6)+(2*(4-2))
    an sich ist es ja egal ob da jetzt Zahlen über Buchstaben stehen solange du vor der Auswertung der Formel alle Buchstaben mit Zahlen substituierst (diese sollten ja vorher bekannt sein)


    1) Zuerst sucht man nach dem ersten ")" das findet man recht schnell. Dann sucht man von da aus rückwärts nach dem nächsten "("
    folgender Ausdruck wird ausgewertet:
    2*7+3


    2) Es ist klar, dass dieser Ausdruck durch das Suchen von "(" zuerst keine weitere Klammer enthält. Es muss also Punkt vor strich gerechnet werden.
    Man Teilt den String in mehrere Substrings auf. Trennzeichen dafür sind alle "+" und "-" Man erhält die Substrings
    2*7 und 3.


    3) Nun kann man den ersten in Leserichtung auswerten (Operatoren suchen und an den Stellen den String Teilen, Zahlen Strings zu Zahlen casten operatoren sorgen für die entsprechende verrechnung)
    4) Aus 2 sind die positionen der Substrings bekannt. Diese werden mit den Ergebnissen ersetzt: 14+3
    5) Wie in 3) wird der ausdruck ausgewertet.
    6) Das ergebnis aus 5) wird in den Substring aus 1) geparsed man erhält also den String:


    (3+6*17-5)*5*(4-5/6)+(2*(4-2))


    Jetzt wird Schritt 1) wiederholt: Man erhält den Substring:
    3+6*17-5


    man geht schritte 2-6 durch und erhält die neue Formel:
    100*5*(4-5/6)+(2*(4-2))


    Wieder Schritte 1-6
    100*5*3,1667+(2*(4-2))


    Wieder Schritte 1-6
    100*5*3,1667+(2*2)


    Wieder Schritte 1-6
    100*5*3,1667+4


    Jetzt sollte der Algorithmus merken dass Schritt 1) nicht mehr geht. Also wird auf die Formel Schritt 2-6 angewendet
    d.h. 2) Substrings nach Trennzeichen "+" und "-" erzeugen:


    100*5*3,1667 und 4


    3) Substrings auswerten:
    1583.4 und 4


    4) Substituieren
    1583.4+4


    5) +/- auswerten
    1587.4


    Schritte 1-6 auf 1587.4 bewirkt nichts. Also ist das Resultat = dem vorherigen Resultat: Die Auswertung ist beendet.


    Man wiederholt also Schritte 1-6 bis es keine Änderung des Resultats daraus gibt.

  • ui... das werd ich mir mal die Tage in Ruhe durcharbeiten.
    Im ersten Lesen kommt mir das trotzdem so vor wie Theorie und Praxis sind 2 unterschiedliche Dinge.
    Natürlich ist die Abfolge so logisch, aber das Ganze ernsthaft im Blueprint nachzubauen sollte schon einiges an Nerven kosten.
    Gerade im Umgang mit Strings ist das im Blueprint nicht so einfach - da fehlt so manche Funktion, die in der Theorie sehr einfach klingt, aber aufgrund von fehlenden Nodes im BP recht kompliziert werden kann.
    Aber ich werd mich mal durchbeißen.
    Vielen lieben Dank für deine aufwändige Antwort! :)

    • Offizieller Beitrag

    das was tomura geschrieben hat, habe ich bereits umgesetzt nur umgekehrt. Dass zuerst klammer ( erkannt wird.
    Sollte eine weitere ( kommen, wird die erste reihenfolge bis zur klammer festgehalten. Nun wird so lange gesucht bis die erste) kommt. Dann wird gerechnet. Ich wollte vorgestern schon den script posten, aber habe ich total verdaddelt. Kann erst am we schicken.


    Punkt vor strich rechnung habe ich ignoriert. Denn jeder der rechnungen durchführt arbeitet mit klammern.

    • Offizieller Beitrag


    Ist etwas durcheinander und ich hoffe, du verstehst, was ich da gemacht habe. Ich bin kein Mathe Ass, desswegen überprüfe mal lieber ^^
    Einige Nodes sind warscheinlich etwas zu viel, aber beim probieren kommt leider so etwas herraus ^^
    FormelRechnung.zip


    Um trotzde,m mal kurz zu erklären, was da passiert. Bsp: 5+(5+(5*5))
    1. Formel wird bearbeitet. Nach jedem Zeichen/Zahl kommt ein Komma: 5,+,(,5,+,(,5,*,5,),). Dann werden durch die Kommas alle Zeichen/Zahlen in ein Array geparst. In jedem Index ist jetzt eine Zahl, oder Zeichen vorhanden.
    2. Dieser Array wird von Anfang bis Ende gelesen um herrauszufinden, wieviel "(" exestieren.
    3. Das gleiche Array wird wieder von Anfang bis Ende gelesen und in eine FormelTemp geschrieben, gleichzeitig wird gerechnet, bis eine "(" kommt. Dann werden die schon gelesenen Zeichen und Zahlen in eine Formel Variable geschrieben und FormelTemp wird wieder neu geschrieben und die schon zusammen gerechneten Zahlen werden gelöscht. So sieht die Formel nun aus: 5+(
    4. Nun wird der Klammerbereich gelesen und gerechnet, bis entweder eine neue "(" kommt (hier wird das gleiche wie in 3. durchgezogen), oder eine ")" kommt, dann wird die ZwischenSumme gebildet und in die Formel Variable hinzugefügt (Hier musste die letzte Klammer von "5+(" entfernt werden). So sieht die Formel jetzt aus: 5+(5+25)
    5. Dann wird wieder alles von vorne , also von 1. bis 4. nochmal durchgezogen. Bis keine Klammer mehr da ist. Dann kommt am Ende der Formel ein E hinzu. 5+30E
    6. Wenn E gelesen wird, dann kommt die Summe.


    Die ZahlTemp Variable ist wichtig, da es ja nicht nur eine Zahl gibt, sondern auch mehrstellige Zahlen. Wenn jetzt 12.1 im Array ist, dann exestiert nicht 12.1 in ein Index, sondern
    Index0=1
    Index1=2
    Index2=.
    Index3=1
    Die werden gelesen, solange bis Klammer, oder Zeichen kommt.

  • Schöne Sache! Vielen Dank! :)


    Es fallen mir aber noch 2 Dinge auf.
    Zum einen funktionieren ausschließlich Formeln mit Klammern - also ein einfaches 1+1 ergibt hier 0
    Und zum anderen ist die Formel bereits mit Werten bestückt.
    Sie muss aber auch - und vorllen Dingen - Variablen auswerten - also z.B. A+B-5 - wobei A und B dann dynamische Float-Eingänge der Funktion wären.
    Das dürfte aber mit einem Replace-Node kaum Probleme bereiten.
    Ich erwähne das nur, falls jemand dein Asset runterlädt und nicht weiß warum einige Dinge nicht so funktionieren.
    Das sind aber nur Kleinigkeiten.


    Ich finde es etwas seltsam, dass das nicht Unreal selbst als feste Funktion anbietet.
    Wenn man näher darüber nachdenkt ist so eine Funktion nämlich unfassbar nützlich und lässt einen dynamisch offen verschiedenen Dinge automatisch zusammenzurechnen ohne exklusive BP dafür anfertigen zu müssen.
    Auch sehr gut denkbar wenn es darum geht Farben oder Texturen zu mischen.
    Man gibt 3 Texturen vor und mit Angabe einer kleinen Formel kommt ganz automatisch hinten raus, was vorne gefordert ist ohne umständliche BPs zu basteln. :)


    Die Tragweite und Faszination dieser Funktion kann man nur begreifen, wenn man sie in der Praxis benutzt.
    Im Grunde braucht man - entsprechend auf andere Rechenoperationen ausgeweitet - nur noch diese Funktion, um Dinge miteinander zu verrechnen.
    Endlich kein kompliziertes Umbauen der Blueprints mehr - sondern einfach innerhalb weniger Sekunden die Formel einfach umgestellt.
    Ich hoffe ihr erkennt das unfassbar große Potential dieser Funktion - vorallem weil wirklich alles komplett dynamisch ist.
    Den Performance-Abfall durch das etwas Mehr an Branches sollte man vernachlässigen können.
    Ich habe das Ganze auf Sinus, Cosinus, Loop-Funktionen, Runden, modulo und noch paar andere Mathe-Sachen erweitert und alles kann man ganz locker in diesem kleinen String definieren.


    Mit den neuen Map-Variablen wird daraus eine super komfortable Lösung:

    • Offizieller Beitrag

    Ich hab mich so sehr auf die Klammern fixiert, sodass ich die ohne Klammern komplett vergessen habe xD
    Das mit den Dynamischen Teil kann man sich ja selber basteln, also das mit den (A+B) usw, dass ist ja keine Schwierigkeit.


    Ich bin sowieso erstaunt, dass BluePrints schon so viel bietet. Es wird nie C++ ersetzen, aber ich finde man hat alles, was man braucht. Das mit der Formel ist eigentlich klar, dass die nicht so vorhanden ist, wie du die haben möchtest, da evtl du zu den 1% gehörst, die das wollen.

  • Das mit der Formel ist eigentlich klar, dass die nicht so vorhanden ist, wie du die haben möchtest, da evtl du zu den 1% gehörst, die das wollen.

    Weil eben die anderen 99% überhaupt nicht verstehen, was sie sich mit dieser Funktion für Arbeit sparen könnten.
    Im Grunde ist die Formel auch eine Art Code-Anweisung auf sehr einfache Art und Weiße.
    Mathematik wird quasi immer gebraucht und ehrlich warum sollte ich jede Mathe-Operation immer wiederr neu anlegen wenn ich nur 5 mal etwas in die Formel eintippe anstatt Berge von neuen Nodes zu basteln, die auch noch völlig zeitaufwändig und unflexibel sind.

  • Also der große und ausschlag gegebende Nachteil ist:
    Es ist im Verhältnis zu fertig kompiliertem Code sehr langsam. Denn bei jedem aufruf der Formel, muss diese interpretiert werden und die Formel kann nicht vom compliler optimiert werden.
    Außerdem ist verbraucht die Formel deutlich mehr speicher als fertig kompilierter code (theoretisch 16 bit pro Zeichen, ich weiß aber gerade nicht ob jedes Zeichen seinen eigenen 32/64 Bit block belegt, damit jedes Zeichen seine eigene Addresse hat)


    Du musst also verhältnismäßig viel performance aufwenden, um nur etwas auszurechnen. Gerade bei einem Spiel ist die Real-Time Fähigkeit ja wichtig.
    An sich ist das eingeben einer Formel ja auch nur 3 sekunden Zeit, wenn man es jetzt in C++ macht. Daher würde keiner einsehen diese "kosten" zu zahlen nur um einen einfachen mathematischen Ausdruck auszuwerten.
    Blueprint hat was mathematik angeht deutlich verbesserungspotential, aber ich sehe Blueprint auch eher als Designer-Tool und wenn Designer irgendwas mit mathematik machen geht es meistens schief. (Sorry an alle Designer, das ist einfach so)



    Es gibt durchaus in anderen Anwendungen Tools die solche Textausdrücke und Textbasierte scriptsprachen interpretieren und ausführen können. Z.B. Matlab (was ich auf der Arbeit hauptsächlich verwende). Diese eignen sich aber eher

  • Ich habe den Performanceabfall getestet und er liegt ähnlich hoch wie wenn ich ähnliche Sachen exklusiv im BP erzeuge.
    Durch die Branches und Switches wird auch immer nur der benötigte Teil gerechnet und nicht etwa die ganze Funktion.
    Die Branches selbst sind nur if-Anweisungen - eines der schnellste C++ Anweisungen überhaupt, die so gut wie gar keine Performance verbrauchen - natürlich nicht wenn es 100 Stück wären. ;)


    Der Speicheraufwand liegt bei im Moment bei 1Kb mehr - ehm.. da können wir schmunzelnd abwinken. ;)
    Dass mathematische Dinge in BPs fehlen bestreitet keiner, aber wenn ich im BP irgendwas miteinander multipliziere wird daraus der exakt gleiche C++ wie wenn ich die Sache direkt in C++ code.
    Gerade in geradlinigen mathematischen Dingen gibt es zwischen C++ und BP fast keinen Unterschied.
    Das kann man gerne mal austesten und die Codes miteeinander vergleichen.


    Und natürlich hast du recht, dass es irgendwann mal schwieriger wird mit der Performance, wenn die Funktion immer mehr ausgebaut wird.
    Ich habe das auch direkt als NAchteil bereits in meinem ersten Post ganz oben benannt.
    In diesem Beispiel ist das aber wirklich vernachlässigbar.


    Die Funktion ist trotzdem immer noch eine Funktion bei der ich z.B. in Data-Tables für recht aufwändige Verrechnungen nur eine kleine Formel halten kann.
    Das muss ich auch direkt in C++ so scripten - das macht dann kaum ein Unterschied.
    Wenn später der Spieler eine Formel direkt auf diese Weiße eingeben kann - nützt einem C++ auch nichts - das muss man da auch so scripten, dass das dann auch so funktioniert.


    Aber probieren über geht über studieren.
    Wer es nicht glaubt - einfach testen!

  • Die Formel muss auch nach dem Compilen des C++ Codes interpretiert werden.
    Zb. durch eine Eingabe des Spielers - da wird der Code nichts mehr nützen.


    Aber das hatten wir bereits beredet:


    Dies ist nur der Fall, da C++ ja zu maschinen Code compiliert wird. Wenn die Formel also erst zur Laufzeit bekannt ist und nicht beim compilieren, gehts nicht.

    Mit dem C++ und compileren hast du absolut recht - daran dachte ich noch gar nicht.
    Nach dem Compiliertem C++ Code kann man die mathematische Formel natürlich nicht als C++ Anweisung auswerten. :/


    Ich verstehe immer nicht warum man sich in einem Forum extra anmeldet, um eine Antwort auf einen Thread zu geben, den man nicht einmal ausreichend genug gelesen hat.
    Der Thread wird dadurch unnötig in die Länge gezogen, was zur Folge hat, dass sich erst recht keiner mehr die Mühe macht alles zu lesen und der Threadersteller auf seinem Problem am Ende sitzen bleibt. :/

  • Es funktioniert, du verwechselt da vielleicht was.


    CNumericalEquation.cpp:



    CNumericalEquation.h:





    Möchtest du hierbei auch "a" und "b" statt nur "A" und "B" verwenden, lösche einfach , ESearchCase::CaseSensitive


    Edit: Ich habe dir mal die beiden Dateien gezipt und unten verlinkt (CNumericalEquation.h, CNumericalEquation.cpp). Die Dateien einfach in Unreal Projects\*Projektname*\Source\*Projektname* legen.
    Habe noch ein Bild dazu gepackt.

  • Unabhängig meines Glaubens daran, dass das funktionieren könnte:


    1. Eingefügt und gestaret -> kein Node dieser Art vorhanden


    2. VS2015 gestartet und Source ausgewählt -> Crash


    3. Beim Versuch zu builden:
    "...CNumericalEquation.cpp' is trying to include 'MathEval.h' as the precompiled header, but that file could not be located in any of the module's include search paths."


    Wo soll er auch die MathEval.h hernehmen? Ich habe die nicht.

  • in UnrealMathUtitlity.h (Modul Core)


    Wusste gar nicht dass es dass gibt. Danke @Metho. Da hat ja Epic echt an alles gedacht.


    Franz99: an sich heißt dass dass es das schon in C++ gibt. MathEval.h im SourceCode von Metho ist die haupt-header-datei des Projekt Moduls aus seinem Beispiel. Wenn du ein C++ Projekt machst, muss da statt MathEval.h die Haupt- Headerdatei des Moduls sein in dem der Code drin ist. Der Compiler sollte da sogar eine ziemlich verständliche meldung geben. Ich würde aber vorher C++ lernen, sonst machst du dir nur probleme, wenn du mit C++ sachen arbeitet.

  • So.
    Über die Konversation hat @Metho mir nochmal eine Schritt für Schritt Erklärung gepostet.
    Das Problem war ursprünglich, dass ich nicht wußte, dass "MathEval" der Projektname sein soll, was ja auch klar ist.
    Ich hatte das komplett übersehen - denn ich habe bereits schon einige andere C++ Codes und immer fängt es an erst einmal meinprojekt.h zu includen.
    Das hätte mir wirklich auffallen müssen. :/


    Das zweite Problem war allerdings, dass sobald das einmal falsch gebuildet wurde halfen auch keine Korrekturen mehr - da kamen trotzdem die fehler, obwohl ich es exakt so abgeändert habe.
    Mit dem Neuanlegen einer C++ Blueprint Library Class hat das nun geklappt.


    Allerdings gibt es wohl ein paar Schwierigkeiten seitens der Berechnung.
    Zu erst habe ich ein paar einfache Dinge wie A+B oder (A+B)*A usw. ausrechnen lassen.
    Das klappt alles sehr gut.
    Dann habe ich zu der Beispielformel von @Tomura gegriffen: (3+6*(2*7+3)-5)*5*(4-5/6)+(2*(4-2))
    Das Node behauptet allerdings das Ergebnis sei: 105.0, was definitiv falsch ist!
    Das richtige Ergebnis ist - wie auch @Tomura schrieb - 1587.333...
    Auch Google bestätigt das Ergebnis. (Lustigerweiße ist unser Thread auch darüber zu finden - gleich unter dem Rechner :D )
    So stellt sich die Frage, was da noch falsch ist.
    Die Länge der Formel oder zu viel Klammern oder doch ein Bug?


    @Metho
    Ich möchte mich an dieser Stelle für die etwas vorzeitige Bewertung deines Postes entschuldigen.
    Für mich klang das aber - hoffentlich nachvollziehbar - für genau den Denkfehler, den wir anfänglich in diesem Thread besprochen hatten.
    Und du wärst bei Leibe nicht der Erste, der Threads überfliegt und eine Antwort gibt, die bereits schon abgehandelt wurde.
    Da du aber trotz meiner vorschnellen Kritik sehr entspannt und freundlich reagiert hast - glaube ich, dass die Entschuldigung schon angenommen wird. :)
    Ich danke dir sehr für deine kompetente Lösung! :thumbup: <3


    C++ ist zwar ähnlich der Codes, die ich gut beherrsche - PHP, Perl, JS ect. - stellt mich trotzdem - gerade wegen dieser includes - vor eine größere Herausforderung.
    Seit den 80ern scripte ich - damals noch in Basic und kenne die Vor- und Nachteile.
    Vorteil - ist die Flexibilität und Performance einer Codesprache.
    Der Nachteil war aber immer, dass wenn man einige Zeit nicht am Code weiter gearbeitet hat, schnell die Übersicht verlieren kann.
    Und hier zeigt sich die Macht der optischen Programmierung in Blueprints.
    Da kommt man sehr schnell wieder rein.


    Ich hoffe wir können noch diesen Rechenfehler analysieren und dann bin ich vollends zufrieden. :)