strconv.ParseComplex ist seit Go 1.15 Bestandteil der Standardbibliothek und schließt die Lücke zwischen der textuellen Repräsentation komplexer Zahlen und Gos eingebauten Typen complex64/complex128. Die Funktion parst Strings wie 1+2i, (3.14-2.71i) oder 2.5e3+1.2e-4i direkt in einen complex128-Wert und ist damit das Pendant zu FormatComplex für den Round-Trip-Zyklus zwischen Text und Zahl.

Im klassischen Web-Backend ist die Funktion eher selten gefragt — komplexe Zahlen tauchen dort kaum auf. Ihre Heimat sind wissenschaftliche und numerische Anwendungen: digitale Signalverarbeitung, FFT-Konfigurationen, Steuerungstechnik, Quantensimulationen oder das Einlesen experimenteller Messdaten aus Text-Dateien. Wer mit Go in diesen Domänen arbeitet, kommt um ParseComplex nicht herum, sobald komplexe Werte aus Strings (CLI-Flags, JSON-Strings, CSV-Spalten) rekonstruiert werden müssen.

Die Signatur folgt dem etablierten Muster der strconv-Parser. Zwei Eingaben, zwei Ausgaben — der bitSize-Parameter steuert die Float-Präzision der beiden Komponenten, der Rückgabetyp bleibt aber einheitlich complex128.

Go signatur.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// func ParseComplex(s string, bitSize int) (complex128, error)
	c, err := strconv.ParseComplex("1+2i", 128)
	fmt.Printf("c=%v  Typ=%T  err=%v\n", c, c, err)
}
Output
c=(1+2i)  Typ=complex128  err=<nil>

s ist der zu parsende String, bitSize muss entweder 64 oder 128 sein. Bei 64 werden Real- und Imaginärteil mit float32-Präzision interpretiert (passend für späteren Cast auf complex64), bei 128 mit voller float64-Präzision. Die Rückgabe ist immer ein complex128 — der Aufrufer cast't selbst auf complex64, falls nötig.

ParseComplex ist erstaunlich flexibel und akzeptiert die gängigen mathematischen Schreibweisen. Real- und Imaginärteil dürfen jeweils Vorzeichen, Dezimalpunkt und Exponenten tragen; das i markiert den Imaginärteil und darf groß oder klein geschrieben werden.

Go formate.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	inputs := []string{
		"3i",            // rein imaginär
		"1+2i",          // Standard
		"(1+2i)",        // mit Klammern
		"-1.5-3.2i",     // negative Vorzeichen
		"2.5e3+1.2e-4i", // Exponential-Notation
		"NaN",           // Special-Value
		"+Inf",          // Unendlich
		"5",             // nur Realteil
	}
	for _, s := range inputs {
		c, err := strconv.ParseComplex(s, 128)
		if err != nil {
			fmt.Printf("%-16q -> Fehler: %v\n", s, err)
			continue
		}
		fmt.Printf("%-16q -> %v\n", s, c)
	}
}
Output
"3i"             -> (0+3i)
"1+2i"           -> (1+2i)
"(1+2i)"         -> (1+2i)
"-1.5-3.2i"      -> (-1.5-3.2i)
"2.5e3+1.2e-4i"  -> (2500+0.00012i)
"NaN"            -> (NaN+0i)
"+Inf"           -> (+Inf+0i)
"5"              -> (5+0i)

Bemerkenswert: ein reiner Realteil wie 5 ist gültig — der Imaginärteil wird zu 0 ergänzt. Umgekehrt ergibt 3i einen Wert mit Realteil 0. Auch NaN und Inf werden korrekt durchgereicht, wie man es von ParseFloat kennt.

Der bitSize-Parameter funktioniert analog zu strconv.ParseFloat: er steuert die Rundung der Komponenten, nicht den Rückgabetyp. Bei bitSize=64 wird so gerundet, dass der Wert ohne weiteren Genauigkeitsverlust in complex64 (also zwei float32) passt. Bei bitSize=128 wird float64-Präzision verwendet.

Go bitsize.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	s := "1.123456789012345+2.987654321098765i"

	c128, _ := strconv.ParseComplex(s, 128)
	c64, _ := strconv.ParseComplex(s, 64)

	fmt.Printf("bitSize 128: %.16f\n", c128)
	fmt.Printf("bitSize  64: %.16f\n", c64)
	fmt.Printf("Cast zu complex64: %v\n", complex64(c64))
}
Output
bitSize 128: (1.1234567890123450+2.9876543210987650i)
bitSize  64: (1.1234568357467651+2.9876544475555420i)
Cast zu complex64: (1.1234568+2.9876544i)

Bei bitSize=64 sieht man deutlich, dass die Nachkommastellen ab der 8. Stelle abweichen — das ist die float32-Genauigkeit, gespeichert in einem float64-Slot. Wer am Ende complex64 braucht, sollte bitSize=64 verwenden, damit der Cast verlustfrei bleibt.

Go akzeptiert komplexe Zahlen sowohl mit als auch ohne umschließende Klammern. Das ist praktisch, weil fmt.Sprintf("%v", c) Klammern erzeugt, während andere Quellen (Python, MATLAB, händisch geschriebene Configs) sie weglassen.

Go klammern.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	a, _ := strconv.ParseComplex("(2.5+1.5i)", 128)
	b, _ := strconv.ParseComplex("2.5+1.5i", 128)
	fmt.Println("Mit Klammern:   ", a)
	fmt.Println("Ohne Klammern:  ", b)
	fmt.Println("Gleich?         ", a == b)
}
Output
Mit Klammern:    (2.5+1.5i)
Ohne Klammern:   (2.5+1.5i)
Gleich?          true

Wichtig: Klammern dürfen nur außen stehen und müssen paarig sein. (2.5+1.5i ohne schließende Klammer erzeugt ErrSyntax. Innerhalb des Ausdrucks sind keine Klammern erlaubt — (2.5)+(1.5i) ist ungültig.

Wie alle strconv-Parser unterscheidet ParseComplex zwischen zwei Fehlerklassen. strconv.ErrSyntax signalisiert eine syntaktisch kaputte Eingabe (Buchstabensalat, fehlendes i, unbalancierte Klammern). strconv.ErrRange tritt auf, wenn ein Teil zahlenmäßig gültig ist, aber den darstellbaren Bereich des Float-Typs sprengt.

Go errors.go
package main

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

func main() {
	cases := []string{
		"abc",      // syntaktischer Müll
		"1+2",      // fehlendes i
		"1+2i+3i",  // doppelter Imaginärteil
		"1e500+2i", // Realteil out of range
		"(1+2i",    // unbalancierte Klammer
	}
	for _, s := range cases {
		_, err := strconv.ParseComplex(s, 128)
		switch {
		case errors.Is(err, strconv.ErrSyntax):
			fmt.Printf("%-12q -> ErrSyntax\n", s)
		case errors.Is(err, strconv.ErrRange):
			fmt.Printf("%-12q -> ErrRange\n", s)
		default:
			fmt.Printf("%-12q -> ok\n", s)
		}
	}
}
Output
"abc"        -> ErrSyntax
"1+2"        -> ErrSyntax
"1+2i+3i"    -> ErrSyntax
"1e500+2i"   -> ErrRange
"(1+2i"      -> ErrSyntax

Bei ErrRange liefert ParseComplex trotzdem einen sinnvollen Wert: die betroffene Komponente wird auf ±Inf gesetzt, der andere Teil bleibt korrekt. Das ist wichtig zu wissen, falls der Code den Fehler bewusst ignoriert.

Der eingebaute complex(re, im)-Builder konstruiert eine komplexe Zahl aus zwei bereits geparsten Float-Werten. ParseComplex ist konzeptionell der String-Frontend dafür — und intern ungefähr äquivalent zu zweimal ParseFloat plus complex().

Go builtin_vs_parse.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// Builtin: nimmt zwei Floats
	a := complex(1.5, 2.5)

	// ParseComplex: nimmt einen String
	b, _ := strconv.ParseComplex("1.5+2.5i", 128)

	// Manuell: ParseFloat zweimal + complex()
	re, _ := strconv.ParseFloat("1.5", 64)
	im, _ := strconv.ParseFloat("2.5", 64)
	c := complex(re, im)

	fmt.Println(a, b, c, a == b, b == c)
}
Output
(1.5+2.5i) (1.5+2.5i) (1.5+2.5i) true true

Faustregel: complex() nutzen, wenn die Werte bereits als numerische Variablen vorliegen. ParseComplex ist der richtige Weg, sobald die Eingabe textuell vorliegt — aus einer Datei, einem CLI-Flag, einem JSON-Feld.

Ein realistisches Szenario: ein Signalverarbeitungs-Tool liest Filter-Koeffizienten aus einer Konfigurationsdatei. Jeder Koeffizient ist ein komplexer Wert, der direkt in die FFT-Pipeline fließt. ParseComplex macht den Loader trivial.

Go fft_config.go
package main

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

const configData = `# FFT Filter Koeffizienten
0.5+0.0i
0.707+0.707i
0.0+1.0i
-0.707+0.707i
-1.0+0.0i`

func loadCoefficients(r *bufio.Scanner) ([]complex128, error) {
	var coeffs []complex128
	for r.Scan() {
		line := strings.TrimSpace(r.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		c, err := strconv.ParseComplex(line, 128)
		if err != nil {
			return nil, fmt.Errorf("zeile %q: %w", line, err)
		}
		coeffs = append(coeffs, c)
	}
	return coeffs, nil
}

func main() {
	sc := bufio.NewScanner(strings.NewReader(configData))
	coeffs, _ := loadCoefficients(sc)
	for i, c := range coeffs {
		fmt.Printf("h[%d] = %v\n", i, c)
	}
}
Output
h[0] = (0.5+0i)
h[1] = (0.707+0.707i)
h[2] = (0+1i)
h[3] = (-0.707+0.707i)
h[4] = (-1+0i)

Der Loader ist robust gegen Leerzeilen und Kommentare, gibt eine klare Fehlermeldung mit Zeileninhalt zurück und nutzt bitSize=128 für volle Präzision — typisch für wissenschaftliche Pipelines, wo float32 schnell unzureichend wird.

Ein zweites Szenario: ein Labor exportiert Impedanz-Messungen als CSV. Jede Zeile enthält eine Frequenz und eine komplexe Impedanz im Format R+Xi. Der Loader parst beide Spalten und baut ein Auswertungs-Slice.

Go impedance.go
package main

import (
	"encoding/csv"
	"fmt"
	"math"
	"math/cmplx"
	"strconv"
	"strings"
)

const csvData = `freq_hz,impedance
100,50+0i
1000,49.8+12.3i
10000,48.1+125.7i
100000,42.5+1257i`

type Measurement struct {
	Freq float64
	Z    complex128
}

func main() {
	r := csv.NewReader(strings.NewReader(csvData))
	rows, _ := r.ReadAll()

	var data []Measurement
	for i, row := range rows {
		if i == 0 {
			continue
		}
		f, _ := strconv.ParseFloat(row[0], 64)
		z, _ := strconv.ParseComplex(row[1], 128)
		data = append(data, Measurement{Freq: f, Z: z})
	}

	for _, m := range data {
		fmt.Printf("f=%-8.0fHz  |Z|=%7.2f Ω  arg=%6.2f°\n",
			m.Freq, cmplx.Abs(m.Z), cmplx.Phase(m.Z)*180/math.Pi)
	}
}
Output
f=100     Hz  |Z|=  50.00 Ω  arg=  0.00°
f=1000    Hz  |Z|=  51.49 Ω  arg= 13.86°
f=10000   Hz  |Z|= 134.59 Ω  arg= 69.05°
f=100000  Hz  |Z|=1257.72 Ω  arg= 88.06°

ParseComplex macht den Import der Impedanz-Spalte zu einer Einzeiler-Operation; das math/cmplx-Paket liefert anschließend Betrag und Phasenwinkel. Ohne ParseComplex müsste man die R+Xi-Strings selbst zerlegen — fehleranfällig und unnötig.

Interessantes

bitSize akzeptiert nur 64 oder 128

Andere Werte (z. B. 32 oder 256) lösen ErrSyntax aus. Das unterscheidet sich von ParseInt, wo bitSize einen ganzen Bereich abdeckt.

Rückgabetyp immer complex128

Auch bei bitSize=64 ist der Rückgabewert ein complex128. Der Aufrufer cast't selbst zu complex64(c), falls der schmalere Typ benötigt wird.

ErrRange liefert ±Inf

Bei Überlauf einer Komponente wird diese auf +Inf oder -Inf gesetzt — der zweite Teil bleibt gültig. Wer den Fehler ignoriert, bekommt also keine NaN-Falle, sondern Unendlich.

Reiner Realteil ist gültig

5 parst zu (5+0i), 5i zu (0+5i). Das i ist das einzige unverzichtbare Element, wenn ein Imaginärteil existieren soll.

Klammern nur außen, paarig

(1+2i) funktioniert, (1+2i nicht. Innere Klammern wie (1)+(2i) sind verboten.

i und Inf case-insensitiv

i und I sind beide gültig als Imaginärmarkierung, ebenso Inf, inf, INF. NaN folgt der gleichen Regel wie bei ParseFloat.

Round-Trip mit FormatComplex

strconv.FormatComplex(c, 'g', -1, 128) plus strconv.ParseComplex(s, 128) ist ein verlustfreier Round-Trip. Format g mit Präzision -1 garantiert exakte Rekonstruktion.

Seit Go 1.15 verfügbar

Vor Go 1.15 musste man ParseFloat zweimal aufrufen und mit complex() kombinieren. Beim Targeting älterer Go-Versionen ist diese manuelle Lösung weiterhin notwendig.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht