Go言語は、システムプログラミングやサーバーサイドの開発で広く知られていますが、実は数値計算、特に科学技術計算において非常に強力なポテンシャルを持っています。

その象徴的な機能の一つが、言語仕様として標準で組み込まれている「complex型(複素数型)」の存在です。

複素数は、数学や物理学、電気工学、信号処理などの専門的な分野において欠かすことのできない概念です。

多くのプログラミング言語では外部ライブラリを必要とする複素数計算を、Go言語ではネイティブなデータ型として直感的に記述することができます。

本記事では、Go言語におけるcomplex型の基本構造から、組み込み関数による操作、さらには「math/cmplx」パッケージを活用した高度な計算までを詳しく解説します。

これからGo言語で高度な数値シミュレーションやデータ解析を行いたいと考えているエンジニアの方にとって、複素数の理解は大きな武器となるでしょう。

Go言語における複素数の基本構造

Go言語には、複素数を扱うためのデータ型としてcomplex64complex128の2種類が用意されています。

これらは、実部 (Real part) と虚部 (Imaginary part) という2つの浮動小数点数を組み合わせて一つの数値を表現します。

complex64とcomplex128の違い

これら2つの型の主な違いは、実部と虚部に割り当てられるメモリサイズと精度にあります。

以下の表に、それぞれの型の構成要素と特徴をまとめました。

型名実部・虚部の型全体のサイズ主な用途
complex64float3264ビット (8バイト)メモリ消費を抑えたいモバイル環境や大規模配列
complex128float64128ビット (16バイト)高い計算精度が求められる科学技術計算(標準的)

現代のコンピュータ環境では、精度と互換性の観点から「complex128」を使用するのが一般的です。

Go言語のデフォルトの浮動小数点数がfloat64であるのと同様に、型推論を用いて複素数を定義した場合は自動的にcomplex128として扱われます。

複素数リテラルによる定義

Go言語では、虚数単位を i を用いて記述することで、直感的に複素数リテラルを作成できます。

例えば 1 + 2i という記述は、実部が1、虚部が2の複素数を表します。

Go
// 複素数リテラルによる宣言
c1 := 3.0 + 4.5i
c2 := 1i // 虚部のみの指定も可能

このように、数学的な表記に極めて近い形でコードを書ける点がGo言語の大きなメリットです。

complex組み込み関数による生成

変数に格納された動的な値から複素数を生成したい場合は、組み込み関数の complex() を使用します。

この関数は、第一引数に実部の値、第二引数に虚部の値を取ります。

Go
package main

import "fmt"

func main() {
    realPart := 1.5
    imagPart := 2.5

    // 変数から複素数を生成
    c := complex(realPart, imagPart)
    
    fmt.Printf("値: %v, 型: %T\n", c, c)
}
実行結果
値: (1.5+2.5i), 型: complex128

このとき、引数として渡す2つの浮動小数点数は、必ず同じ型 (float32同士、またはfloat64同士) でなければならない点に注意してください。

複素数の基本操作と演算

複素数を定義した後は、そこから成分を取り出したり、四則演算を行ったりする必要があります。

実部と虚部の抽出

複素数型から実部と虚部の値をそれぞれ個別に取得するには、組み込み関数の real()imag() を使用します。

Go
c := 5.0 + 12.0i

r := real(c) // 5.0を取得
i := imag(c) // 12.0を取得

これらの関数が返す値の型は、元の複素数がcomplex128であればfloat64に、complex64であればfloat32になります。

データの加工や、別の浮動小数点演算と組み合わせる際に頻繁に使用される重要な関数です。

基本的な算術演算

Go言語では、複素数に対しても通常の数値型と同様に算術演算子を使用できます。

加算 +、減算 -、乗算 *、除算 / のすべてがサポートされています。

Go
package main

import "fmt"

func main() {
    a := 2 + 3i
    b := 4 + 5i

    // 加算
    fmt.Println("加算:", a + b)
    // 減算
    fmt.Println("減算:", a - b)
    // 乗算
    fmt.Println("乗算:", a * b)
    // 除算
    fmt.Println("除算:", a / b)
}
実行結果
加算: (6+8i)
減算: (-2-2i)
乗算: (-7+22i)
除算: (0.5609756097560976+0.04878048780487805i)

複素数の乗算や除算は手計算で行うと複雑な手順が必要ですが、Go言語のランタイムがこれらを自動的に処理してくれます。

なお、複素数同士の比較演算においては ==!= のみが使用可能です。

複素数には大小関係(順序)という概念が存在しないため、 <> といった演算子を使用するとコンパイルエラーになります。

math/cmplxパッケージによる高度な計算

標準の組み込み関数だけでは、絶対値の計算や三角関数などの複雑な処理を完結させることはできません。

そこで活用するのが、標準ライブラリの math/cmplx パッケージです。

絶対値と偏角の取得

複素数平面上での「原点からの距離」を表す絶対値を取得するには cmplx.Abs() を使用します。

また、実軸の正の方向とのなす角(偏角)を取得するには cmplx.Phase() を使用します。

Go
package main

import (
    "fmt"
    "math/cmplx"
)

func main() {
    c := 3.0 + 4.0i

    // 絶対値 (√(3^2 + 4^2) = 5)
    abs := cmplx.Abs(c)
    // 偏角 (ラジアン)
    phase := cmplx.Phase(c)

    fmt.Printf("絶対値: %v\n", abs)
    fmt.Printf("偏角: %v\n", phase)
}
実行結果
絶対値: 5
偏角: 0.9272952180016122

これらは物理現象の振幅や位相を解析する際に非常に多用される関数です。

極形式と直交形式の変換

複素数は、実部と虚部で表す「直交形式」の他に、絶対値と偏角で表す「極形式」という表現方法があります。

cmplx.Polar() 関数を使えば、一つの複素数から絶対値と偏角を同時に取得できます。

逆に、絶対値と偏角から複素数を生成したい場合は cmplx.Rect() 関数を使用します。

Go
// 極形式への変換
r, theta := cmplx.Polar(c)

// 極形式から直交形式への変換
c2 := cmplx.Rect(r, theta)

共役複素数の算出

虚部の符号を反転させた「共役複素数」を求めるには cmplx.Conj() を使用します。

共役複素数は、複素数の除算を定義する際や、行列の計算、信号の自己相関を求める際などに利用されます。

Go
c := 1 + 2i
conj := cmplx.Conj(c) // 1 - 2i

指数関数・対数関数・三角関数

math/cmplxパッケージには、実数用の math パッケージと同様の数学関数が複素数版として網羅されています。

  • cmplx.Exp(c):指数関数 e^c
  • cmplx.Log(c):自然対数
  • cmplx.Sqrt(c):平方根
  • cmplx.Sin(c) / cmplx.Cos(c) / cmplx.Tan(c):三角関数
  • cmplx.Pow(x, y):複素数のべき乗

特に cmplx.Sqrt(-1) のような計算を行っても、パニックを起こさず正しく 0+1i という結果が得られる点は、複素数型を利用する最大の利点と言えるでしょう。

複素数型の実用的なユースケース

実際にcomplex型がどのような場面で役立つのか、具体的な3つのシナリオを見ていきましょう。

1. 電気回路のインピーダンス計算

交流回路の解析において、抵抗、コイル、コンデンサの影響を統合した「インピーダンス」は複素数で表現されます。

抵抗成分を実部、リアクタンス成分を虚部として扱うことで、電圧と電流の関係をオームの法則と同じような形式 V = I * Z で計算できます。

Go
package main

import (
    "fmt"
    "math/cmplx"
)

func main() {
    // 電流 I = 2 + 1i [A]
    // インピーダンス Z = 10 + 5i [Ω]
    I := 2 + 1i
    Z := 10 + 5i

    // 電圧 V = I * Z
    V := I * Z

    fmt.Printf("電圧の瞬時値(複素数): %v V\n", V)
    fmt.Printf("電圧の実効値(絶対値): %.2f V\n", cmplx.Abs(V))
}

このように、複雑な位相のずれを伴う計算をシンプルな数式としてコードに落とし込めます。

2. 信号処理と高速フーリエ変換 (FFT)

デジタル信号処理の核となる「フーリエ変換」は、時間領域の信号を周波数領域に変換する手法です。

この変換の過程では、すべてのデータが複素数として扱われます。

Go言語でオーディオデータの解析や画像フィルタリングを実装する場合、[]complex128 型の配列を操作することになります。

2026年現在のGoのエコシステムでは、多くの高度な数値計算ライブラリがこの組み込みのcomplex型をベースに構築されています。

3. フラクタル図形の描画

数学的な美しさを持つマンデルブロ集合やジュリア集合の描画には、複素数の反復計算が用いられます。

「z = z^2 + c」という複素数平面上の漸化式が発散するかどうかを判定するロジックは、complex型を用いることで驚くほど簡潔に記述できます。

Go
func isInMandelbrot(c complex128) bool {
    z := 0 + 0i
    for i := 0; i < 100; i++ {
        z = z*z + c
        if cmplx.Abs(z) > 2 {
            return false
        }
    }
    return true
}

もし複素数型がなければ、実部と虚部の計算を手動で展開する必要があり、コードの可読性は著しく低下してしまいます。

複素数を使用する際の注意点

非常に便利なcomplex型ですが、利用にあたっていくつか留意すべき点があります。

浮動小数点数の精度問題

complex型は内部的にfloat32やfloat64を使用しているため、浮動小数点数特有の丸め誤差が発生します。

例えば、理論上は 0 になるはずの計算結果が 1e-16 のような極めて小さな値として残ることがあります。

厳密な比較が必要な場合は、 == で直接比較するのではなく、一定の許容誤差(イプシロン)を設けて判定する工夫が必要です。

パフォーマンスへの配慮

複素数の演算は、実数の演算に比べて CPU 負荷が高くなります。

例えば、1つの複素数乗算には、実数同士の乗算4回と加算2回が必要です。

大量のデータをリアルタイムで処理する際には、不要な複素数変換が発生していないか、計算アルゴリズムが最適化されているかを確認することが重要です。

型変換の明示

Go言語は型に対して厳格な言語です。

float64 の値を complex128 に代入したり、その逆を行ったりする場合には、必ず明示的な型変換が必要になります。

Go
var f float64 = 10.5
// c := f + 0i // これはエラー
c := complex(f, 0) // 正しい方法

暗黙的な型変換が行われないことでバグを防いでいますが、慣れるまではエラーに遭遇しやすいポイントかもしれません。

まとめ

Go言語におけるcomplex型は、単なる数値型の一つという枠を超え、科学的・工学的な課題をエレガントに解決するための強力なツールです。

標準の complex64complex128 という2つの型を使い分け、組み込み関数の real(), imag(), complex() をマスターすることで、複素数計算の基礎を固めることができます。

さらに math/cmplx パッケージを併用することで、極形式の変換や高度な数学関数の適用も容易になります。

電気工学や信号処理といった伝統的な分野から、最新のデータ解析に至るまで、Go言語の複素数サポートは開発者の思考を妨げない直感的なインターフェースを提供しています。

今回学んだ知識を活かし、ぜひ実務や趣味のプロジェクトで高度な数値計算に挑戦してみてください。

Go言語が持つ「シンプルながらも強力」という設計思想が、複素数の扱いにおいても貫かれていることを実感できるはずです。