strconv.ParseInt ist die universelle Eingangstür, wenn ein String in einen signed Integer überführt werden soll und dabei mehr Kontrolle gefragt ist, als strconv.Atoi bietet. Statt sich auf Basis 10 und int zu beschränken, akzeptiert ParseInt eine wählbare Basis zwischen 2 und 36 sowie eine Ziel-Bitbreite, gegen die der geparste Wert geprüft wird. Das macht die Funktion zur ersten Wahl für Hex-Farben, Oktal-Permissions, Binärflags und alles, was außerhalb des dezimalen Standardfalls liegt.

Hinter dem schlichten Signaturkopf steckt ein präzise spezifiziertes Verhalten: der Vorzeichenpräfix wird respektiert, optional erkennt die Funktion automatisch 0x-, 0o- und 0b-Präfixe, und im Fehlerfall liefert sie einen typisierten *NumError, dessen Err-Feld zwischen Syntax- und Range-Fehlern unterscheiden lässt. Wer diese Mechanik einmal verinnerlicht hat, deckt mit ParseInt einen Großteil aller realen Parse-Aufgaben sauber ab.

Die Funktion lebt im Paket strconv und gibt immer einen int64 zurück — unabhängig davon, ob ein kleinerer Bereich angefordert wurde. Das ist eine bewusste Designentscheidung: ein einziger Rückgabetyp hält die API klein, und der Aufrufer übernimmt den expliziten Cast in den eigentlich gewünschten Typ. Dieser Cast ist nach erfolgreicher Range-Prüfung verlustfrei, weil ParseInt garantiert, dass der Wert in die angefragte Bitbreite passt.

Der zweite Rückgabewert ist ein error, der bei Erfolg nil ist und im Fehlerfall ein *strconv.NumError enthält. Letzteres ist kein opaker Wrapper, sondern ein typisiertes Struct mit den Feldern Func, Num und Err, die später per errors.Is ausgewertet werden können.

Go signatur.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// func ParseInt(s string, base int, bitSize int) (int64, error)
	n, err := strconv.ParseInt("12345", 10, 64)
	fmt.Printf("Wert: %d  Typ: %T  Fehler: %v\n", n, n, err)
}
Output
Wert: 12345  Typ: int64  Fehler: <nil>

Der Rückgabewert lässt sich anschließend gefahrlos auf einen schmaleren Typ casten, sobald die Range über bitSize bestätigt wurde. Ohne diese Bestätigung ist ein Cast riskant, weil Go einen Überlauf still wegtruncatet — ParseInt schützt also nicht nur vor Syntax-Müll, sondern auch vor stillen Wertverlusten.

Der zweite Parameter steuert das Zahlensystem. Erlaubt sind alle ganzzahligen Basen von 2 bis 36 — die Obergrenze ergibt sich aus dem verfügbaren Alphabet: zehn Ziffern plus 26 Buchstaben, wobei Groß- und Kleinschreibung gleichwertig sind. Basis 2 parst Binärstrings ohne Präfix, Basis 8 Oktalzahlen, Basis 16 Hex-Werte und so weiter. Bei einer Basis außerhalb dieses Bereichs liefert ParseInt einen ErrSyntax-Fehler.

Ein Sonderwert ist base == 0: dann inspiziert die Funktion den String selbst und leitet die Basis aus einem Präfix ab. 0x oder 0X bedeutet hexadezimal, 0o oder 0O oktal, 0b oder 0B binär. Ohne Präfix gilt Basis 10. Das ist exakt das Verhalten, das Go-Literale im Quellcode haben — ParseInt mit base 0 ist also der direkte Spiegel des Lexers.

Go basis.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	beispiele := []struct {
		s    string
		base int
	}{
		{"1010", 2},   // binär
		{"755", 8},    // oktal
		{"ff", 16},    // hex, klein
		{"FF", 16},    // hex, groß
		{"z", 36},     // Basis 36
		{"0xff", 0},   // Auto: hex
		{"0o755", 0},  // Auto: oktal
		{"0b1010", 0}, // Auto: binär
		{"42", 0},     // Auto: dezimal
	}

	for _, b := range beispiele {
		n, err := strconv.ParseInt(b.s, b.base, 64)
		fmt.Printf("%-8s base=%-2d -> %d  err=%v\n", b.s, b.base, n, err)
	}
}
Output
1010     base=2  -> 10  err=<nil>
755      base=8  -> 493  err=<nil>
ff       base=16 -> 255  err=<nil>
FF       base=16 -> 255  err=<nil>
z        base=36 -> 35  err=<nil>
0xff     base=0  -> 255  err=<nil>
0o755    base=0  -> 493  err=<nil>
0b1010   base=0  -> 10  err=<nil>
42       base=0  -> 42  err=<nil>

Wichtig: wenn explizit eine Basis angegeben wird, darf das entsprechende Präfix nicht im String stehen. ParseInt("0xff", 16, 64) schlägt fehl, weil das x an Position 1 in Basis 16 kein gültiges Zeichen ist. Das Präfix gehört ausschließlich zum base 0-Modus.

Der dritte Parameter legt fest, wie viele Bits der geparste Wert maximal belegen darf. Erlaubt sind die Werte 0, 8, 16, 32 und 64. Da ParseInt signed Integer verarbeitet, ist die zulässige Range jeweils zweikomplementär — ein Bit weniger für den Positivbereich, ein zusätzlicher Wert im Negativbereich.

Der Sonderwert bitSize == 0 bedeutet „so breit wie der native int auf der Zielplattform". Auf 64-Bit-Systemen ist das gleichbedeutend mit bitSize == 64, auf 32-Bit-Systemen mit bitSize == 32. Wer plattformportablen Code schreibt, sollte den Wert daher explizit setzen, statt sich auf 0 zu verlassen.

bitSizeminmax
8-128127
16-32 76832 767
32-2 147 483 6482 147 483 647
64-9 223 372 036 854 775 8089 223 372 036 854 775 807
Go bitsize.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// Überlauf-Demonstration
	_, err := strconv.ParseInt("200", 10, 8)
	fmt.Printf("200 in int8 -> %v\n", err)

	n, err := strconv.ParseInt("-128", 10, 8)
	fmt.Printf("-128 in int8 -> %d, err=%v\n", n, err)
}
Output
200 in int8 -> strconv.ParseInt: parsing "200": value out of range
-128 in int8 -> -128, err=<nil>

Der Cast nach dem Parse ist erst dann sicher: int8(n), int16(n), int32(n) arbeiten verlustfrei, wenn bitSize korrekt gesetzt war. Genau hier liegt der praktische Mehrwert gegenüber einem blinden Cast direkt nach AtoiParseInt macht den Range-Check explizit und meldet ihn als typisierten Fehler.

ParseInt akzeptiert ein führendes + oder - direkt vor der ersten Ziffer. Das Vorzeichen gehört nicht zur Basis-Konvention, sondern wird vor der Ziffernanalyse abgespalten. Damit funktioniert -ff in Basis 16 genauso wie -1010 in Basis 2 oder -0x2A mit base 0.

Mehrfache Vorzeichen, ein Vorzeichen an falscher Position oder ein einsames +/- ohne Ziffern führen zu ErrSyntax. Whitespace um den Wert herum wird nicht akzeptiert — wer aus einer CSV oder Konfigdatei parst, muss vorher mit strings.TrimSpace aufräumen.

Go vorzeichen.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	werte := []string{"+42", "-42", "-0xff", "--1", "+", " 42"}
	for _, s := range werte {
		n, err := strconv.ParseInt(s, 0, 64)
		fmt.Printf("%-8q -> %d  err=%v\n", s, n, err)
	}
}
Output
"+42"    -> 42  err=<nil>
"-42"    -> -42  err=<nil>
"-0xff"  -> -255  err=<nil>
"--1"    -> 0  err=strconv.ParseInt: parsing "--1": invalid syntax
"+"      -> 0  err=strconv.ParseInt: parsing "+": invalid syntax
" 42"    -> 0  err=strconv.ParseInt: parsing " 42": invalid syntax

Der asymmetrische Range von Zweikomplement-Typen schlägt hier zu: -128 passt in int8, +128 nicht. Wer Werte aus externen Quellen verarbeitet, sollte sich dieses Detail bei Range-Tests bewusst sein, sonst werden Edge-Cases am unteren Ende fälschlich als Überlauf gewertet.

Seit Go 1.13 dürfen Zahlenliterale im Quellcode Unterstriche als Lesbarkeitstrenner enthalten — 1_000_000 ist ein gültiges int-Literal. Diese Schreibweise ist eine reine Lexer-Konvention und wird von strconv.ParseInt nicht akzeptiert. Ein String wie "1_000" führt zu ErrSyntax, völlig unabhängig von der gewählten Basis.

Das ist ein häufiger Stolperstein, wenn Entwickler annehmen, alle Go-Literal-Features stünden auch zur Laufzeit zur Verfügung. Wer Underscore-Eingaben unterstützen will, muss sie vor dem Parse mit strings.ReplaceAll(s, "_", "") entfernen — oder besser noch validieren, ob die Underscores an sinnvollen Positionen stehen, bevor sie entfernt werden.

Go underscore.go
package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	roh := "1_000_000"

	_, err := strconv.ParseInt(roh, 10, 64)
	fmt.Printf("direkt: err=%v\n", err)

	bereinigt := strings.ReplaceAll(roh, "_", "")
	n, err := strconv.ParseInt(bereinigt, 10, 64)
	fmt.Printf("bereinigt: %d  err=%v\n", n, err)
}
Output
direkt: err=strconv.ParseInt: parsing "1_000_000": invalid syntax
bereinigt: 1000000  err=<nil>

Diese Asymmetrie zwischen Quellcode und Laufzeit-Parser ist bewusst gewählt: strconv modelliert das Parsen von Daten, nicht von Source-Code. Wer Daten im Go-Literal-Format konsumieren will, greift zu go/scanner oder go/constant, nicht zu strconv.

strconv.Atoi(s) ist ein dünner Wrapper um ParseInt(s, 10, 0) — er fixiert die Basis auf 10 und die Bitbreite auf den nativen int. Wer nur dezimale Werte erwartet und den int-Typ direkt verwenden will, fährt mit Atoi knapper und ohne expliziten Cast. Sobald aber eine andere Basis, ein definierter Range-Check oder explizit int64 ins Spiel kommt, ist ParseInt die richtige Wahl.

strconv.ParseUint ist das vorzeichenlose Pendant: gleiche Parameter, aber Rückgabetyp uint64, und ein führendes - ist verboten. Wer also Werte parst, die per Definition nicht negativ sein können — Größenangaben, Counter, Bitmasken — sollte zu ParseUint greifen, weil der Typ die Invariante schon mitführt.

Go vergleich.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	i, _ := strconv.Atoi("42")
	fmt.Printf("Atoi:      %d  (%T)\n", i, i)

	n, _ := strconv.ParseInt("2A", 16, 32)
	fmt.Printf("ParseInt:  %d  (%T)\n", n, n)

	u, _ := strconv.ParseUint("ff", 16, 8)
	fmt.Printf("ParseUint: %d  (%T)\n", u, u)

	_, err := strconv.ParseUint("-1", 10, 64)
	fmt.Printf("ParseUint -1: err=%v\n", err)
}
Output
Atoi:      42  (int)
ParseInt:  42  (int64)
ParseUint: 255  (uint64)
ParseUint -1: err=strconv.ParseUint: parsing "-1": invalid syntax

Eine Faustregel: Atoi für Konfigwerte und Kommandozeilen-Eingaben in Basis 10, ParseInt für alles mit Basis-Wahl oder Bitbreiten-Check, ParseUint wenn negative Werte semantisch ausgeschlossen sind.

Der Fehler aus ParseInt ist immer ein *strconv.NumError. Dieses Struct trägt drei Felder: Func (Name der parsenden Funktion, hier ParseInt), Num (der Eingabestring) und Err (die eigentliche Ursache, entweder strconv.ErrSyntax oder strconv.ErrRange). Die Trennung dieser beiden Fehlerklassen ist wichtig, weil sie unterschiedliche Reaktionen verlangt.

ErrSyntax heißt: die Eingabe ist nicht parsierbar — Tippfehler, falsches Format, unerlaubte Zeichen. Hier ist Validierung auf Eingabeseite gefragt. ErrRange heißt: das Format stimmt, aber der Wert sprengt die angeforderte Bitbreite. Hier ist entweder die Bitbreite zu klein gewählt, oder die Eingabe muss als Fehler gemeldet werden. Mit errors.Is lässt sich das sauber unterscheiden.

Go numerror.go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

func klassifiziere(s string) {
	_, err := strconv.ParseInt(s, 10, 8)
	switch {
	case err == nil:
		fmt.Printf("%-8q OK\n", s)
	case errors.Is(err, strconv.ErrSyntax):
		fmt.Printf("%-8q Syntax-Fehler\n", s)
	case errors.Is(err, strconv.ErrRange):
		fmt.Printf("%-8q Range-Fehler\n", s)
	}

	var nerr *strconv.NumError
	if errors.As(err, &nerr) {
		fmt.Printf("         Func=%s Num=%q\n", nerr.Func, nerr.Num)
	}
}

func main() {
	klassifiziere("42")
	klassifiziere("abc")
	klassifiziere("999")
}
Output
"42"     OK
"abc"    Syntax-Fehler
         Func=ParseInt Num="abc"
"999"    Range-Fehler
         Func=ParseInt Num="999"

Eine Eigenheit: bei ErrRange ist der Rückgabewert nicht 0, sondern der jeweilige Range-Grenzwert (MaxInt64 oder MinInt64 für bitSize == 64). Das ist selten brauchbar und wird in der Praxis ignoriert — entscheidend ist der Fehler-Branch.

Ein klassischer Anwendungsfall ist das Zerlegen eines CSS-Farbwerts wie #ff8800 in seine drei 8-Bit-Komponenten. Jede Komponente belegt zwei Hex-Ziffern, was exakt in einen uint8 passt — bitSize == 8 schützt also automatisch vor unsinnigen Eingaben.

Go hex_color.go
package main

import (
	"fmt"
	"strconv"
	"strings"
)

type Color struct{ R, G, B uint8 }

func ParseHexColor(s string) (Color, error) {
	s = strings.TrimPrefix(s, "#")
	if len(s) != 6 {
		return Color{}, fmt.Errorf("ungültige Länge: %q", s)
	}

	parseKomponente := func(teil string) (uint8, error) {
		n, err := strconv.ParseUint(teil, 16, 8)
		return uint8(n), err
	}

	r, err := parseKomponente(s[0:2])
	if err != nil {
		return Color{}, fmt.Errorf("rot: %w", err)
	}
	g, err := parseKomponente(s[2:4])
	if err != nil {
		return Color{}, fmt.Errorf("grün: %w", err)
	}
	b, err := parseKomponente(s[4:6])
	if err != nil {
		return Color{}, fmt.Errorf("blau: %w", err)
	}
	return Color{r, g, b}, nil
}

func main() {
	for _, s := range []string{"#ff8800", "#00ff00", "#xyzxyz"} {
		c, err := ParseHexColor(s)
		fmt.Printf("%-9s -> %+v  err=%v\n", s, c, err)
	}
}
Output
#ff8800   -> {R:255 G:136 B:0}  err=<nil>
#00ff00   -> {R:0 G:255 B:0}  err=<nil>
#xyzxyz   -> {R:0 G:0 B:0}  err=rot: strconv.ParseUint: parsing "xy": invalid syntax

Hier zeigt sich der Wert des Bitbreiten-Parameters in der Praxis: hätten wir bitSize == 64 gewählt und danach blind nach uint8 gecastet, würde "ff0" (vier statt zwei Stellen pro Komponente) als Wert 4080 durchlaufen und zu 240 truncatet werden — ein leiser Bug. Mit bitSize == 8 knallt das sofort als ErrRange.

In Konfigdateien für Embedded-Systeme oder Treiber sind gemischte Zahlensysteme der Normalfall: Adressen in Hex, Permissions in Oktal, Counts in Dezimal. ParseInt mit base 0 nimmt einem die Fallunterscheidung ab, sobald die Eingabe per Konvention das passende Präfix mitführt.

Go config_mixed.go
package main

import (
	"fmt"
	"strconv"
	"strings"
)

func ParseConfigValue(rohwert string) (int64, error) {
	return strconv.ParseInt(strings.TrimSpace(rohwert), 0, 64)
}

func main() {
	config := map[string]string{
		"base_address":  "0x40020000",
		"file_perms":    "0o644",
		"flag_mask":     "0b10110000",
		"retry_count":   "5",
		"signed_offset": "-0x10",
	}

	for k, v := range config {
		n, err := ParseConfigValue(v)
		if err != nil {
			fmt.Printf("%-15s = %-12s FEHLER: %v\n", k, v, err)
			continue
		}
		fmt.Printf("%-15s = %-12s -> %d\n", k, v, n)
	}
}
Output
base_address    = 0x40020000   -> 1073872896
file_perms      = 0o644        -> 420
flag_mask       = 0b10110000   -> 176
retry_count     = 5            -> 5
signed_offset   = -0x10        -> -16

Dieser Ansatz funktioniert, weil die Konfig-Konvention das Präfix verbindlich vorgibt. Für vom Nutzer eingegebene Werte ohne Präfix-Disziplin sollte man die Basis lieber explizit setzen, sonst werden Eingaben wie "010" überraschend als Oktal 8 interpretiert — ein klassischer Phantomfehler aus C-geprägten Sprachen.

Interessantes

Rückgabewert ist immer int64

Auch bei bitSize == 8 liefert ParseInt einen int64 — der Aufrufer castet explizit. Nach erfolgreicher Range-Prüfung ist der Cast verlustfrei.

base 0 erkennt 0x, 0o, 0b

Mit base == 0 parst ParseInt Go-Literal-Präfixe: 0x hex, 0o oktal, 0b binär, sonst Basis 10. Praktisch für Konfigs.

Präfix nur bei base 0

ParseInt("0xff", 16, 64) schlägt fehl — das 0x-Präfix gehört ausschließlich zum base 0-Modus.

bitSize 0 ist plattformabhängig

bitSize == 0 entspricht der nativen int-Breite — auf 64-Bit-Systemen 64, auf 32-Bit-Systemen 32. Für portablen Code lieber explizit setzen.

Underscore wird nicht akzeptiert

Go-Literale erlauben 1_000_000, ParseInt aber nicht. Vor dem Parse mit strings.ReplaceAll säubern, falls nötig.

ErrSyntax vs. ErrRange unterscheiden

Mit errors.Is(err, strconv.ErrSyntax) und errors.Is(err, strconv.ErrRange) lassen sich Format- und Wertbereichsfehler getrennt behandeln.

Whitespace muss vorher weg

ParseInt toleriert keine umgebenden Leerzeichen — vorher strings.TrimSpace aufrufen, sonst gibt es ErrSyntax.

Atoi ist der Spezialfall

strconv.Atoi(s) ist äquivalent zu ParseInt(s, 10, 0) — knapp und ohne Cast, aber nur für Dezimal-Werte im int-Bereich.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das strconv-Paket — String-Zahl-Konvertierung

Zur Übersicht