namespace cpp {}

C++ lernen, kennen, anwenden

Benutzer-Werkzeuge

Webseiten-Werkzeuge


lernen:datum

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.


lernen:datum [2020-07-27 09:21] (aktuell) – angelegt - Externe Bearbeitung 127.0.0.1
Zeile 1: Zeile 1:
 +====== Wie alt bist du (in Tagen) ? ======
 +> Selbst das längste Leben lässt sich in Sekunden ausdrücken.
 +>>---  Robert Merle: Madrapur
 +
 +Idee aus: [Hartmut Ring: Texteditoren in C. McGraw-Hill (1989), S. 10-17.]
 +
 +===== Aufgabe =====
 +Wie alt bist du?
 +In Jahren weiß das jeder sofort. Aber in Tagen gemessen? 
 +Es sind die einfachen Fragen, die schwierig zu beantworten sind.
 +
 +Schreibe ein Programm in C++, welches mit dem Aufruf 
 +
 +  tageseit 1967-03-22
 +
 +(Datum gemäß EN) die entsprechende Zahl auf den Bildschirm schreibt. 
 +Es spricht nichts dagegen, 
 +den gefundenen Algorithmus für ein abgewandeltes Programm einzusetzen:
 +
 +  tagebis 2002-12-24
 +
 +Für den Anfang genügt auch ein Programm, 
 +bei dem beide Daten einzugeben sind:
 +
 +  tageseit
 +  Anfangsdatum (Jahr Monat Tag): 1967 3 22
 +  Enddatum     (Jahr Monat Tag): 2002 12 24
 +  Tage = ????
 +
 +Anstelle der Fragezeichen sollte die Anzahl der Tage 
 +vom Anfangs- bis zum Enddatum stehen. 
 +Zur Vereinfachung kann vorerst angenommen werden, 
 +dass der Nutzer sich nicht vertippt 
 +und die Daten in der richtigen Folge eingegeben werden.
 +
 +===== Lösungsansatz =====
 +==== 1. Fassung ====
 +<code>
 +A Lebenstage im Geburtsjahr bestimmen
 +B Tage der Zwischenjahre hinzurechnen
 +C Tage im aktuellen Jahr (bis heute) hinzuzählen
 +
 +Beispiel (22.03.1967 bis 24.12.2002):
 +
 +A)  verbleibende Tage im März 31-22 = 9
 +    Tage im April ........... +30   = 39
 +    Tage im Mai ............. +31   = 70
 +    Tage im Juni ............ +30   = 100
 +    Tage im Juli ............ +31   = 131
 +    Tage im August .......... +31   = 162
 +    Tage im September ....... +30   = 192
 +    Tage im Oktober ......... +31   = 223
 +    Tage im November ........ +30   = 253
 +    Tage im Dezember ........ +31   = 284
 +
 +   Wäre das Startjahr Schaltjahr und der Startmonat Januar oder Februar,
 +   wäre noch der Schalttag zu addieren.
 +
 +B) für alle Zwischenjahre 1968 bis 2001 die Jahreslängen hinzuzählen,
 +   zusammengefasst:
 +    33 Jahre je 365 Tage ...  +10945= 11229
 +    bei Schaltjahr je ein Tag +9    = 11238 (1968,72,76,80,84,88,92,96,2000)
 +
 +C) für alle vollen Monate im aktuellen Jahr
 +    Tage im Januar .......... +31   = 11308
 +    Tage im Februar ......... +30   = 11338
 +    Tage im März ............ +31   = 11369
 +    Tage im April ........... +30   = 11399
 +    Tage im Mai ............. +31   = 11430
 +    Tage im Juni ............ +30   = 11460
 +    Tage im Juli ............ +31   = 11491
 +    Tage im August .......... +31   = 11522
 +    Tage im September ....... +30   = 11552
 +    Tage im Oktober ......... +31   = 11583
 +    Tage im November ........ +30   = 11613
 +
 +   Wäre das aktuelle Jahr Schaltjahr und aktueller Monat mindestens März,
 +   wäre noch der Schalttag zu addieren.
 +
 +    Tage im aktuellen Monat.. +24   = 11637
 +                                      =====
 +</code>
 +Die ganze Rechnung klappt nicht, wenn Anfangs- und Endjahr das gleiche sind. 
 +Dann wäre genau die Länge des Jahres abzuziehen:
 +
 +<code>
 +D Falls Anfangs- gleich Endjahr:
 +    - (365+evtl. Schalttag)         = Ergebnis 
 +</code>
 +Diese erste Version ließe sich wie folgt implementieren (als Funktion). 
 +Würden Programmierer nach Quelltextzeilen bezahlt, gäbe das ein hübsches Sümmchen. 
 +(Vielleicht würden wir noch ein bisschen mehr hinzuzaubern ;-)
 +
 +<code cpp>
 +int anzahltage(int startjahr, int startmonat, int starttag, 
 +               int endjahr,   int endmonat,   int endtag)
 +{
 +  int tage = -starttag;          // Teil A: Rest des Startjahres
 +  if (startmonat==1)  tage += 31;
 +  if (startmonat<=2)
 +  {
 +    tage += 28; 
 +    if (startjahr%4==0)   tage++; // Schaltjahr: Julianische Regel
 +    if (startjahr%100==0) tage--; // doch keins: Gregorianische Regel
 +    if (startjahr%400==0) tage++; // trotzdem eins: Gregorianische Ausnahme
 +  }
 +  if (startmonat<=3)  tage += 31;
 +  if (startmonat<=4)  tage += 30;
 +  if (startmonat<=5)  tage += 31;
 +  if (startmonat<=6)  tage += 30;
 +  if (startmonat<=7)  tage += 31;
 +  if (startmonat<=8)  tage += 31;
 +  if (startmonat<=9)  tage += 30;
 +  if (startmonat<=10) tage += 31;
 +  if (startmonat<=11) tage += 30;
 +  if (startmonat<=12) tage += 31;
 +
 +  for (int jahr=startjahr+1; jahr<endjahr; jahr++) // Teil B: Zwischenjahre
 +  {
 +    tage += 365;
 +    if (jahr%4==0)   tage++;
 +    if (jahr%100==0) tage--;
 +    if (jahr%400==0) tage++;
 +  }
 +
 +  if (endmonat>1)  tage += 31;   // Teil C: Anfang des Endjahres
 +  if (endmonat>2)
 +  {
 +    tage += 28);
 +    if (endjahr%4==0)   tage++;
 +    if (endjahr%100==0) tage--;
 +    if (endjahr%400==0) tage++;
 +  }
 +  if (endmonat>3)  tage += 31;
 +  if (endmonat>4)  tage += 30;
 +  if (endmonat>5)  tage += 31;
 +  if (endmonat>6)  tage += 30;
 +  if (endmonat>7)  tage += 31;
 +  if (endmonat>8)  tage += 31;
 +  if (endmonat>9)  tage += 30;
 +  if (endmonat>10) tage += 31;
 +  if (endmonat>11) tage += 30;
 +
 +  tage += endtag;
 + 
 +  // Teil D: Korrektur 
 +
 +  if (startjahr == endjahr)
 +  {
 +    tage -= 365;
 +    if (startjahr%4==0)   tage++;
 +    if (startjahr%100==0) tage--;
 +    if (startjahr%400==0) tage++;
 +  }
 +  return tage; // endlich! richtig??
 +}
 +</code>
 +Für C-Fremdlinge seien einige komische Zeichen erklärt: 
 +''+='' erhöht die links stehende Variable um den rechten Wert, 
 +''-='' zieht ab, 
 +''++'' und ''--'' zählen die Variable eins rauf bzw. runter, 
 +''a%b'' wird a modulo b gelesen und berechnet den Rest der Division von a durch b.
 +
 +Die Berechnung mag zwar richtig sein, aber beim Lesen befallen einen doch Zweifel. 
 +Aufwändig (frühere Schreibung: aufwendig) ist die Lösung.  
 +(Man könnte die Wand hoch gehen, wie langsam die Kiste ist.) 
 +Und besonders elegant ist sie auch nicht, höchstens als Kunstschmiedearbeit. 
 +Programmieren ist schließlich (auch) eine Kunst. 
 +Die Kunst beim Programmieren besteht jedoch im Streben nach Einfachheit und Wiederverwendbarkeit.
 +
 +==== 2. Fassung ====
 +Als erstes sticht die viermalige Verwendung der Schaltjahr-Regel hervor. 
 +Wiederverwendung wurde zwar auch betrieben, 
 +aber nur durch Kopieren, Einfügen und Ändern von Quelltext.
 +Kein Grund, stolz zu sein. Dieser Schaltjahr-"Eiertanz" lässt sich herausziehen:
 +
 +<code cpp>
 +bool schaltjahr(int jahr)
 +{
 +  if (jahr%400 == 0) return 1; // besondere Schaltjahre: 1600, 2000, 2400
 +  if (jahr%100 == 0) return 0; // andere Jahrhunderte nicht
 +  if (jahr%4   == 0) return 1; // gewoehnliche Schaltjahre
 +  /* else */         return 0; // gemeine Jahre
 +}
 +</code>
 +oder unter Ausnutzung logischer Verknüpfungen UND (&&) und ODER (||):
 +
 +<code cpp>
 +//: tageseit.cpp : Berechnung der Tagesdifferenz - R.Richter 2002-10-06
 +///////////////////////////////////////////////////////////////////////
 +
 +bool schaltjahr(int jahr)
 +{
 +  return (jahr%4==0 && jahr%100!=0) || jahr%400==0;
 +}
 +//~
 +</code>
 +Ganze 20 Zeilen lassen sich einsparen, z.B. im Teil B stünde dann nur
 +<code cpp>
 +    tage += 365 + schaltjahr(jahr);
 +</code>
 +Schaut man etwas genauer hin, 
 +lassen sich der Teil A und der (unschöne) Teil D zusammenfassen. 
 +Das Vertauschen der Reihenfolge ist möglich, da nur Additionen der Tage stattfinden. 
 +Eigentlich wird da weiter nichts betrieben, 
 +als auf den Anfang des Geburtsjahres zurückzurechnen 
 +oder vom Anfang des Geburtsjahres bis zum Geburtstag zu zählen. 
 +Dies geschieht im wesentlichen mit dem gleichen Quelltext wie im Teil C. 
 +Diese Zahl ist als negativer Anfangswert zu nehmen (Teil A'). 
 +Danach kann von Beginn des Geburtsjahres in Jahresschritten vorwärtsgegangen werden (Teil B').
 +Teil C bleibt, wie er war. 
 +
 +<code cpp>
 +int anzahltage(int startjahr, int startmonat, int starttag, 
 +               int endjahr,   int endmonat,   int endtag)
 +{
 +  int tage = 0;     // Teil A': zurueck zum Anfang des Startjahres
 +  if (startmonat>1)  tage -= 31;
 +  if (startmonat>2)  tage -= 28 + schaltjahr(startjahr);
 +  if (startmonat>3)  tage -= 31;
 +  if (startmonat>4)  tage -= 30;
 +  if (startmonat>5)  tage -= 31;
 +  if (startmonat>6)  tage -= 30;
 +  if (startmonat>7)  tage -= 31;
 +  if (startmonat>8)  tage -= 31;
 +  if (startmonat>9)  tage -= 30;
 +  if (startmonat>10) tage -= 31;
 +  if (startmonat>11) tage -= 30;
 +  tage -= starttag;
 + 
 +  for (int jahr=startjahr; jahr<endjahr; jahr++) // Teil B': ganze Jahre
 +  {
 +    tage += 365 + schaltjahr(jahr);
 +  }
 +
 +  if (endmonat>1)  tage += 31;  // Teil C: Anfang des Endjahres
 +  if (endmonat>2)  tage += 28 + schaltjahr(endjahr);
 +  if (endmonat>3)  tage += 31;
 +  if (endmonat>4)  tage += 30;
 +  if (endmonat>5)  tage += 31;
 +  if (endmonat>6)  tage += 30;
 +  if (endmonat>7)  tage += 31;
 +  if (endmonat>8)  tage += 31;
 +  if (endmonat>9)  tage += 30;
 +  if (endmonat>10) tage += 31;
 +  if (endmonat>11) tage += 30;
 +  tage += endtag;
 +
 +  return tage; 
 +}
 +</code>
 +Wir sind zwar noch nicht schneller, aber der Formulierung ist übersichtlicher geworden. 
 +
 +> Wenn zwei Wege zum Ergebnis führen, ist wahrscheinlich der einfachere auch der richtige.
 +
 +sagt Roger Penrose. Vermutlich geht es noch einfacher.
 +
 +==== 3. Fassung ====
 +Die fortlaufenden Tests kosten einen Haufen Rechenzeit. 
 +Hier wäre ein direkter Sprungbefehl wünschenswert. 
 +Aber wir sollen goto ja nicht verwenden. 
 +Der am 07.08.2002 verstorbene Edsger Wybe Dijkstra hätte sonst keine gesegnete Ruhe: 
 +
 +> Wenn Sie unsauber programmieren und dabei denken, Dijkstra hätte das nicht gemocht, 
 +> so ist dies genug Unsterblichkeit für mich.
 +
 +Nun, es geht ohne goto. Es gibt noch genügend andere "schlimme" Konstruktionen in C. 
 +Lernen wir etwas C-Syntax hinzu:
 +
 +<code cpp>
 +  switch(endmonat)
 +  { 
 +    case 12: tage += 30; // immer die Vormonate drauf rechnen
 +    case 11: tage += 31;
 +    case 10: tage += 30;
 +    case 9:  tage += 31;
 +    case 8:  tage += 31;
 +    case 7:  tage += 30;
 +    case 6:  tage += 31;
 +    case 5:  tage += 30;
 +    case 4:  tage += 31;
 +    case 3:  tage += 28 + schaltjahr(endjahr);
 +    case 2:  tage += 31; 
 +             break;
 +    case 1:  break;
 +  };
 +</code>
 +Die Mehrfachverzweigung springt sofort an die richtige Stelle 
 +und wurstelt sich bis zum nächsten Abbruch (break) durch. 
 +Auch eine Art goto, ohne es so zu nennen. 
 +Java-Fans brüsten sich damit, dass in Java goto nicht erlaubt ist,
 +dafür kann break ein Sprungziel angeben. Wie originell: goto ohne goto!
 +
 +Die fortlaufende Addition macht auch noch zu viel (unnütze) Arbeit. 
 +Wir können das ändern, in dem wir dem Compiler diese Arbeit abnehmen.
 +(Natürlich erfordert das geistige Anstrengung. Nichts ist kostenlos!)
 +
 +<code cpp>
 +  switch(endmonat)
 +  { case 1:  break;
 +    case 2:  tage += 31;  break;
 +    case 3:  tage += 59;  break;
 +    case 4:  tage += 90;  break;
 +    case 5:  tage += 120; break;
 +    case 6:  tage += 151; break;
 +    case 7:  tage += 181; break;
 +    case 8:  tage += 212; break;
 +    case 9:  tage += 243; break;
 +    case 10: tage += 273; break;
 +    case 11: tage += 304; break;
 +    case 12: tage += 334; break;
 +  };
 +  if (endmonat > 2) tage=+ schaltjahr(endjahr);
 +
 +</code>
 +Jetzt wird nur noch eine Addition ausgeführt. Gut, nicht? Es geht noch besser.
 +
 +Solche Mehrfachverzweigungen, die nur einzelne Werte nachschlagen,
 +sind wiederum Ungetüme, die durch ein anderes Konzept aus dem Rennen geschlagen werden, 
 +durch eine Tabelle oder Liste (Feld oder indizierte Variable):
 +
 +<code cpp>
 +int anzahltage(int startjahr, int startmonat, int starttag, 
 +               int endjahr,   int endmonat,   int endtag)
 +{
 +  int const monatstage[] = 
 +  { 
 +    0,   0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 
 +  //[0] Jan Feb Mar Apr Mai  Jun  Jul  Aug  Sep  Okt  Nov  Dez
 +  };
 +
 +  int tage = 0;
 +
 +  tage += monatstage[startmonat] + starttag;    // Teil A'
 +  if (startmonat > 2) tage += schaltjahr(startjahr);
 +
 +  tage = -tage; // Tage bis einschliesslich Starttag werden abgezogen
 +
 +  for (int jahr=startjahr; jahr<endjahr; jahr++) // Teil B'
 +  {
 +    tage += 365 + schaltjahr(jahr);
 +  }
 +
 +  tage += monatstage[endmonat] + endtag;        // Teil C'
 +  if (endmonat > 2) tage+= schaltjahr(endjahr);
 +
 +  return tage; 
 +}
 +</code>
 +Die Anfangswertbelegung mit 0 und die Umkehr der Tageszahl ist weitschweifig,
 +verdeutlicht aber die inhaltliche Gleichheit der Teile A' und C'
 +Ein Bild sagt mehr als Tausend Worte. 
 +(Frei übersetzt ins Computer-Englisch: A picture counts more than ... Kwords.)
 +Machen wir uns ein Bild von dem, was geschieht:
 +
 +  Anfang
 +  Startjahr   A'
 +  |-------------------Start             C' 
 +  |------------------------------|------------Ende
 +              B'                 Anfang
 +                                 Ende
 +
 +Das soll uns auf die nächste Idee bringen,
 +mit der die zeitraubende Schleife (Teil B') aufgelöst wird.
 +Lässt sich der Abstand von Start bis Ende ohne die Schleife bestimmen?
 +Natürlich geht das. 
 +Wir müssen nur eine einheitliche Skalenbeschriftung anbringen,
 +an der wir den Wert ablesen. 
 +Dann können wir einfach die Differenz der Skalenwerte als Abstand nehmen!
 +
 +==== 4. Fassung ====
 +Die neue Lösung basiert auf dem Zusammenhang
 +
 +  Anzahl der Tage = TagesNummer(Enddatum) - TagesNummer(Startdatum)
 +
 +Für die TagesNummer-Umrechnung können wir die Teil B' und C' zusammenfassen. 
 +Dabei fällt allerdings der Anfang des Startjahres als Bezugspunkt weg. 
 +Was nehmen wir stattdessen?
 +
 +Das Nächstliegende ist,
 +einfach alle Jahre seit Anfang der (unseren) Zeitrechung zu nehmen. 
 +Es ist (für unsere Zwecke) auch gar nicht so wichtig, 
 +ob es ein Jahr Null gab oder nicht. 
 +
 +(Es gab keins. Auch vorher haben schon Menschen gelebt,
 +vermutlich, ohne das Gefühl gehabt zu haben, vorzeitliche Wesen zu sein.
 +Man fühlt sich eher am Ende einer Epoche: "fin de siècle"-Stimmung.
 +Vorher rechnete man "ad urbe condita",
 +ab dem sagenhaften Gründungsdatum der "ewigen" Stadt (Rom) 753 v.u.Z.
 +durch die Wolfskinder Romulus und Remus.
 +Tatsächlich ist Rom wohl um 575 v.u.Z. unter etruskischer Herrschaft entstanden.
 +
 +Noch eher, ab 776 v.u.Z., rechneten die Griechen in Olympiaden.
 +Israeliten setzten die Erschaffung der Welt als Anfangspunkt fest,
 +nach ihren Vorstellungen 3761 v.u.Z.; die Ägypter 4713 v.u.Z.
 +Für Mohammedaner beginnt die Zeitrechnung im Jahr 622 u.Z. mit der Hedschra,
 +der Flucht Mohammeds.
 +Buddhisten rechnen immer in Jahreszyklen, die nach Tieren benannt sind: 
 +Tiger, Drache usw. 
 +Japaner rechnen in Regierungsjahren ihres jeweiligen Kaisers,
 +z.Z. Akihito, eine Methode, die lange Regierungszeiten voraussetzt,
 +um praktisch zu sein.
 +
 +Unsere Zeitrechnung (nach Christi Geburt) wurde auch nicht im Jahre 1 eingeführt,
 +sondern erst im Jahre 532 u.Z. durch den Mönch Dionysius Exiguus 
 +[Schlag nach Natur, S.88]. 
 +Christi Geburt zu Weihnachten kurz vor Beginn des Jahres 1 u.Z.
 +ist so genau nicht überliefert.
 +Es gibt Theorien, dass dies um mehrere Jahre danebenliegt. 
 +So könnte der Stern von Bethlehem eine Konjunktion,
 +d.h. ein Vorbeigang, von Jupiter und Saturn im Jahre 7 v.u.Z. gewesen sein,
 +der Astronomen aus der Fremde, uns als die drei Könige dem Morgenland bekannt,
 +in Bewegung brachte zum besten Beobachtungsplatz.
 +Es gibt Menschen, die die Gestalt Jesus für eine Erfindung halten.
 +Und es gibt Menschen, die ungefähr 3 Jahrhunderte 
 +für eine Erfindung spät-mittelalterlicher Urkundenfälscher halten
 +[Illig: Das erfundene Mittelalter]. Klammer zu.)
 +
 +Rechnen wir also so, als ob es ein Jahr Null gegeben hätte,
 +in dem Maria schwanger war.
 +
 +<code cpp>
 +int tagesNr(int jahr, int monat, int tag)
 +{
 +  int const monatstage[] = 
 +  { 
 +    0,   0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 
 +  //[0] Jan Feb Mar Apr Mai  Jun  Jul  Aug  Sep  Okt  Nov  Dez
 +  };
 +
 +  int tage = 365*jahr            // Normaljahre
 +             + jahr/           // Anzahl gewoehnliche Schaltjahre
 +             - jahr/100          // ohne Jahrhunderte
 +             + jahr/400          // aber mit 400er Schaltjahren
 +             + monatstage[monat] // Vormonate
 +             + tag;
 +
 +  if (monat<=2 && schaltjahr(jahr)) tage--; // vor dem Schalttag
 +
 +  return tage;
 +}
 +
 +int anzahltage(int startjahr, int startmonat, int starttag, 
 +               int endjahr,   int endmonat,   int endtag)
 +{
 +  return tagesNr(endjahr,   endmonat,   endtag)
 +        -tagesNr(startjahr, startmonat, starttag); 
 +}
 +</code>
 +Für weit zurückliegende Datumsangaben ist die 
 +Anwendung der Differenzberechnung mit Vorsicht zu genießen.
 +In den katholischen Ländern verordnete Papst Gregor XIII.,
 +dass der 15. Oktober unmittelbar auf den 4. Oktober 1582 folgte,
 +um die Fehler des Julianischen Kalenders auszugleichen,
 +und künftig die Hundertjahr- sowie die Vierhundertjahr-Schaltregel zu befolgen.
 +Das protestantische Deutschland vollzog diesen Schritt am 1. März 1700,
 +England im September 1752, Schweden 1753, Japan 1873, die Türkei 1916,
 +Russland 1918, Rumänien 1919, Griechenland erst 1923.
 +
 +==== 5. Fassung ====
 +Die Festlegung, wann das Jahr beginnt, ist willkürlich.
 +Erst im Spätmittelalter setzte sich der Januar als Jahresanfang durch.
 +Die alten Römer begannen ihr Jahr mit dem Monat,
 +der nach dem Kriegsgott Mars benannt ist (Martius = März).
 +Mit der Öffnung des Mars-Tempels konnte wieder ein Feldzug beginnen,
 +um neue Provinzen zu unterwerfen.
 +Die Monatsnamen September (septem = der siebte)
 +bis Dezember (decem = der zehnte) künden noch von dieser Zählweise.
 +Der etwas kürzer geratene Februar beschloss das Jahr.
 +Der Schalttag befand sich also ursprünglich am Jahresende.
 +Er wurde auf Julius Caesars Vorschlag eingeführt 
 +(Julianischer Kalender 46 v.u.Z.), 
 +um den Fehler zwischen Kalenderjahr (365 Tage) 
 +und Sonnenjahr (365,2422 Tage) zu mindern.
 +
 +Lassen wir das Jahr ab März beginnen 
 +und schlagen Januar und Februar dem Vorjahr zu, 
 +können wir die Schaltjahrprozedur einsparen. 
 +Allerdings müssen wir die Monatstabelle neu berechnen:
 +
 +<code cpp>
 +int tagesNr(int jahr, int monat, int tag)
 +{
 +  if (monat > 2) monat -= 3;  // Maerz:  monat==0
 +  else       
 +  {
 +    jahr--; monat += 9; 
 +  }
 +
 +  int const monatstage[] = 
 +  { 
 +      0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337  
 +  // Mar Apr Mai Jun Jul  Aug  Sep  Okt  Nov  Dez  Jan  Feb
 +  //  0            5    6    7    8    9    10   11
 +  };
 +
 +  return 365*jahr                        // Normaljahre
 +         + jahr/4 - jahr/100 + jahr/400  // Schalttage
 +         + monatstage[monat]             // Vormonate
 +         + tag;
 +}
 +</code>
 +==== 6. Fassung ====
 +Jetzt erst zahlt sich auch die Umstellung aus! 
 +Die mit März beginnende Monatstabelle enthält eine Regelmäßigkeit. 
 +Sie ist nicht ganz einfach zu entdecken, unter Zeitdruck schon gleich gar nicht. 
 +Hier brauchen wir eine Erleuchtung. 
 +Eine genaue (!) Zeichnung auf Millimeterpapier kann auf die Sprünge helfen: 
 +Es ist (fast) eine Gerade. 
 +Durch Experimentieren (Tabellenkalkulation!) findet man den linearen Ausdruck, 
 +der die Tabelle überflüssig macht:
 +
 +  monatstage[monat] = (306*monat+5)/10
 +
 +Die Fünf in der Klammer erzwingt die korrekte Rundung zur nächstliegenden Ganzzahl.  
 +Und noch eine letzte Überlegung: 
 +Vier Jahre haben 4*365+1 = 1461 Tage, von Jahrhunderten abgesehen. 
 +
 +<code cpp>
 +int tagesNr(int jahr, int monat, int tag)
 +{
 +  if(monat>2) monat -= 3; // Maerz: monat==0
 +  else       
 +  {
 +    jahr--; monat += 9; 
 +  }
 +  return 1461*jahr/4 - jahr/100 + jahr/400 // ganze Jahre
 +         + (306*monat+5)/10                // Vormonate
 +         + tag;
 +}
 +</code>
 +Jetzt ist die Tagesnummerberechnung kompakt. 
 +Die Ideen, die zu dieser Formel geführt haben, 
 +sind am Quelltext nicht mehr nachvollziehbar. 
 +Die "magischen" Zahlen 1461 und 306 scheinen "vom Himmel gefallen" zu sein. 
 +Unter diesem Blickwinkel gesehen, wurde hier evtl. schon zu viel optimiert, 
 +auf Kosten der Verständlichkeit.
 +
 +==== 7. Fassung ====
 +Werden Datum-Angaben häufig benutzt, kann es lästig werden, 
 +jeweils drei Parameter an die Funktionen übergeben zu müssen. 
 +Aus diesem Grund lassen sich solche Dinge zu einem zusammengesetzten Typ, 
 +Datensatz, Verbund oder Struktur genannt, bündeln.
 +
 +<code cpp>
 +//> tageseit.cpp 
 +
 +struct Datum
 +{
 +  int jahr, monat, tag;
 +};
 +
 +int tagesNr(Datum d)
 +{
 +  if (d.monat > 2) d.monat -= 3; // Maerz: d.monat==0
 +  else       
 +  {
 +    d.jahr--; d.monat += 9; 
 +  }
 +  return 1461*d.jahr/4 - d.jahr/100 + d.jahr/400 // ganze Jahre
 +         + (306 * d.monat + 5)/10                // Vormonate
 +         + d.tag;
 +}
 +
 +int anzahltage(Datum start, Datum ende)
 +{
 +  return tagesNr(ende) - tagesNr(start); 
 +}
 +//~
 +</code>
 +==== 8. Fassung ====
 +Computerprogramme, die Nutzereingaben abfragen, 
 +müssen damit rechnen, dass falsche, 
 +sogar offensichtlich unsinnige Eingaben getätigt werden. 
 +Einige davon lassen sich durch eine Gültigkeitsprüfung erkennen:
 +
 +<code cpp>
 +//> tageseit.cpp
 +
 +bool istgueltig(Datum d)
 +{
 +  if(d.tag<1 || d.monat<1 || d.monat>12 || d.jahr<0) return 0;
 +
 +  int const tage[] = 
 +  {
 +    0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
 +    // Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez 
 +  };
 +  return d.tag <= tage[d.monat] + (d.monat==2 && schaltjahr(d.jahr));
 +}
 +//~
 +</code>
 +Da brauchen wir auch die Schaltjahrbestimmung wieder. Nichts war umsonst!
 +
 +==== 9. Fassung ====
 +In Programmen gibt es viele seichte, triviale Stellen. 
 +Nicht zu einfache Programme sind jedoch an einigen Stellen auch tief oder knifflig. 
 +Eine solche Frage ist die nach dem aktuellen Datum. 
 +Der Nutzer soll dieses nicht eingeben müssen.
 +
 +Hier werden Kenntnisse der Systemschnittstelle benötigt. 
 +Da es um Zeitangaben geht, 
 +sollten wir zuerst in der ''<ctime>''-Bibliothek nachsehen. 
 +Dort gibt es eine Funktion ''time()'', die die aktuelle Zeit liefert, 
 +allerdings in einer sehr unhandlichen Form: 
 +die Anzahl der Sekunden seit dem 1. Januar 1970, 0.00 Uhr Weltzeit (GMT). 
 +Diese Zeitangabe lässt sich mit einer Funktion ''localtime()''
 +in Ortszeit umrechnen und aufbrechen in Jahr, Monat, Tag, Stunde usw. 
 +Zu beachten ist, 
 +dass Monate ab 0 (Januar) beginnend zählen und 1900 als Jahr Null angegeben wird.
 +
 +<code cpp>
 +//> tageseit.cpp
 +
 +#include <ctime>
 +
 +Datum heute()
 +{
 +  using namespace std;
 +
 +  time_t jetzt = time(nullptr);
 +  struct tm* t = localtime(&jetzt);
 +
 +  Datum d;
 +  d.jahr  = t->tm_year+1900;
 +  d.monat = t->tm_mon +1;
 +  d.tag   = t->tm_mday;
 +  return d;
 +
 +//~
 +</code>
 +Die Funktion ''localtime()'' übernimmt die Adresse (''&''
 +einer ''time_t''-Typ-Variable und liefert einen Zeiger (''*''), 
 +über den mit ''->'' auf die Komponenten einer 
 +systeminternen Variable vom Typ ''tm'' zugegriffen werden kann.
 +
 +==== 10. Fassung ====
 +Nun fehlt noch das Hauptprogramm. Das Einfachste wäre
 +
 +<code cpp>
 +#include <iostream>
 +
 +int main()
 +{
 +  Datum start, ende;
 +
 +  std::cout << "Anfangsdatum (Jahr Monat Tag): ";
 +  std::cin >> start.jahr >> start.monat >> start.tag;
 +
 +  std::cout << "Enddatum     (Jahr Monat Tag): ";
 +  std::cin >> ende.jahr >> ende.monat >> ende.tag;
 + 
 +  std::cout << "Tage = " << anzahltage(start, ende) << '\n';
 +  return 0;
 +}
 +</code>
 +Es soll aber mit einem Datum als Parameter aufrufbar sein:
 +
 +  tageseit 1967-03-22
 +
 +Das Datum ist nach EN anzugeben. 
 +Wird kein Datum angegeben, wird das Geburtsdatum im Nutzerdialog erfragt. 
 +Das Programm berechnet die Anzahl der Tage bis zum aktuellen Datum, 
 +das nicht eingegeben wird. Ungültige Eingaben sind zurückzuweisen.
 +
 +<code cpp>
 +//> tageseit.cpp
 +
 +#include <iostream>
 +#include <cstdlib>
 +
 +int main(int argc, char* argv[])
 +{
 +  Datum geburt;
 +
 +  if (argc == 1) // Aufruf ohne Parameter
 +  {
 +    std::cout << "seit Datum (Jahr-Monat-Tag): ";
 +    char puffer[20];
 +    std::cin.width(20);
 +    std::cin >> puffer;
 +    std::cin.ignore(200,'\n');
 +    geburt.jahr  = atoi(puffer);
 +    geburt.monat = atoi(puffer+5);
 +    geburt.tag   = atoi(puffer+8);
 +  }
 +  else if (argc == 2) // Datum als Argument
 +  {
 +    geburt.jahr  = atoi(argv[1]);
 +    geburt.monat = atoi(argv[1]+5);
 +    geburt.tag   = atoi(argv[1]+8);
 +  }
 +
 +  if (!istgueltig(geburt))
 +  {
 +    std::cerr << "Kein gueltiges Datum.\n";
 +    return 1;
 +  }
 +
 +  std::cout << "Tage = " << anzahltage(geburt, heute()) << '\n';
 +  return 0;
 +}
 +//~
 +</code>
 +===== Abschluss und Ausblick =====
 +Damit erfüllt das {{tageseit.cpp|Programm}} die ihm gestellte Aufgabe. 
 +
 +In diesem Text ist vorgesehen, 
 +dass ab den mit ''%%//:%%'' beginnenden Zeilen 
 +bzw. mit ''%%//>%%'' fortsetzend 
 +bis zur mit ''%%//~%%'' beginnenden Zeile stehender Quelltext 
 +in Dateien mit dem dahinterstehenden Namen zu schreiben ist. 
 +Auf diese Weise werden Quelltext und Dokumentation 
 +nach der Idee des Literate Programming von Donald E. Knuth zusammengefasst. 
 +Prinzip one source:
 +Halte Quelle und Dokumentation stets konsistent, am besten an einem Ort.
 +
 +Das Herausziehen der Quellen (aus einer ASCII-Klartextdatei) 
 +kann ein [[anwenden:onesource:extract|Hilfsprogramm]] erledigen, 
 +eine schöne Übungsaufgabe in Sachen Dateiarbeit. 
 +Die Idee dafür habe ich aus [Bruce Eckel: Thinking in C++].
 +
 +Das endgültige Programm umfasst nur 95 Zeilen!
 +Hier ist noch ein Testrahmen:
 +
 +<code>
 +//# make.bat
 +call cc tageseit.cpp
 +tageseit
 +//~
 +</code>
 +Auf dem Weg zur Lösung haben wir einen weiten Bogen geschlagen. 
 +Angefangen haben wir bei den Problemen der Algorithmierung, 
 +mit denen Programmierer unabhängig von der Sprache zu tun haben, 
 +in der sie Algorithmen niederschreiben.
 +  -  Im ersten Versuch begegneten uns die C-Sprachkonzepte Funktion, Zählschleife und bedingte Anweisung.
 +  -  Logische Verknüpfungen und die Nutzung von Funktionen als wiederverwendbare Programmbausteine kamen beim zweiten Versuch hinzu.
 +  -  Mehrfachverzweigungen, Entscheidungstabellen und indizierte Variablen (Felder) sind im dritten Versuch eng miteinander verknüpft und ineinander überführbar.
 +  -  Im vierten Anlauf kam die Idee der Umrechnung in eine geeignetere Darstellung auf; ein neues Bezugssystem kann umwälzende Auswirkungen auf das zu bewältigende Problem haben. Das Problem zerfällt in zwei Teilprobleme, ein schwieriges, die Umrechnungsfunktion, und ein triviales, die Differenzbildung. Insgesamt es ist jedoch einfacher als alle vorherigen Versuche.
 +  - Bei Version 5 brachte der Wechsel des Bezugspunktes erst eine kleine Verbesserung, 
 +  -  in Teil 6 mit mehr geistiger Anstrengung sogar Einsicht in einen unbekannten funktionalen Zusammenhang.
 +  -  Die Bündelung inhaltlich zusammengehöriger Daten im Teil 7 bildet die Grundlage der Datenabstraktion in objektbasierten Programmen.
 +  -  Die Teilaufgabe 8 bekümmerte sich um die Plausibilität von Eingabedaten. Das GIGO-Prinzip der Datenverarbeitung lautet: garbage in, garbage out. Schickt man Datenmüll in eine Berechnung, so wird auch wieder Datenmüll herauskommen.
 +  -  Im Teil 9 wurde ein kleiner Ausflug in die Programmierschnittstelle des Systems unternommen.
 +  -  Die Kommandozeilenparameter im abschliessenden Teil 10 zeigen, wie die Oberfläche von Systemdienstprogrammen mit den Nutzereingaben verbunden wird. 
 +
 +Mit der einfachen Frage am Anfang wurde ein großer Bereich 
 +der Programmiergrundlagen berührt. 
 +Dabei haben wir aber keine Dateien benutzt, 
 +schließlich muss das Programm keine Daten speichern.
 +
 +Man könnte den Typ ''Datum'' auch in objektorientierter Weise weiterentwickeln, 
 +aber dieses Programmierbeispiel ist schon lang genug. Schluss jetzt!
 +
  

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki