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"); } }