 |
Wer sich die Aufstellung der römischen Zahlen auf meiner Webseite über Zahlendarstellungen
angesehen hat, wird sich vielleicht gefragt haben, nach welchen Algorithmus ich diese zahlreichen römischen
Zahlen ermittelt habe. Schließlich habe ich nicht über 4000 römische Zahlen von Hand eingetippt. Andererseits
beinhalten gewöhnliche Programmiersprachen keine Option zur Ausgabe der Zahlen in römischer Darstellung.
Ich habe eine Routine zur Berechnung der römischen Schreibweise einer Zahl selbst programmiert. Der folgende
Screenshot zeigt diese Methode in der Programmiersprache MAGIK, einer sehr stark an Smalltalk angelehnten
Sprache, die von dem aus der Universitätsstadt Cambridge in Großbritanniern stammenden Softwareunternehmen
Smallworld Systems (heutiger Name: GE Smallworld Systems) im Jahr 1988 in Ermangelung einer plattformunabhängigen
Standartisierung von Smalltalk entwickelt wurde, um darin eine unter verschiedenen Betriebssystemen verfügbare Datenbankanwendung für räumliche
Daten (Spatial Data) zu programmieren, das Smallworld GIS.
Wer bereits mit anderen Programmiersprachen gearbeitet hat, wird sich in die Syntax recht schnell
einfinden. Besonderheiten der Sprache MAGIK sind:
Schlüsselwörter, d.h. festgelegte Sprachkonstrukte, werden durch einen Unterstrich am Anfang gekennzeichnet.
Das Zeichen "<<" (Doppelpfeil nach links) kennzeichnet eine Variablenzuweisung, wie sie in anderen
Sprachen durch ":=" oder "=" definiert wird. Mit Plus oder Minus, also "+<<" oder "-<<", wird daraus das
In- oder Dekremieren einer Variablen. Eine Schleife (loop-endloop) ohne Iterationsbedingung (for-over) ist
eine Endlosschleife. Schleifen können unabhängig davon, ob es Endlosschleifen sind oder nicht, mit dem
Schlüsselwort leave verlassen werden. Mit dem Schlüsselwort continue wird der aktuelle Schleifendurchlauf
abgebrochen und mit den nächsten Schritt fortgefahren. Mit dem Schlüsselwort return schließlich wird die
gesamte MAGIK-Methode direkt verlassen und ein Rückgabewert geliefert.
Im Sinne der Objektorientierung sind auch Prozeduren Objekte, genauso wie z.B. Zahlen und Zeichenketten.
Deshalb kann man sie einfach durch Hinschreiben definieren und lokalen Variablen zuweisen.
Im Gegensatz zu
prozeduralen Sprachen muß bei objektorientierten Sprachen zwischen einzelnen Zeichen (Characters) und
Zeichenketten (Strings bzw. Character-Vectors) unterschieden werden. Denn beide haben ganz verschiedene
Methoden: Ein einzelnes Zeichen kann z.B. nach seinem ASCII-Code gefragt werden oder kann Auskunft
darüber geben, ob es ein Klein- oder Großbuchstabe, ein Ziffer oder ein Sonderzeichen ist. Eine Zeichenkette
dagegen kann Auskunft darüber geben, wie lang sie ist, kann Teile von sich ausschneiden oder sich
in Teile zerlegen. Als Elemente enthält die Zeichenkette einzelne Zeichen.
In der Sprache MAGIK ist die Unterscheidung zwischen Zeichen und Zeichenketten syntaktisch so umgesetzt, daß
einzelne Zeichen durch das Prozentzeichen als Präfix definiert werden, während (wie in den meisten
Programmiersprachen üblich) Zeichenketten in Hochkommata eingefaßt werden. So bezeichnet also %A den
Großbuchstaben A, während "A" die Zeichenkette der Länge Eins ist, die als Element den Großbuchstaben A
enthält.
Bei der unten wiedergegebenen Methode, die zu einer gegebenen Zahl die römische Darstellung als
Zeichenkette liefert, ist bemerkenswert, daß gar keine Methodenaufrufe vorkommen. Mit Ausnahme der
Tatsache, daß Prozeduren (weil es Objekte sind) lokalen Variablen zugewiesen und durch runde
Klammern (die eine Kurzform des Methodenaufrufs ".invoke()", zu deutsch "führe Dich aus", sind) gestartet
werden, ist die Methode also weniger objektorientiert, sondern mehr prozedural gehalten.
|
 |
Wie man sieht, definiere ich zunächst zwei Prozeduren, die über die Wertigkeiten der römischen Ziffern und
die Ziffern selbst (als Großbuchstaben) iterieren. Die Prozedur ziffern iteriert in aufsteigender
Reihenfolge, die Prozedur ziffern2 in absteigender Reihenfolge. Für die zu berechnende Zeichenkette
definiere ich eine Variable r. In einer großen Schleife wird bei jedem Schritt diese Zeichenkette
um römische Ziffern ergänzt und die als Variable zahl übergebene umzurechnede Zahl um den entsprechenden
Betrag vermindert. Ist die Zahl schließlich auf Null gesunken, wird die ermittelte römische Darstellung der
Zahl als Zeichenkette zurückgeliefert.
Bei jedem Schritt prüfe ich zunächst, ob an dieser Stelle die Subtraktion zweiter römischer Ziffern
sinnvoll ist. Wenn die Variable zahl aktuell den Wert 47 hat, soll ja nicht die Ausgabe r um
den Buchstaben X ergänzt und danach mit 37 weitergerechnet werden, sondern es soll die Ausgabe r um
die Buchstabenkombination VL ergänzt und mit der Zahl 7 weitergerechnet werden.
Dazu durchlaufe ich mit der Variablen nr1 als Kandidaten für das, was zu subtrahieren ist, die Wertigkeiten
der römischen Ziffern in aufsteigender Reihenfolge, wobei c1 der jeweils entsprechende Buchstaben ist.
Diese Schleife kann ich abbrechen, wenn ich zu einer Ziffer gekommen bin, die doppelt so viel wert ist wie der
noch zu untersuchende Wert in der Variablen zahl. Als Kandidaten für den zu addierenden Buchstaben, von
dem c1 subtrahiert werden soll, durchlaufe ich in absteigender Reihenfolge die Wertigkeiten der römischen Ziffern
mit der Variablen nr2. Die zugehörigen Buchstaben stehen in der Variablen c2. Diese Schleife muß ich
abbrechen, wenn der zu addierende Wert gleich oder größer dem doppelten des zu subtrahierenden Wertes ist. Denn es
sollen die Fünfer-Werte nicht von den darüberliegenden glatten Werten abgezogen werden. Sonst würde es zu dem
Kuriosum kommen, daß z.B. die Zahl 5 statt durch V durch die Subtraktion VX dargestellt würde.
Ausgedrückt mit den Variablen c1 und c2 durchlaufe ich mit den beiden Schleifen also folgende
Kombinationen römischer Ziffern in dieser Reihenfolge: IM, ID, IC, IL, IX, IV, VM, VD, VC, VL, XM, XD, XC, XL,
LM, LD, CM, CD. Der Wert dieser Kombinationen ist jeweils nr2-nr1. Am einfachsten ist es natürlich,
wenn der Inhalt der Variablen zahl exakt einer dieser Werte ist. Dann habe ich die darzustellende Zahl
komplett abgearbeitet und kann direkt aus der Methode herausspringen.
Andernfalls muß ich ermitteln, ob es Sinn macht, die dargestellten Kombinationen zu verwenden. Dies ist dann
der Fall, wenn es danach nur noch mit kleineren Ziffern weitergeht. Anders ausgedrückt: In dem Wert, der sich
aus der aktuell zu betrachtenden Zahl plus der in der römischen Darstellung zu subtrahierenden Ziffer
ergibt, darf die zu addierende Ziffer nur noch einmal enthalten. Denn z.B. soll die Zahl 19 nicht als IXX, sondern
als XIX dargestellt werden. Ich darf aber nicht einfach nur testen, ob nr2-nr mindestens so groß wie zahl ist. Sonst
würde z.B. bei der Zahl 6 festgestellt, daß die Subtraktion IV (5) paßt und danach mit 2 weitergerechnet. Letztlich
würde dann die 6 als IVII dargestellt. Statt dessen muß ich testen, ob die aktuelle Zahl plus der zu subtrahierenden
Ziffer einen glatten Wert bezüglich der zu addierenden Ziffer ergibt. Damit die Subtraktion IV Sinn macht, muß
die aktuelle Zahl plus Eins ein glatter Wert bezüglich der Fünf sein.
Die aktuell zu betrachtende Zahl muß dabei aber nicht derart einfach sein. So soll
z.B. bei der Zahl 1986 festgestellt werden, daß eine Subtraktion keinen Sinn macht, wenn aber der Algorithmus
bei der Zahl 986 angelangt ist, soll festgestellt werden, daß die Subtraktion LM (1000-50 ergibt 950) sehr wohl
Sinn macht, um danach mit der Zahl 36 weiterzurechnen. Letztlich soll ja 1986 als MLMXXXVI dargestellt werden.
Nach der oben skizzierten Regel sollte ich also feststellen, daß 986 plus 50 (also 1036) ein glatter Wert
bezüglich der Zahl Tausend (römisch M) ist. Ich darf also den Begriff "glatter Wert" nicht wörtlich nehmen,
sondern muß bei der Betrachtung ggf. abrunden. Aber nicht beliebig abrunden, sondern abrunden auf das nächste
Vielfache einer römischen Ziffer, die kleiner oder gleich der subtrahierten Ziffer, und die echt kleiner als
die addierte Ziffer ist. Die Rolle dieser Ziffer übernimmt in einer Schleife die Variable nr3. Diejenigen
Schleifendurchläufe, bei denen nr3 echt größer als nr1 ist oder bei denen nr3 größer
als gleich nr2 ist, werden dabei übersprungen. Das Abrunden wird mittels der Ganzzahldivision "div"
und anschließender Multiplikation durchgeführt. Bei dem Abrunden muß genau die Zahl nr2 getroffen werden.
Dann wird die Ziffernfolge c1+c2 in der römischen Zahl ergänzt und die entsprechende Wertigkeit nr2-nr1
von der noch zu betrachtenden Zahl abgezogen.
Die Feststellung einer römischen Ziffer, die nach dem Additionsverfahren hinzugezogen werden soll, ist
dagegen recht einfach: Es wird einfach die höchstwertigste Ziffer, die größer oder gleich der Zahl ist,
zu der Darstellung ergänzt und ihr Wert von der noch zu betrachtenden Zahl abgezogen.
Man kann sich fragen, ob man die Methode nicht wesentlich vereinfachen kann, indem man nicht nur die
einzelnen römischen Ziffern, sondern auch deren Subtraktionskombinationen als Konstanten in Form von
Iteratorprozeduren hinterlegt. Grundsätzlich
ist dazu anzumerken, daß natürlich jeder Algorithmus, der nur auf einer endlichen Eingabemenge angewendet
wird, oder der Rechenschritte tut, die nur endlich viele Varianten haben, dadurch vereinfacht werden kann,
daß man die Ergebnisse von Berechnungen, die er durchführt, fest einprogrammiert.
Man könnte also den Algorithmus zur Berechnung der römischen Zahlen sogar ganz enorm
vereinfachen, indem man alle römischen Zahlen von 1 bis 1000 als Konstanten einfügt. Für jedes weitere
Tausend einer Zahl müsste dann nur noch jeweils der Buchstabe M vor die Ausgabe geschrieben werden. Zur einmaligen
Erstellung (Programmierung) des Algorithmus würde dann natürlich das Ermitteln der römischen Zahlen 1 bis 1000 gehören.
Doch genau dies soll der Programmierer ja nicht tun. Rechnen soll der Komputer und nicht der
Programmierer. Der Komputer soll dem Menschen Rechenaufwand abnehmen, nicht umgekehrt! Was der Rechenknecht
nicht tun kann, ist das Denken. Der Mensch muß also mit Hilfe seines Verstands überlegen, welche Rechenschritte
im einzelnen zu tun sind, er sollte die auf Grundlage dieser Überlegungen zu tätigende Ausführung (Berechnung) dann
aber dem Komputer überlassen. Insofern ist der obige Algorithmus mit der minimal erforderlichen Vorgabe von Konstanten,
nämlich den römischen Ziffern und ihren Wertigkeiten, genau richtig.
|