SS 2006 Prof. Dr. Ulf Rehmann, Fakultät für Mat

Werbung
04 (Fri Apr 28 05:44:13 2006), p. 1
Übungen zur Vorlesung “Informatik A” SS 2006
Prof. Dr. Ulf Rehmann, Fakultät für Mathematik
Übungsblatt 4
Das Programm float.cc kann Eigenschaften der Implementierung von Gleitpunktzahlen (oder “Fließkommazahlen”) demonstrieren.
Es verwendet das Programm bits.cc, um die Binärkodierung der Mantisse von long double darzustellen.
Die Funktion long double float inc(long double) gibt zu einer Gleitpunktzahl vom Typ long double die jeweils nächstgrößere solche Zahl zurück.
Das Programm main() erwartet die Eingabe einer Gleitpunktzahl, gibt sie dann erst dezimal und sodann
in “Normalform” aus - Beispiel für die eingegebene Zahl 2.3:
x
: 2.3
x
: 2.3000000000000000000
2^1 :: 10010011 00110011 00110011 00110011 00110011 00110011 00110011 00110011
Dabei bedeutet die letzte Zeile die Darstellung
21 · (20 + 2−3 + 2−6 + · · · + 2−63 )
(1)
Die 64 Bits nach “::” oben in werden also mit i = 0, . . . , 63 durchnumeriert und als Koeffizienten der 2i
in der Darstellung (1) verwendet.
Weiter gibt das Programm main() die auf die Eingabe folgende nächstgrößere Gleitpunktzahl (diesen
Typs) aus, hier y genannt, und sodann die Summe x+y. Hier die Ausgaben für die Eingaben 1 und
1.0000000000000000001:
Eingabe x : 1
x
: 1.0000000000000000000
2^0 :: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
y
: 1.0000000000000000001
2^0 :: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
x+y
: 2.0000000000000000000
2^1 :: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
Eingabe x : 1.0000000000000000001
x
: 1.0000000000000000001
2^0 :: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
y
: 1.0000000000000000002
2^0 :: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010
x+y
: 2.0000000000000000004
2^1 :: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010
Die Summe ist vielleicht nicht das, was Sie erwarten; siehe Aufgabe 4.2
04 (Fri Apr 28 05:44:13 2006), p. 2
Bei Eingabe größerer Zahlen scheint die Inkrementierung zur nächstgrößeren Gleitpunktzahl deutlich
“gröber” zu sein (siehe Aufgabe 4.2).
Eingabe x : 333
x
: 333.0000000000000000000
2^8 :: 10100110 10000000 00000000 00000000 00000000 00000000 00000000 00000000
y
: 333.0000000000000000278
2^8 :: 10100110 10000000 00000000 00000000 00000000 00000000 00000000 00000001
x+y
: 666.0000000000000000000
2^9 :: 10100110 10000000 00000000 00000000 00000000 00000000 00000000 00000000
Aufgabe 4.1: Zeigen Sie, dass die Zeile (1) die Zahl 2.3 = 2 + 3/10 bestmöglich - unter Verwendung von
64 Bits für die Mantisse - approximiert. Ist die Darstellung exakt?
(4)
Achtung: Es steht eine 2 vor der Klammer, der “Binärbruch” approximiert also die Dezimalzahl 1.15.
ii) Zeigen Sie anhand der Berechnungen mit Summen von (inversen) Zweierpotenzen, dass die oben
berechnete Summe jeweils eine geeignete Näherung der wahren Summe ist. Tun Sie das gleiche für die
Eingabe 2.7.
(4)
iii) Die “berechnete” Summe ist teils größer, teils kleiner als die wahre Summe. Stellen Sie aufgrund
von Experimenten eine Hypothese auf, nach welchen Gesichtspunkten gerundet wird.
(4)
Aufgabe 4.2: i) Finden Sie zwei Gleitpunktzahlen x, y, beide ungleich 0, so dass für den Rechner x+y ==
x gilt, und benutzen Sie sies, um zu zeigen, dass die Addition von Gleitpunktzahlen für einen Rechner
nicht unbedingt assoziativ ist.
(6)
Aufgabe 4.3: i) Begründen Sie, wieso die Differenz aufeinanderfolgender Gleitpunktzahlen mit wachsender Größe der Zahlen zunimmt. Untersuchen Sie auch kleine Gleitpunktzahlen. Ist hier die Ausgabe
des Programms (durch check() veranlasst) noch adäquat? Änderungsvorschläge?
(4)
ii) Beim Programm “wurzel.c” vom Übungsblatt 2 ist oft als Abbruchbedingung eine Abschaätzung der
Näherungsdifferenzen gegen eine kleine Zahl, z.B. 10−10 genannt worden. Ist das im Lichte der Betrachtungen in i) noch sinnvoll? Was wäre ein besserer Vorschlag? Begründung?
(4)
Hinweis: Für die Lösung der Aufgaben ist im Grunde nicht erforderlich, das Programm float in allen
Einzelheiten zu verstehen.
Man kann aber damit einige Eigenschaften der Gleitpunktzahlen studieren.
Aufgabe 4.4: Die Programme fib lin und fib lin II verwenden beide einen linearen Algorithmus zur
Berechnung der Fibonacci-Zahlen, trotzdem sind die Laufzeit-Unterschiede drastisch.
Versuchen Sie eine analoge Verbesserung des Programms fib log, und machen Sie Laufzeitvergleiche
unter Verwendung der Langzahlen-Bibliothek.
(4)
Himweis: Die Funktion matrix power(matrix, int) kann als friend der Klasse matrix implementiert
werden, die Multiplikationen können komponentenweise innerhalb dieser Funktion ohne Verwendung des
für matrix überlagerten *-Operators implementiert werden.
04 (Fri Apr 28 05:44:13 2006), p. 3
// float.cc
// Das Programm nimmt eine Fliesskommazahl als Argument x auf und gibt
// die naechstgroessere darstellbare Fliesskommazahl x zurueck
// Sodann wird die Summe x+y berechnet und zurueckgegeben
//
#include <stdio.h>
#include <iostream.h>
template <class T>
ostream& bits(ostream &os, const T& x) {
char *p = (char *) &x;
// Wandle Adresse von x in char-Adresse
int k = sizeof(x);
k = (k==12)?k-2:k;
// Fuer long double werden nur 10 Byte verwendet !
for (int i = k-1; i >= 0; i--) {
// Durchlaufen der Bytes von x
for (int c = 128; c > 0; c >>= 1)
// ein Byte bitweise ausgeben
os << ( p[i] & c ? 1 : 0 );
os << " ";
// Trennung der Bytes
}
return os << "\n";
}
// "Komponenten" von long double:
typedef struct LD {
unsigned char s;
/* sign, Vorzeichen */
unsigned short e;
/* Exponent
*/
unsigned long long m;
/* Mantisse
*/
} LD;
// zerlegt long double u in die Komponenten
//Vorzeichen, Exponent, Mantisse
LD decompose(long double u) {
LD uu; int i;
unsigned char *p = (unsigned char *) &u;
// Vorzeichen-Bit:
uu.s = ( p[9] & (1<<7) ) ? 1:0;
// erstes und zweites Exponenten-Byte, zusammengefasst:
uu.e = ((p[9] << 8) + p[8]);
// Vorzeichen-Bit aus Exponenten entfernt
(uu.e <<= 1) >>=1;
// Bias = Exponent, der als Codierung bei 1 = 2^0 verwendet wird:
// Bias : 0x3fff;
// Die Exponenten e werden codiert durch die Zahl Bias + e
// den "wahren" Exponenten erhaelt man also durch u.ee -= 0x3fff
uu.m = p[7];
for (i = 6; i >= 0; i--)
uu.m = (uu.m << 8) + p[i];
return uu;
}
// Gibt zu einem long double die naechstgroessere zurueck
// nur fuer positive u !
long double float inc(long double u) {
long double uu = u;
unsigned char *p = (unsigned char*)&uu;
unsigned long long *ll = (unsigned long long*)&p[0];
// Mantisse inkrementieren:
*ll = *ll + 1;
if (*ll == 0) { // groesste Mantisse == 0, also incrementiere Exponent
04 (Fri Apr 28 05:44:13 2006), p. 4
unsigned short *ss = (unsigned short*)&p[8];
*ss++;
}
return uu;
}
void check(char *s, long double u) {
LD uu;
printf("%-10s : %.19Lf\n",s, u);
uu = decompose(u);
printf ("2^%d :: ", uu.e-0x3fff);
bits(cout, uu.m);
}
main() {
LD xx,yy;
long double x, y;
printf("long long: %d, long double %d\n",
sizeof(long long),sizeof(long double)-1);
while(1) {
printf("%-10s : ","Eingabe x"); scanf("%Lf",&x);
check("x",x);
y = float inc(x);
check("y",y);
check("x+y",x+y);
printf("\n");
}
}
Herunterladen