Zahlen

Ebenfalls von besonderer Bedeutung sind die verschiedenen Zahlentypen. Es gibt Zahlen mit Vorzeichen und ohne, Zahlen mit Kommawerten und ohne und diese jeweils in verschiedener Größe (mit entsprechendem Speicherbedarf).

Vorzeichenlose Zahlen sind vom Typ uint (unsigned integer, vorzeichenlose Ganzzahlen), vorzeichenbehaftete Zahlen sind vom Typ int (integer). Beide gibt es in den Größen 8, 16, 32 und 64 Bit Kapazität, die entsprechenden Datentypen heißen uint8, uint16, uint32 und uint64 bzw. int8, int16, int32 und int64.[1]

package main

import "fmt"

func main() {
    var i uint64
    // die Variable i darf nun z.B. keine
    // negativen Werte speichern
    i = 4
    fmt.Println("i =", 2*i)
}

Die Größenangaben 8, 16, 32 und 64 beziehen sich auf die Anzahl der Bit, die die Datentypen speichern können (und damit auf den Platzverbrauch). Ein uint16 kann beispielsweise die Zahlen 0 bis 216-1 speichern, also 0 bis 65535. Die größte Zahl, die sich mit uint64 speichern lässt, ist etwas mehr als 18 Trillionen. Die Zahlen, die auch negative Werte speichern können, können nur etwa halb so große Werte speichern, beispielsweise bei int8 den Zahlenbereich von -128 bis 127. Vorsicht ist gegeben, wenn sie über den Zahlenbereich hinausgehen:

package main

import "fmt"

func main() {
    // int8 kann keine Werte größer als 127 speichern
    var i int8
    i = 127
    i = i + 1
    fmt.Println(i)
}

Die Ausgabe ist etwas überraschend -128. Das liegt an der Art und Weise, wie die Zahlen intern dargestellt werden (Zweierkomplement). Man muss also aufpassen, dass alle Berechnungen innerhalb des Wertebereichs liegen!

Der Datentyp byte ist noch wichtig, da Datenquellen (Dateien auf der Festplatte oder Informationen, die über das Internet übertragen werden) immer als Reihe (slice) von byte übergeben werden. Es ist ein Alias von uint8, speichert also Werte von 0 bis 255. Man kann Zeichenketten (string) leicht in Bytes umwandeln (in der Kodierung UTF-8), alle anderen Datenstrukturen erfordern eine eigene Routine.

Gleitkommazahlen

Benötigt man Werte mit Nachkommastellen oder sehr große Werte, bieten sich sogenannte »Gleitkommazahlen« an. Sie heißen deswegen so, weil die Anzahl der Nachkommastellen, die gespeichert werden, abhängig vom Wert der Zahl ist.

Gleitkommazahlen gibt es in Go als float32 und float64. Interessant an den Datentypen ist, dass sie sehr kleine Werte (noch viel kleiner als z.B. 0,000000003) und sehr große Werte speichern können. Der kleinste positive Wert bei float64 liegt bei etwa 5×10-324, der größte Wert bei ca. 1,8×10308. Man muss ein wenig aufpassen, denn die Gleitkommazahlen speichern nicht immer exakte Werte. Vergleicht man Gleitkommazahlen miteinander, funktioniert das nicht immer:

package main

import "fmt"

func main() {
    // a und b sind vom Typ float64
    a := 0.1
    b := 0.2
    if a+b == 0.3 {
        fmt.Println("Na klar!")
    } else {
        fmt.Println("Wohl verrechnet...?")
    }
}
Eigentlich ist 0.1 + 0.2 = 0.3, doch die Gleitkommazahlen können nicht alle Zahlen exakt repräsentieren. Die if-Fallunterscheidung wird im Kapitel Fallunterscheidungen behandelt.

In diesem Fall sind a und b vom Typ float64 und ergeben in der Summe nicht genau 0.3, sondern ein Wert, der minimal größer ist (versuche doch mal herauszufinden, wie groß er ist). Die Lösung für den Fall oben wäre der Vergleich, ob sich die Summe a+b und der Wert 0.3 um nicht mehr als einen sehr kleinen Wert (z.B. 0,000001) unterscheiden.

Gleitkommazahlen sollte man nicht verwenden, wenn man exakte Werte benötigt. Es ist verlockend, beispielsweise Geldbeträge (4,99 Euro) als Gleitkommazahl zu speichern. Damit handelt man sich schnell Fehler oder unvorhersehbare Situationen ein. Viele Fragen in den üblichen Internet-Foren (nicht nur in Bezug auf Go) kommen von mangelnder Genauigkeit der Gleitkommazahlen. Speichere solche Werte lieber als Ganzzahlen (z.B. 499) und füge das Komma bei der Ausgabe ggf. selber ein.

Wenn man gewisse Ungenauigkeiten akzeptieren kann, eignen sich float32 und float64 wunderbar zum Rechnen.


1. Die Datentypen int und uint ohne Größenangaben sind je nach Prozessortyp 32 oder 64 Bit groß, beide haben aber immer dieselbe Größe, d.h. wenn uint 64 Bit groß ist, ist auch int 64 Bit groß.