Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Parser für mathematische Formeln
Andreas Adler
myCSharp.de - Member



Dabei seit:
Beiträge: 30

beantworten | zitieren | melden

Hallo Th69,

das Projekt Excel-Parser habe ich erstmal auf Eis gelegt, da es letztlich nur "nice to have" und kein "must have" war.
Dafür benutze ich deinen FormulaParser sowie den ConditionalParser und alles in allem leistet dein Parser super Arbeit.

Mir sind jetzt aber zwei Probleme aufgefallen, vielleicht könntest du (oder gerne auch jemand anderes) ein wenig Licht auf die Sache werfen.

Ich verwende den ConditionalParser<double, FormulaParser>.
Nehmen wir einmal folgende Formel an: ((1) + (-1)) == 0; hier erhalte ich die Fehlermeldung "Missing bracket: )". Aber eigentlich fehlt ja keine Klammer. Auf die Klammern um die Zahlen herum könnte man zwar verzichten (dann funktioniert die Auswertung der Formel auch), aber dennoch sollte das ja kein Problem darstellen, oder?

Das ist jetzt kein dramatisches Problem, man kann es ja leicht umgehen. Ich wollte es trotzdem erwähnen, weil es sich für mich wie ein Bug darstellt.
Das eigentlich gewichtigere Problem das ich habe ist der Vergleich von double-Werten, wobei die double-Ungenauigkeit ein falsches Ergebnis liefert.

Z.B. habe ich folgende Formel: (5819.03 - 2666 - 3153.03) == 0; der linke Vergleichswert ergibt eigentlich den Wert 0; dadurch müsste die Prüfung auf Gleichheit mit dem rechten Wert true zurückgeben. Tatsächlich liefert in diesem Fall dein Parser das Ergebnis false; was sehr ärgerlich ist, weil das Ergebnis nicht richtig ist (und somit meine Anwendung "falsch" funktioniert).

Ich hab mir die Equal-Methode im Conditional-Parser angeschaut; bei der o.a. Formel kommt für x der Wert -0.00000000000045474735088646412 und für y der Wert 0.0. Die Überprüfung x.Equals(y) ist dementsprechend aus Sicht des Computers richtig.
Ich hab hier auch testweise (im Debugger) die Überprüfung unter Berücksichtigung der Ungenauigkeit durchgeführt, die du und pdelvo in den vorherigen Beiträgen erwähnt habt, mit Epsilon = 1e-15; aber auch hier ist das Ergebnis noch false.

Gibt es für dieses Problem eine einfache und korrekte Lösung?

Danke und Gruß,
Andreas
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5991
Herkunft: Leipzig

beantworten | zitieren | melden

Hi Andreas Adler,

ich schätze mal, für das Problem gibt es keine einfache und zuverlässige Lösung. Der Wert des Epsilons muß so gewählt sein, daß es größer ist, als die bei der Berechnung enstehenden Ungenauigkeiten. Und die hängen wiederum von den Werten ab, mit denen du rechnest. Denn höhere Werte führen zu höheren Ungenauigkeiten. Also müßtest du dir ein Epsilon festlegen, welches deinen Erfordernissen entspricht. Dann könntest du diese Gleichung:
x - y == 0

so umschreiben:
Abs(x - y) < Epsilon


Christian
Weeks of programming can save you hours of planning
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4189

Themenstarter:

beantworten | zitieren | melden

Hallo Andreas,

bzgl. des ConditionalParsers kommt der Parser mit den zwei Hierarchie-Ebenen (Vergleiche <-> Formeln) bei den Klammern durcheinander. Dazu müßte man stattdessen wohl einen Parser schreiben (der dann aber auch wieder die Unterscheidung zwischen bool und T kennen müßte).

Und bzgl. des double-Vergleichs hätte ich zwei Vorschläge:
- Umstellung auf decimal (anstatt double), da du ja anscheinend mit Geldwerten (Konten) agierst
- In Anlehnung an The Floating-Point Guide - Comparison die Vergleichsfunktion so schreiben:


public static bool NearlyEqual(double a, double b, double epsilon)
{
	if (a == b) // shortcut, handles infinities
		return true;
	
	double diff = Math.Abs(a - b);

	if (a == 0 || b == 0)
	{
		// a or b is zero
		// relative error is less meaningful here
		return diff < epsilon;
	}
	else
	{
		double absA = Math.Abs(a);
		double absB = Math.Abs(b);

		// use relative error
		return diff / (absA + absB) < epsilon; // alternativ stattdessen Math.Max(absA, absB)
	}
}
Es wird also der relative Fehler verglichen, außer einer der beiden Werte ist 0!

Evtl. mußt du dann doch einen anderen (mächtigeren) Parser, wie muparser - fast math parser library, NCalc - Mathematical Expressions Evaluator for .NET oder Mathos Parser benutzen!?

PS: Ich habe gerade gesehen, daß ich genau 2000 Beiträge hier im Forum verfasst habe, dies dann also mein Beitrag #2001 ist, und das nach knapp 6 Jahren (d.h. fast jeden Tag ein Beitrag - ich bin wohl forumssüchtig?).
private Nachricht | Beiträge des Benutzers
Andreas Adler
myCSharp.de - Member



Dabei seit:
Beiträge: 30

beantworten | zitieren | melden

Hallo,

danke für die Antworten und den Hinweis auf die anderen Parser.

Ja du hast Recht, ich arbeite mit Geldwerten; über eine Umstellung von double auf decimal hatte ich auch schon nachgedacht; ich glaub ich hatte es sogar schon einmal versucht, bin aber an irgendeinem Punkt gescheitert... Aber ich kann das ja nochmal versuchen.

Eine andere Idee von mir war, die Beträge vor dem Vergleich auf die nötige Genauigkeit zu runden, also zwei Stellen. Dazu müsste man die round-Methode noch leicht anpassen (was kein Problem ist), sodass sie zwei Parameter entgegennimmt, einmal den Betrag und einmal die Anzahl der Dezimalstellen.

Ich werde mal schauen, ich denke die Umstellung auf decimal wäre generell in meinem Anwendungsfall von Vorteil. Das werde ich als nächstes versuchen.

Danke!



[offtopic]P.S.: Glückwunsch zu 2000 Beiträgen! Aber ich glaube von einer Forumssucht kann man bei einem Beitrag pro Tag noch nicht sprechen. ;)[/offtopic]
private Nachricht | Beiträge des Benutzers