Mein Projekt: Formelparser und Funktionsplotter
- VB.NET
- .NET (FX) 4.0
Sie verwenden einen veralteten Browser (%browser%) mit Sicherheitsschwachstellen und können nicht alle Funktionen dieser Webseite nutzen.
Hier erfahren Sie, wie einfach Sie Ihren Browser aktualisieren können.
Hier erfahren Sie, wie einfach Sie Ihren Browser aktualisieren können.
Es gibt 59 Antworten in diesem Thema. Der letzte Beitrag () ist von Carbonunit.
-
-
Also ich werde da mal ein bisschen herumprobieren. Erstmal die Daten eines Graphen berechnen und in einer Tabelle speichern, das kann man ja immer brauchen. Dann aus dieser Tabelle im Paint-Event zeichnen, ausprobieren wie das mit der Backgound-Image-Eigenschaft funktioniert, wenn ich zuerst in eine Datei zeichne und die dann lade oder eben das MS-Chart-Control nehmen, wobei das meiner Faulheit wohl am ehesten entgegen kommt. Eigentlich will ich die Zeichnung machen und gestalten, aber nicht dauernd den technischen Kram, dass die auch nicht plötzlich wieder verschwindet, richtig skaliert beim Resize etc. Wenn sich darum jemand im Hintergrund kümmert, ist mir das schon recht.
-
Carbonunit schrieb:
...aber nicht dauernd den technischen Kram, dass die auch nicht plötzlich wieder verschwindet, richtig skaliert beim Resize etc. Wenn sich darum jemand im Hintergrund kümmert, ist mir das schon recht.
Das ist ein Standard, und ist äusserst leistungsfähig.
Wenn du allerdings dich in OwnerDrawing-Grundlagen vertiefen willst, dann bastel halt selbst was. Ist auch schön, sich mit Matrix und GraphicsPath auszukennen.
Macht aber Arbeit, und wird immer nur ein Spezialfall sein (und evtl. sogar punktuelle Vorteile gegenüber MsChart rausholen können).
Ach guck - hier hab ich sogar ein': activevb.de/cgi-bin/tippupload…4/Ownerdrawn_ChartControl
-
Hallo, ich will zunächst die drei Möglichkeiten, also Paint-Event, in Datei zeichnen und als Backgroundimage laden und MS-Chart Control mal ausprobieren. Nur dafür müßte es wenigstens irgendwas zeichnen. Das tuts aber nicht. Im Folgenden der Code, von dem ich denke, dass er fürs Zeichnen im Paint-Event relevant ist. Sicher fehlt noch irgendwas, nur ich weiß nicht was.
Dieser Code wird aufgerufen, wenn der Menuepunkt Funtion in neuem Fenster zeichnen gewählt wird:
VB.NET-Quellcode
- Private Sub mnuDrawNewWindow_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuDrawNewWindow.Click
- Dim blnGetOn As Boolean = False
- Dim frmNewChild As frmDraw
- blnGetOn = OpenScaleEdit() ' Dialog zur Definition des Koordinatenkreuzes
- If blnGetOn Then
- ' Neues Fenster anzeigen
- frmNewChild = AddNewWindow(gobjFunctions.GetName)
- With frmNewChild
- ' Koordinatenkreuz festlegen und Zeichnen
- .DefineXAxis(frmDefineScale.X_AxisStart, frmDefineScale.X_AxisEnd, frmDefineScale.X_AxisTicks)
- .DefineYAxis(frmDefineScale.Y_AxisStart, frmDefineScale.Y_AxisEnd, frmDefineScale.Y_AxisTicks)
- .SetScale() ' Maßstabsfaktoren berechnen
- .DrawScale() ' Koordinazenkreuz zeichnen
- blnGetOn = .CalcNewGraph() ' Graphen berechnen
- End With
- gobjFunctions.ShowFunction()
- End If
- End Sub
frmDraw ist eine Form ohne Rahmen, die als eiziges Steuerelement eine Picturebox hat (picDraw), welche die komplette Fläche der Form einnimmt und auf der eigentlich die Zeichnung zu sehen sein soll.
Dieser Code berechnet und zeichnet das Koordinatenkreuz:
VB.NET-Quellcode
- Friend Sub DrawScale()
- Dim ptStart_XAxis As System.Drawing.Point
- Dim ptEnd_XAxis As System.Drawing.Point
- Dim ptStart_YAxis As System.Drawing.Point
- Dim ptEnd_YAxis As System.Drawing.Point
- Dim penScale As System.Drawing.Pen = Pens.Black
- Dim intXAxisY As Integer = 0
- Dim intYAxisX As Integer
- ' Wo liegt die X-Achse?
- With mtypAxisX
- If CBool(.dblStart < 0.0) And CBool(.dblEnd > 0.0) Then ' Von Minus bis Plus
- ' Y-Achse irgendwo in der Mitte
- intYAxisX = Math.Abs(CInt(.dblStart * mdblFactorX))
- ElseIf CBool(.dblStart < 0.0) And CBool(.dblEnd <= 0.0) Then ' Nur negagtive Seite
- ' Y-Achse liegt ganz rechts
- intYAxisX = picDraw.Width
- ElseIf CBool(.dblStart >= 0.0) And CBool(.dblEnd > 0.0) Then ' Nur positive Seite
- ' Y-Achse liegt ganz links
- intYAxisX = 0
- End If
- End With
- ' Wo liegt die Y-Achse?
- With mtypAxisY
- If CBool(.dblStart < 0.0) And CBool(.dblEnd > 0.0) Then ' Von Minus bis Plus
- ' X-Achse irgendwo in der Mitte
- intXAxisY = Math.Abs(CInt(.dblStart * mdblFactorY))
- ElseIf CBool(.dblStart < 0.0) And CBool(.dblEnd <= 0.0) Then ' Nur negagtive Seite
- ' X-Achse liegt ganz oben
- intXAxisY = 0
- ElseIf CBool(.dblStart >= 0.0) And CBool(.dblEnd > 0.0) Then ' Nur positive Seite
- ' X-Achse liegt ganz unten
- intXAxisY = picDraw.Height
- End If
- End With
- ' X-Achse definieren
- ptStart_XAxis.X = 0
- ptStart_XAxis.Y = intXAxisY
- ptEnd_XAxis.X = picDraw.Width
- ptEnd_XAxis.Y = intXAxisY
- ' Y-Achse definieren
- ptStart_YAxis.X = intYAxisX
- ptStart_YAxis.Y = 0
- ptEnd_YAxis.X = intYAxisX
- ptEnd_YAxis.Y = picDraw.Height
- With Me.picDraw.CreateGraphics
- .DrawLine(penScale, ptStart_XAxis, ptEnd_XAxis)
- .DrawLine(penScale, ptStart_YAxis, ptEnd_YAxis)
- End With
- End Sub
Wenn ich unmittelbar nach dessen Ausführung einen Breakpoint setze und die VB-IDE minimiere, so daß mein Programm im Vordergrund ist, liegt das Koordinatenkreuz auch genau da, wo es hin soll.
Dieser Code berechnet den Graphen für die vorgegebene X-Achse und ruft Invalidate auf, um das Paint-Event auszulösen:
VB.NET-Quellcode
- Friend Function CalcNewGraph() As Boolean
- Dim blnSuccess As Boolean = False
- Dim dblStart As Double = mtypAxisX.dblStart ' Gleichzeitig auch X-Offset
- Dim dblEnd As Double = mtypAxisX.dblEnd
- Dim dblYOffset As Double = mtypAxisY.dblEnd
- Dim dblX As Double = dblStart
- Dim dblY As Double = 0.0
- Dim intSteps As Integer = GetSteps()
- Dim dblAxisLen As Double = Math.Abs(dblEnd - dblStart)
- Dim dblStep As Double = dblAxisLen / intSteps
- Dim ptResult As System.Drawing.Point
- Dim intError As Integer = ValfDeclares.ERR_SUCCESS
- Dim intProgressBarLen As Integer = FPlotMain.gobjFunctions.ProgressbarMax - FPlotMain.gobjFunctions.ProgressbarMin
- Dim dblProgressFactor As Double = CDbl(dblAxisLen / intProgressBarLen)
- Do
- ' Berechnen
- intError = FPlotMain.gobjFunctions.ActiveFunction.Calculate(dblY, dblX)
- blnSuccess = CBool(intError < ValfDeclares.ERR_CRITICAL) ' Keine kritischen Fehler aufgetreten?
- If blnSuccess Then
- If CBool(intError = ValfDeclares.ERR_SUCCESS) Then
- ' Nur, wenn auch keine mathematischen Fehler aufgetreten sind,
- ' wie Funktion nicht definiert oder Disision durch Null, dann zeichnen
- ' Punkt in der Grafik berechnen und speichern
- ' Offset berechnen, Nullpunkt oben links
- ptResult.X = CInt((dblX - dblStart) * mdblFactorX)
- ptResult.Y = CInt((dblY + dblYOffset) * mdblFactorY)
- mlistGraph.Add(ptResult)
- End If
- dblX += dblStep ' Nächster X-Wert
- FPlotMain.gobjFunctions.SetProgressbar(CInt(Math.Abs(dblX / intProgressBarLen * dblProgressFactor)))
- End If
- Loop Until CBool(dblX > dblEnd) Or Not blnSuccess
- Me.picDraw.Invalidate()
- Return blnSuccess
- End Function
Das Paint-Event wird auch gefeuert. Darin wird das Koordinatenkreuz nochmal gezeichnet und die berechneten Punkte sollen als Graph ausgegeben werden:
VB.NET-Quellcode
- Private Sub picDraw_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles picDraw.Paint
- ' Hier wird gezeichnet
- Dim intIdx As Integer = 0
- Dim penGraph As System.Drawing.Pen = Pens.Black
- ' Koordinatenkreuz
- DrawScale()
- ' Graphen zeichnen
- For intIdx = 1 To mlistGraph.Count - 1
- Me.picDraw.CreateGraphics.DrawLine(penGraph, mlistGraph(intIdx - 1), mlistGraph(intIdx))
- Next intIdx
- End Sub
Wenn ich unmittelbar danach einen Breakpoint setze und die IDE minimiere, ist das Koordinatenkreuz immer noch da, aber vom Graphen ist nichts zu sehen. Läuft das Programm normal weiter, ist die ganze Fläche weiß, also auch das Koordinatenkreuz verschwindet wieder.
Was genau fehlt da noch? Welche Informationen braucht ihr ggf. noch?
Gruß
Carbonunit -
wie ich sehe - mein gegebener Link hat dich ja nicht weiter beeinflusst.
versuch 2 Dinge zu beherzigen:- Vergiss
Control.CreateGraphics()
! nur mit dem Graphics, was dir im Paint-Event gegeben wird zeichnen!! - Nur im Paint-Event zeichnen und evtl. in Methoden, die du von da aus aufrufst
Alles woanders gezeichnete Zeug ist nicht persistent und verschwindet bei Gelegenheit wieder - haste nun ja gesehen.
OwnerDrawing
Wie gesagt: Die Thematik ist sehr schwierig, und an die Qualität des ChartControls (was Beschriftung, Skalierung, Maus-Interaktion etc angeht) ist von uns Hobby-Programmierern eh nicht heranzukommen.
Es mag unkreativ erscheinen, aber besseres Programmieren, und auch besseres Programmieren Lernen ist, professionelle Infrastruktur richtig benutzen zu lernen, anstatt Selbstgebasteltes in die Welt zu setzen.
- Vergiss
-
Carbonunit schrieb:
Loop Until CBool(dblX > dblEnd) Or Not blnSuccess
Öhm das CBool kannste dir sparen, ein Vergleich erzeugt immer einen boolean als Resultat.
faxe1008
-
ErfinderDesRades schrieb:
wie ich sehe - mein gegebener Link hat dich ja nicht weiter beeinflusst.
Der Link aus #43? Hab ich ehrlich gesagt nicht so richtig verstanden...
ErfinderDesRades schrieb:
Ich hab auch ein OwnerDrawing-Tut gemacht
Das sieht imteressant aus, damit komme ich wohl weiter.
ErfinderDesRades schrieb:
Es mag unkreativ erscheinen, aber besseres Programmieren, und auch besseres Programmieren Lernen ist, professionelle Infrastruktur richtig benutzen zu lernen, anstatt Selbstgebasteltes in die Welt zu setzen.
Das ist natürlich richtig, aber ich will wenigstens einmal sehen wie das geht mit dem Ownerdrawing. Dann kann ich mich besser entscheiden, welchen Weg es weiter geht.
faxe1008 schrieb:
Öhm das CBool kannste dir sparen, ein Vergleich erzeugt immer einen boolean als Resultat.
Auch richtig, aber das wird doch sicher wegoptimiert? Oder nicht?
Ist so ein bisschen wie das Blinken beim Autofahren. Das habe ich mir bei jedem Richtungswechsel angewöhnt, egal ob auch jemand da ist, der es sieht oder nicht.
Ergänzung: Mein Koordinatenkreuz bleibt schon mal erhalten, Graph erscheint gar nicht, da stimmt möglicherweise auch was mit den Koordinaten nicht... Es geht voran, muss erstmal etwas die Architektur umbauen.
Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Carbonunit“ ()
-
Carbonunit schrieb:
Der Link aus #43? Hab ich ehrlich gesagt nicht so richtig verstanden...
Das ist in diesem Fall garnet so falsch, weil dann hast du verstanden, dasses verdammt kompliziert werden wird.Carbonunit schrieb:
aber ich will wenigstens einmal sehen wie das geht mit dem Ownerdrawing.
Carbonunit schrieb:
...CBool ...
Ist so ein bisschen wie das Blinken beim Autofahren. Das habe ich mir bei jedem Richtungswechsel angewöhnt, egal ob auch jemand da ist, der es sieht oder nicht.
Beim Autofahren darf kein Unfall passieren, aber beim Proggen sollen Exceptions auftreten!
Wenn Exceptions auftreten dürfen, wo immer Mist gecodet ist, dann ist die Wahrscheinlichkeit exorbitant höher, dass kein Mist gecodet ist - wenn sie nicht auftreten.
An einer Exception stirbt niemand - sie tut nicht einmal weh. Sondern daran verbesserst du dein Programm.
Und Typumwandlungen jeder Art sind sowieso mit äusserster Vorsicht zu behandeln - und wo immer geht zu vermeiden. Weil jede Typumwandlung deaktiviert lokal die Compiler-Typ-Kontrolle.
VB.NET-Quellcode
Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()
-
Hallöchen,
hier das erste Beweisfoto:
Da ist natürlich noch längst nichts in einem vorzeigbaren Zustand, aber die Sache mit dem Ownerdrawing funktioniert schon mal.
Jeder Speicherplatz meines Funktionsspeichers enthält jetzt noch zwei Listen. Einmal die Rohdaten mit den x- und y-Werten, die sich aus der Berechnung ergeben.
Dann eine zweite, gleichgroße Liste mit System.Drawing.Point-Objekten. Aus dieser Liste zeichne ich im Paint-Event der Picturebox meine Kurve. Die wird jedes Mal neu berechnet, wenn sich die Größe des Fenster geändert hat. Die Faktoren und Offset-Werte für x und y errechne ich im Resize-Event.
Das hat den schönen Nebeneffekt, dass sich mein Bild immer schön der Fenstergröße anpaast. Noch geht das alles auch sehr schnell, mal sehen, was passiert, wenn mehrere Kurven auf einer Picturebox sind.
Eine Baustelle sind natürlich noch die Skalenstriche für das Koordinatenkreuz und so einige Menuepunkte, bei denen noch gar nichts passiert. Aber eine wichtige Klippe ist umschifft.
Gelernt habe ich heute auch wieder was. So ein Konstrukt ist Mist (Pseudocode, VB-ähnlich):
Da hat mein Code bestimmte Dinge einfach nicht gemacht, obwohl der Debugger angezeigt hat, dass SomeTextFromMyObject den zum Index passenden Wert hatte. Tatsächlich übergeben wurde aber ein anderer Wert.
Beginn und Ende des With-Blocks gehören IN die For-Schleife, dann gehts. Das IsNumeric kein .NET ist, weiß ich jetzt auch, wird noch angepasst.
Zu den CBool-Funktionen beim If: Ja, ihr habt recht, da bemerke ich Fehler sicher schneller, wenn es dann kracht. Schmeiß ich wieder aus, wo ich das sehe.
Gruß
Carbonunit -
IsNumeric
Visual Studio - Empfohlene Einstellungen -
ErfinderDesRades schrieb:
deaktiviere den MVB-Namespace-GeneralImport!
Ja, muss ich noch machen, das gibt im Moment über 80 Fehler, meistens bei so Sachen wie Mid, Len, Instr oder den nicht sichtbaren Zeichen wie vbCrLf. Wenn ich mal wieder Lust zur öden Überarbeitung habe, wird das erledigt. Oder erstmal Dateiweise. Also Generalimport deaktivieren und überall Import Microsoft.VisualBasic drüberschreiben. Sind zum Glück nicht mal alle Dateien des Projekts, wo das drinsteckt.
So habe ich das mit Option Strict On gemacht, da gibt es noch zwei Stellen, wo es ohne die Einstellung hakt. Ich schäm mich ja auch...
Das hat aber alles einen TODO-Kommentar und steht damit in der Überarbeitungsliste. -
-
Gelöschter Benutzer schrieb:
Und ich dachte immer als "erfahrener" Informatiker macht man gleich alles richtig..
Ehrlich gesagt hätte ich mehr nach der Vorstellung deiner Person erwartet.
Niemand macht immer alles richtig.
Ich bin seit sieben Jahren aus dem Job raus und VB.NET war in den letzten Jahren bis 2010 auch nicht mehr meine Hauptaufgabe. Dafür klappt es aber immer noch ganz ordentlich.
Im Support, dazu noch in der Höhle des Löwen (Microsoft) ging es vor allem darum, dass die Leute ihr Zeug ans Fliegen bekommen haben, da war es erstmal egal, ob sie das mit .NET oder dem alten Zeug gemacht haben. Schön programmiert wurde da nicht, eher effizient im Sinne von "Schnell das Problem lösen".
Das war in den letzten Jahren nur noch Geschäft, da musstest du so viele Cases (Anfragen) wie möglich wegschaffen.
Jetzt kann ich endlich wieder mal pfriemeln und basteln. -
Hallöchen,
der Funktionsplotter wächst und gedeiht und auch das Ownerdrawing im Paint-Event läuft so flüssig, dasss ich keinen Bedarf für eine Alternative sehe.
Diese vier Kurven...
...wurden in kürzester Zeit gezeichnet. Die Bedienung der Oberfläche hat insgesamt länger gedauert, als das Zeichnen. Das Ganze läuft in zwei Schritten ab:
Zuerst fülle ich eine Liste (List(Of T) mit den Rohdaten, also den Ergebnissen der Funktion f(x) für die vom Koordinatenkreuz vorgegebenen x-Werte.
Dann multipliziere ich das mit den Vergrößerungsfaktoren, die sich aus der Größe des Koordinatenkreuzes und der Größe der Zeichenfläche in Pixeln ergeben. Dazu kommen Offsets für den Nullpunkt, der ja nicht mehr oben links in der Ecke liegt, sondern dort, wo sich die Achsen des Kordinatenkreuzes treffen. Diese Wertepaare kommen in eine zweite Tabelle und aus dieser Tabelle wird im Paint-Event in einer Schleife die Zeichnung erstellt. Das geht schnell genug, um das Fenster flüssig verkleinern und vergrößern zu können mit der Maus
Auch der kleine Ausflug in die Editor-Programmierung hat etwas damit zu tun. Mit diesem Editor, der jetzt wunderbar funktioniert und etwa den Funktionsumfang von Notepad hat, will ich später Makros bearbeiten für meine eigene kleine Makrosprache. Die wird ein bisschen an GW-BASIC erinnern , aber ohne Zeilennummern und man wird Variablen deklarieren müssen. Dafür gibt es GOSUB..Return und solche Geschichten. Alles direkt Zeile für Zeile interpretiert und ausgeführt.
Anständig programmiert wird natürlich auch, alle Module compilieren jetzt mit gesetztem Option Strict On und der hier sogenannte "Deppen-Namespace" Microsoft.VisualBasic wird so langsam zurückgedrängt. Ich benutze ihn noch, um die wunderbar nützlichen Konstanten wie vbCrLf zu definieren, aber dann nur explizit dafür, er wird nicht mehr importiert, bzw. nur noch bei wenigen Modulen. Einen sinnvollen Ersatz für Asc und Chr, also ASCII-Wert eines Zeichens ermitteln und Zeichen aus ASCII-Wert dastellen, habe ich noch nicht gefunden.
Gruß
Carbonunit -
erstmal congratulations! - sieht sehr gut aus!
Carbonunit schrieb:
"Deppen-Namespace" Microsoft.VisualBasic wird so langsam zurückgedrängt. Ich benutze ihn noch, um die wunderbar nützlichen Konstanten wie vbCrLf zu definieren
Carbonunit schrieb:
Einen sinnvollen Ersatz für Asc und Chr, also ASCII-Wert eines Zeichens ermitteln und Zeichen aus ASCII-Wert dastellen, habe ich noch nicht gefunden.
immer feste Visual Studio - Empfohlene Einstellungen studieren, ich glaub, da empfehle ich auch iwo den GeneralImportVb6=Micorsoft.VisualBasic
Damit wäre der Deppen-Namespace unter dem KürzelVb6
nachwivor verfügbar.
Also statt ihn einzubinden, hiesse es (etwas umständlicher):Vb6.Asc(), Vb6.Chr()
Aber vlt. hab ich diese Finessen auch erst in Zwei Fragen: Aufteilung eines Programms und VB 2017 auseinandergesetzt - ich bin grad zu faul (und zu betrunken), das alles nochma durchzugehen.
-
Einen System.Microsoft.Wasauchimmer - Namespace gibts bei mir nicht.
Für die nützlichen Sachen aus dem Deppen-Namepsace, die schlecht zu ersetzen sind, hab ich das jetzt so gemacht:
VB.NET-Quellcode
- Friend Const VBCRLF As String = Microsoft.VisualBasic.ControlChars.CrLf ' Zeilenvorschub
- ' Ersatz für Asc
- Friend Function VB6_Asc(ByVal chrIn As Char) As Integer
- Return Microsoft.VisualBasic.Asc(chrIn)
- End Function
- ' Ersatz für Chr
- Friend Function VB6_Chr(ByVal intIn As Integer) As Char
- Return Microsoft.VisualBasic.Chr(intIn)
- End Function
-
-
Hallöchen,
heute mal ein Codeschnippsel. Die ist der Teil des Funktionsplotters, in dem die einmalige Arbeit der Umstellung von InFix-Notation (5+3) zu PostFix.Notation (5 3 +) erledigt wird. Darin steckt auch das meiste Hirnschmalz. Ich muss zugeben, dass ich damals nicht alle Erklärungen vom @ErfinderDesRades dazu verstanden habe. Aber als ich einen WikiPedia-Artikel dazu mit dem Bildchen von einem Gleisdreick als Rangierbahnhof gesehen habe, da hat es Klick gemacht.
Der Code funktioniert, es rechnet bisher richtig und auch zügig. Auch die Kommentare lasse ich mal drin. Wichtig für einen Funktionsplotter ist auch die Laufvariable, so wie eventuelle Konstante, wie im Term A*x^3+B*x^2+C*x+D. Ich nennen die "Symbole" und habe dafür eine extra Liste angelegt. Konstante werden bei der Umwandlung in die PostFix-Notation durch ihren Wert ersetzt. Von der Laufvariablen (oder auch mehreren Laufvariablen) merke ich mir die Position(en) im PostFix-Term und ändere die Werte vor jeder Berechnung.
VB.NET-Quellcode
- ' Überträgt Tokens aus der Liste in InFix-Notation ( 3; +; 4;) in die
- ' Liste in PostFix-Notation ( 3; 4; +;)
- Private Function InFixTokensToPostFix() As Integer
- ' Der Algorithnus heißt auch "Shunting-Yard" also Rangierbahnhof.
- ' Dieser Rangierbahnhof ist ein Gleisdreieck, bei dem der rechte Ast der Input ist (Tokens in InFix-Anordnung),
- ' der untere Ast der Stack und der linke Ast der Output, also die Tokens (Waggons) in PostFix Anordnung.
- ' Regeln:
- ' Bevor ein neuer Operator oder Token auf den Stack kommt, werden alle Tokens höheren
- ' oder gleichen Ranges in die PostFix-Liste geschrieben. Es kommen nur Operatoren auf den Stack,
- ' Zahlen gehen direkt in die Ausgabe.
- ' Ausnahmen:
- ' Öffnende Klammern (Rang 0) werden auf den Stack gepusht, verdrängen aber dort nichts
- ' Schließende Klammern erzwingen ein Auslesen des Stacks und Schreiben in die PostFix-Liste.
- ' Danach wird die zugehörige öffnende Klammer vom Stack gepoppt und verworfen
- ' Sonderfälle sind die Klammern, Laufvariable, weil die noch immer als Name vorliegt und das #EOT
- Dim intError As Integer = ERR_SUCCESS
- ' Ende der Schleifen
- Dim blnDone As Boolean = False ' Äußere Schleife
- Dim blnDoneInnerLoop As Boolean = False ' Innere Schleife zum Flushen des Klammerausdrucks
- ' Stack für Tokens
- Dim stackTokensInFix As New Stack(Of OneToken)
- ' OperatorenRänge
- Dim intRankOnInFixStack As Integer = 0
- ' Rang des Tokens
- Dim intRankNewToken As Integer = 0
- ' Numerisches Token
- Dim blnNumericNewToken As Boolean = False
- ' Text des Tokens
- Dim strTokenText As String = ""
- ' Klammerebenen zählen, solange größer Null ist mindestens eine Klammereben noch offen
- Dim inBracketLevel As Integer = 0
- ' True, wenn Laufvariable das Token ist
- Dim blnIsControlVar As Boolean = False
- ' InFixListe auf Anfang
- SetInFixTermToStart()
- ' PostFix-Liste löschen
- mlistTokensPostFix.Clear()
- ' InFix-Stack löschen
- stackTokensInFix.Clear()
- Do
- ' InFixToken laden
- Dim objNewInFixToken As New OneToken
- objNewInFixToken = GetNextInFixToken()
- 'PostFixToken definieren
- Dim objNewPostFixToken As New OneSimpleToken
- ' Eigenschaften des neuen PostFixTokens setzen
- strTokenText = objNewInFixToken.TokenText ' TokenText
- objNewPostFixToken.TokenText = strTokenText
- objNewPostFixToken.TokenValue = objNewInFixToken.TokenValue
- blnIsControlVar = TokenIsControlVar(strTokenText) ' Ist das die Laufvariable?
- If blnIsControlVar Then
- ' Laufvariable muss beim Sortieren als Zahl behandelt werden
- objNewPostFixToken.TokenArguments = 0 ' Hat keine Argumente
- intRankNewToken = MAX_OPERATOR_RANK ' Höchster Rang
- blnNumericNewToken = True ' Numerisches Token
- Else
- objNewPostFixToken.TokenArguments = objNewInFixToken.TokenArguments
- ' Übrige Eigenschaften des InFix-Tokens merken
- intRankNewToken = objNewInFixToken.TokenOpRank
- blnNumericNewToken = objNewInFixToken.TokenIsNumeric
- End If
- ' Ende des Terms erreicht?
- blnDone = CBool(objNewInFixToken.TokenText = END_OF_TERM)
- If (intRankNewToken = 0) Then ' Sonderfall öffnende Klammer
- stackTokensInFix.Push(objNewInFixToken) ' Auf den Stack damit
- inBracketLevel += 1
- ElseIf (strTokenText = SPECIAL_CLOSE_BRACKET) Then ' Sonderfall schließende Klammer
- ' Stack bis zur öffnenden Klammer flushen, öffnende und schließende Klammer verwerfen
- blnDoneInnerLoop = False
- Do
- If (stackTokensInFix.Peek.TokenText <> SPECIAL_OPEN_BRACKET) Then
- ' Wenn keine öffnende Klammer oben auf dem Stack
- intError = FlushTokenFromStackToPostFixList(stackTokensInFix)
- Else
- ' öffnende Klammer verwerfen
- If CBool(stackTokensInFix.Count) Then
- stackTokensInFix.Pop()
- Else
- intError = ERR_OP_STACK_UNDERFLOW
- End If
- blnDoneInnerLoop = True
- End If
- Loop Until blnDoneInnerLoop
- inBracketLevel -= 1
- ElseIf (strTokenText = END_OF_TERM) Then
- Do While CBool(stackTokensInFix.Count) And CBool(intError = ERR_SUCCESS)
- ' So lange noch Tokens auf dem Stack sind
- intError = FlushTokenFromStackToPostFixList(stackTokensInFix)
- Loop
- mlistTokensPostFix.Add(objNewPostFixToken) ' EOT ebenfalls in die Liste
- blnDone = True ' Fertig
- ElseIf blnNumericNewToken Then
- ' Token ist eine Zahl oder die Laufvariable als Platzhalter, direkt zur Ausgabe
- mlistTokensPostFix.Add(objNewPostFixToken)
- ElseIf (intRankNewToken > 0) Then
- ' Prüfen ob gleich- oder höherrangige Tokens auf dem Stack
- intRankOnInFixStack = GetRankOnInFixStack(stackTokensInFix)
- ' Abweisende Schleife, die nicht ausgeführt wird, wenn der neue Rang größer, als der des Tokens auf dem Stack ist.
- Do While (intRankNewToken <= intRankOnInFixStack) And CBool(intError = ERR_SUCCESS)
- ' So lange der Rang des neuen Tokens kleiner oder gleich dem des Tokens auf dem Stack ist...
- intError = FlushTokenFromStackToPostFixList(stackTokensInFix)
- ' Rang des Tokens auf dem Stack ermitteln
- intRankOnInFixStack = GetRankOnInFixStack(stackTokensInFix)
- Loop
- ' Stack geflusht, jetzt neues Token auf den Stack
- stackTokensInFix.Push(objNewInFixToken)
- End If
- Loop Until blnDone Or (intError <> ERR_SUCCESS)
- If intError = ERR_SUCCESS Then
- mintTermStatus = STATUS_PROCESSED
- End If
- Return intError
- End Function
Die Klasse für ein Token, weches dann auch seinen Rang kennt und weiß, wieviele Argumente es hat:
VB.NET-Quellcode
- Option Strict On
- Imports System.Collections.Generic
- ' Stellt ein Element oder auch Token der Term-Liste für Valf dar
- ' Kann auch für die Liste der Symbole verwendet werden
- ' Klasse enthält einen Double Wert und Text in einem Feld
- Public Class OneToken
- Inherits ValfDeclares
- ' ----- Variable -----
- ' Der Inhalt des Tokens
- Private mdblValue As Double = 0.0 ' Wenn Zahl, dann ist Text die String-Repräsentation der Zahl.
- Private mstrText As String = "" ' Wenn Text, dann ist Zahl 0.0
- ' Liste der Operatoren, der Index bestimmt den Operatoren-Rang
- Private mstrOperators(MAX_OPERATOR_RANK) As String
- ' Die Eigenschaften des Tokens
- Private mblnIsNumeric As Boolean = False ' True, wenn Zahl
- Private mintOpRank As Integer = 0 ' Rang des Operatos
- Private mintArguments As Integer = 0 ' Anzahl der Argumente des Operators
- ' Fehlercode
- Private mintErrorCode As Integer = ERR_SUCCESS
- ' Der Wert des Tokens, falls es eine Zahl ist
- ' Wenn es keine Zahl ist, kommt hier 0.0 zurück
- Friend ReadOnly Property TokenValue As Double
- Get
- TokenValue = mdblValue
- End Get
- End Property
- ' Das Token wird als String behandelt und intern zu Double gecastet,
- ' so dass dieser Wert zur Berechnung sofort abgerufen werden kann.
- ' Alle Eigenschaften des Tokens werden bei der Zuweisung ermittelt.
- Friend Property TokenText As String
- Get
- TokenText = mstrText
- End Get
- ' Hier erfolgt die Ermittlung aller übrigen Eigenschaften
- Set(ByVal strNewValue As String)
- ' Eigenschaften des Tokens bestimmen
- mintErrorCode = SetTokenProperties(strNewValue)
- End Set
- End Property
- ' Ist das Token eine Zahl oder nicht?
- Friend ReadOnly Property TokenIsNumeric As Boolean
- Get
- TokenIsNumeric = mblnIsNumeric
- End Get
- End Property
- ' Operatoren-Rang des Tokens, relevant bei der Sortierung in PostFix-Notation
- Friend ReadOnly Property TokenOpRank As Integer
- Get
- TokenOpRank = mintOpRank
- End Get
- End Property
- ' Anzahl der Argumente des Tokens, relevant wenn es ein Operator ist
- Friend ReadOnly Property TokenArguments As Integer
- Get
- TokenArguments = mintArguments
- End Get
- End Property
- ' Token fehlerfrei erstellt?
- Friend ReadOnly Property ErrorCode As Integer
- Get
- ErrorCode = mintErrorCode
- End Get
- End Property
- Private Function SetTokenProperties(ByVal strToken As String) As Integer
- Dim intError As Integer = ERR_SUCCESS
- mstrText = strToken
- mintArguments = 0 ' Standardmäßig hat das Token keine Argumente
- mblnIsNumeric = IsDouble(mstrText) ' Numerisch oder nicht?
- If mblnIsNumeric Then
- ' Token ist eine Zahl
- ' Typumwandlung, um den Double-Wert schnell zur Hand zu haben
- mdblValue = CDbl(mstrText)
- ' Eine Zahl hat keine Argumente, oben schon festgelegt
- mintOpRank = MAX_OPERATOR_RANK ' Eine Zahl hat den höchsten Operatoren-Rang,
- ' verdrängt damit das davor liegende Token mit gleichem Rang vom Stack,
- ' also nur eine andere Zahl.
- Else
- ' Token ist ein Operator oder Name
- ' Anzahl Argumente bestimmen
- If OPERATOR_BINARY.Contains(mstrText) Then
- mintArguments = 2 ' binärer Operator
- ElseIf OPERATOR_UNARY.Contains(mstrText) Then
- mintArguments = 1 ' unärer Operator
- End If
- ' Operatorenrang bestimmen
- mintOpRank = GetOpRank(mstrText)
- If mintOpRank > ERR_BASE Then ' "(" - nur öffnende Klammer
- ' Das ist ein Name...
- ' Sollte nicht auftreten, weil Namen von Symbolen noch in der Postfix-Notations-Phase
- ' durch ihre Werte ersetzt werden, also Fehler
- intError = mintOpRank
- End If
- End If
- Return intError
- End Function
- ' Operatorenrang ermitteln
- Private Function GetOpRank(ByVal strOperator As String) As Integer
- Dim intOpRank As Integer = ERR_VALF_SYNTAX ' Wenn nicht gefunden, stimmt was nicht
- Dim intIdx As Integer = 0
- For intIdx = 0 To mstrOperators.GetUpperBound(0)
- If mstrOperators(intIdx).Contains(strOperator) Then
- intOpRank = intIdx
- Exit For
- End If
- Next intIdx
- Return intOpRank
- End Function
- ' Operatoren nach Rangfolge
- Private Sub LoadOperatorList()
- mstrOperators(0) = OPERATOR_RANK_0
- mstrOperators(1) = OPERATOR_RANK_1
- mstrOperators(2) = OPERATOR_RANK_2
- mstrOperators(3) = OPERATOR_RANK_3
- mstrOperators(4) = OPERATOR_RANK_4
- mstrOperators(5) = OPERATOR_RANK_5
- mstrOperators(6) = OPERATOR_RANK_6
- mstrOperators(7) = OPERATOR_RANK_7
- mstrOperators(8) = OPERATOR_RANK_8
- mstrOperators(9) = OPERATOR_RANK_9
- mstrOperators(10) = OPERATOR_RANK_10
- End Sub
- Public Sub New()
- LoadOperatorList()
- End Sub
- End Class
In ValfDeclares sind die Regeln hardcodiert, wie die Operatorenränge (Punkt vor Strich) und welcher Operator wieviele Argumente hat. Wenn das Gerüst erstmal steht, ist es auch recht einfach boolesche Operatoren nachzurüsten. Hier habe ich mit Ausrufezeichen und WICHTIG!! WICHTIG!! nicht gespart, weil eine Änderung hier die Rechenregeln kaputt macht.
VB.NET-Quellcode
- #Region " !!! WICHTIG !!! Operatoren-Rang und unäre/binäre Operatoren"
- ' Diese Konstanten legen die mathematisch korrekte Ausführung der Berechnungen fest!
- '!!! - Unäre und Binäre Operatoren - !!! - WICHTIG- !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!!
- '!!! - Operatore-Rangfolge - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!!
- ' Rückgabe am Ende des Terms
- Protected Const END_OF_TERM As String = "#EOT"
- ' Festlegung der Operatorenränge nach Zugehörigkeit zu diesen Gruppen von Zeichen
- Protected Const OPERATOR_RANK_0 As String = SPECIAL_OPEN_BRACKET
- Protected Const OPERATOR_RANK_1 As String = "<=>" ' Vergleichsoperatoren
- Protected Const OPERATOR_RANK_2 As String = "%" ' or
- Protected Const OPERATOR_RANK_3 As String = "&" ' and
- Protected Const OPERATOR_RANK_4 As String = "NOT"
- Protected Const OPERATOR_RANK_5 As String = "+" & SPECIAL_MINUS ' Addition und Subtraktion(+-)
- Protected Const OPERATOR_RANK_6 As String = "*/" ' Multiplikation und Division
- Protected Const OPERATOR_RANK_7 As String = "^" ' Potenz
- Protected Const OPERATOR_RANK_8 As String = "SINCOSTANATNEXPSQR" ' Funktionen (Sin, Cos, etc)
- Protected Const OPERATOR_RANK_9 As String = SPECIAL_CLOSE_BRACKET
- Protected Const OPERATOR_RANK_10 As String = END_OF_TERM
- Protected Const MAX_OPERATOR_RANK As Integer = 10
- ' Unäre Operatoren (mit einem Argument)
- Protected Const OPERATOR_UNARY As String = OPERATOR_RANK_4 & OPERATOR_RANK_8
- ' Binäre Operatoren (mit zwei Argumenten)
- Protected Const OPERATOR_BINARY As String = OPERATOR_RANK_1 & OPERATOR_RANK_2 & OPERATOR_RANK_3 & OPERATOR_RANK_5 & OPERATOR_RANK_6 & OPERATOR_RANK_7
- '!!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!! - WICHTIG - !!!
- #End Region
Nach der Vorarbeit ist die eigentliche Berechnung stures Abarbeiten der PostFix-Tokenliste. Zahlen kommen auf den Ergebnis-Stack. Operatoren holen entweder ein oder zwei Argumente vom Stack, berechnen sie und das Ergebnis geht auf den Stack: Wenn das Ende des Terms erreicht ist, liegt das Endergebnis hoffentlich als einziger Wert auf dem Stack. Hier der Code dazu:
VB.NET-Quellcode
- ' Ausrechnen des Terms
- Friend Function Calculate(ByRef dblResult As Double, Optional ByVal dblVarValue As Double = Double.NaN) As Integer
- Dim intError As Integer = ERR_SUCCESS
- ' Das Token aus der PostFix-Liste
- Dim dblTokenValue As Double = 0.0 ' Wert des Tokens
- Dim strTokenText As String = "" ' Text des Tokens
- Dim intTokenArguments As Integer = 0 ' Anzahl Argumente des Tokens (wenn 0, dann Zahl)
- Dim dblCalcStack As New Stack(Of Double) ' Rechenstack für Zwischenergebnisse
- Dim dblRightArg As Double = 0.0 ' Argument rechts vom Operator
- Dim dblLeftArg As Double = 0.0 ' Argument links vom Operator
- If mintTermStatus >= STATUS_PROCESSED Then
- ' Stacks initialisieren
- dblCalcStack.Clear()
- ' Wert der Laufvariablen eintragen, wenn diese angegeben wurde
- If Not Double.IsNaN(dblVarValue) Then
- SetControlVarValue(dblVarValue)
- End If
- ' Elementeliste auf Anfang
- SetPostFixTermToStart()
- Do ' ------- Loop Until -----
- 'Nächstes PostFix-Token holen
- GetNextPostFixToken(dblTokenValue, strTokenText, intTokenArguments)
- If (strTokenText <> END_OF_TERM) Then
- If (intTokenArguments = 0) Then
- ' Token ist Zahl, kommt auf den Stack
- dblCalcStack.Push(dblTokenValue)
- ElseIf dblCalcStack.Count >= intTokenArguments Then
- ' Token ist ein Operator mit einem oder zwei Argumenten
- If (intTokenArguments = 1) Then
- dblRightArg = dblCalcStack.Pop
- dblLeftArg = 0.0
- ElseIf (intTokenArguments = 2) Then
- dblRightArg = dblCalcStack.Pop
- dblLeftArg = dblCalcStack.Pop
- End If
- ' Ausrechnen
- intError = Calc(strTokenText, dblRightArg, dblLeftArg, dblResult)
- If (intError < ERR_CRITICAL) Then
- ' Ergebnis auf den Stack, auch wenn mathematisch nicht definiert
- ' oder Überlauf etc.
- dblCalcStack.Push(dblResult)
- End If
- Else
- intError = ERR_VAR_STACK_UNDERFLOW
- End If
- End If
- Loop Until (strTokenText = END_OF_TERM) Or (intError > ERR_SUCCESS)
- If (intError < ERR_CRITICAL) Then
- ' Ergebnis vom Stack holen
- If CBool(dblCalcStack.Count) Then
- dblResult = dblCalcStack.Pop
- Else
- intError = ERR_VAR_STACK_UNDERFLOW
- End If
- End If
- Else
- intError = ERR_TERM_NOT_LOADED
- End If
- If intError < ERR_CRITICAL Then
- ' Berechnet ohne syntaktiche Fehler
- mintTermStatus = STATUS_CALCULATED
- Else
- mintTermStatus = STATUS_ERROR
- End If
- mintErrorCode = intError
- Return intError
- End Function
Manches erscheint vielleicht umständlich, aber es geht mir auch um Lesbarket des Codes.
Was man damit so machen kann, dazu im nächsten Post.
Gruß
CarbonunitDieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Carbonunit“ ()
-
Weiter gehts.
Wichtig für die Mathe-Hausaufgaben waren die Ableitungen der Funktion. Die werden gebraucht, um Verschiedenes über die Funktion herauszufinden. Schon wenn man sie zeichnet, wird Manches klarer:
Hier habe ich die Original-Funktion (FXC) und deren erste Abletung (FXC'), sowie die zweite Ableitung (FXC'') dargestellt. Die Ableitung zeigt immer die Steigung der Ausgangsfuntion an. Die Lage von Nullstellen in der Ableitung gibt wichtige Informationen über die Ausgangsfunktion.
Die Berechnung erfolgt mit dem Vorwärtsdifferenzquotienten nach der Formel: f'(x) = (f(x+dx) -f(x)) / dx
Unschwer zu erkennen, dass man durch nochmalige Anwendung der Formel auf das Ergebnis auch die zweite Ableitung berechnen kann.
Hier der Code dazu:
VB.NET-Quellcode
- ' Berechnet die erste und zweite Ableitung der Funktion mit dem Vorwärtsdifferenzquotienten
- Friend Function Derivation(ByVal intLevel As Integer, ByVal dblX As Double, ByRef dblResult As Double) As Integer
- ' intLevel : Level der Ableitung: 0 = f(x), 1 = f'(x), 2 = f''(x) , >=3 = Fehler
- ' dblX : Wert für X, an dem die Ableitung gebildet wird
- ' dblResult : Ergebnis
- ' Rückgabe : Fehlercode
- Dim intError As Integer = ERR_SUCCESS
- Dim dblY As Double = 0.0 ' Zwischenergebnisse
- Dim dblYdx As Double = 0.0
- Select Case intLevel
- Case 0 ' f(x)
- If IsConcat() Then
- intError = CalcConcat(dblX, dblResult)
- Else
- intError = Calculate(dblResult, dblX)
- End If
- Case 1 ' 1. Ableitung: f'(x) = (f(x+dx1) - f(x)) / dx1
- If IsConcat() Then
- intError = CalcConcat(dblX + mdblDelta1, dblYdx)
- Else
- intError = Calculate(dblYdx, dblX + mdblDelta1)
- End If
- If intError = ERR_SUCCESS Then
- If IsConcat() Then
- intError = CalcConcat(dblX, dblY)
- Else
- intError = Calculate(dblY, dblX)
- End If
- dblResult = (dblYdx - dblY) / mdblDelta1
- End If
- Case 2 ' 2. Ableitung: f''(x) = (f'(x+dx2) - f'(x)) / dx2
- ' Rekursion !
- intError = Derivation(1, dblX + mdblDelta2, dblYdx)
- If intError = ERR_SUCCESS Then
- intError = Derivation(1, dblX, dblY)
- dblResult = (dblYdx - dblY) / mdblDelta2
- End If
- Case Else ' Höhere Ableitung nicht implementiert, die Werte für Delta wären dann zu groß
- ' und es wird ungenau.
- intError = ERR_DERIVATION_LEVEL_TOO_HIGH
- End Select
- Return intError
- End Function
mdblDelta1 und mdblDelta2 sind Werte zwischen 10^-7 und 10^-12 für Delta1 (erste Ableitung), sowie 10^-1 und 10^-6 für Delta2 (zweite Ableitung). Vor allem bei der zweiten Ableitung darf das Delta nicht mehr zu klein sein, die Kurve franst dann schnell aus. Aber beide Ableitungen liefern die nötigen Informationen. Die Nullstellen der ersten Ableitung zeigen Minima und Maxima der Funktion an, die Nullstellen der zweiten Ableitung zeigen Wendepunkte der Funktion an. Der Wendepunkt ist dort, wo sich die Richtung der Kurve ändert. Wenn man sich vorstellt, man würde die Kurve entlangradeln, muss man am Wendepunkt aufhören nach links zu lenken, ab da lenkt man nach rechts (oder umgekehrt...).
Die Nullstellen suche ich nach der Halbierungsmethode. Das funktioniert so: Es gibt einen Startwert (meinetwegen +1,0) und ein Intervall (+/-0,5). Jetzt berechne ich den Wert der Funktion oder der Ableitung für den Startwert und das Intervall um den Startwert herum, also für x=0,5, x=1,0 und x=1,5. Wenn sich irgendwo zwischen diesen Punkten das Vorzeichen ändert, liegt dort eine Nullstelle. Nehmen wir an, das ist zwischen x=1,0 und x=1,5. Jetzt ist mein neuer Startwert x=1,25 und das Intervall ist +/-0,25. Damit beginnt das Spiel von Neuem, bis das Ergebnis für f(x) unter einem vorher eingestellten Grenzwert liegt, in meinem Fall 10^-14.
Auch dazu der Code:
VB.NET-Quellcode
- ' Nullstellensuche mit dem Halbierungsverfahren
- Friend Function Bisection(ByVal dblXStart As Double, _
- ByVal intDerivationLevel As Integer, _
- ByRef dblXResult As Double, _
- ByRef dblYResult As Double, _
- ByRef intIterations As Integer) As Boolean
- Dim blnFound As Boolean = False
- Dim intError As Integer = ValfDeclares.ERR_SUCCESS
- Dim blnStop As Boolean = False ' True, wenn im angegebenen Bereich keine Nullstelle existiert
- Dim dblInterval As Double = mdblBisectionInterval
- Dim dblYStart As Double = 0.0
- Dim dblXMinus As Double = dblXStart - dblInterval
- Dim dblYMinus As Double = 0.0
- Dim dblXPlus As Double = dblXStart + dblInterval
- Dim dblYPlus As Double = 0.0
- With FPlotMain.gobjFunctions.ActiveFunction
- intIterations = 0
- Do
- intError = .Derivation(intDerivationLevel, dblXStart, dblYStart)
- If intError = ValfDeclares.ERR_SUCCESS Then
- blnFound = CheckBisectionResults(dblXStart, dblYStart, dblXResult, dblYResult)
- intError = .Derivation(intDerivationLevel, dblXMinus, dblYMinus)
- If intError = ValfDeclares.ERR_SUCCESS Then
- blnFound = CheckBisectionResults(dblXMinus, dblYMinus, dblXResult, dblYResult)
- intError = .Derivation(intDerivationLevel, dblXPlus, dblYPlus)
- If intError = ValfDeclares.ERR_SUCCESS Then
- blnFound = CheckBisectionResults(dblXPlus, dblYPlus, dblXResult, dblYResult)
- If Not blnFound Then
- ' Noch nicht gefunden, Interval halbieren
- dblInterval = dblInterval / 2
- If Math.Sign(dblYStart) <> Math.Sign(dblYMinus) Then
- ' Im negativen Bereich liegt eine Nullstelle
- dblXStart = dblXStart - dblInterval
- ElseIf Math.Sign(dblYStart) <> Math.Sign(dblYPlus) Then
- ' Im positiven Bereich liegt eine Nullstelle
- dblXStart = dblXStart + dblInterval
- Else
- ' Keine Nullstelle im untersuchten Bereich, Suche abbrechen
- Dim strText As String = "Im angegebenen Bereich befindet sich "
- blnStop = True
- Select Case intDerivationLevel
- Case 0
- strText = strText & "keine Nullstelle."
- Case 1
- strText = strText & "kein Minimum oder Maximum."
- Case 2
- strText = strText & "kein Wendepunkt."
- End Select
- MessageBox.Show(strText)
- End If
- dblXMinus = dblXStart - dblInterval
- dblXPlus = dblXStart + dblInterval
- End If
- End If
- End If
- End If
- intIterations += 1
- Loop Until blnFound Or blnStop Or (intError <> ValfDeclares.ERR_SUCCESS)
- If CBool(intError) Then
- blnFound = False
- MessageBox.Show(FPlotMain.gobjFunctions.GetErrorMessage(intError))
- End If
- If blnFound And CBool(intDerivationLevel) Then
- ' Wert für f(x) ausgeben, wenn es sich um Minima/Maxima oder Wendepunkte handelt
- intError = .Derivation(0, dblXResult, dblYResult)
- End If
- End With
- Return blnFound
- End Function
- ' Resultate der Halbierungsmethode auf Unterschreitung des Limits prüfen und Ergebnis merken
- Private Function CheckBisectionResults(ByVal dblXNew As Double, ByVal dblYNew As Double, ByRef dblXResult As Double, ByRef dblYResult As Double) As Boolean
- Dim blnFound As Boolean = False
- Dim dblLimit As Double = mdblBisectionLimit
- blnFound = Math.Abs(dblYNew) < dblLimit ' Ergebnis klein genug?
- If blnFound Then ' Ergebnis merken
- dblXResult = dblXNew
- dblYResult = dblYNew
- End If
- Return blnFound
- End Function
Gruß
Carbonunit -
Hallo,
momentane Baustelle ist eine kleine Makrosprache, die in meinem Funktionsplotter Aufgaben automatisieren soll. Dabei habe ich gar nicht den Ehrgeiz, die Grenzen der objektorientierten Programmierung zu erweitern. Viel eher will ich ein bisschen nostalgisch werden und mit GOSUB und Sprungmarken hantieren. Es gibt aber keine Zeilennnummern und ein unbedingter Sprung mit GOTO ist auch nicht vorgesehen. Damit kann man einfach zu viel Unsinn anrichten.
Den Formelinterpreter habe ich um ein paar boolesche Operatoren, wie Vergleiche, AND, OR und NOT erweitert. Das war nicht weiter schwierig, die Operatoren müssen den richtigen Rang bekommen, also NOT vor AND vor OR vor Vergleichen und alles im Rang unterhalb von Plus und Minus. Fest verdrahtet werden muss dann noch die eigentliche Berechnung der Operatoren und schon funktionieren auch boolesche Ausdrücke. Das ist wichtig für einfache Kontrollstrukturen, mit denen ich angefangen habe. Dazu gibt es einen Debugger, um die Makros auch testen zu können, ohne im Debugger von VB jedes Makro im Debugger des Debuggers zu debuggen. Der sieht so aus:
Anders, als noch beim guten alten GW-BASIC, an dem ich mich etwas orientiere, muss man bei meiner Sprache die Variablen einmal deklarieren. Numerische Variable, wie NUMD (Double), NUMI (Integer) oder BOOL (Boolean) nimmt der Formelinterpreter in der Symbol-Liste auf. Das sind im Hintergrund alles Double, darum werde ich die Typprüfungen auch nicht sehr streng machen. Strings (Datentyp TEXT) erfordern eine eigene Verwaltung. Zeichenketten aneinanderhängen funktioniert schon. Für Stringvergleiche wird es eine Funktion geben, deren Ergebnis in einer BOOL-Variablen landet und dann weiterverarbeitet werden kann.
Wer die Funktion direkt in einem mathematischen Ausdruck aufrufen will, wird wohl den Fehler "Formula too complex" ernten, an den ich mich auch noch beim Spaghetti-Coding erinnern kann.
Die Kommentare in dem obigen Code sind natürlich sinnfrei, sollen aber hier als Test dienen, ob die bei der Ausführung wirklich ignoriert werden. Der Interpreter ist recht primitiv. Was er zu tun hat, sagt ihm der erste Begriff in der Codezeile. Das muss immer ein Schlüsselwort sein, auch bei Variablenzuweisungen. Ganz alte BASIC-Dialekte hatten da auch noch ein LET, man musste also schreiben LET a%=3, statt a%=3. Bei GW-BASIC konnte man das LET schon weglassen, aber es hat noch zum Sprachumfang gehört.
Mein Debugger kann Haltepunkte setzen, ganz einfach durch Voranstellen von "<b>" in der Codezeile. Dann schaltet der Debugger in den Einzelschrittmodus. Ich kann auch einen bedingten Haltepunkt formulieren, also z.B. "IndexO=2", um das Programm anzuhalten, wenn der Index der äußeren Schleife 2 beträgt.
Subs und Functions gibt es nicht. Als Kontrollstrukturen sind bis jetzt IF..THEN..[ELSE]..END IF, LOOP..UNTIL, WHILE..WEND, FOR..TO..[STEP]..NEXT und GOSUB..RETURN vorgesehen. Dazu kommt nach CHAIN als fast vergessenes Relikt aus GW-BASIC. Dieser Befehl lädt neuen Code unter Beibehaltung der bisher verwendeten Variablen. Damit nichts durcheinander kommt, darf CHAIN aber nicht ausgeführt werden, so lange das aktuell laufende Programm noch Adressen auf dem Rücksprungstapel hat. Diese Adressen sind natürlich nur Zeilennummern, meine MacroEngine macht intensiven Gebrauch von den schönen List(Of T)-Arrays.
Eine Fehlerbehandlung wird durch eine feste Sprungmarke definiert. Dorthin springt die Ausführung im Fehlerfall und man kann versuchen, den Fehler zu behandeln oder sich freundlich vom User verabschieden. Bei nichtbehandeten Fehlern soll auch der Debugger aufgerufen werden.
Wenn ich die String-Funktionen etabliert habe (Alte Bekannte wie INSTR, MID, VAL, RIGHT, LEFT, usw.) werde ich Funktionalitäten aus F-Plot selbst aufrufbar machen. So soll das Makro Dialoge öffnen können, die Textfelder mit Werten füllen, die Berechnung starten und das Ergebnis speichern können.
Das alles ist als Spaß-Projekt gedacht und ich habe damit gar nicht den Ehrgeiz, technisch an vorderster Front zu stehen. Dafür müsste ich wohl zuerst die sieben Jahre alte Entwicklungsumgebung (VB-Express 2010) austauschen. Wobei ich natürlich weitere Innovationen auch nicht ausschließe. Je nachdem, wie es mich interessiert.
Gedacht ist die Sprache für die Automatisierung von Abläufen innerhalb des Funktionsplotters F-Plot, meinetwegen das Laden und Zeichnen von zehn Funktionen in zehn verschiedenen Fenstern. Natürlich lädt das zu Spaghetti-Programmierung ein, was ja auch mal wieder ganz spaßig sein kann. Ich habe damals jedenfalls gerne auch mal mit GW-BASIC herumgespielt und ergreife jetzt die Gelegenheit, mir mein Spielzeug selbst zusammenzubauen.
Gruß
Carbonunit
-
Ähnliche Themen
-
9 Benutzer haben hier geschrieben
- Carbonunit (35)
- ErfinderDesRades (14)
- faxe1008 (5)
- VB3-Guru (1)
- VB1963 (1)
- Trade (1)
- RushDen (1)
- Gonger96 (1)
- Gast (1)