Blatt zu Kurs 12

Werbung
Computergrundkenntnisse und Programmieren, WS 07/08, Übung 12: Numerik der Standardbibliothek
Bei der Behandlung von physikalischen Problemen spielen numerische Rechnungen eine wichtige
Rolle. Die C++ Standardbibliothek bietet dazu einige nützlich Klassen die hier kurz vorgestellt werden. Zusätzlich ist es oft nötig Programme aus Bibliotheken zu verwenden. Dieses Thema wird im
letzten Kurs angesprochen.
Mathematische Standardfunktionen
Wir haben bereits einige Standardfunktionen aus der Headerdatei <cmath> verwendet. Hier eine
(fast) vollständige Liste aller darin enthaltenen Funktionen:
double
double
double
double
double
double
double
double
double
double
double
double
double
double
double
double
double
double
double
abs (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Absolutwert
ceil (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . Kleinster Integer nicht kleiner als x
floor (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . Größter Integer nicht größer als x
sqrt (double x)
. . . . . . . . . . . . . . . . . . . . . . . Quadratwurzel, x darf nicht negativ sein
pow (double x, double e)
. . . . . . . . . . . . . . . xe . Fehler, falls x == 0 und e <= 0,
oder falls x < 0 und e nicht integer.
pow (double x, int n)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xn .
sin (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sinus
cos (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cosinus
tan (double x) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tangens
asin (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arcus-Sinus
acos(double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arcus-Cosinus
atan (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arcus-Tangens
atan2 (double x, double y)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . atan(x/y)
sinh (double x) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sinus-hyperbolicus
cosh (double x) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cosinus-hyperbolicus
tanh (double x) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tangens-hyperbolicus
exp (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exponentialfunktion (Basis e)
log (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Logarithmus zur Basis e,
x muss größer Null sein.
log10 (double x)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Logarithmus zur Basis 10,
x muss größer Null sein.
Diese Funktionen sind auch für die Typen float und long definiert. Bei Aufruf mit beispielsweise
einem int Argument wird, wo möglich, in double konvertiert.
Die Technik, dass man einen Funktionsnamen mehrfach vergeben kann und der Compiler die
Funktionen anhand des Typs der Argumente identifiziert, nennt man Überladen von Funktionen. Offensichtlich ist dies bei den bereits vorgefertigten mathematischen Standardfunktionen sehr
praktisch. Sie können die Technik des Überladens aber auch für selbst geschriebene Funktionen
verwenden (siehe Aufgabe 12.2).
Ein anderes Beispiel wäre die Berechnung der Gammafunktion: Bei einem ganzzahligen, nichtnegativen Argument n ist die Gammafunktion durch n! gegeben. Sie können also zwei gleichnamige Unterprogramme schreiben, double gamma (int n) und double gamma (double x) die
sich nur durch den Typ des Arguments unterscheiden. Wird das Unterprogramm gamma dann mit
einem positiven ganzzahligen Argument aufgerufen, dann können Sie die Berechnung auf die Berechnung von n! zurückführen. Der Compiler verwendet die numerisch viel aufwändigere Rechnung
der Gammafunktion für reelle Argumente nur dann, wenn das Argument wirklich vom Typ double
ist.
1
Komplexe Zahlen
Mit der Headerdatei complex werden die Klassen und Unterprogramme zum Arbeiten mit komplexen
Zahlen eingebunden. Das nachfolgende Beispielprogramm illustriert die wichtigsten Optionen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <cmath>
#include <complex>
using namespace std;
int main() {
complex<double> c1,c2,I;
double dd = 2.7;
c1 = complex<double> (2,3.5432);
c2 = polar(1.0,0.4);
I = complex<double> (0,1);
cout
cout
cout
cout
cout
cout
cout
cout
cout
cout
cout
cout
cout
cout
cout
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
endl;
" c1
" c2
" I
" conj(c1)
" c1 + I
" c1 + dd
" c1 * I
" Re c1
" Im c1
" 2.5 * c1
" log(c2)
" sqrt(I)
" abs(c1)
" arg(c2)
=
=
=
=
=
=
=
=
=
=
=
=
=
=
"
"
"
"
"
"
"
"
"
"
"
"
"
"
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
c1 << endl;
c2 << endl;
I << endl;
conj(c1) << endl;
c1+I << endl;
c1+dd << endl;
c1*I << endl;
c1.real() << endl;
c1.imag() << endl;
2.5*c1 << endl;
log(c2) << endl;
sqrt(I) << endl;
abs(c1) << endl;
arg(c2) << endl;
cout << endl << " Deine eigene komplexe Zahl : ";
cin >> c1;
cout << " Deine Zahl : " << c1 << endl << endl;
}
Wie aus dem Beispiel hervorgeht können Sie Variablen für komplexe Zahlen mit der Anweisung
complex<double> definieren (dabei kann double auch durch float oder long ersetzt werden.
Den Variablen kann man dann auf zwei Arten Werte zuweisen: Entweder durch Angabe von Realund Imaginärteil, rr und ii, in der Zuweisung complex<double> (rr,ii) oder durch Angabe
von Absolutbetrag und Phase, dd und pp, in der komplexwertigen Funktion polar(dd,pp).
Die komplexen Variablen gehorchen den üblichen Rechenregeln für komplexe Zahlen. Insbesondere sind die Operationen + , - , * , / implementiert. Falls bei diesen Verknüpfungen eine der
beiden Zahlen eine reelle oder ganze Zahl ist, wird diese korrekt in eine komplexe Zahl umgewandelt.
2
Die reellen Methoden .real() und .imag() liefern Real- und Imaginärteil der komplexen Zahl,
die komplexwertige Funktion conj() das komplex konjugierte des Arguments. Die reellwertigen
Funktionen abs() und arg() berechnen den Betrag einer komplexen Zahl, beziehungsweise die
Phase in ihrer Polardarstellung.
Die mathematischen Funktionen sin(z), cos(z), tan(z), sinh(z), cosh(z), tanh(z),
exp(z), sqrt(z), log(z), log10(z) sind gemäß ihrer Definition für komplexe Zahlen z implementiert. Zusätzlich dazu gibt es noch die Potenzfunktionen pow(z,n), pow(z,r), pow(z1,z2),
pow(r,z), wobei n eine integer-Variable ist und r reell.
Eine komplexe Zahl wird im Format (x,y) ausgegeben, und kann in den Formen (x,y), (x)
und x eingelesen werden, wobei bei den letzten beiden Möglichkeiten der Imaginärteil Null bleibt.
Valarrays:
Neben den simplen Arrays die wir am Anfang kennen gelernt haben, und den sehr allgemeinen Containerklassen gibt es eine weitere Vektorstruktur, sogenannte Valarrays. Dies sind eindimensionale
Vektoren für int, double oder complex<double> Zahlen (beziehungsweise deren Kurz- oder Langvarianten), die auf hohe Verarbeitungsgeschwindigkeit optimiert sind. Hier ein Beispielprogramm,
das den Gebrauch von Valarrays demonstriert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include
#include
#include
#include
<iostream>
<cmath>
<valarray>
<complex>
using namespace std;
void fill( valarray<int>& vi, valarray< complex<double> >& vc );
// ----------------------------------------------------------------------int main() {
valarray<double> uu(4),vv(4),ww(4),zz(4);
double su, sv, sw;
int nn;
nn = uu.size();
cout << endl;
cout << "nn = " << nn << endl << endl;
for (int i = 0; i < nn; i++) uu[i] = i;
vv = uu*uu;
ww = vv + uu - 0.1;
zz = sin(uu);
for (int i = 0; i < nn; i++)
cout << uu[i] << " "<< vv[i] << " " << ww[i] << " " << zz[i] << endl;
3
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
su = uu.sum();
sv = vv.sum();
sw = ww.sum();
cout << su << " "<< sv << " " << sw << endl << endl;
// ----------------------------------------------------------------------int shilf[] = {2,4,6,8};
valarray<int> ss(shilf,4);
valarray< complex<double> > cc;
fill(ss,cc);
for (int i = 0; i < nn; i++) cout << ss[i] << " "<< conj(cc[i]) << endl;
cout << endl;
}
// ----------------------------------------------------------------------void fill( valarray<int>& vi, valarray< complex<double> >& vc ) {
int ni = vi.size();
vc.resize(ni);
for (int i = 0; i < ni; i++) vc[i] =
complex<double>(i,vi[i]);
}
Valarrays werden mit dem Anweisung valarray<typ> name(leng) definiert (siehe Zeile 14). Wird
bei der Definition die Länge des Vektors weggelassen (Zeile 43), so wird eine Vektor der Länge Null
erzeut, dem man später mit der Methode .resize(leng) sein Länge zuweisen kann. Im Gegensatz
zu normalen Variablen werden Valarrays bei der Deklaration mit 0 initialisiert. Es gibt aber auch
die Möglichkeit ein Valarray mit einem vorgegebenen normalen array gleicher Länge zu initialisieren
(siehe Zeilen 40 und 41).
Die Methode .size() gibt die Länge des Valarrays als integer Variable an, die Methode .sum()
die Summe der Einträge. Die einzelnen Einträge können mit vv[i] angesprochen werden wobei die
Variable i wie üblich von i = 0 bis v.size()-1 läuft.
Valarrays können sehr einfach manipuliert und miteinander verknüpft werden (Zeilen 25, 26,
27). Jede Operation wird dabei immer punktweise, d.h. für jeden Eintrag einzeln ausgeführt.
Es empfiehlt sich, Valarrays bei der Übergabe in ein Unterprogramm explizit mit & für die
Übergabe per Referenz zu deklarieren. Dies erlaubt es (wie im Beispiel demonstriert) erst im Unterprogramm die Größe festzulegen.
4
Aufgabe 12.1:
Um eine wenig mit den komplexen Zahlen in einem C++ Programm zu spielen betrachten wir eine
komplexe Folge zn ∈ C, n = 0, 1, ... mit der Rekursionsgleichung
zn+1 = zn2 .
(1)
Dabei sind offensichtlich die beiden reellen Werte z = 0 und z = 1 Fixpunkte der Rekursionsgleichung, d.h., sie werden von (1) auf sich selbst abgebildet. ist z = 0 ein attraktiver Fixpunkt,
während z = 1 repulsiv ist. Das bedeutet, dass im ersten Fall ein von z = 0 etwas unterschiedlicher
Startwert schnell wieder zu z = 0 konvergiert, während kleine Änderungen um z = 1 von diesem
Wert wegführen. Verwendet man die Polardarstellung
z = reiϕ
,
r, ϕ ∈ R , r ≥ 0 , ϕ ∈ [0, 2π) ,
(2)
so sieht man, dass alle Startwerte z0 mit r < 1 zu z = 0 konvergieren, während die Folge für
Startwerte mit r > 0 divergiert. Für Startwerte mit r = 1 und ϕ > 0 rotieren die Folgenwerte auf
dem Einheitskreis.
Schreiben Sie ein Programm, bei dem Sie einen Startwert z0 in der Polardarstellung eingeben
(r und ϕ einlesen). Iterieren Sie dann die Folge (1) nter Verwendung der komplexen Arithmetik von
C++ , und geben Sie Real- und Imaginärteil als reelle Zahlenpaare x, y in einen File aus. Zeichnen
Sie dann unter Verwendung des in Kurs 7 besprochenen Grafikprogrammes “xmgrace” die Folge für
einige Startwerte z0 . Das Ergebnis könnte zum Beispiel so aussehen wie in der Figur unten.
1.0
0.5
0.0
-0.5
-1.0
-1.0
-0.5
0.0
0.5
r = 0.950, ϕ = − 0.100
r = 0.999, ϕ = 0.001
r = 0.900, ϕ = 1.500
r = 1.001, ϕ = − 0.020
5
1.0
Aufgabe 12.2:
Die Norm eines Vektors v mit n reellen Einträgen ist gegeben durch
v
u n
uX
||v|| = t vi2 ,
(3)
v
u n
uX
||v|| = t vi∗ vi .
(4)
i=1
und bei komplexen Einträgen durch
i=1
Schreiben Sie zwei reellwertige Unterprogramme, double norm( valarray<double>& vv ) und
double norm( valarray< complex<double> >& vv ), die jeweils ein Valarray (einmal reell,
einmal komplex) übergeben bekommen und die Norm berechnen. Der übergebene Vektor darf dabei
nicht geändert werden und Sie sollen, wo möglich, die Methoden .size() und .sum() verwenden. Testen Sie die beiden Unterprogramme in einem gemeinsamen Hauptprogramm um auch das
Überladen von Funktionen zu üben.
6
Herunterladen