PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Erstes C++-Programm im Studium, bitte um Check vor Abgabe.


Colin MacLaren
2012-12-14, 14:42:28
Hi,

ich habe gerade meinen ersten Beleg, der direkt in die Note eingeht, zu Ende gebracht und wollte vor der Abgabe mal die Profis drüberschauen lassen, ob mir nicht irgendetwas Dummes passiert ist, dann ist die Prüfungszulassung direkt futsch. Im ersten Semester ist die Effizienz bezüglich Ausführungsgeschwindigkeit und Speicherverbrauch egal.

Aufgabenstellung war die händische Berechnung einer Cotangens-Funktion in C++, wobei nur die Grundrechenoperatoren sowie Bedingungen und Schleifen verwendet werden dürfen.

Detailliert findet ihr die Aufgabenstellung hier: http://www.tu-chemnitz.de/informatik/friz/Grundl-Inf/Beleg/texte/beleg112.php

Danke.


#include <iostream>
#include <cmath>
using namespace std;
//Anzahl der Summanden der Laurent-Entwicklung
#define SUMMANDEN 12
// Anzahl der dafür benötigten Bernoulli-Zahlen (= 2 * SUMMANDEN + 1)
#define INDIZESBN 25
// Größe von Epsilon
#define EPSILON 1e-8

double betrag(double x)
{
if (x < 0)
x = 0 - x;
return x;
}

double binominalkoeffizient(double n, int k)
{
double zaehler = n, nenner = 1;
int binozaehler;
if (k == 0 || n == k)
return 1;
if (k < 0)
{
cout << "k kleiner 0!\n\n";
return 0;
}
//Nenner berechnen
for (binozaehler = 1; binozaehler <= k; binozaehler++)
nenner *= binozaehler;
//k Stellen im Zähler heruntergehen
for (binozaehler--; binozaehler > 1; binozaehler--)
zaehler *= --n;
return zaehler/nenner ;
}


double summand (double x, int zaehlersummand)
{
int i,n,k;
// Da eine Division durch ganzahliges N nur 0 ausgibt, benötigen wir eine zusätzliche Variable ndezpluseins.
double zwischenerg = 1.0, summe, ndezpluseins;
// Definition des Feldes, mit Index 0 = 1 wie auf Wikipedia
double Bn[INDIZESBN] = {1};
// Berechnung der Bernoulli-Zahlen Bn[n].
for (n=1; n <= INDIZESBN - 1; n++)
{
summe = 0;
for (k = 0; k <= n-1; k++)
summe += binominalkoeffizient(n+1, k) * Bn[k];
ndezpluseins = n + 1;
summe *= (-1/ndezpluseins);
Bn[n] = summe;
}
// Berechnung des Summanden laut Summenformel der Laurent-Entwicklug in der Aufgabenstellung.
for (i = 1; i <= 2*zaehlersummand; i++)
zwischenerg *= 2;
zwischenerg *= betrag(Bn[2*zaehlersummand]);
for (i = 1; i <= 2*zaehlersummand; i++)
zwischenerg /= i;
for (i = 1; i <= 2*zaehlersummand-1; i++)
zwischenerg *= x;
return zwischenerg;
}


double cotangens(double x)
{
double wert = 1/x;
int n;
for (n = 1; n <= SUMMANDEN && summand (x,n) > EPSILON; n++)
wert -= summand (x,n);
return wert;
}

int main()
{
double x;
const double Pi = 3.14159265;
cout << "Geben Sie eine Zahl, deren Betrag im Intervall (0, Pi) liegt, an: ";
cin >> x;
cout << "\n";
if (betrag(x) <= 0 || betrag(x) >= Pi)
{
cout << "Die von Ihnen eigegebene Zahl entspricht nicht dem Wertebereich.\n";
return 0;
}
cout << "Das Programm hat folgenden Wert ermittelt: " << cotangens(betrag(x)) << "\n";
cout << "Die Standardbibliothek <cmath> ermittelt: " << 1 / tan(x) << "\n\n";
cout << "Die Differenz betraegt: " << betrag(1.0 / tan(x) - cotangens(betrag(x))) << "\n\n";
if (x < 0)
cout <<"\nHinweis:\n--------\n1/tan(x) mittels <cmath> liefert fuer negative x das falsche Vorzeichen,\nda die Cotangensfunktion im Intervall (-Pi,Pi) gerade ist.\n\n";
return 0;
}

Exxtreme
2012-12-14, 14:48:30
Bei der main-Funktion fehlt die abschliessende Klammer.

TheGamer
2012-12-14, 15:10:15
Bei der main-Funktion fehlt die abschliessende Klammer.

Steht da, nur nicht im CODE Block.

Simon
2012-12-14, 21:22:10
Keine defines, koennen die meisten Debugger nicht aufloesen und zeigen nur die nackten Werte an. Beschissen zum Debuggen weil es meist uebertrieben wird, also am besten gar nicht erst angewoehnen.

Colin MacLaren
2012-12-14, 23:01:43
Das lehrt der Dozent so :) Der findet das total toll.

PHuV
2012-12-14, 23:51:24
Die meisten Dozenten haben aber noch nie praktisch in SW-Projekten gearbeitet. ;)

del_4901
2012-12-15, 00:16:58
Wir hatten gestern erst nen Bewerber, der das auch so gemacht hat, wir haben Ihm dann einstimmig davon abgeraten. ;)

samm
2012-12-15, 00:21:20
Die meisten Dozenten haben aber noch nie praktisch in SW-Projekten gearbeitet. ;)Vorurteil, das ich nicht bestätigen kann. Genau zwei meiner Informatik-Dozenten waren Theoretiker oder Uni-exklusive Softwarebastler, alle andern waren aktive Programmierer.

Colin McLaren:
Hab jetzt nicht nachgerechnet, ob alles richtig berechnet wird, aber einige Hinweise:

- stilistisch finde ich neben den schon kritisierten #define (die du ruhig lassen kannst, wenn der Prof das explizit so will) Deklaration/Initialisierung in Listenform nicht gerade übersichtlich, vgl.
double zwischenerg = 1.0, summe, ndezpluseins;

- Für die Funktion "betrag()" gäbe es in cmath auch "abs()".

- Für eine Funktion "int main()" wird konventionellerweise im Fehlerfall einen Wert ungleich 0 zurückgegeben, z.B. hier einfach 1. 0 steht üblicherweise nur für erfolgreichen Abschluss.

- Die Überprüfung "k < 0" in der Funktion "binominalkoeffizient" ist im Grunde unnötig, da es in deinem Fall soweit ich gesehen habe immer >=0 ist. Schaden wird's trotzdem nicht, falls irgendwas unerwartet schief geht (Überlauf oder so), oder die Funktion wiederverwendet werden soll.

nalye
2012-12-15, 00:28:56
Geht aber mit negativen Werten über Umwege ;)


nalye@raspberrypi ~ $ ./a.out
Geben Sie eine Zahl, deren Betrag im Intervall (0, Pi) liegt, an: 1/(2-4)

Das Programm hat folgenden Wert ermittelt: 0.642093
Die Standardbibliothek <cmath> ermittelt: 0.642093

Die Differenz betraegt: 2.50455e-09

Ich weiß, kein Fehler per se, aber sowas sollte man eventuell noch abfangen?

PHuV
2012-12-15, 02:02:07
Vorurteil, das ich nicht bestätigen kann. Genau zwei meiner Informatik-Dozenten waren Theoretiker oder Uni-exklusive Softwarebastler, alle andern waren aktive Programmierer.

Wo? Es ist sicher nicht überall gleich, aber das was ich so an den Unis bisher kennen gelernt habe.... Theoretiker.

pest
2012-12-15, 11:37:24
schön isses nicht, und da lassen sich einige berechnungen einsparen

bei sowas kringeln sich mir die zehennägel

for (binozaehler--; binozaehler > 1; binozaehler--)
zaehler *= --n;


den binomialkoeff. berechnet man besser rekursiv

int binom (int a, int b)
{
int tempA, tempB;
if (( b == 0 ) || (a == b)){
return 1;
}
/* rekursion */
else
{
tempA = binom (a - 1, b);
tempB = binom (a - 1, b - 1);
return tempA + tempB;
}
}

Berni
2012-12-15, 14:05:03
Man kann auch noch Berechnungen sparen indem man die Ergebnisse in der main-Funktion in Variablen zwischenspeichert anstatt bei beiden Ausgaben neu zu berechnen.
Edit: Und PI könnte man aus der math.h (M_PI) nehmen anstatt ihn (ungenau) selbst zu definieren.

del_4901
2012-12-15, 15:40:58
schön isses nicht, und da lassen sich einige berechnungen einsparen

bei sowas kringeln sich mir die zehennägel
Ich find seine itterative Version besser. Der Loop kostet doch kaum was. In deiner rekursiven Variante wertest du dafuer staendig den Branch aus.

Colin MacLaren
2012-12-15, 16:13:07
Danke für die Tipps. Rekursion wird erst im neuen Jahr gelehrt ;) Die Math-Funktion dürfen wir nur für den Vergleich am Ende heranziehen, damit wir algorithmisches Denken lernen.

Demirug
2012-12-15, 17:07:42
Bist du sicher das deine Abbruchbedingung richtig ist?

In der Aufgabe steht "Die Berechnung soll abbrechen, wenn die Differenz zweier aufeinanderfolger Glieder z i und z i+1 kleiner als ein zu definierender Abbruchwert epsilon wird."

In deinem Code sehe ich aber nur ein "summand (x,n) > EPSILON". Das wäre dann eigentlich "Die Berechnung soll abbrechen, wenn ein Glied z i kleiner oder gleich einem zu definierender Abbruchwert epsilon wird."

Ansonsten weiß ich ja nicht auf was da bei euch Wert gelegt wird. Bei ein paar Sachen gäbe es bei einem Codereview von mir haue.

pest
2012-12-15, 18:17:36
wertest du dafuer staendig den Branch aus.

och, ich glaube das Berechnen aller Bernoullizahlen, für jeden Summanden erneut, kostet mehr

pest
2012-12-15, 19:35:31
ich hätte mir bei der aufgabenstellung auch nicht soviel aufwand gemacht (wozu sonst steht die tabelle da)


double zi(double x,int n)
{
double Bn[12]={1./6,1./30,1./42,1./30,5./66,691./2730,7./6,3617./510,43867./798,174611./330,854513./138,236364091./2730};
double coeff=2.0;
for (int t=2;t<=2*n;++t) coeff*=(2.0*x)/double(t);
return coeff*Bn[n-1];
}

double Cot(double x,double eps)
{
double erg=1.0/x;
double z=zi(x,1);
double s=z;
for (int n=2;n<=12;++n) {
double t=zi(x,n);
if (fabs(z-t)<eps) break;
z=t;s+=t;
};
return erg-s;
}

Watson007
2012-12-15, 20:19:18
ich rate dem Threadstarter sich Studentengruppen anzuschließen wenn er schon bei der ersten Aufgabe im Forum fragt. Soll ja nicht herablassend klingen.... aber der Schwierigkeitsgrad wird sich sicher noch deutlich steigern.

Ohne Studentengruppen kommt man eh nicht durchs Studium.

Aber ich glaube er hat mich eh auf Ignore gesetzt... hat ja noch nie auf meine Beiträge geantwortet:freak:

del_4901
2012-12-15, 20:24:10
och, ich glaube das Berechnen aller Bernoullizahlen, für jeden Summanden erneut, kostet mehrDafuer ne LuT zu nehmen ist wieder ne andere Optimierung. Mir ging es nur um die Berechnung der Binomialkoeffizienten.

PHuV
2012-12-16, 03:14:48
Leute, laßt doch bitte mal die Kirche im Dorf, ja? Er ist ein Einsteiger und Anfänger, und Ihr bombadiert ihn schon mit so viel Zeugs!

Wichtig ist am Anfang, das der Code für den Lernenden selbst verständlich und nachvollziehbar ist, und erst mal seiner Intuition entspricht. Ob das am Anfang nun schöner und effizienter Code ist, ist doch nicht so wichtig.

Das andere kommt doch mit der Zeit und viel Programmierung und Erfahrung, oder? Habt Ihr Euch mal Euren Code von früher angeschaut. Ganz ehrlich, wem kommt da nicht das Grausen? Selbst in der Schaffensperiode fragt man sich schon teilweise nach einem halben Jahr, warum man das so komisch programmiert hat, und es fällt dann einem eine viel bessere Lösung/Code/Algorithmus ein. Ist doch auch klar, wenn mal etwas Abstand gewonnen wird, und man die Sache neutraler betrachten kann, da fällt immer wieder etwas auf. ;)

Kommt, ist noch kein Coder vom Himmel gefallen, oder? Code-Effizienz ist nicht immer gut und angebracht, so meine langjährige Erfahrung. Wenn es nicht 100%ig auf Performance und Laufzeit ankommt, schreibe ich lieber einfach verständlichen Code. Das zahlt sich auf lang Sicht für die SW-Pflege besser aus!

pest
2012-12-16, 03:18:39
Ich denke, dass hat nicht viel mit programmieren zu tun. Gut programmieren kann ich auch nicht.

Man sollte halt einfach lieber 30min nachdenken bevor man anfängt. Z.B. statt 3 Schleifen alles in eine packen, erfordert ein bisschen Schmalz.

P.S. Warum der Prof. den Abstand der Partialsummenglieder |S_(n-1) - S_(n+1)| als Abbruchkriterium haben möchte ist mir allerdings etwas schleierhaft bzw. ungewohnt, ich habe es noch abgeändert

PHuV
2012-12-16, 03:40:41
Generell hast Du recht. Aber wer im Alltaggeschäft steckt, und gerade mal schnell etwas braucht, um etwas zu testen oder zu prüfen, der hat oftmals nicht die Zeit, alles so lange zu überdenken.

del_4901
2012-12-16, 05:40:25
Ich finde programmieren ist ein kreativer Prozess. Man sollte auch nicht zu lange ueber alles Nachdenken, denn sonst faengt man an das Problem zu "zerdenken". Man muss dann natuerlich seinen Prozess anpassen um Fehler die gemacht werden wieder ausbuegeln zu koennen (z.B. durch Refactoring).

PS: Zeit fuer den Profiler sollte immer sein :ugly:

pest
2012-12-16, 05:46:32
natürlich, es geht ja nicht um pre-mature-optimierung.

allerdings ist es mir schon häufig passiert, dass ein refactoring prozess darin endete, dass ich komplette teile neuschreiben musste.

das wäre nicht passiert, hätte ich das problem anfangs genau durchdacht.

würde man z.b. später feststellen, dass der hier programmierte funktionsaufruf kritisch ist, müsste man alles neuschreiben.

drauflosprogrammieren endet halt häufig in code den man nach 1 monat nicht mehr auf anhieb versteht.

del_4901
2012-12-16, 06:08:12
natürlich, es geht ja nicht um pre-mature-optimierung. Optimierung geht ja eigentlich auch nur, wenn man vorher gemessen hat. ;)

allerdings ist es mir schon häufig passiert, dass ein refactoring prozess darin endete, dass ich komplette teile neuschreiben musste.In meinem Arbeitsprozess ist die Zeit von vornherein mit eingeplant und ausserdem spaare ich auch viel Zeit indem ich immer nur das absolut Noetigste mache. (Ja ich weis das macht die Leute verrueckt, funktioniert fuer mich aber sehr gut)

das wäre nicht passiert, hätte ich das problem anfangs genau durchdacht.Aber nicht zuviel, sonst endet es schnell in Overengineering.

würde man z.b. später feststellen, dass der hier programmierte funktionsaufruf kritisch ist, müsste man alles neuschreiben.Das passiert mir nur noch wenn ich wieder mal in Overengineering verfalle und wieder Dinge eingeplant habe die ich zum gegebenen Zeitpunkt (noch) nicht brauche. Einfache und minimalistische Loesungen sind meisst die besten, denn zurueckbauen kostet viel mehr Zeit als das refactoren/generalisieren. Oder ich war einfach zu faul und hab zulange nicht den Profiler gebraucht.

drauflosprogrammieren endet halt häufig in code den man nach 1 monat nicht mehr auf anhieb versteht.Das ist aujch wieder genau das andere extrem. Allerdings hat man mit den Jahren immer mehr Erfahrung, um dann nicht mehr bewusst Nachzudenken muessen.

Ectoplasma
2012-12-16, 10:09:55
Optimierung geht ja eigentlich auch nur, wenn man vorher gemessen hat. ;)

Moment ... das Laufzeitverhalten (groß O Notation) eines Algorithmus, sollte einem schon grob bekannt sein, bevor man anfängt.

PHuV
2012-12-16, 14:40:28
Na ja, das ist aber noch nicht alles, da ja verwendete Plattform, Compiler etc. auch eine Rolle spielen. Gleicher Code kann auf unterschiedlichen Plattformen zu ganz anderen Laufzeiten führen, je nachdem wie gut/schlecht der Compiler optimiert/nicht optimiert, CPU entsprechend ausgelegt ist, Bibliotheken das entsprechend hergeben etc. Nicht umsonst macht man dann irgendwann doch einen Haufen #ifdef usw. rein.

del_4901
2012-12-16, 15:20:59
Na ja, das ist aber noch nicht alles, da ja verwendete Plattform, Compiler etc. auch eine Rolle spielen. Gleicher Code kann auf unterschiedlichen Plattformen zu ganz anderen Laufzeiten führen, je nachdem wie gut/schlecht der Compiler optimiert/nicht optimiert, CPU entsprechend ausgelegt ist, Bibliotheken das entsprechend hergeben etc.Algorithmisch kann der compiler an der O-Notation nichts aendern. Aber es gibt Platformen die moegen keine jumps und branches. Oder die neue tolle Optimierung ist 30% langsamer weil der Cache ineffizient genutzt wird.

Nicht umsonst macht man dann irgendwann doch einen Haufen #ifdef usw. rein.Brrrrrr, ich kenne ne Codebase wo das so lief, da ging dann irgendwann gar nichs mehr.

Moment ... das Laufzeitverhalten (groß O Notation) eines Algorithmus, sollte einem schon grob bekannt sein, bevor man anfängt.Man sollte wissen was die O Notation ist, aber nur darauf allein sollte man sich nicht verlassen. http://dice.se/wp-content/uploads/CullingTheBattlefield.pdf

RLZ
2012-12-16, 15:54:47
Ich bin leicht verwirrt über die Diskussion.
Er zeigt uns sein ersten C++ Programm und hier wird gleich über Optimierung gesprochen.
Falls man in dem Stadium neben der generellen Funktionsfähigkeit etwas bemängeln möchte, sollte es wohl eher um die Codelesbarkeit gehen.

Odal
2012-12-16, 19:59:25
LHabt Ihr Euch mal Euren Code von früher angeschaut. Ganz ehrlich, wem kommt da nicht das Grausen? Selbst in der Schaffensperiode fragt man sich schon teilweise nach einem halben Jahr, warum man das so komisch programmiert hat, und es fällt dann einem eine viel bessere Lösung/Code/Algorithmus ein. Ist doch auch klar, wenn mal etwas Abstand gewonnen wird, und man die Sache neutraler betrachten kann, da fällt immer wieder etwas auf. ;)


Das passiert mir häufig das ich, wenn ich nach ein paar Wochen wieder über code von mir schau, mir denke WTF warst du besoffen als du das verzapft hast :D

Ich finde programmieren ist ein kreativer Prozess. Man sollte auch nicht zu lange ueber alles Nachdenken, denn sonst faengt man an das Problem zu "zerdenken". Man muss dann natuerlich seinen Prozess anpassen um Fehler die gemacht werden wieder ausbuegeln zu koennen

seh ich ähnlich, sowas ist eh meist ein iterativer Prozess und es ist eigentlich kaum noch etwas wie es anfangs mal war wenn ich mal fertig bin. Das hat meist auch nicht mit schlechtem Softwaredesign zutun.

Aber so generelle Sachen wie defines/macros oder using ausserhalb von Funktionen sollte man sich besser mal gleich abgewöhnen.

Colin MacLaren
2012-12-17, 22:33:01
Danke erstmal für die Hinweise. Wichtig war mir, dass keine groben Fehler drin sind, die das Ergebnis verfälschen. Das ist die Grundlagen-Veranstaltung, ich habe vor ziemlich genau zwei Monaten die erste Zeile Code in meinem Leben geschrieben. Ich werd' mich die nächsten zwei Jahre zunächst durch Kaiser/Kechler: C++: Von den Grundlagen zur professionellen Programmierung schlagen und dort alle Übungsaufgaben mitnehmen. Bisher macht es ja Spaß, auch wenn ich vor der Algorithmen-Klausur mit ihrer 80% Durchfallquote etwas Bammel habe ;)

pest
2012-12-18, 17:28:12
für kleine epsilon geben deine und meine version andere ergebnisse aus

Pinoccio
2012-12-18, 17:51:02
für kleine epsilon geben deine und meine version andere ergebnisse ausDas wird dich wohl nicht verwundern. Addition ist - bei positiven Zahlen - halt angenehm gut konditioniert. Die Berechnung der Binomialkoeffizienten tatsächlich mit Multiplikation und anschließender Division ist da dann ungünstiger.

Die Betragsfunktion ist auch ein ganz kleines bißchen falsch. Sie behandelt -0 nicht richtig, sondern liefert dafür -0 zurück. (Nach IEEE754. Manche Sprachen, z. B. Haskell sehen das aber auch wieder anders.) Spielt für dieses Programm aber wohl keine Rolle.

mfg

pest
2012-12-18, 23:50:35
nein es liegt an der abbruchbedingung

Pinoccio
2012-12-19, 10:44:34
nein es liegt an der abbruchbedingungAchso. SIehste mal, wie aufmerksam ich C-Code lese ..

mfg