Zinsrechnung


In dieser Aufgabe soll das momentane Guthaben auf einem Sparkonto berechnet werden. Als Eingabe dienen zwei Textdateien, einzahlung.txt und zinssatz.txt.
In der Datei einzahlung.txt werden Ein- und Auszahlungen auf das Konto mit Tag der Wertstellung aufgelistet. In der Datei zinssatz.txt werden Änderungen des jährlichen Guthabenszinssatzes mit Datum der Änderung aufgelistet.

Format von einzahlung.txt: Alle Zeilen sind chronologisch geordnet. Als erstes steht in jeder Zeile das Datum der Wertstellung im Format TT.MM.JJJJ, dann folgen beliebig viele, aber mindestens ein Leerzeichen, dann folgt der verbuchte Umsatz als übliche Fließpunktzahl in einer beliebigen Währung (aber immer in der selben). Positive Zahlen repräsentieren Einzahlungen, negative Zahlen Abbuchungen.

Beispiel:

01.03.2006 +34.5
07.03.2006 +13.25
05.05.2006 -60
02.08.2006 -30
30.11.2006 +1534.87
30.11.2006 +220.17
20.12.2006 -548.22
02.01.2007 -345

Format von zinssatz.txt: Analog zu einzahlung.txt, jedoch wird die Fließpunktzahl als Zinssatz in Prozent pro Jahr interpretiert.

Beispiel:

05.07.2006 2.75
01.12.2006 3.0

Ihr Programm kann davon ausgehen, dass die Dateien keine Fehler enthalten und braucht keine Konsistenzprüfungen durchzuführen. Die Berechnung des Guthabens verläuft folgendermaßen:
• Jeder Monat hat nach Definition 30 Tage.
• Da in den Eingabe-Dateien jeweils das Datum der tatsächlichen Wertstellung angegeben wird, kann der Fall nicht auftreten, dass dort ein Datum angegeben ist,
das auf den 31. Tag eines Monats fällt oder kein Geschäftstag der Bank ist.
• Das Startguthaben des Kontos vor dem ersten Eintrags in einzahlung.txt beträgt 0.00.
• Einzahlungen zählen ab dem Tag ihrer Wertstellung zum Guthaben und geben ab diesem Tag Zinsen.
• Auszuzahlende Beträge werden nur bis zum Tag direkt vor der Wertstellung verzinst. Reicht das Guthaben für eine Auszahlung nicht aus, so wird die Buchung
ignoriert. Das Guthaben kann nie negativ werden.
• Für alle Tage vor dem ersten Datum in zinssatz.txt werden keine Zinsen bezahlt. Der neue Zinssatz gilt jeweils ab dem in der Datei angegebenen Datum
(einschließlich).
• Zinserträge werden für den Zeitraum bis 30.12. jedes Jahres berechnet und am 01.01. des darauffolgenden Jahres wie eine gewöhnliche Einzahlung gutgeschrieben. Für jeden Tag t (Achtung, jeder Monat hat genau 30 Tage!) werden die Zinsen nach der Formel g(t) · z(t)/36000 berechnet, wobei g(t) das Guthaben am Tag t darstellt und z(t) den am Tag t gültigen jährlichen Zinssatz (z. B. 2.75).
• Wird das Sparkonto aufgelöst, so werden Zinsen bis zum Tag vor der Auflösung berechnet und mit ausgezahlt.

Schreiben Sie ein Programm guthaben, das den Benutzer ein Auflösungsdatum eingeben lässt und anhand der Dateien zinssatz.txt und einzahlung.txt das Guthaben des Sparkontos zu diesem Datum berechnet. Liegt das Auflösungsdatum so früh, dass noch weitere Buchungen oder Zinssatzänderungen ausstehen, so werden die ausstehenden Ereignisse ignoriert und das Konto vorzeitig aufgelöst. Lassen Sie Ihr Programm ein Protokoll ausgeben, das alle relevanten Aktionen und den am Auflösungsdatum ausgezahlten Kontostand enthält.
Ihr Programm muss die Dateien zunächst vollständig einlesen und den Inhalt in Listen verwalten. Lösungen ohne den Gebrauch von Listen werden nicht akzeptiert.
Hinweis: Auf der Webseite wird ein Paket Dates angeboten. Dieses Paket stellt einen Datentyp zur Verarbeitung eines Datums zur Verfügung. Sie können das Paket einsetzen (auch verändern oder erweitern) um Ihre Implementierung zu vereinfachen.

Beispiel für einen Programmablauf:

Lesen von einzahlung.txt...
Lesen von zinssatz.txt...
Berechnung bis (Datum): 4.4.2007
Kontobewegung am 01.03.2006: 34.50
Kontobewegung am 07.03.2006: 13.25
Fehlgeschlagene Buchung am 05.05.2006: -60.00
Zinssatzänderung am 05.07.2006 auf 2.75 % p.a.
Kontobewegung am 02.08.2006: -30.00
Kontobewegung am 30.11.2006: 1534.87
Kontobewegung am 30.11.2006: 220.17
Zinssatzänderung am 01.12.2006 auf 3.00 % p.a.
Kontobewegung am 20.12.2006: -548.22
Zinsgutschrift am 01.01.2007: 4.32
Kontobewegung am 02.01.2007: -345.00
Zinsgutschrift am 04.04.2007: 6.88
Guthaben bei Kontoauflösung am 04.04.2007: 890.77
--  FILE:    zins.adb
--  PROJECT: Programmieruebungen, Uebungsblatt 11
--  VERSION: 1.0
--  DATE:    27.01.2007
--  AUTHOR:  http://www.CodeWelt.com
--
-------------------------------------------------------------------
-- 
--  Aufgabe 11.1: Zinsrechnung
--
--  In dieser Aufgabe wird das momentane Guthaben auf einem
--  Sparkonto berechnet. Als Eingabe dienen zwei Textdateien,
--  einzahlung.txt und zinssatz.txt. In der Datei einzahlung.txt
--  werden Ein- und Auszahlungen auf das Konto mit jährlichen
--  Guthabenszinssatzes mit Datum der änderung aufgelistet.
--  In der zinssatz.txt: Analog zu einzahlung.txt, jedoch wird
--  der Float-Wert als Zinssatz in Prozent pro Jahr interpretiert.
--  Das Package zins bietet Funktionalität rund um Zinsrechnung.
--
-------------------------------------------------------------------
with Ada.Float_Text_IO, Ada.Strings.Fixed,
     Ada.Strings.Unbounded.Text_IO, Ada.Strings.Unbounded, Dates,
     Ada.Text_IO, Ada.Unchecked_Deallocation;
use  Ada.Strings.Unbounded.Text_IO, Ada.Strings.Unbounded, Dates,
     Ada.Text_IO;
 
package body Zins is
 
   procedure Free is new Ada.Unchecked_Deallocation (Eintrag, Zeiger);
 
   --  Die globalen Zeiger auf die beiden Listen.
   ZahlungAnchor, ZinsAnchor : Zeiger := null;
 
   --  EXCEPTION Bad_Date
   --
   --  Wird immer dann erhoben wenn ein Datum (z.B.)
   --  nicht aus Punkten und numerischen Zeichen
   --  besteht.
   Bad_Date : exception;
 
   --  PROCEDURE Destroy
   --
   --  Es wird der Speicher beider Listen, ZahlungAnchor
   --  und ZinsAnchor, freigegeben und am ende die
   --  globalen Zeiger auf null gesetzt.
   procedure Destroy is
      ZahlungAnchorx, ZahlungAnchorxCopy : Zeiger := ZahlungAnchor;
      ZinsAnchorx, ZinsAnchorxCopy : Zeiger := ZinsAnchor;
   begin
      --  Die Liste ZahlungAnchor wird freigegeben.
      while ZahlungAnchorx /= null loop
         ZahlungAnchorxCopy := ZahlungAnchorx;
         ZahlungAnchorx := ZahlungAnchorx.Next;
         Free (ZahlungAnchorxCopy);
      end loop;
      ZahlungAnchor := ZahlungAnchorx;
      --  Die Liste ZinsAnchor wird freigegeben.
      while ZinsAnchorx /= null loop
         ZinsAnchorxCopy := ZinsAnchorx;
         ZinsAnchorx := ZinsAnchorx.Next;
         Free (ZinsAnchorxCopy);
      end loop;
      ZinsAnchor := ZinsAnchorx;
   end Destroy;
 
   --  FUNCTION Get_Date
   --
   --  Es wird das als Unbounded_String übergebene
   --  Datum zu einem typ Dates.Date aus dem Package
   --  Dates umgewandelt. Es wird die Exception
   --  Bad_Date erhoben wenn der String nicht
   --  konvertiert werden kann.
   --
   --  PARAMETERS:
   --  + Date_Text - Das Datum als Text welches
   --  zum echten Datum umgewandelt werden soll.
   --  RETURNS:
   --  Die Funktion liefert das übergebene Datum
   --  als typ Dates.Date zurück.
   function Get_Date
      (Date_Text : in Unbounded_String)
      return Dates.Date
   is
      Day_Dot, Month_Dot, Day, Month, Year : Integer := 0;
   begin
      --  Das Datum darf nicht weniger als 5 zeichen haben und
      --  nicht mehr als 10.
      if Length (Date_Text) < 5 or Length (Date_Text) > 10 then
         raise Bad_Date;
      end if;
      --  Es wird der erste Punkt von links gelesen gesucht und die Position
      --  in Day_Dot hinterlegt.
      Day_Dot := Ada.Strings.Fixed.Index (To_String (Date_Text), ".",
      Ada.Strings.Forward);
      --  Es wird der erste Punkt von rechts gelesen gesucht und die Position
      --  in Month_Dot hinterlegt.
      Month_Dot := Ada.Strings.Fixed.Index (To_String (Date_Text), ".",
      Ada.Strings.Backward);
      --  Es wird der Teil vom Datum als Text betrachtet der beim ersten
      --  Zeichen anfüngt und bis zum Day_Dot geht. Danach wird dieser Teil
      --  zum Integer gewandelt und gespeichert. Wenn dabei ein
      --  Constraint_Error erhoben wird, ist ein nicht numerisches Zeichen
      --  dabei und das Datum somit nicht lesbar.
      Day := Integer'Value (Slice (Date_Text, 1, Day_Dot - 1));
      --  ähnliche Vorgehensweise für Monat und Jahr.
      Month := Integer'Value (Slice (Date_Text, Day_Dot + 1, Month_Dot - 1));
      Year := Integer'Value (Slice (Date_Text, Month_Dot + 1,
      Length (Date_Text)));
      if Day > 31 or Day < 1 or Month > 12 or Month < 1 then
         raise Bad_Date;
      end if;
      --  Die gelesenen Integer werden mit hilfe der Create Funktion
      --  aus dem Package Dates zum typ Date gemacht und zurückgegeben.
      return Dates.Create (Day, Month, Year);
   exception
      when Constraint_Error =>
         raise Bad_Date;
   end Get_Date;
 
   --  FUNCTION Zerlege
   --
   --  Beim Einlesen der zwei Dateien wird
   --  jede Zeile in ihre Teile zerlegt.
   --  Um nacher mit den daraus gewonnenen Daten
   --  weiterarbeiten zu können.
   --
   --  PARAMETERS:
   --  + Line - Die aktuelle Zeile aus der Datei
   --  als Unbounded_String welche in ein Datum
   --  und einen Float-Wert zerlegt werden soll.
   --  RETURNS:
   --  Die Funktion liefert den in der zins.ads
   --  definierten Typ Date_Float zurück. Dieser
   --  Typ enthält die gewonnenen Daten.
   function Zerlege
      (Line : in Unbounded_String)
      return Date_Float
   is
      Zerlegt : Date_Float;
      Date_Text : Unbounded_String := Null_Unbounded_String;
      Space : Integer := 0;
   begin
      --  Es wird die Position der Leerzeile ermittelt die
      --  Datum und Float-Wert trennt.
      Space := Ada.Strings.Fixed.Index (To_String (Line), " ",
      Ada.Strings.Forward);
      Zerlegt.Zahl := Float'Value (Slice (Line, Space + 1, Length (Line)));
      Date_Text := To_Unbounded_String (Slice (Line, 1, Space - 1));
      --  Mit hilfe der Get_Date funktion die oben beschrieben ist
      --  wird ein typ Dates.Date gewonnen.      
      Zerlegt.Datum := Get_Date (Date_Text);
      --  Der nun mit Daten gefüllte Type Date_Float wird zurückgegeben.
      return Zerlegt;
   end Zerlege;
 
 
   --  PROCEDURE Run
   --
   --  Die Prozedur setzt das Startdatum auf den ersten
   --  Eintrag in der ZahlungAnchor Liste und führt solange
   --  Schritte aus bis das vom Benutzer eingegebene Datum
   --  erreicht ist.
   procedure Run is
      Eingabe : Unbounded_String := Null_Unbounded_String;
      EingabeDatum, DatumWalker : Dates.Date;
      ZahlungAnchorCopy : Zeiger := ZahlungAnchor;
      ZinsAnchorCopy : Zeiger := ZinsAnchor;
      Guthaben, Zinssatz, Zinsen : Float := 0.00;
      GibZinsen : Boolean := False;
   begin
      Put ("Berechnung bis (Datum): ");
      Get_Line (Eingabe);
      EingabeDatum := Get_Date (Eingabe);
      --  Das Startdatum wird auf das Datum des ersten
      --  Eintrags in der ZahlungAnchor Liste gesetzt.
      DatumWalker := ZahlungAnchorCopy.Datum;      
      while Dates."<" (DatumWalker, EingabeDatum) loop
         --  Zinserträge werden für den Zeitraum bis 30.12 jedes Jahres
         --  berechnet und am 01.01. des darauffolgenden Jahres wie eine
         --  gewöhnliche Einzahlung gutgeschrieben.
         if GibZinsen = True then
            Put ("Zinsgutschrift am " & Dates.Image (DatumWalker) & ": ");
            Ada.Float_Text_IO.Put (Zinsen, 0, 2, 0);
            New_Line;
            Guthaben := Guthaben + Zinsen;
            Zinsen := 0.00;
            GibZinsen := False;
         end if;
         --  Falls an einem Tag mehrere Kontobewegungen stattfinden
         --  wird hier solange ausgeführt bis alle am aktuellen Tag
         --  stattgefundenen Bewegungen ausgeführt sind.
         while ZahlungAnchorCopy.Datum = DatumWalker loop
            --  Das Konto kann nie negativ werden.
            if ZahlungAnchorCopy.Zahl < 0.00 and
            abs (ZahlungAnchorCopy.Zahl) > Guthaben then
               Put ("Fehlgeschlagene Buchung am " &
               Dates.Image (DatumWalker) & ": ");
               Ada.Float_Text_IO.Put (ZahlungAnchorCopy.Zahl, 0, 2, 0);
               New_Line;
            else
               Put ("Kontobewegung am " & Dates.Image (DatumWalker) & ": ");
               Ada.Float_Text_IO.Put (ZahlungAnchorCopy.Zahl, 0, 2, 0);
               New_Line;
               Guthaben := Guthaben + ZahlungAnchorCopy.Zahl;
            end if;
            if ZahlungAnchorCopy.Next = null then
               exit;
            else
               ZahlungAnchorCopy := ZahlungAnchorCopy.Next;
            end if;
         end loop;
         --  Es wird geprüft ob in der Liste ZinsAnchor für den aktuellen
         --  Tag eine Zinssatzänderung gemacht wird.
         if ZinsAnchorCopy.Datum = DatumWalker then
            Put ("Zinssatzänderung am " & Dates.Image (DatumWalker) & " auf ");
            Ada.Float_Text_IO.Put (ZinsAnchorCopy.Zahl, 0, 2, 0);
            Put (" % p.a.");
            New_Line;
            --  Wenn ja wird der neue Zinssatz ab sofort verwendet.
            Zinssatz := ZinsAnchorCopy.Zahl;
            if ZinsAnchorCopy.Next /= null then
               ZinsAnchorCopy := ZinsAnchorCopy.Next;
            end if;
         end if;
         --  Die Zinsen werden täglich ermittelt.
         Zinsen := Zinsen + (Guthaben * (Zinssatz / 36000.00));
         if Month (DatumWalker) = 12
         and DatumWalker = End_Of_Month (DatumWalker) then
            GibZinsen := True;
         end if;
         DatumWalker := Dates."+" (DatumWalker, 1);
      end loop;
      --  Wird das Sparkonto aufgelöst, so werden Zinsen bis zum Tag vor
      --  der Auflösung berechnet und mit ausgezahlt.
      Put ("Zinsgutschrift am " & Dates.Image (EingabeDatum) & ": ");
      Ada.Float_Text_IO.Put (Zinsen, 0, 2, 0);
      New_Line;
      Guthaben := Guthaben + Zinsen;
      Zinsen := 0.00;
      Put ("Guthaben bei Kontoauflösung am " & Dates.Image (EingabeDatum) &
      ": ");
      Ada.Float_Text_IO.Put (Guthaben, 0, 2, 0);
      New_Line;
   exception
      when Bad_Date =>
         Put_Line ("Bitte das Datum korrekt eingeben (e.g. 1.1.2007)");
         Run;
   end Run;
 
 
   --  PROCEDURE Load_einzahlung_txt
   --
   --  In der Prozedur Load_einzahlung_txt wird die Datei
   --  Zeile für Zeile eingelesen, zerlegt und die Bestandteile
   --  der Zeile in einen neuen Eintrag in der Liste ZahlungAnchor
   --  für die weitere Verwendung gespeichert.
   procedure Load_einzahlung_txt is
      File : Ada.Text_IO.File_Type;
      AktuelleZeile : Unbounded_String := Null_Unbounded_String;
      Zerlegt : Date_Float;
      Last : Zeiger := ZahlungAnchor;
   begin
      Put_Line ("Lesen von einzahlung.txt...");
      Open (File, in_file, "einzahlung.txt");
      while not End_Of_File (File) loop
         Get_Line (File, AktuelleZeile);
         Zerlegt := Zerlege (AktuelleZeile);
         if ZahlungAnchor = null then
            --  falls zuvor leere Liste, dann einzellige Liste erzeugen
            ZahlungAnchor := new Eintrag'(Datum => Zerlegt.Datum,
            Zahl => Zerlegt.Zahl, Next => null);
         else
            --  letzten Eintrag suchen
            Last := ZahlungAnchor;
            while Last.Next /= null loop
               Last := Last.Next;
            end loop;
            --  ... und Next des letzten Eintrags auf einen neuen Eintrag
            --  setzen
            Last.Next := new Eintrag'(Datum => Zerlegt.Datum,
            Zahl => Zerlegt.Zahl, Next => null);
         end if;
      end loop;
      Close (File);
   end Load_einzahlung_txt;
 
 
   --  PROCEDURE Load_zinssatz_txt
   --
   --  In der Prozedur Load_zinssatz_txt wird die Datei
   --  Zeile für Zeile eingelesen, zerlegt und die Bestandteile
   --  der Zeile in einen neuen Eintrag in der Liste ZinsAnchor
   --  für die weitere Verwendung gespeichert.
   procedure Load_zinssatz_txt is
      File : Ada.Text_IO.File_Type;
      AktuelleZeile : Unbounded_String := Null_Unbounded_String;
      Zerlegt : Date_Float;
      Last : Zeiger := ZinsAnchor;
   begin
      Put_Line ("Lesen von zinssatz.txt...");
      Open (File, in_file, "zinssatz.txt");
 
      while not End_Of_File (File) loop
         Get_Line (File, AktuelleZeile);
         Zerlegt := Zerlege (AktuelleZeile);
 
         if ZinsAnchor = null then
            --  falls zuvor leere Liste, dann einzellige Liste erzeugen
            ZinsAnchor := new Eintrag'(Datum => Zerlegt.Datum,
            Zahl => Zerlegt.Zahl, Next => null);
         else
            --  letzten Eintrag suchen
            Last := ZinsAnchor;
            while Last.Next /= null loop
               Last := Last.Next;
            end loop;
            --  ... und Next des letzten Eintrags auf einen neuen Eintrag
            --  setzen
            Last.Next := new Eintrag'(Datum => Zerlegt.Datum,
            Zahl => Zerlegt.Zahl, Next => null);
         end if;
      end loop;
      Close (File);
   end Load_zinssatz_txt;
 
end Zins;