Kapitel 2: Grundlagen / 2.5 Einfache Datentypen / 2.5.3 Gleitkommazahlen

Gleitkommazahlen (floating-point numbers) sind Zahlen mit Nachkommastellen, sie bilden also die reellen Zahlen ab.

Swift kennt zwei Typen von Gleitkommazahlen:

Double verwendet 64 Bit Speicher (8 Byte) und erreicht damit eine Genauigkeit von mindestens 15 Ziffern (vor und nach dem Komma zusammengenommen).

Float verwendet nur 32 Bit Speicher (4 Byte) und die Genauigkeit beträgt mindestens 6 Ziffern.

Swiftintern werden Gleitkommastellen in der mathematischen Exponentialschreibweise gespeichert (genaugenommen einer binären Version davon, aber zum Verstehen eignet sich die folgende Form besser):

m • 10e

Beispielsweise wird die Zahl 0,0125 als 1,25 • 10−2 gespeichert und die Zahl 125 als 1,25 • 102

Bei m • 10e hat also m immer die Form: Eine Ziffer vor dem Komma, dann das Komma, dann maximal so viele Stellen nach dem Komma, wie es der zur Verfügung stehende Speicher zulässt. Und e hat die Aufgabe, das Komma um e-viele Ziffern nach rechts zu schieben. Ist e negativ, wird entsprechend nach links verschoben.

In der Computerversion der Exponentialschreibweise ersetzt man die 10 durch ein e, das sieht dann so aus:

1,25 • 10−2 schreibt sich in Swift 1.25e-2 und 1,25 • 102 als 1.25e2.

Aber man kann in Swift natürlich auch die normale Kommaschreibweise verwenden, für die beiden Beispiele wäre das 0.0125 bzw. 125.0 oder schlicht 125. Wie im englischen üblich, wird aber ein Punkt statt eines Kommas verwendet. Im Code sieht das Ganze dann so aus:

var d: Double = 1.25e-2
var f: Float = 0.0125

Hier wird zweimal die Zahl 0,0125 in je einer Variablen gespeichert. Da d und f aber verschiedene Typen haben, führt der folgende Versuch (analog zu dem mit zwei Integer-Typen) zu einem Fehler:

var d: Double = 1.25e-2
var f: Float = 0.0125
var h = d + f

Führt zu:

Could not find an overload for ‚+‘ that accepts the supplied arguments

Der Compiler sucht nach einer sog. Überladung von +, also nach einer Vorschrift, die ihm erklärt was zu tun ist, wenn links vom +-Zeichen ein Double und rechts davon ein Float steht; er findet keine solche Vorschrift, also gibt es eine Exception. (Sie könnten ihm grundsätzlich eine solche Vorschrift auch liefern, indem Sie eine Überladung für + programmierten – das ist jedoch erst der Stoff eines späteren Kapitels).

Die Speicherung in Exponentialschreibweise hat den Vorteil – oder besser: die Eigenschaft – dass in einigen Fällen Zahlen auch größer sein können oder mit einer deutlich höheren Präzision gespeichert werden können, als das mit dem benannten 6 bzw. 15 Ziffern möglich wäre. Immer dann nämlich, wenn sie sich besonders gut in Exponentialschreibweise speichern lassen, wie zum Beispiel 0,00000000000000000000000000000000001, also 1e-35 oder 5 Trillionen (eine 5 mit 18 Nullen) als 5e18.

Gleitkommazahlen können also sehr groß und auch sehr präzise sein, aber leider nicht beides gleichzeitig. Befinden Sie sich in einem „normalen“ Zahlenbereich, also beispielsweise innerhalb der 15 Ziffern von Double, dann kann Ihnen nicht allzuviel passieren. Aber stellen Sie sich vor, Sie addieren die beiden oben genannten Zahlen:

var präzise: Double = 1e-35
var groß: Double = 5e18
var summe: Double = groß + präzise
println(summe)

Erzeugt die Ausgabe:

5e+18

Der Anteil von präzise an der Summe wurde also schlicht weggerundet. Aber was könnte Double auch sonst tun? Die Exponentialschreibweise kann das Komma nicht gleichzeitig weit nach links und weit nach rechts verschieben. Und eine Fehlermeldung wegen des Rundens erscheint auch nicht angebracht.

Es ist also wie es ist: Das Rechnen mit Gleitkommazahlen ist sehr fehleranfällig. Kann die mathematische Genauigkeit nicht geliefert werden, dann wird gerundet. Behalten Sie dies bei Ihren Berechnungen nicht genau im Blick, dann können sie leicht ein ganz und gar unerwartetes Ergebnis bekommen.

Dieses Thema des fehlerhaften Rechnens ist so umfangreich, dass sich eigens ein Zweig der Mathematik, die Numerik, damit beschäftigt: Wie kann man Mathematik auf einem Computer betreiben, und wie kann man die dabei zwangsläufig entstehenden Fehler so unterhalb eines Fehlerminimums halten, dass das Ergebnis eine berechenbare Genauigkeit behält.

Allzu detailreich kann ich also auf Rechenfehler nicht eingehen, in diesem Kapitel soll ja lediglich der Typ der Gleitkommazahl vorgestellt werden, eines möchte ich aber doch noch erwähnen, da es die Quelle von sehr vielen Fehlern ist:

Wenn Sie zwei Gleitkommazahlen auf Gleichheit prüfen und nicht ganz genau wissen, dass es zu keiner internen Rundung gekommen ist, dann prüfen Sie bitte nicht, ob diese beiden Zahlen gleich sind, sondern ob die Differenz dieser beiden Zahlen kleiner als ein Fehlerwert ist (zahl1 – zahl2 < fehlerwert). Denn nur weil zwei Zahlen mathematisch gleich sind, müssen sie numerisch (also entsprechend der numerischen Mathematik) noch lange nicht gleich sein.

Eine Frage ist nun noch offen:

Wenn Sie die Wahl haben, ob Sie Float oder Double für eine Gleitkommazahl verwenden, was sollten Sie wählen?

Die Antwort ist diesmal nicht so strikt, wie in anderen Fällen; sie lautet: Float ist okay, aber Double ist besser. Mit Double reduzieren Sie schlicht die Wahrscheinlichkeit, in ein Rundungsproblem zu laufen. Und wenn Sie in Ihrem Code nicht immer zwischen Float und Double wechseln, dann müssen Sie auch nicht andauernd nachsehen, welchen Typ diese oder jene Variable genau hat. Und der dritte Grund ist, dass aus den zwei vorhergenannten fast alle Programmierer ohne groß nachzudenken Double verwenden, es sich also fast um eine Konvention handelt.

Float und Double verfügen noch über eine Besonderheit:

Es lassen sich nämlich die Werte unendlich (infinity), minus unendlich (-infinity) und keine Zahl (not a number = nan) abbilden:

var unendlich: Double = 1.0/0.0
var minusUnendlich: Double = -1.0/0.0
var keineZahl: Double = 0.0/0.0

println(unendlich)
println(minusUnendlich)
println(keineZahl)

Erzeugt folgende Ausgaben:

inf
-inf
nan

Wenn Sie sich lieber nicht mehr als nötig mit Mathematik beschäftigen, dann können Sie die nun folgende Erklärung überspringen, ansonsten will ich Ihnen zeigen, warum man hier durch Null teilen darf:

In allen drei Fällen wird durch Null geteilt, was innerhalb der reelen Zahlen nicht erlaubt ist, da das Ergebnis die Menge der reellen Zahlen verlassen würde. Fügt man aber den reellen Zahlen die beiden Zahlen ∞ und -∞ hinzu (mathematisch beschriebe sich das als: ℝ ∪ {∞, -∞} und wäre gar nicht unüblich), dann würde sich ∞ als Ergebnis von x/0 geradezu anbieten (da x/y für y→0, also für sich 0 nähernde y, immer größer wird und sich ∞ nähert). Jedenfalls solange x konstant und x > 0 ist. Sollte x < 0 sein, dann nähert sich x/0 der Zahl -∞. Und sollte x = 0 sein, dann ist das Ergebnis nicht einmal mehr in unserer neuen Menge, sondern keine Zahl (nan).

In Swift bilden Double und Float also genaugenommen nicht ℝ ab, sondern ℝ ∪ {∞, -∞}.

Kapitel 2.5.3: Gleitkommazahlen

2 Gedanken zu „Kapitel 2.5.3: Gleitkommazahlen

  • 23. Oktober 2014 um 14:54
    Permalink

    Hallo,

    es ist Ihnen ein Fehler unterlaufen. Im Code oben steht, dass Variable d den Wert 1.25e-2 und Variable e den Wert 0.125 enthält. Sie schreiben dann im folgenden Satz: „Hier wird zweimal die Zahl 0,125 in je einer Variablen gespeichert.“. Dies ist nicht korrekt, da die Variable d den Wert 1.25e-2 = 0,0125 hat und nicht 0,125. Vielleicht verbessern Sie die Stelle im Tutorial, damit Programmieranfänger nicht unnötig verwirrt werden.

    Viele Grüße, Altan

    Antworten
    • 23. Oktober 2014 um 15:55
      Permalink

      Hallo Altan,

      danke für den Hinweis, das haben Sie gut gesehen. Ich habe es jetzt korrigiert.

      Markus.

      Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.