Swing Graphikrechner

visar77

Mitglied
Ich versuche einen Graphikrechner zu programmieren (sowas wie Geogebra nur viel einfacher), welches mathematische Funktionen mittels Graphics2D zeichnet. Dies klappt problemlos. Problem ist nur, dass der Rechner selber herausfinden soll, wo die Nullstellen liegen. Dabei stoß auf ein Problem mit double, da diese ja nicht null sein können und ich immer runden muss, wodurch ich aber zigfache Nullstellen als Output bekomme. Hier der spezielle Code für die Zeichnung des Graphen:
Java:
//Funktionsgraphen
        g2d.setStroke(new BasicStroke(2));
        int Genauigkeit = 10000;
        for(int x = (-16*Genauigkeit); x<(16*Genauigkeit); x++) {
            //y ist die Funktion
            double y = Math.sin(x);
            /* Probleme:
             * -exponentialfunktionen
             * -sinus und cosinus
             * -nicht alle Nullstellen werden erkannt
             */
            //Da x sehr groß ist und somit nicht bei der Zeichnung der Funktion eingesetzt werden kann, verwendete ich x2
            double x2 = x/(Genauigkeit/1.0);
            if(y == 0) {
                System.out.println("Nullstelle bei:"+x2);
            }
            //Das selbe gilt mit y2
            double y2 = Math.sin(x2);
            
            g2d.setColor(Color.red);
            //Diese Zeile zeichnet den Graphen
            g2d.draw(new Line2D.Double(breite/2+x2*(höhe/20), höhe/2-y2*(höhe/20),breite/2+x2*(höhe/20), höhe/2-y2*(höhe/20)));
        }
Für die, die mein Code ausführen wollen:
Java:
import java.awt.*;
import java.awt.geom.Line2D;

import javax.swing.JFrame;
public class Main extends Canvas{

    private static final long serialVersionUID = 4648172894076113183L;
    private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    private static double breite = screenSize.getWidth();
    private static double höhe = screenSize.getHeight();
    public static final double MIN_VALUE = 4.94065645841246544e-324;
    
    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;

        //x-Achse
        Polygon dreieck = new Polygon();
        dreieck.addPoint((int)breite/2, 0);
        dreieck.addPoint(((int)breite/2)-60,40);
        dreieck.addPoint(((int)breite/2)+60,40);
        g2d.setColor(Color.black);
        g2d.fill(dreieck);
        g2d.setStroke(new BasicStroke(4));
        g.setColor(Color.black);
        g.drawLine((int)breite/2, 42, (int)breite/2,(int) höhe);
        
        //y-Achse
        Polygon dreieck2 = new Polygon();
        dreieck2.addPoint((int)breite -15,(int) höhe/2);
        dreieck2.addPoint((int)breite -60,(int)höhe/2 -60);
        dreieck2.addPoint((int)breite -60,(int)höhe/2 +60);
        g2d.fill(dreieck2);
        g.setColor(Color.black);
        g.drawLine(0, (int) höhe/2, (int)breite-42,(int) höhe/2);
        
        
        //Nummerierung
        //x-Achse
        int xKoordinaten = 9;
        for(int i = (int) (höhe/20); i<(int) (höhe*19/20); i+=(int)höhe/20) {
            g2d.setStroke(new BasicStroke(3));
            g.drawLine((int)breite/2+10,i+3,(int)breite/2-10,i+3);
            g2d.setStroke(new BasicStroke(1));
            g.drawLine(0, i+3, (int)breite, i+3);
            if(xKoordinaten != 0) {
                g.setFont(new Font("Monospaced",Font.BOLD,20));
                g.drawString(""+xKoordinaten, (int)breite/2-43, i+10);
            }
            xKoordinaten -= 1;
            
        }
        //y-Achse positiv
        int yKoordinaten = 0;
        for(int i = (int) breite/2+3; i<(int) breite-40; i+=(int)höhe/20) {
            g2d.setStroke(new BasicStroke(3));
            g.drawLine(i-3,(int)höhe/2+10,i-3,(int)höhe/2-10);
            g2d.setStroke(new BasicStroke(1));
            g.drawLine(i-3, 0, i-3, (int)höhe);
            if(yKoordinaten != 0) {
                g.setFont(new Font("Monospaced",Font.BOLD,20));
                g.drawString(""+yKoordinaten, i-7, (int) höhe/2+39);
            }
            yKoordinaten += 1;
            
        }
        //y-Achse negativ
        int yminusKoordinaten = 0;
        for(int i = (int) breite/2+3; i>40; i-=(int)höhe/20) {
            g2d.setStroke(new BasicStroke(3));
            g.drawLine(i-3,(int)höhe/2+10,i-3,(int)höhe/2-10);
            g2d.setStroke(new BasicStroke(1));
            g.drawLine(i-3,0,i-3,(int)höhe);
            if(yminusKoordinaten != 0) {
                g.setFont(new Font("Monospaced",Font.BOLD,20));
                g.drawString(""+yminusKoordinaten, i-7, (int) höhe/2+39);
            }
            yminusKoordinaten -= 1;
                    
        }
        
        
        //Funktionsgraphen
        g2d.setStroke(new BasicStroke(2));
        int Genauigkeit = 10000;
        for(int x = (-16*Genauigkeit); x<(16*Genauigkeit); x++) {
            //y ist die Funktion
            double y = Math.sin(x);
            /* Probleme:
             * -exponentialfunktionen
             * -sinus und cosinus
             * -nicht alle Nullstellen werden erkannt
             */
            //Da x sehr groß ist und somit nicht bei der Zeichnung der Funktion eingesetzt werden kann, verwendete ich x2
            double x2 = x/(Genauigkeit/1.0);
            if(y == 0) {
                System.out.println("Nullstelle bei:"+x2);
            }
            //Das selbe gilt mit y2
            double y2 = Math.sin(x2);
            
            g2d.setColor(Color.red);
            //Diese Zeile zeichnet den Graphen
            g2d.draw(new Line2D.Double(breite/2+x2*(höhe/20), höhe/2-y2*(höhe/20),breite/2+x2*(höhe/20), höhe/2-y2*(höhe/20)));
        }
        
            
    }
    
    public static void main(String[] args) {
        JFrame f = new JFrame("Graphikrechner by Visar (Genius) Lumi");
        Main m = new Main();
        f.getContentPane().add(m, BorderLayout.CENTER);
        m.addKeyListener(new KeyInput());
        f.setSize(new Dimension((int)breite,(int)höhe));
        f.setVisible(true);

    }

}
 

visar77

Mitglied
Es gibt selbstverständlich sehr viel bessere numerische Verfahren, um für einige Klassen von Funktionen die Nullstellen zu finden, statt jede einzelne Stelle der Funktion abzutasten: https://en.wikipedia.org/wiki/Root-finding_algorithms
Zuverlässig alle Nullstellen jeder möglichen Funktion zu finden, ist nicht möglich.
Es geht tatsächlich, wenn du Websiten wie Geogebra (link: https://www.geogebra.org/graphing) oder so anschaust, geben sie dir die Nullstellen auf 10 oder so Nachkommastellen heraus, dies liegt höchstwahrscheinlich nicht nur an irgendwelchen Algorithmen.
 

httpdigest

Top Contributor
Es geht tatsächlich, wenn du Websiten wie Geogebra (link: https://www.geogebra.org/graphing) oder so anschaust, geben sie dir die Nullstellen auf 10 oder so Nachkommastellen heraus, dies liegt höchstwahrscheinlich nicht nur an irgendwelchen Algorithmen.
Ich habe nicht behauptet, dass es überhaupt nicht geht, sondern, dass das Finden von Nullstellen analytisch nur für eine sehr begrenzte Klasse von Funktionen (z.B. nur Polynomen bis zum Grad 4) möglich ist und alles andere immer durch numerische Näherungsalgorithmen gelöst wird: https://wiki.geogebra.org/en/Roots_Command
Because this algorithm is numeric, it may not find all the roots in some cases.
Selbstverständlich ist der Algorithmus, den Geogebra für nicht-Polynome oder Polynome höher als Grad 4 nutzt, ein numerischer (es geht nicht anders) und sehr wahrscheinlich das Newton-Raphson-Methode. Und diese findet prinzipiel nicht immer alle Nullstellen.
Nicht zuletzt sei zu erwähnen, dass es sich bei Geogebra auch um ein Computeralgebrasystem handelt. Hier zu glauben, dass keine sehr elaborierte Algorithmen im Spiel sind, ist einfach nur sehr naiv.
Dein Verfahren ist natürlich auch ein Algorithmus, nur eben ein sehr ineffizienter.

Bitte lies dir den von mir oben referenzierten Wikipedia-Artikel durch. Dort wird alles erklärt.

EDIT:
Eine Beispielfunktion, die sogar ein Polynom ist, für die der Geogebra Graphing Calculator ungültige Nullstellen berechnet: f(x) = x^8 - x^7. Diese Funktion hat mathematisch exakt genau zwei Nullstellen bei x=0 und x=1.
Der Geogebra Graphing Calculator meldet jedoch vier Nullstellen. Drei um x=0 herum (aber niemals bei x=0) und eine korrekt bei x=1.
Die Anzahl an falsch ermittelten Nullstellen nimmt zu, wenn du die Exponenten jeweils immer weiter um 2 erhöhst, obwohl sich dadurch die echten Nullstellen der Funktion nicht verändern. Dieses Verhalten ist inhärent in dem auf Näherung basierenden numerischen Verfahren.
 
Zuletzt bearbeitet:
G

Gelöschtes Mitglied 9001

Gast
Direkt auf y = 0 testen geht tatsächlich nicht, weil nicht unendlich genau gerechnet werden kann. Du kannst dich, wie schon geschrieben wurde, mit den verschiedenen Iterationsverfahren beschäftigen.
Eine sehr einfache Möglichkeit ist zunächst der Test, ob die Funktion die x-Achse gekreuzt hat, also ob sich das Vorzeichen von y gegenüber dem vorherigen Schritt geändert hat. Dann weißt du, dass sich zwischen x und dem vorherigen x mindestens 1 Nullstelle befinden muss, sofern (wichtig) die Funktion für alle x dazwischen definiert ist. Auf diese Weise findest du freilich nicht die Nullstellen, bei denen die x-Achse nicht gekreuzt wird, also wenn z.B. ein lokales Minimum auf der x-Achse liegt. Dafür brauchst du dann die erste Ableitung, die sich auch numerisch ermitteln läßt. Dann ist es aber zum Newton-Verfahren auch nicht mehr weit.
 

mihe7

Top Contributor
Dazu braucht man noch nicht mal die Ableitung, wenn man sich mit einem langsameren Verfahren zufrieden gibt. Das Bisektionsverfahren ist sehr einfach zu implementieren, weil man - wie bei der binären Suche - immer die Hälfte des noch verbleibenden Intervalls ausschließt.
 

visar77

Mitglied
Ne, das ist Zauberei, in jedem Fall irgendwas mit dunkler Materie, die Stephen Wolfram vor Jahren entdeckt haben muss.
Aha, sehr hilfreich.
Direkt auf y = 0 testen geht tatsächlich nicht, weil nicht unendlich genau gerechnet werden kann. Du kannst dich, wie schon geschrieben wurde, mit den verschiedenen Iterationsverfahren beschäftigen.
Eine sehr einfache Möglichkeit ist zunächst der Test, ob die Funktion die x-Achse gekreuzt hat, also ob sich das Vorzeichen von y gegenüber dem vorherigen Schritt geändert hat. Dann weißt du, dass sich zwischen x und dem vorherigen x mindestens 1 Nullstelle befinden muss, sofern (wichtig) die Funktion für alle x dazwischen definiert ist. Auf diese Weise findest du freilich nicht die Nullstellen, bei denen die x-Achse nicht gekreuzt wird, also wenn z.B. ein lokales Minimum auf der x-Achse liegt. Dafür brauchst du dann die erste Ableitung, die sich auch numerisch ermitteln läßt. Dann ist es aber zum Newton-Verfahren auch nicht mehr weit.
Klingt gut, da ich bei der Bestimmung von Nullstellen, die die x-Achse berühren, keine Probleme habe.
Dazu braucht man noch nicht mal die Ableitung, wenn man sich mit einem langsameren Verfahren zufrieden gibt. Das Bisektionsverfahren ist sehr einfach zu implementieren, weil man - wie bei der binären Suche - immer die Hälfte des noch verbleibenden Intervalls ausschließt.
Das Bisektionsverfahren klappt auch nicht immer, deswegen würde ich eher andere Algorithmen nutzen. Zudem bin ich mir nicht sicher ob die Bestimmung von Nullstellen bei einer trigonometrischen Funktion mit solchen Algorithmen überhaupt funktionieren würde.
 

mihe7

Top Contributor
Das Bisektionsverfahren klappt auch nicht immer,
Du brauchst ein Intervall, in dem die Funktion stetig ist und einen (1) Nullpunkt (Funktionswert) enthält. Dann klappt das. Die Stetigkeit brauchst Du immer. Beim Newton-Verfahren musst Du dagegen recht nah am Nullpunkt beginnen, sonst läufst Du Gefahr, dass Du über den Nullpunkt "hinausschießt" oder das Oszillieren beginnst. Beim Bisektionsverfahren kannst Du die Genauigkeit recht einfach vorab berechnen, da das Intervall ja in jedem Schritt halbiert wird.
 

visar77

Mitglied
Du brauchst ein Intervall, in dem die Funktion stetig ist und einen (1) Nullpunkt (Funktionswert) enthält. Dann klappt das. Die Stetigkeit brauchst Du immer. Beim Newton-Verfahren musst Du dagegen recht nah am Nullpunkt beginnen, sonst läufst Du Gefahr, dass Du über den Nullpunkt "hinausschießt" oder das Oszillieren beginnst. Beim Bisektionsverfahren kannst Du die Genauigkeit recht einfach vorab berechnen, da das Intervall ja in jedem Schritt halbiert wird.
Wie soll aber mein Programm diese Stetigkeit finden? Und nach welchen Kriterien soll er die Intervalle setzen?
 

httpdigest

Top Contributor
Wie soll aber mein Programm diese Stetigkeit finden?
Gar nicht. Man nimmt an, dass die zu untersuchende Funktion im betrachteten Intervall stetig ist. Ansonsten funktionieren die Algorithmen nicht.
Und nach welchen Kriterien soll er die Intervalle setzen?
Für "Bracketing"-basierte Verfahren z.B. suchst du zwei Stellen x0 und x1 der Funktion (z.B. durch zufälliges Wählen von x0 und x1), für die f(x0)*f(x1) < 0 ist. Oder mit anderen Worten, zwei Stellen, bei der die Funktion unterschiedliche Vorzeichen hat. Hier findest du dann aber auch nur Nullstellen von ungeradem Grad, also z.B. keine doppelten Nullstellen wie etwa bei f(x)=x² oder f(x)=(x-1)*(x-1).
Ich würde dir aber einfach generell empfehlen, ersteinmal Literatur zu dem Thema zu konsultieren.
Ich habe dir ja bereits einen Wikipedia-Artikel genannt. Weitere gute Quellen findest du z.B. durch die Google-Suche "find roots of function numerically".
Wenn man am Anfang eines Themas steht, bringt es eigentlich immer mehr bzw. ist es effektiver, sich ersteinmal selbst in die Materie einzulesen, statt in einem Forum nach Hilfe zu fragen.
 

visar77

Mitglied
Gar nicht. Man nimmt an, dass die zu untersuchende Funktion im betrachteten Intervall stetig ist. Ansonsten funktionieren die Algorithmen nicht.

Für "Bracketing"-basierte Verfahren z.B. suchst du zwei Stellen x0 und x1 der Funktion (z.B. durch zufälliges Wählen von x0 und x1), für die f(x0)*f(x1) < 0 ist. Oder mit anderen Worten, zwei Stellen, bei der die Funktion unterschiedliche Vorzeichen hat. Hier findest du dann aber auch nur Nullstellen von ungeradem Grad, also z.B. keine doppelten Nullstellen wie etwa bei f(x)=x² oder f(x)=(x-1)*(x-1).
Ich würde dir aber einfach generell empfehlen, ersteinmal Literatur zu dem Thema zu konsultieren.
Ich habe dir ja bereits einen Wikipedia-Artikel genannt. Weitere gute Quellen findest du z.B. durch die Google-Suche "find roots of function numerically".
Wenn man am Anfang eines Themas steht, bringt es eigentlich immer mehr bzw. ist es effektiver, sich ersteinmal selbst in die Materie einzulesen, statt in einem Forum nach Hilfe zu fragen.
Ja, du hast Recht. Ich werde mich mehr mit diesen Algorithmen verfassen, sollte ich noch ein paar Fragen haben, werde ich mich melden. Danke an alle für die zahlreichen Antworten.
 

Neue Themen


Oben