近年、多くのプログラミング言語で「複素数 (complex number)」がサポートされるようになりました。たとえば、C言語では 1999 年に発行された規格 C99 で複素数型が導入されました。Go 言語や Python でも複素数型をサポートしていますし、複素数用の標準ライブラリを用意している言語 (C++, C#, Ruby, Haskell など) も多くあります。
他の言語では、FORTRAN や Common Lisp が昔から複素数型をサポートしています。Common Lisp の場合、基本的な数学関数でも複素数を適用できるのであれば、引数に複素数を渡して計算することができます。C# の場合、ライブラリ System.Numerics に複素数を表す構造体 Complex が用意されているので、簡単に複素数を扱うことができます。今回は 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 で判別することができます。
数学では複素数 \(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\) になります。また、下記のようにド・モアヴルの公式も導出することができます。
簡単な例を示しましょう。
> 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\) とします。
複素数の四則演算は次のようになります。
これらの演算は 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; ∞>]