M.Hiroi's Home Page

お気楽C#プログラミング超入門

複素数


Copyright (C) 2022 Makoto Hiroi
All rights reserved.

はじめに

近年、多くのプログラミング言語で「複素数 (complex number)」がサポートされるようになりました。たとえば、C言語では 1999 年に発行された規格 C99 で複素数型が導入されました。Go 言語や Python でも複素数型をサポートしていますし、複素数用の標準ライブラリを用意している言語 (C++, C#, Ruby, Haskell など) も多くあります。

他の言語では、FORTRAN や Common Lisp が昔から複素数型をサポートしています。Common Lisp の場合、基本的な数学関数でも複素数を適用できるのであれば、引数に複素数を渡して計算することができます。C# の場合、ライブラリ System.Numerics に複素数を表す構造体 Complex が用意されているので、簡単に複素数を扱うことができます。今回は C# の複素数についてまとめてみました。

●C# の数

C# の数はプリミティブな型で大きく分けると、整数 (int, long など) と実数 (float, double など) の 2 種類があります。System.Numerics に用意されている構造体 BigInteger を使うと、任意の桁の整数 (多倍長整数) を扱うことができます。

実数は浮動小数点数 (floating point number) として表現されます。浮動小数点数には IEEE 754 という標準仕様があり、近代的なプログラミング言語のほとんどは、IEEE 754 に準拠した浮動小数点数をサポートしています。浮動小数点数はすべての小数を正確に表現することはできません。このため、実数は近似的 (不正確) な値になります。

IEEE 754 には通常の数値以外にも、負のゼロ (-0.0)、正負の無限大 (∞, -∞)、NaN (Not a Number, 非数) といった値が定義されています。これらの値は C# でも取り扱うことができます。dotnet script の場合、負のゼロは -0、正負の無限大は ∞ と -∞ 、NaN は NaN と表示されます。

●無限大

一般に、無限大は値のオーバーフロー、ゼロ除算 (数値 / 0.0)、数学関数の計算結果 (たとえば log(0.0)) などで発生します。なお、浮動小数点数のゼロ除算でエラー (例外) を送出する処理系 (たとえば Python など) もあります。なお、double 型の無限大は、ライブラリ Double の定数 PositiveInfinity, NegativeInfinity に定義されています。

簡単な実行例を示します。

$ dotnet script
> 1e308
1E+308
> 1e308 * 2
∞
> -1e308
-1E+308
> -1e308 * 2
-∞
> 1.0 / 0.0
∞
> -1.0 / 0.0
-∞
> Math.Log(0.0)
-∞
> Double.PositiveInfinity
∞
> Double.NegativeInfinity
-∞

C# の場合、double 型の無限大の判定はライブラリ Double に用意されている関数を使います。

> Double.IsInfinity(1.0 / 0.0)
true
> Double.IsInfinity(-1.0 / 0.0)
true
> Double.IsInfinity(1.0)
false
> Double.IsPositiveInfinity(1.0 / 0.0)
true
> Double.IsNegativeInfinity(-1.0 / 0.0)
true
> Double.IsNegativeInfinity(1.0 / 0.0)
false

無限大は他の数値と比較したり演算することもできますが、結果が NaN になることもあります。

> var a = 1.0 / 0.0;
> a
∞
> var b = -1.0 / 0.0;
> b
-∞
> a < b
false
> a > b
true
> a == b
false
> a == a
true
> a > 0
true
> b < 0
true
> a < 0
false
> b > 0
false
> a + 100
∞
> a - 100
∞
> a * 100
∞
> a / 100
∞
> a - a
NaN
> a / a
NaN
> a + b
NaN
> a * 0
NaN

●負のゼロ

負のゼロ (-0.0) は、計算結果が負の極めて小さな値でアンダーフローになったとき発生します。また、正の値を負の無限大で除算する、負の値を正の無限大で除算する、負の値と 0.0 を乗算しても -0.0 が得られます。

> -1e-323
-1E-323
> -1e-323 / 2
-5E-324
> -1e-323 / 4
-0
> -1.0 / (1.0 / 0.0)
-0
> 1.0 / (-1.0 / 0.0)
-0
> -1.0 * 0
-0

標準 (IEEE 754) では、演算子 (== など) による 0.0 と -0.0 の比較は等しいと判定されます。C# の場合、-0.0 は関数 Double.IsNegative で判別することができます。また、任意の正の数を -0.0 で除算すると -∞ になるので、それを使って判別することもできます。

> var a = 0.0;
> var b = -0.0;
> a
0
> b
-0
> a == b
true
> Double.IsNegative(a)
false
> Double.IsNegative(b)
true
> 1 / a
∞
> 1 / b
-∞
> (1 / a) < 0
false
> (1 / b) < 0
true

-0.0 は数学関数 (たとえば Math.Atan2 など) や複素数の演算処理などで使われます。

> Math.Atan2(0.0, -1.0)
3.141592653589793
> Math.Atan2(-0.0, -1.0)
-3.141592653589793
> Math.Sqrt(0.0)
0
> Math.Sqrt(-0.0)
-0

●非数

NaN は数ではないことを表す特別な値 (非数) です。一般的には 0.0 / 0.0 といった不正な演算を行うと、その結果は NaN になります。たとえば、負数の平方根は虚数になりますが、関数 Math.Sqrt に負数を与えると浮動小数点数では表現できないので NaN になります。

> 0.0 / 0.0
NaN
> Math.Sqrt(-1.0)
NaN
> var a = 1.0 / 0;
> a
∞
> a / a
NaN
> Double.IsNaN(a / a)
true
> Double.IsNaN(-0.0)
false

C# の場合、NaN は関数 Double.IsNaN で判別することができます。

●C# の複素数

数学では複素数 \(z\) を \(x + yi\) と表記します。x を実部、y を虚部、i を虚数単位といいます。虚数単位は 2 乗すると -1 になる数です。実部と虚部の 2 つの数値を格納するデータ構造を用意すれば、プログラミング言語でも複素数を表すことができます。

C# で複素数を利用するときはライブラリ System.Numerics を使います。C# の複素数は構造体で、データ型は Complex になります。複素数はコンストラクタ Complex で生成します。

var z = new Complex(double x, double y);

第 1 引数 x が実部、第 2 引数 y が虚部になります。複素数 z の実部はプロパティ Real で、虚部はプロパティ Imaginary で取得することができます。複素数の虚部の符号を反転することを「複素共役」といいます。複素共役は関数 Conjugate で求めることができます。

簡単な例を示しましょう。

> using System.Numerics;
> var a = new Complex(1, 2);
> a
[<1; 2>]
> a.GetType()
[System.Numerics.Complex]
> a.Real
1
> a.Imaginary
2
> Complex.Conjugate(a)
[<1; -2>]

dotnet script では複素数を [<実部; 虚部>] と表示します。このほかに、Complex には次の定数が定義されています。

> Complex.ImaginaryOne
[<0; 1>]
> Complex.One
[<1; 0>]
> Complex.Zero
[<0; 0>]
> Complex.Infinity
[<∞; ∞>]
> Complex.NaN
[<NaN; NaN>]

複素数は極形式 \(z = r (\cos \theta + i \sin \theta)\) で表すことができます。r を絶対値、\(\theta\) を偏角といいます。絶対値はプロパティ Magnitude で求めることができます。偏角は複素平面において正の実軸とベクトル (x, y) との角度を表します。偏角はプロパティ Phase で求めます。Phase の返り値 \(\theta\) は \(-\pi \leq \theta \leq \pi\) (\(\pi\) : 円周率) です。

極形式 \(z = r (\cos \theta + i \sin \theta)\) で複素数を生成するときは関数 FromPolarCoordinates を使います。

Complex FromPolarCoordinates (double magnitude, double phase);

第 1 引数が絶対値、第 2 引数が偏角 θ を表します。

複素数 z はオイラーの公式 \(e^{i\theta} = \cos \theta + i \sin \theta\) を使って \(z = r e^{i\theta}\) と書くこともできます。ここで、\(\theta\) に \(pi\) を代入するとオイラーの等式 \(e^{i\pi} + 1 = 0\) になります。また、下記のようにド・モアヴルの公式も導出することができます。

\( z^n = (re^{i\theta})^n = r^n e^{in\theta} = r^n (\cos n\theta + i \sin n\theta) \)

簡単な例を示しましょう。

> a
[<1; 2>]
> a.Magnitude
2.23606797749979
> a.Phase
1.1071487177940904
> Complex.FromPolarCoordinates(a.Magnitude, a.Phase)
[<1.0000000000000002; 2>]

> (new Complex(-1.0, 0.0)).Phase
3.141592653589793
> (new Complex(-1.0, 1.0)).Phase
2.356194490192345
> (new Complex(0.0, 1.0)).Phase
1.5707963267948966
> (new Complex(1.0, 1.0)).Phase
0.7853981633974483
> (new Complex(1.0, 0.0)).Phase
0
> (new Complex(1.0, -1.0)).Phase
-0.7853981633974483
> (new Complex(0.0, -1.0)).Phase
-1.5707963267948966
> (new Complex(-1.0, -1.0)).Phase
-2.356194490192345
> (new Complex(-1.0, -0.0)).Phase
-3.141592653589793

C# の場合、\(-1.0+0.0i\) の偏角 \(\theta\) は \(\pi\) になり、\(-1.0-0.0i\) の偏角は \(-\pi\) になります。ゼロと負のゼロを区別しないプログラミング言語では、偏角 \(\theta\) の範囲を \(-\pi \lt \theta \leq \pi\) に制限して、\(-1.0 + 0.0i \ (= -1.0 - 0.0j)\) の偏角を \(\pi\) とします。

●複素数の四則演算

複素数の四則演算は次のようになります。

\( \begin{array}{l} (a + bi) + (c + di) = (a + c) + (b + d)i \\ (a + bi) - (c + di) = (a - c) + (b - d)i \\ (a + bi) \times (c + di) = (ac - bd) + (bc + ad)i \\ (a + bi) \div (c + di) = \dfrac{ac + bd + (bc - ad)i}{c^2 + d^2} \end{array} \)

これらの演算は C# の演算子 +, -, * , / で行うことができます。複素数の場合、大小の比較演算は使えませんが、等値の判定は演算子 == や /= で行うことができます。簡単な例を示しましょう。

> var a = new Complex(1, 2);
> var b = new Complex(3, 4);
> a
[<1; 2>]
> b
[<3; 4>]
> a + b
[<4; 6>]
> a - b
[<-2; -2>]
> a * b
[<-5; 10>]
> a / b
[<0.44; 0.08>]
> a == a
true
> a == new Complex(1, 2)
true
> a == b
false
> a != b
true
> b != b
false

実部と虚部の値は無限大や NaN になることもあります。

> var c = new Complex(1e300, 1e300);
> c
[<1E+300; 1E+300>]
> c * c
[<NaN; ∞>]

初版 2022 年 2 月 26 日