Exact Real Numbers in Haskell

Werbung
Fakultät für Informatik
Lehrstuhl für Logik und Verifikation
Exact Real Numbers in Haskell
Julia Martini
Seminar Fortgeschrittene Konzepte der funktionalen
Programmierung (SS15)
Betreuer:
Fabian Immler
Leitung:
Prof. Tobias Nipkow
Abgabetermin: 24.06.2015
Inhaltsverzeichnis
1 Vorwort
2
2 Ansätze zur Berechnung reeller Zahlen
2
3 Realisierung exakter reeller Arithmetik durch unendliche Sequenzen
3
4 Lazy Evaluation in Haskell
6
5 Zahlen als Sequenz in Haskell definieren
6
6 Arithmetische Operationen auf Sequenzen
8
7 Zusammenfassung
9
8 Kritik
10
1
1
Vorwort
Diese Arbeit beschäftigt sich mit Möglichkeiten, reelle Zahlen zu berechnen
und möchte dabei insbesondere auf das Verfahren Exact Real Numbers“ auf”
merksam machen und skizzieren, wie man das etwa in Haskell implementieren
könnte. Im Wesentlichen geht es darum, dass Zahlen als Berechnungsvorschriften repräsentiert werden und Operatoren dadurch realisiert werden, indem Berechnungsvorschriften von Zahlen zu einer Berechnungsvorschrift für eine neue
Zahl zusammengesetzt werden. Solche Berechnungsvorschriften können als Folge von Ziffern in Form von unendlichen Listen implementiert werden, die in
Haskell mit Lazy Evaluation soweit wie nötig ausgelesen werden können. Es
werden ein paar Beispiele an Haskell-Code gezeigt.
2
Ansätze zur Berechnung reeller Zahlen
Meistens werden reelle Zahlen in Computern mit fester Genauigkeit gerechnet. Zum Beispiel mit Fließkommaarithmetik fester Stelligkeit, wie z.B. 64 Bit
double-precision (Plu98). Dabei werden die vordersten Stellen einer Zahl berechnet und am Ende muss gerundet werden. Wenn nun zum Beispiel zwei
Zahlen addiert werden müssen, lässt sich das Stelle für Stelle von hinten leicht
realisieren mit Übertrag, weil alle Zahlen die gleiche Stelligkeit und damit auch
Genauigkeit haben. Schwierig wird es, wenn sich Rundungsfehler akkumulieren und dabei größere Ungenauigkeiten entstehen können. Das Verfahren der
Rechnung liefert schlichtweg keine Garantie, wie genau das Ergebnis ist, was
man als einen großen Nachteil von Fließkommaarithmetik sehen kann (Plu98).
Um diesen Rundungsfehlern zu begegnen, gibt es Verfahren zur Fehlerabschätzung mit denen maximale Abweichungen garantiert werden können
(Plu98). Dies ermöglicht eine Einschätzung der Genauigkeit des Ergebnisses,
falls jedoch ein genaueres Ergebnis benötigt wird, wird das mit diesem Verfahren nicht geliefert.
Mit sogenannter Intervallarithmetik können bessere Ergebnisse erzielt werden, indem bei der Berechnung stetes ein Paar von reellen Zahlen angegeben
wird, welches ein Intervall beschreibt, in dem die zu berechnende Zahl liegt
(Plu98). Auch wenn dieses Verfahren eine größere Sicherheit über die Exaktheit der Ergebnisse liefert, kann es jedoch sein, dass es keine ausreichend hohe
Genauigkeit zu Verfügung stellt.
Ein weiterer Ansatz ist symbolische Berechnung, die vor allem in Mathematik-Programmen wie Maple, MATLAB oder Mathematica Anwendung findet. Wenn für Zahlen Berechnungsvorschriften in Form von Verknüpfung von
Funktionen gegeben sind, kann eine solche Berechnungsvorschrift symbolisch“
”
2
in andere, äquivalente Berechnungsvorschriften überführt werden, durch Ausnutzung mathematischer Umformungen. Der Nachteil eines solchen Ansatzes
ist zunächst, dass er nicht immer einsetzbar ist, wenn es keine entsprechende
symbolische Repräsentation oder Umformung gibt, und außerdem oft das Ergebnis einer symbolischen Umformung wieder in nummerischer Form angefragt
ist, sodass dennoch weitere nummerische Darstellungen von reellen Zahlen erforderlich sind (Plu98).
In dieser Arbeit untersucht ist primär das Verfahren der exakten reellen
Arithmetik, mit dem man Berechnungen beliebig genau dynamisch durchführen
kann. Mit exakt“ ist hier keineswegs gemeint, dass man jede reelle Zahl wirk”
lich exakt, also komplett ausgibt, da es reelle Zahlen gibt, die unendlich viele
Stellen haben. Sondern damit ist gemeint, dass man eine reelle Zahl auf so
viele Stellen ausgeben kann, wie man sie gerade benötigt. Es gibt reelle Zahlen
mit nur endlich vielen Stellen. Ein Verfahren für die exakte Berechnung reeller Zahlen könnte dann verwendet werden, um solche komplett auszugeben,
ohne vorher zu wissen, wie viele Stellen sie haben. Es kann aber auch sein,
dass bei unendlich-stelligen Zahlen eine flexible Genauigkeit benötigt wird, obwohl die benötigte Genauigkeit des Endergebnisses festgelegt ist. Das passiert,
wenn eine Genauigkeit vorgegeben ist, die am Ende der Berechnung erreicht
werden muss, aber die Genauigkeit in den Zwischenschritten – die dazu unter
Umständen sehr viel größer sein muss – dann automatisch, dynamisch nach den
Erfordernissen generiert werden muss, um die Genauigkeit des Endergebnisses
zu gewährleisten. Das ist eine wesentliche Anforderung, die bei FließkommaArithmetik nicht gewährleistet ist, weil dort zwar eine feste Zahl an Stellen
vorgegeben ist, jedoch keineswegs garantiert wird, dass dort alle Stellen des
Endergebnisses richtig sind. Ein Verfahren zur exakten reellen Arithmetik,
kann jedoch nicht (wie kein anderes Verfahren) unberechenbare reelle Zahlen
berechnen. Insofern geht es hier um ein Verfahren zur dynamischen, beliebig
genauen Berechnung berechenbarer reeller Zahlen. Das Ergebnis darf dabei
nicht gerundet sein und jede Stelle muss richtig, also exakt“ sein.
”
3
Realisierung exakter reeller Arithmetik durch
unendliche Sequenzen
Die wesentliche Idee hinter exakter reeller Arithmetik ist das Implementieren von Datenstrukturen, die unendliche Sequenzen wiedergeben können. Damit sind keineswegs unendlich große Datenstrukturen gemeint, sondern Datenstrukturen, die nach Bedarf wachsen. Im mathematischen Sinn wird nichts
anderes realisiert als eine unendliche Folge, von der nach Bedarf ein endlicher
3
Teil der Folgenglieder berechnet wird. Es gibt viele Möglichkeiten, reelle Zahlen als Folge darzustellen. So wird zum Beispiel in der Analysis die eulersche
Zahl häufig als eine Folge von Partialsummen definiert:
∞
X
1
1
1
1
e=
= 1 + + + + ...
n!
2! 3! 4!
n=0
Man könnte nun eine Datenstruktur realisieren, die sukzessive diese Folge von
Partialsummen und damit die eulersche Zahl beliebig genau ausgibt (da die
Folge gegen die eulersche Zahl konvergiert). Allerdings ist diese Folge spezifisch
für eine ganz bestimmte Zahl. Für einen allgemeineren Ansatz muss man für
jede Zahl eine Folge finden, die gegen diese konvergiert. Ein universelles Schema
dafür ist die sogenannte Kettenbruchdarstellung einer Zahl. Jean Vuillemin
hat einen Artikel darüber geschrieben, wie man exakte reelle Arithmetik mit
Kettenbruchdarstellung realisieren kann (Vui89).
Wir werden hier eine andere Folge verwenden, die gegen eine reelle Zahl
konvergiert: die Dezimaldarstellung. Eine Zahl in unserer reellen Arithmetik
soll also eine unendliche Sequenz von Dezimalstellen sein, was nichts anderes
ist, als ein Programm, das sukzessive Dezimalstellen berechnet (Plu98). Es wird
also für jede Zahl eine Berechnungsvorschrift angegeben - und keine endliche
Anzahl an Stellen – und solche Berechnungsvorschriften stellen wir uns als
unendliche Folgen von Dezimalstellen vor. Eine solche Dezimaldarstellung einer
∞
P
Zahl z entspricht einer Folge a1 , a2 , a3 , . . . wobei a =
ak · 10−i . Da eine
k=0
Folge auch durch eine Funktion Repräsentiert werden kann entspricht sie der
Funktion f : N − > 0, ..., 9 mit f (n) = an . Wir werden später zeigen, wie man
solche Berechnungsvorschriften in Haskell realisieren kann.
Reelle Arithmetik umfasst jedoch nicht nur das Repräsentieren von einzelnen reellen Zahlen, sondern es soll mit diesen Zahlen auch gerechnet werden
können. Wenn also zu jeder Zahl eine Sequenz an Dezimalstellen (in Form
einer Berechnungsvorschrift) in einem einheitlichen Format gefunden wurde,
müssen zur Schaffung einer exakten reellen Arithmetik noch Rechenoperationen auf diesen Sequenzen definiert werden. Dazu müssen nun - weil die Zahlen
in Form von Berechnungsvorschriften, die auch als Funktionen aufgefasst werden können, vorliegen – Funktionen miteinander verknüpft werden. Es muss
also eine Funktion definiert werden, die als Eingabe Funktionen erhält. Um
das umzusetzen, liegt es nahe, eine funktionale Programmiersprache zu verwenden. Wir werden später erläutern, warum sich insbesondere Haskell mit
seiner Lazy Evaluation“ für eine einfache Umsetzung anbietet.
”
Man bedenke, dass auch beim Ansatz der Fließkommaarithmetik Zahlen
4
häufig mit irgendwelchen Funktionen definiert sind, es also sehr wohl auch dort
so etwas wie die Sequenzen in der exakten reellen Arithmetik geben kann. Der
Unterschied ist allerdings, dass dort die Funktionen Zahlen fester Länge berechnen, die dann wiederum an andere Funktionen übergeben werden, während
in der exakten Arithmetik die Sequenzen mit anderen Sequenzen zu neuen Sequenzen operiert werden, und dann erst auf Zahlen endlicher Länge heruntergebrochen werden.
Um aber eine Operation auf zwei Sequenzen zu realisieren, zum Beispiel
das Berechnen der Addition zweier Zahlen, gibt es mehrere Möglichkeiten und
man muss sich auf eine festlegen. Während bei der Fließkomma-Arithmetik die
Addition an der letzten definierten Ziffer beginnt und der Übertrag von dort
mit nach vorne genommen wird, ist es bei einer Operation auf zwei Sequenzen
nicht ganz so einfach, eine sinnvolle Definition zu finden. Denn wenn man die
Addition zum Beispiel an Stelle x beginnt und dann keinen Übertrag bis an
die erste Stelle bekommt, stellt sich die Frage, ob man nicht an einer Stelle
rechts von x hätte beginnen sollen, einen Übertrag zu berechnen, der sich
dann vielleicht bis an die erste Stelle durchzieht. Das sieht man an folgendem
Beispiel:
Sei a = 0,264 und b = 0,137. Dann gilt
a1 + b 1 = 2 + 1 = 3 = c 1
a2 + b 2 = 6 + 3 = 9 = c 2
a3 + b3 = 4 + 7 = 11 = c3
Wenn nur ab der zweiten Stelle berechnet wird von rechts nach links, ist
a + b = 0, 39. Wenn die dritte Stelle hinzugenommen wird ist a + b = 0, 401.
Durch das hinzufügen der dritten Stelle hat sich also etwas an der ersten stelle
im Ergebnis verändert.
Es gibt mehrere Ansätze, um mit dieser Problematik umzugehen (also eine
Definition zu wählen). So verwendet zum Beispiel Plume Ziffern, die auch
negatives Vorzeichen haben können, was ihm hilft, dieses Problem zu lösen
(Plu98). Jerzy Karczmarczuk verwendet in seinem Paper The most Unreliable
”
Technique in the World to compute “ normale Dezimalziffern und geht dann,
wenn an einer Stelle unklar ist, ob dort ein Übertrag stattfindet, in seiner
Berechnung in Form eines Lookaheads so viele Schritte im Voraus nach hinten,
bis die Information vorhanden ist und gibt dann erst eine weitere Stelle aus
(Kar98). Was hier am besten ist, oder welche Ansätze es mit welchen Vor- und
Nachteilen- gibt, soll hier jedoch nicht im Detail diskutiert werden, da hier nur
einführend gezeigt werden soll, wie man vorgehen könnte, um exakte reelle
Arithmetik in Haskell zu implementieren.
5
4
Lazy Evaluation in Haskell
In dieser Arbeit wird skizziert, wie man eine exakte reelle Arithmetik in Haskell
umsetzen könnte. Dazu müssen unendliche Sequenzen in Haskell dargestellt
werden können und Operationen der Arithmetik realisiert werden. Beides wird
in den folgenden Absätzen untersucht anhand von Code-Beispielen, wie man
so etwas in Haskell implementieren kann. Der Erfolg für dieses Vorhaben hängt
maßgeblich davon ab, wie man das Umsetzen einer beliebig genauen Berech”
nung“ implementiert. Dazu gibt es eine Eigenschaft von Haskell, die Haskell
für die beabsichtigten Implementierungen prädestiniert macht. Damit wird die
Implementierung des beliebig genau“ einfach und effizient.
”
Haskell-Programme werden lazy“ ausgewertet. Das bedeutet, dass Berech”
nungen verzögert (oder gar nicht ausgeführt) werden, wenn sie nicht für andere
Berechnungen benötigt werden (Has15). Das heißt, ein Argument wird nicht
dann ausgewertet, wenn es übergeben wird, sondern nur wenn es auch wirklich
gebraucht wird.
Dieses Konzept werden wir uns im Folgenden zu Nutzen machen, wenn
Sequenzen in Haskell als unendliche“ Listen implementieren. Das sind Listen,
”
die unendlich lange ausgewertet werden können, aber nur so weit ausgewertet
werden, wie sie gebraucht werden.
5
Zahlen als Sequenz in Haskell definieren
In Haskell kann man eine Sequenz als eine Funktion implementiert werden,
die das erste Element der Sequenz zurückgibt, verknüpft mit einem rekursiven
Aufruf von sich selbst. Würde man diesen rekursiven Aufruf komplett auswerten, würde man eine unendliche“ Liste erhalten, das ganze würde nicht
”
terminieren. Da Haskell allerdings Lazy Evaluation unterstützt, wird eine Sequenz nur so lange ausgewertet wie benötigt. Das macht es sehr einfach, mit
Haskell eine Sequenz zu implementieren.
Die Implementierung einer solchen Sequenz hängt allerdings von der Zahl
ab, die man implementieren möchte, also muss für jede Zahl eine eigene Sequenz implementiert werden. Im einfachsten Fall ist eine solche Sequenz ohne
einen Input-Parameter, weil die Sequenz sozusagen als Konstante für eine Zahl
steht, und gibt dann einfach eine Ziffer verknüpft mit einem rekursiven Aufruf von sich selbst aus. Allerdings kann man natürlich auch gleich eine ganze
Klasse von Zahlen mit einer parametrisierten Funktion definieren, die dann je
nach Parameter die entsprechende Sequenz ausgibt. Wir werden jetzt ein paar
Beispiele zeigen, wie das aussehen könnte.
6
Dabei gilt zu beachten ein einheitliches Format festzulegen, um mit den
Zahlen rechnen. Beispielsweise das Dezimalsystem. Dort wäre dann zu klären
an welcher stelle das Komma gesetzt werden muss. Man könnte die Position
des Kommas z.B. mit einer separaten Zahl modellieren. Im Folgenden wird
kein genauer Formalismus eingehalten, sonder nur gezeigt wie man Zahlen ungefähr in Haskell modellieren könnte.
Beispiel 1 (Plu98):
from n = ( n : from ( n+1))
Diese Sequenz bildet die Ziffernfolge n, n+1, n+2, . . . und definiert somit für
jedes n eine eigene Sequenz. Hierbei würde es sich nicht um ein Dezimalsystem
handeln, weil an jeder Stelle beliebig große natürliche Zahlen stehen dürfen.
Beispiel 2 (Kar98):
t w o s e v e n t h s = 0 : cycle [ 2 , 8 , 5 , 7 , 1 , 4 ]
Diese Funktion implementiert die Sequenz von Ziffern, welche die Zahl 2/7
darstellt, die nämlich periodisch ist, also: 0, 285714.
Beispiel 3 (Kar98):
s Z e r o = repeat 0
Gibt einfach eine Folge von Nullern als Ziffern zurück.
Beispiel 4:
z w e i h u n d e r s i e b z e h n = [ 2 , 1 , 7 ] : repeat 0
Jede Zahl mit endlich vielen Ziffern kann wie in diesem Beispiel auch als Sequenz dargestellt werden, indem einfach die endlich vielen Ziffern aufgeführt
werden und danach die 0-Sequenz angehängt wird. Die Stelle des Kommas ist
Definitionssache.
Beispiel 5 (Kar98):
f c n n d = l e t ( a , b ) = quotRem n d in a : f c n ( 1 0 ∗ b ) d
Die Funktion fcn gibt für jede rationale Zahl nd eine dazugehörige Sequenz in
Dezimaldarstellung zurück. Die dabei verwendete Funktion quotRem liefert
dabei ein Tupel (a,b) mit a = n ÷ d = n mod d (Ganzzahldivision) und b =
n - (a · d) (b ist der Rest von n : d).
7
6
Arithmetische Operationen auf Sequenzen
Um nun mit dieser Art der Repräsentation von Zahlen auch rechen zu können,
müssen arithmetische Operationen definiert werden. Ebenso wie wir gerade nur
für ein paar Zahlen beispielhaft gezeigt haben, wie man sie darstellen kann,
kann man höchstens beispielhaft zeigen, wie diverse arithmetische Operationen
definiert werden könnten, da es mindestens so viele Operationen gibt wie es
Funktionen von den reellen Zahlen in die reellen Zahlen gibt und das sind ganz
schön viele.
Sicherlich wäre es hilfreich, grundlegende Operationen wie die Addition zu
implementieren. Eine sehr einfacher Vorschlag für eine Addition ist folgender:
Beispieladdition 1 (Plu98):
add ( a : x ) ( b : y ) = ( a + b ) : ( add x y )
Diese Addition würde bei einigen wenigen Sequenzen ganz gut funktionieren. Zum Beispiel wenn man die Zahl nimmt, die nur aus Einsern besteht
und zu der Zahl addiert, die nur aus Zweiern besteht, was die Zahl ergeben
würde, die nur aus Dreiern besteht, indem alle Ziffern paarweise addiert wurden. Schwieriger wird es schon, wenn ein Überlauf berücksichtigt werden soll
und ein Übertrag behandelt werden soll. Wir zeigen noch ein ausgeklügelteres
Beispiel für eine Addition:
Beispieladdition 2 (Kar98):
u <+> v = l e t (w0 : wq) = zipWith (+) u v in c p r w0 wq where
c p r u0 ( u1 : uq ) | u1<9 = u0 : c p r u1 uq
| u1>9 = ( u0+1) : c p r ( u1 −10) uq
| otherwise = l e t v@( v0 : vq)= c p r u1 uq in
i f v0<10 then u0 : v e l s e ( u0+1) : ( v0 −10) : vq
Hier wird zunächst alles elementweise addiert und der Übertrag wird durch
einen Lookahead bestimmt, der entsprechend viele Schritte vorausberechnet,
um den Übertrag feststellen zu können, bevor der eigentliche nächste Rekursionsschritt ausgeführt wird. In der Fallunterscheidung beim Lookahead wird an
jeder erreichten nachfolgenden Stelle folgende drei Fälle überprüft: Wenn die
Ziffer kleiner 9 ist, kann es keinen Übertrag auf alle Ziffern links davon geben,
sodass dies als exakt ausgegeben werden können und der aktuelle Lookahead
beendet ist. Wenn die Ziffer größer 9 ist, gibt es einen Übertrag nach links
und der Lookahead ist beendet, weil nach dem Übertag die Ziffern links davon
exakt bestimmt sind. Falls die Ziffer gleich 9 ist, muss der Lookahead weiter
nach rechts fortgesetzt werden, weil weiter rechts noch ein Übertrag notwendig
8
werden könnte, der dann weiter gegeben werden müsste.
Es könnte Probleme geben, wenn Zahlen addiert werden, sodass der Lookahead für bereits das Prüfen des Übertrags an einer einzigen Ziffer unendlich
weit nach hinten gehen müsste. Das bedeutet, bei dieser Implementierung kann
es sein, dass der Lookahead nicht terminiert. Egal wie viele Schritte aber für
den Lookahead (der nicht-lazy-rekursiv ausgewertet wird) nötig sind, werden
bei der eigentlichen Rekursion, die lazy ausgewertet wird, nur Stellen ausgegeben, bei denen die Addition stimmt. Das heißt, es kommt zu keinerlei
Abschätzungsfehlern bei den lazy ausgewerteten Stellen der Addition zweier
Sequenzen. Das einzig Limitierende ist der zur Verfügung stehende Speicher,
aber die Implementierung ermöglicht es zumindest bei genügend Ressourcen,
entsprechend genaue Resultate zu liefern. Vom Prinzip her werden zwei Sequenzen zu einer zusammengefasst, welche beliebig genau ausgewertet werden
kann, indem die zusammengefassten Sequenzen so weit wie nötig ausgewertet werden. Zwei Sequenzen werden dabei zu einer neuen verknüpft. Manche
arithmetische Operationen lassen sich dabei leichter implementieren als andere.
Beispielsubtraktion (Kar98):
Eine Subtraktion kann durchgeführt werden mithilfe von Komplementbildung
und Addition. Dazu kann folgende Funktion für ein 9-Komplement genutzt
werden:
neg ( u0 : uq ) = ( negate u0 − 1 ) : map ( 9 −) uq
7
Zusammenfassung
Wir haben Gründe genannt, warum es sinnvoll sein kann, reelle Zahlen im
Rechner ohne Rundung dynamisch auf nötig viele Stellen genau zu berechnen.
Dann haben wir beschrieben, wie solch eine exakte reelle Arithmetik über Sequenzen realisiert werden kann und wie solche Sequenzen in der Programmiersprache Haskell mithilfe von Lazy Evaluation implementiert werden können.
Dazu haben wir Beispiele genannt von Zahlen, die als Sequenz definiert wurden. Damit mit Zahlen in einer solchen Repräsentation auch gerechnet werden
kann, müssen Operationen definiert werden, die zwei Sequenzen lazy zu einer
neuen verknüpfen. Wir haben Beispiele genannt, wie eine solche Verknüpfung
in Haskell realisierbar ist.
9
8
Kritik
Ein Problem bei exakter reeller Arithmetik könnte sein, dass es passieren
könnte, dass bei der Definition einer Rechenoperation auf mehrere Sequenzen (man könnte ja beliebig komplizierte Operationen definieren wollen) es
Worst-Case-Zahlen geben könnte, welche sich mit dieser Operation schlecht
verknüpfen lassen. Mit schlecht“ ist hier die Effizienz gemeint. Es könnte
”
sein, dass eine Operation die zu verknüpfenden Sequenzen sehr weit auswerten
muss, um nur eine einzige Stelle selbst auswerten zu müssen. Diese Schwierigkeit wurde auch in der Arbeit von Plume beobachtet (Plu98).
Ein weiteres Problem ist die Unvorhersagbarkeit, wie viel Zeit und Speicher
benötigt ist, um eine Sequenz auf eine gewünschte Genauigkeit hin auszuwerten. Zwar hat man beim Verfahren der Berechnung exakter reeller Zahlen eine
Garantie, dass jede Stelle der Ausgabe stimmt, aber man hat eben nicht immer eine Garantie, dass man auch so viele Stellen bekommt, wie man gerne
hätte, bzw. die verwendete Rechnung terminiert, weil nicht immer absehbar ist,
welcher Rechenaufwand damit verbunden ist. Auch lässt sich die Berechnung
dieser Art von Arithmetik nicht durch Hardware-Implementierungen effizienter
gestalten, da mehr als nur ein konstanter Speicherbedarf nötig ist.
Nachdem allerdings mit der Berechnung exakter reeller Zahlen Berechnungen durchgeführt werden können, die mit gängigen Methoden wie FließkommaArithmetik nicht durchgeführt werden können, kann es je nach Anwendung
seine Daseinsberechtigung haben. Wie man exakte reelle Arithmetik im Detail
effizient umsetzt, wäre nun über diese Arbeit hinausgehend näher zu untersuchen.
10
Literatur
[Has15] Lazy evaluation - HaskellWiki. https://wiki.haskell.org/Lazy evaluation.
Version: 10.03.2015
[Kar98] Karczmarczuk, Jerzy: The Most Unreliable Technique in the World
to compute pi. (1998)
[Plu98] Plume, Dave:
A Calculator for Exact Real Number
Computation 4th year project Departments of Computer
Science and Artificial Intelligence University of Edinburgh.
http://www.dcs.ed.ac.uk/home/mhe/plume/. Version: 1998
[Vui89] Vuillemin, Jean: Exact real computer arithmetic with continued
fractions. (1989)
11
Herunterladen