Complex構造体は.NET Framework 4より使用できるようになった構造体で、複素数型を表す構造体です。 実部・虚部それぞれをDouble型の値で格納し、一つの型として扱えるようにした型となっています。

なお、BigInteger構造体と同様、Complex構造体はアセンブリSystem.Numerics.dllに含まれています。 使用する場合は、System.Numerics.dllへの参照を追加する必要があります。

§1 演算子

Complex構造体には、四則演算子・等価/不等価演算子が定義されているため、プリミティブな実数型と同じように演算式を記述することができます。

using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Complex a = 1.0;
    Complex b = new Complex(0.0, 1.0);
    Complex c = a + b;

    Console.WriteLine(a);
    Console.WriteLine("-{0} = {1}", b, -b);
    Console.WriteLine("{0} + {1} = {2}", a, b, c);
    Console.WriteLine("{0} - {1} = {2}", a, b, a - b);
    Console.WriteLine("{0} * {1} = {2}", a, b, a * b);
    Console.WriteLine("{0} / {1} = {2}", a, b, a / b);

    if (a == b)
      Console.WriteLine("{0} == {1}", a, b);
    else
      Console.WriteLine("{0} != {1}", a, b);

    if (a != c)
      Console.WriteLine("{0} != {1}", a, c);
    else
      Console.WriteLine("{0} == {1}", a, c);
  }
}
実行結果
(1, 0)
-(0, 1) = (0, -1)
(1, 0) + (0, 1) = (1, 1)
(1, 0) - (0, 1) = (1, -1)
(1, 0) * (0, 1) = (0, 1)
(1, 0) / (0, 1) = (0, -1)
(1, 0) != (0, 1)
(1, 0) != (1, 1)

また、IEquatable<BigInteger>も実装しているので、このインターフェースを使用した比較も行えます。

IEquatableインターフェイスおよびIEquatableインターフェイスを使った比較については等価性の定義と比較を参照してください。



§2 型変換

Complex構造体は、Doubleなどの実数型、Int32などの整数型からの暗黙の型変換をサポートしています。 型変換によりComplex構造体を構築した場合、値は実部として扱われ、虚部は常に0となります。 Decimal, BigIntegerからも型変換を行うことができますが、オーバーフローや精度が失われる可能性があるため明示的な型変換を行う必要があります。

using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Complex a = 1.0; // double
    Complex b = 100; // int
    Complex c = (Complex)(1.0m / 3.0m); // decimal

    Console.WriteLine(a);
    Console.WriteLine(b);
    Console.WriteLine(c);
  }
}
実行結果
(1, 0)
(100, 0)
(0.333333333333333, 0)

PointF構造体などからの変換はサポートされていないので、コンストラクタを使う必要があります。

using System;
using System.Drawing;
using System.Numerics;

class Sample {
  static void Main()
  {
    PointF pt = new PointF(3.0f, 4.0f);

    //Complex z = (Complex)pt;
    // error CS0030: 型 'System.Drawing.PointF' を型 'System.Numerics.Complex' に変換できません

    Complex z = new Complex(pt.X, pt.Y);

    Console.WriteLine(z);
  }
}

また、Complex型から他の実数型・整数型へ直接変換することはできないので、実部・虚部を個別に取得して変換する必要があります。

§3 実部・虚部・絶対値・偏角

Complexに格納されている値の状態を知るために、次のようなプロパティが用意されています。

Complexのプロパティ
プロパティ
Real 複素数の実部の値(Re z)を取得する。
Imaginary 複素数の虚部の値(Im z)を取得する。
Magnitude 複素数の絶対値(|z|)を取得する。
返される値はMath.Sqrt(z.Real * z.Real + z.Imaginary * z.Imaginary)と同じ。
Phase 複素数のガウス平面上での偏角(arg z)を取得する。 単位はラジアン。
返される値はMath.Atan2(z.Imaginary, z.Real)と同じ。
using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Complex z = new Complex(0.86603, 0.50000);

    Console.WriteLine(z);
    Console.WriteLine("Re z = {0}", z.Real);
    Console.WriteLine("Im z = {0}", z.Imaginary);
    Console.WriteLine("|z| = {0}", z.Magnitude);
    Console.WriteLine("arg z = {0}", z.Phase);
  }
}
実行結果
(0.86603, 0.5)
Re z = 0.86603
Im z = 0.5
|z| = 1.00000398044208
arg z = 0.523596477499666

なお、絶対値 |z| はComplex.Absメソッドでも得られます。

using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Complex z = new Complex(0.86603, 0.50000);

    Console.WriteLine(z);
    Console.WriteLine("|z| = {0}", z.Magnitude);
    Console.WriteLine("|z| = {0}", Complex.Abs(z));
  }
}
実行結果
(0.86603, 0.5)
|z| = 1.00000398044208
|z| = 1.00000398044208

§4 定数

Complex構造体には、次のような定数を参照するための静的フィールドが用意されています。

Complexの定数フィールド
フィールド 定数
Zero 実部・虚部ともに0のComplex (0 + i0)
One 実部が1、虚部が0のComplex (1 + i0)
ImaginaryOne 実部が0、虚部が1のComplex、虚数単位 (0 + i1)
using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Console.WriteLine(Complex.Zero);
    Console.WriteLine(Complex.One);
    Console.WriteLine(Complex.ImaginaryOne);

    Console.WriteLine("{0} * {0} = {1}",
                      Complex.ImaginaryOne,
                      Complex.ImaginaryOne * Complex.ImaginaryOne);
  }
}
実行結果
(0, 0)
(1, 0)
(0, 1)
(0, 1) * (0, 1) = (-1, 0)

§5 極形式

絶対値と偏角からComplex型を構築するには、FromPolarCoordinatesメソッドを使います。 このメソッドを使う場合、

  1. Complex構造体は直行形式で値を格納するため、極形式→直行形式の変換が行われることによる誤差が生じる場合がある
  2. Phaseプロパティで得られる偏角は -π ~ +π の範囲に正規化されるため、メソッドに指定した偏角とは異なる値となる場合がある

の2点に注意が必要です。

using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    double r = 1.0;
    double theta = 4.0 * Math.PI / 3.0;

    Complex z = Complex.FromPolarCoordinates(r, theta);

    Console.WriteLine("{0}{1} = {2} + i{3}", r, theta, z.Real, z.Imaginary);
    Console.WriteLine("r = {0}", z.Magnitude);
    Console.WriteLine("Θ = {0}", z.Phase);
  }
}
実行結果
1∠4.18879020478639 = -0.5 + i-0.866025403784438
r = 1
Θ = -2.0943951023932

§6 共役・逆数

共役複素数を得るには、Conjugateメソッドが使えます。 得られる結果は、単純に虚部の符号を反転した値をコンストラクタに指定した場合と同じです。

using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Complex z = new Complex(3.0, 4.0);

    Console.WriteLine(z);
    Console.WriteLine(Complex.Conjugate(z));
    Console.WriteLine(new Complex(z.Real, -z.Imaginary));
  }
}
実行結果
(3, 4)
(3, -4)
(3, -4)

逆数を得るには、Reciprocalメソッドが使えます。

using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Complex z = new Complex(3.0, 4.0);

    Console.WriteLine(z);
    Console.WriteLine(Complex.Reciprocal(z));
  }
}
実行結果
(3, 4)
(0.12, -0.16)

逆数を1に対する除算で求める場合と、Reciprocalメソッドで求める場合の違いは、値(除数)が0だった場合の結果の違いです。 1に対する除算で逆数を求めた場合、値が0の場合は結果が非数NaNとなります。 一方で、Reciprocalメソッドで逆数を求めた場合は、値が0の場合でも結果は0となります。

using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Console.WriteLine(Complex.Zero);
    Console.WriteLine(1 / Complex.Zero);
    Console.WriteLine(Complex.Reciprocal(Complex.Zero));
  }
}
実行結果
(0, 0)
(NaN (非数値), NaN (非数値))
(0, 0)

§7 数学関数

Mathクラスに用意されている数学関数は、Complexをサポートしていません。 代わりに、これらの関数はComplex構造体のメソッドとして提供されています。 Complexには次のようなメソッドが用意されています(rはDoubleの数、z, wはComplexの数)。 Mathクラスと同様、いずれも静的メソッドです。

Complexに用意されている数学関数とそれに相当するMathクラスのメソッド
関数 Complexのメソッド 相当するMathクラスのメソッド
絶対値 |z| Complex.Abs(z) Math.Abs
累乗 zr Complex.Pow(z, r) Math.Pow
累乗 zw Complex.Pow(z, w) Math.Pow
ネイピア数の累乗 ez Complex.Exp(z) Math.Exp
平方根 √z Complex.Sqrt(z) Math.Sqrt
自然対数 ln z Complex.Log(z) Math.Log
常用対数 log10z Complex.Log10(z) Math.Log10
対数 logrz Complex.Log(z, r) Math.Log
三角関数 sin z, cos z, tan z Complex.Sin(z), Complex.Cos(z), Complex.Tan(z) Math.Sin, Math.Cos, Math.Tan
逆三角関数 sin-1z, cos-1z, tan-1z Complex.Asin(z), Complex.Acos(z), Complex.Atan(z) Math.Asin, Math.Acos, Math.Atan
双曲線関数 sinh z, cosh z, tanh z Complex.Sinh(z), Complex.Cosh(z), Complex.Tanh(z) Math.Sinh, Math.Cosh, Math.Tanh
関数 Complexのメソッド 相当するMathクラスのメソッド

実部・虚部が非数NaNや無限大を含む場合の動作と結果は、Mathクラスのものとほぼ同じとなります。 詳しくは各メソッドのリファレンスを参照してください。

§8 書式

Complex構造体は書式指定子を指定した文字列化をサポートしています。 Double型の値に適用できる書式指定文字を指定することができ、実部・虚部それぞれの値に書式が適用されます。

using System;
using System.Numerics;

class Sample {
  static void Main()
  {
    Complex z = new Complex(0.86603, 0.500000);

    Console.WriteLine("{0}", z);
    Console.WriteLine("{0:F2}", z);
    Console.WriteLine("{0:E2}", z);
  }
}
実行結果
(0.86603, 0.5)
(0.87, 0.50)
(8.66E-001, 5.00E-001)

Complex型を文字列化する場合、その形式は常に "(実部, 虚部)" となります。 これ以外の形式にしたい場合は、個別に書式を指定するか、カスタム書式プロバイダを実装する必要があります。

書式の種類については書式指定子、書式プロバイダに関しては書式の定義と実装およびカルチャと書式・テキスト処理・暦を参照してください。

§9 Complexの使用例・マンデルブロ集合

Complex構造体を使った例として、マンデルブロ集合の描画を行ってみます。

マンデルブロ集合の描画
// csc /unsafe+ /r:System.Drawing.dll /r:System.Numerics.dll /r:System.Windows.Forms.dll mandelbrot.cs

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Numerics;
using System.Windows.Forms;

class Mandelbrot : Form {
  private static readonly Size windowSize = new Size(720, 720);
  private static readonly RectangleF view = RectangleF.FromLTRB(-1.5f, -1.0f, +0.5f, +1.0f);
  private static readonly Color colorFill = Color.White;
  private Bitmap bitmapMandelbrot = null;
  private Stopwatch stopwatch = new Stopwatch();

  static void Main()
  {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Mandelbrot());
  }

  public Mandelbrot()
  {
    this.ClientSize = windowSize;

    stopwatch.Start();

    bitmapMandelbrot = GenerateMandelbrotImage();

    stopwatch.Stop();
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    e.Graphics.DrawImageUnscaled(bitmapMandelbrot, 0, 0);

    e.Graphics.DrawString(stopwatch.Elapsed.ToString(), SystemFonts.DefaultFont, Brushes.Black, 0.0f, 0.0f);
  }

  private Bitmap GenerateMandelbrotImage()
  {
    Bitmap bitmap = new Bitmap(this.ClientSize.Width,
                               this.ClientSize.Height,
                               PixelFormat.Format24bppRgb);

    BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                                      ImageLockMode.WriteOnly,
                                      PixelFormat.Format24bppRgb);

    RenderMandelbrot(data);

    bitmap.UnlockBits(data);

    return bitmap;
  }

  private unsafe void RenderMandelbrot(BitmapData data)
  {
    double scaleX = view.Width  / (double)data.Width;
    double scaleY = view.Height / (double)data.Height;

    for (int y = 0; y < data.Height; y++) {
      byte* pixel = (byte*)data.Scan0.ToPointer() + y * data.Stride;
      double ci = view.Bottom - y * scaleY;

      for (int x = 0; x < data.Width; x++) {
        const int nDivLimit = 200;
        const double magDivLimit = 2.0;

        double cr = view.Left + x * scaleX;
        Complex c = new Complex(cr, ci);
        Complex z = Complex.Zero; // z(0)

        pixel[0] = colorFill.B;
        pixel[1] = colorFill.G;
        pixel[2] = colorFill.R;

        for (int n = 0; n < nDivLimit; n++) {
          Complex zn = (z * z) + c; // z(n + 1) = z(n)^2 + c

          if (zn.Magnitude <= magDivLimit) {
            z = zn;

            continue;
          }
          else {
            Color col = FromHsv(360 * n / nDivLimit, 255, 255);

            pixel[0] = col.B;
            pixel[1] = col.G;
            pixel[2] = col.R;

            break;
          }
        }

        pixel += 3;
      } // for x
    } // for y
  }

  private Color FromHsv(int h, int s, int v)
  {
    if (s == 0)
      return Color.FromArgb(v, v, v);

    byte p = (byte)((v * (255 - s)) / 255);
    int ht = h * 6;
    int d = ht % 360;

    ht /= 360;

    if ((ht % 2) == 0) {
      // ht = 0, 2, 4
      byte t = (byte)((v * (255 - s * (360 - d) / 360)) / 255);

      switch (ht) {
        case 0:  return Color.FromArgb(v, t, p);
        case 2:  return Color.FromArgb(p, v, t);
        default: return Color.FromArgb(t, p, v);
      }
    }
    else {
      // ht = 1, 3 ,5
      byte q = (byte)((v * (255 - s * d / 360)) / 255);

      switch (ht) {
        case 1:  return Color.FromArgb(q, v, p);
        case 3:  return Color.FromArgb(p, q, v);
        default: return Color.FromArgb(v, p, q);
      }
    }
  }
}