Unterabschnitte


A.2.9 Einfache Integration

Es werden drei verschiedene Lösungen vorgestellt, die sich in der eigentlichen Integration kaum unterscheiden. Allerdings ist die Trennung des Integrierens von der zu integrierenden Funktion jeweils anders gelöst:

(Anmerkung: allen drei Versionen gemeinsam ist die Tatsache, daß in der Schleife zum Aufsummieren der Teilflächen unnötigerweise eine Multiplikation mit der unveränderlichen Streifenbreite stattfindet. Das Programm ließe sich optimieren, indem in der Schleife nur die Funktionswerte addiert werden, und nach der Schleife dann mit der Intervalllänge multipliziert wird. Dies habe ich aus zwei Gründen unterlassen: zum einen ist die vorliegende Form näher an der Aufgabenstellung und damit vermutlich leichter zu verstehen; zum anderen kann zumindest ein guter C++-Compiler diese Optimierung auch selbständig durchführen.)


A.2.9.1 Java: Verwendung einer abstrakten Basisklasse

Hier wird in einer abstrakten Basisklasse einfache_integration die eigentliche Integration implementiert, aber die zu integrierende Funktion nur deklariert, also ihre Implementation weggelassen:

// Time-stamp: "27.05.02 23:11 einfache_integration.java klaus@lap2.wachtler.de"
//
// Lösung der Übungsaufgabe "Einfache Integration"
// (Variante mit einer abstrakten Basisklasse zum Integrieren, in
// deren Ableitungen dann die zu integrierende Funktion überschrieben
// wird)

abstract public class einfache_integration
{

  // zu integrierende Funktion; muß in einer abgeleiteten Klasse
  // überschrieben werden!
  abstract double f( double x );

  // Funktion, die eine Funktion f im Intervall [a,b] näherungsweise
  // integriert, indem das Intervall in gleichbreite Streifen etwa
  // der Breite dx zerlegt wird,
  // für jeden Streifen eine Höhe bestimmt wird (als Funktionswert in
  // der Streifenmitte), und die Flächen aller Streifen aufsummiert
  // werden.
  double integriere( double a, double b, double dx )
  {
    // Wenn a größer ist als b, dann vertauschen:
    if( a>b )
    {
      double tmp = a; a = b; b = tmp;
    }

    // Anzahl der Streifen im Intervall:
    int    AnzahlStreifen = (int)( ( b - a )/dx );

    // Dadurch, daß die Anzahl der Streifen ganzzahlig ist, kann es
    // vorkommen, daß AnzahlStreifen*dx nicht genau die Intervalllänge
    // ergibt, sondern etwas weniger. Deshalb dx anpassen:
    dx = ( b - a )/AnzahlStreifen;

    double Integral = 0.0;        // bisherige Summe
    double xMitte   = a + 0.5*dx; // x in der Mitte des aktuellen Streifens

    for( int i=0; i<AnzahlStreifen ; i++, xMitte+=dx )
    {
      Integral += dx*f( xMitte );
    }

    return Integral;
  }

}

Von einer solchen abstrakten Klasse kann kein Objekt erzeugt werden, weil die Implementation der Methode f( double ) noch fehlt.

Vielmehr muß der Aufrufer erst noch für jede zu integrierende Funktion eine Ableitung bilden, die dann in dieser Methode f( double ) definiert, was integriert werden soll:

// Time-stamp: "(23.05.02 12:11) test_einf_integr.java [Klaus@Wachtler.de]"
//
// Testprogramm zur Lösung der Übungsaufgabe "Einfache Integration"
// (Variante mit einer abstrakten Basisklasse zum Integrieren, in
// deren Ableitungen dann die zu integrierende Funktion überschrieben
// wird)
//
// javac test_einf_integr.java einfache_integration.java
// java test_einf_integr

import java.lang.Math;

// Integrieren der Sinusfunktion:
class einfache_integration_sin extends einfache_integration
{
    double f( double x )
    {
        return Math.sin( x );
    }
}

// Integrieren von x^2:
class einfache_integration_xmalx extends einfache_integration
{
    double f( double x )
    {
        return x*x;
    }
}

public class test_einf_integr
{
    public static void main( String[] args )
    {
        // Ein Stückchen Sinus integrieren:
        einfache_integration_sin Isin
            = new einfache_integration_sin();
        double integral_sin = Isin.integriere( 0.0, 3.14159365, 0.1 );
        System.out.println( "Isin.integriere( 0.0, 3.14159365, 0.1 ) = "
                            +
                            integral_sin
                            );

        // Quadratfunktion integrieren:
        einfache_integration_xmalx Ixmalx
            = new einfache_integration_xmalx();
        double integral_xmalx = Ixmalx.integriere( 0.0, 10.0, 0.1 );
        System.out.println( "Ixmalx.integriere( 0.0, 10.0, 0.1 ) = "
                            +
                            integral_xmalx
                            );
    }

}

Diese Vorgehensweise wirkt ziemlich umständlich; vor allem wenn mehrere Kombinationen aus Integrationsverfahren und zu integrierenden Funktionen durchgespielt werden sollen.


A.2.9.2 Java: Verwendung eines funktionalen Objekts

Durch die Verwendung eines funktionalen Objekts (oder auch Funktionsobjekts) gelingt eine bessere Trennung des Integrationsverfahrens von der zu integrierenden Funktion.

Dazu ist zuerst eine Klasse oder eine Schnittstelle nötig, mit der die zu integrierende Funktion beschrieben werden kann:

// Time-stamp: "30.05.02 13:49 FunktObj.java klaus@lap2.wachtler.de"
//
// Interface eines funktionalen Objekts zur Lösung der
// Übungsaufgabe "Einfache Integration" und anderer ähnlicher Probleme.

interface FunktObj
{
    double f( double x );
}
Ein Objekt dieses Typs ist das sogenannte funktionale Objekt, und muß die Methode double f( double x ) implementieren.

Das Integrationsverfahren muß die zu integrierende Funktion nicht kennen, sondern ruft einfach über das funktionale Objekt die darin enthaltene Methode auf:

// Time-stamp: "(23.05.02 14:58) einfache_integration.java [Klaus@Wachtler.de]"
//
// Lösung der Übungsaufgabe "Einfache Integration"
// (Variante mit der Übergabe eines funktionalen Objekts)

public class einfache_integration
{
  // Funktionales Objekt, dessen Methode double f( double x ) die
  // zu integrierende Funktion repräsentiert:
  FunktObj    funobj = null;


  // Dem Konstruktor wird ein funktionales Objekt übergeben, dessen
  // Methode f( double ) bestimmt, was integriert werden soll.
  einfache_integration( FunktObj f ) { funobj = f; }

  // Funktion, die eine Funktion f im Intervall [a,b] näherungsweise
  // integriert, indem das Intervall in gleichbreite Streifen etwa der
  // Breite dx zerlegt wird, für jeden Streifen eine Höhe bestimmt
  // wird (als Funktionswert in der Streifenmitte), und die Flächen
  // aller Streifen aufsummiert werden.
  double integriere( double a, double b, double dx )
  {
    // Wenn a größer ist als b, dann vertauschen:
    if( a>b )
    {
      double tmp = a; a = b; b = tmp;
    }

    // Anzahl der Streifen im Intervall:
    int    AnzahlStreifen = (int)( ( b - a )/dx );

    // Dadurch, daß die Anzahl der Streifen ganzzahlig ist, kann es
    // vorkommen, daß AnzahlStreifen*dx nicht genau die Intervalllänge
    // ergibt, sondern etwas weniger. Deshalb dx anpassen:
    dx = ( b - a )/AnzahlStreifen;

    double Integral = 0.0;        // bisherige Summe
    double xMitte   = a + 0.5*dx; // x in der Mitte des aktuellen Streifens

    for( int i=0; i<AnzahlStreifen ; i++, xMitte+=dx )
    {
      Integral += dx*funobj.f( xMitte );
    }

    return Integral;
  }

}

Erst beim Aufrufer werden das Integrationsverfahren und die zu integrierende Funktion (in Form des ,,funktionalen Objekts``) zusammengebracht:

// Time-stamp: "(23.05.02 16:31) test_einf_integr.java [Klaus@Wachtler.de]"
//
// Testprogramm zur Lösung der Übungsaufgabe "Einfache Integration"
// (Variante mit der Übergabe eines funktionalen Objekts)
//
// javac test_einf_integr.java FunktObj.java einfache_integration.java
// java test_einf_integr

import java.lang.Math;

// funktionales Objekt: Berechnet in f() die Sinusfunktion:
class Sinusfunktion implements FunktObj
{
    public double f( double x )
    {
        return Math.sin( x );
    }
}

// funktionales Objekt: Berechnet in f() die Funktion x^2:
class Quadratfunktion implements FunktObj
{
    public double f( double x )
    {
        return x*x;
    }
}

public class test_einf_integr
{
    public static void main( String[] args )
    {
        // Ein Stückchen Sinus integrieren:
        Sinusfunktion fSin = new Sinusfunktion();
        einfache_integration Isin
            = new einfache_integration( fSin );
        double integral_sin = Isin.integriere( 0.0, 3.14159365, 0.1 );
        System.out.println( "Isin.integriere( 0.0, 3.14159365, 0.1 ) = "
                            +
                            integral_sin
                            );

        // Quadratfunktion integrieren:
        Quadratfunktion fQuadrat = new Quadratfunktion();
        einfache_integration Ixmalx
            = new einfache_integration( fQuadrat );
        double integral_xmalx = Ixmalx.integriere( 0.0, 10.0, 0.1 );
        System.out.println( "Ixmalx.integriere( 0.0, 10.0, 0.1 ) = "
                            +
                            integral_xmalx
                            );
    }

}


A.2.9.3 C++: Funktionszeiger

Die beiden in Java beschriebenen Verfahren, das Integrieren von der zu integrierenden Funktion möglichst weit zu trennen, und zur eigentlichen Berechnung dann doch zusammenzuführen, sind in C++ auch möglichA.2. Sie erscheinen hier aber unnötig kompliziert: eigentlich geht es ja nur darum, zum Integrieren einmal die eine Funktion anzugeben und dann bei Bedarf eine andere. Das läßt sich in C und C++ viel einfacher bewerkstelligen, indem man einfach die zu integrierende Funktion (genauer gesagt: ihre Anfangsadresse im Speicher) als Parameter übergibt (siehe dazu auch [KW-C]):

// Time-stamp: "24.05.02 12:48 einfache_integration.cpp Administrator@AW41"
//
// Lösung der Übungsaufgabe "Einfache Integration"
//
// ANSI-C++
//
// Linux:    g++ -Wall einfache_integration.cpp && a.out
//
// VC++ 6.0: cl /Zi /GX einfache_integration.cpp
//           .\einfache_integration

#include <iostream>
using namespace std;

// Funktion, die eine Funktion f im Intervall [a,b] näherungsweise
// integriert, indem das Intervall in gleichbreite Streifen etwa der
// Breite dx zerlegt wird, für jeden Streifen eine Höhe bestimmt wird
// (als Funktionswert in der Streifenmitte), und die Flächen aller
// Streifen aufsummiert werden.
//
// Eingabeparameter:
// - double a:        erste Intervallgrenze
// - double b:        zweite Intervallgrenze
//                    (falls a>b ist, werden die beiden vertauscht)
// - double dx:       gewünschte Streifenbreite (wird intern so
//                    angepaßt, daß sich eine ganze Anzahl Streifen
//                    ergibt)
// - double( *f )    
//    ( double x ):   f ist ein Zeiger auf eine Funktion, die
//                    einen double-Parameter x entgegennimmt,
//                    und einen double-Wert zurückliefert:
//                    das ist die zu integrierende Funktion
//
// Rückgabewert:
// Näherung für das Integral
double integriere( double a,
                   double b,
                   double dx,
                   double( *f )( double x )
                   )
{
  // Wenn a größer ist als b, dann vertauschen:
  if( a>b )
  {
    double tmp = a; a = b; b = tmp;
  }

  // Anzahl der Streifen im Intervall:
  int    AnzahlStreifen = (int)( ( b - a )/dx );

  // Dadurch, daß die Anzahl der Streifen ganzzahlig ist, kann es
  // vorkommen, daß AnzahlStreifen*dx nicht genau die Intervalllänge
  // ergibt, sondern etwas weniger. Deshalb dx anpassen:
  dx = ( b - a )/AnzahlStreifen;

  double Integral = 0.0;        // bisherige Summe
  double xMitte   = a + 0.5*dx; // x in der Mitte des aktuellen Streifens

  // Jetzt kommt das Integrieren:
  for( int i=0; i<AnzahlStreifen ; i++, xMitte+=dx )
  {
    Integral += dx*f( xMitte );
  }

  return Integral;
}


//////////////////////////////////////////////////////////////////////
//
// Testprogramm:
//

// Quadratfunktion:
double xmalx( double x )
{
  return x*x;
}

#include <cmath> // für die Winkelfunktionen


#define PI 3.14159265


int main( int nargs, char **args )
{
  cout << "integriere( 0.0, PI, 0.1, sin )      = "
       << integriere( 0.0, PI, 0.1, sin )   // sin() aus math.h
       << endl;
  cout << "integriere( 0.0, PI/2, 0.1, cos )    = "
       << integriere( 0.0, PI/2, 0.1, cos ) // cos() aus math.h
       << endl;
  cout << "integriere( 0.0, 10.0, 0.1, xmalx )  = "
       << integriere( 0.0, 10.0, 0.1, xmalx )
       << endl;
  return 0;
} // main( int nargs, char **args )

Diese Vorgehensweise ist nicht nur schnell programmiert, sondern läuft auch unschlagbar schnell. Was will man mehr? Ach ja, es ist nicht so richtig objektorientiert...

www.wachtler.de