M.Hiroi's Home Page

Go Language Programming

お気楽 Go 言語プログラミング入門

[ PrevPage | Golang | NextPage ]

複素数

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

●Go 言語の数

Go 言語は標準で整数、浮動小数点数、複素数を使うことができます。下表に数値の種類を示します。

表 : Go 言語の数値
型名範囲
int8-128 ~ 127 (8 bit)
int16-32768 ~ 32767 (16 bit)
int32-2147483648 ~ 2147483647 (32 bit)
int64-9223372036854775808 ~ 9223372036854775807 (64 bit)
int32 bit 処理系ならば int32, 64 bit 処理系ならば int64
uint80 ~ 255 (8 bit)
byteuint8 の別名
uint160 ~ 65535 (16 bit)
uint320 ~ 4294967295 (32 bit)
uint640 ~ 18446744073709551615 (64 bit)
uint32 bit 処理系ならば uint32, 64 bit 処理系ならば uint64
float32±1.1754944E-38 ~ 3.4028235E+38 (32 bit 単精度)
float64±2.22507E-308 ~ 1.79769E+308 (64 bit 倍精度)
complex64float32 の実数部と虚数部を持つ複素数
complex128float64 の実数部と虚数部を持つ複素数

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

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

●無限大

一般に、無限大は値のオーバーフロー、ゼロ除算 (数値 / 0.0)、数学関数の計算結果 (たとえば log(0.0)) などで発生します。Go 言語の標準ライブラリ math には正負の無限大を生成する関数 Inf() と無限大を判定する関数 IsInf() が用意されています。

func Inf(sign int) float64
func IsInf(f float64, sign int) bool

関数 Inf() は引数 sign が 0 以上ならば正の無限大を、負ならば負の無限大を返します。関数 IsInf() は引数 f が無限大か判定します。このとき、引数 sign が正ならば正の無限大、負ならば負の無限大、0 ならば正負の無限大を判定します。なお、Print() や Printf() などの出力関数は正負の無限大を +Inf, -Inf と表示します。

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

リスト : 無限大の使用例 (inf1.go)

package main

import (
    "fmt"
    "math"
)

func main() {
    a := math.Inf(1)
    b := math.Inf(-1)
    c := math.MaxFloat64
    d := -math.MaxFloat64
    z := 0.0

    fmt.Printf("a = %g\n", a)
    fmt.Printf("b = %g\n", b)
    fmt.Printf("c = %g\n", c)
    fmt.Printf("d = %g\n", d)

    fmt.Printf("IsInf(a, 1) = %t\n", math.IsInf(a, 1))
    fmt.Printf("IsInf(a, 0) = %t\n", math.IsInf(a, 0))
    fmt.Printf("IsInf(a, -1) = %t\n", math.IsInf(a, -1))
    fmt.Printf("IsInf(b, 1) = %t\n", math.IsInf(b, 1))
    fmt.Printf("IsInf(b, 0) = %t\n", math.IsInf(b, 0))
    fmt.Printf("IsInf(b, -1) = %t\n", math.IsInf(b, -1))
    fmt.Printf("IsInf(c, 1) = %t\n", math.IsInf(c, 1))
    fmt.Printf("IsInf(c, 0) = %t\n", math.IsInf(c, 0))
    fmt.Printf("IsInf(c, -1) = %t\n", math.IsInf(c, -1))

    fmt.Printf("c * 2 = %g\n", c * 2.0)
    fmt.Printf("d * 2 = %g\n", d * 2.0)
    // fmt.Printf("1.0 / 0.0 = %g\n", 1.0 / 0.0)  コンパイルエラー (division by zero)
    fmt.Printf("1.0 / 0.0 = %g\n", 1.0 / z)
    fmt.Printf("log(0.0) = %g\n", math.Log(0.0))
}
$ go run inf1.go
a = +Inf
b = -Inf
c = 1.7976931348623157e+308
d = -1.7976931348623157e+308
IsInf(a, 1) = true
IsInf(a, 0) = true
IsInf(a, -1) = false
IsInf(b, 1) = false
IsInf(b, 0) = true
IsInf(b, -1) = true
IsInf(c, 1) = false
IsInf(c, 0) = false
IsInf(c, -1) = false
c * 2 = +Inf
d * 2 = -Inf
1.0 / 0.0 = +Inf
log(0.0) = -Inf

無限大は他の数値と比較したり演算することもできますが、結果が非数 (NaN, 出力関数では NaN と表示) になることもあります。

リスト : 無限大の使用例 (inf2.c)

package main

import (
    "fmt"
    "math"
)

func main() {
    a := math.Inf(1)
    b := math.Inf(-1)
    fmt.Printf("a = %g\n", a)
    fmt.Printf("b = %g\n", b)

    fmt.Printf("a == a => %t\n", a == a)
    fmt.Printf("a == b => %t\n", a == b)
    fmt.Printf("a != a => %t\n", a != a)
    fmt.Printf("a != b => %t\n", a != b)
    fmt.Printf("a > b => %t\n", a > b)
    fmt.Printf("a < b => %t\n", a < b)
    fmt.Printf("a > 0.0 => %t\n", a > 0.0)
    fmt.Printf("a < 0.0 => %t\n", a < 0.0)
    fmt.Printf("b < 0.0 => %t\n", b < 0.0)

    fmt.Printf("a + 10.0 = %g\n", a + 10.0)
    fmt.Printf("a - 10.0 = %g\n", a - 10.0)
    fmt.Printf("a * 10.0 = %g\n", a * 10.0)
    fmt.Printf("a / 10.0 = %g\n", a / 10.0)

    fmt.Printf("a + a = %g\n", a + a)
    fmt.Printf("a * a = %g\n", a * a)
    fmt.Printf("a - a = %g\n", a - a)
    fmt.Printf("a / a = %g\n", a / a)
    fmt.Printf("a + b = %g\n", a + b)
    fmt.Printf("a * 0.0 = %g\n", a * 0.0)
}
$ go run inf2.go
a = +Inf
b = -Inf
a == a => true
a == b => false
a != a => false
a != b => true
a > b => true
a < b => false
a > 0.0 => true
a < 0.0 => false
b < 0.0 => true
a + 10.0 = +Inf
a - 10.0 = +Inf
a * 10.0 = +Inf
a / 10.0 = +Inf
a + a = +Inf
a * a = +Inf
a - a = NaN
a / a = NaN
a + b = NaN
a * 0.0 = NaN

●負のゼロ

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

なお、Go 言語はプログラムで -0.0 と書いても負のゼロにはなりません。標準ライブラリ math の関数 Copysign() を使うと、負のゼロを得ることができます。また、関数 Signbit() を使って負数を判定することができます。

func Copysign(x, y float64) float64
func Signbit(x float64) bool

Copysign() は引数 x と同じ大きさで引数 y と同じ符号の数値を返します。Signbit() は引数 x が負数または負のゼロであれば真を返します。IEEE 754 では、演算子 (Go 言語では ==) による 0.0 と -0.0 の比較は等しいと判定されます。負のゼロの判定は Signbit() を使ってください。

リスト : 負のゼロ (nzero1.go)

package main

import (
    "fmt"
    "math"
)

func main() {
    a := -1e-323
    b := math.Inf(1)
    c := math.Inf(-1)
    z1 := 0.0
    z2 := math.Copysign(0, -1)
    fmt.Printf("a = %g\n", a)
    fmt.Printf("z1 = %g\n", z1)
    fmt.Printf("z2 = %g\n", z2)

    fmt.Printf("a / 2 = %g\n", a / 2.0)
    fmt.Printf("a / 4 = %g\n", a / 4.0)
    fmt.Printf("1.0 / -inf = %g\n", 1.0 / c)
    fmt.Printf("-1.0 / inf = %g\n", -1.0 / b)
    fmt.Printf("-1.0 * 0.0 = %g\n", -1.0 * z1)
    fmt.Printf("z1 == z2 => %t\n", z1 == z2)
    fmt.Printf("z1 != z2 => %t\n", z1 != z2)
    fmt.Printf("Signbit(z1) => %t\n", math.Signbit(z1))
    fmt.Printf("Signbit(z2) => %t\n", math.Signbit(z2))
}
$ go run nzero1.go
a = -1e-323
z1 = 0
z2 = -0
a / 2 = -5e-324
a / 4 = -0
1.0 / -inf = -0
-1.0 / inf = -0
-1.0 * 0.0 = -0
z1 == z2 => true
z1 != z2 => false
Signbit(z1) => false
Signbit(z2) => true

なお、-0.0 は数学関数 (math.h の関数 atan2 など) や複素数の演算処理などで使われます。

リスト : 負のゼロ (nzero2.go)

package main

import (
    "fmt"
    "math"
)

func main() {
    z1 := 0.0
    z2 := math.Copysign(0, -1)
    fmt.Printf("sqrt(0.0) => %g\n", math.Sqrt(z1))
    fmt.Printf("sqrt(-0.0) => %g\n", math.Sqrt(z2))
    fmt.Printf("atan2(0.0, -1.0) => %g\n", math.Atan2(z1, -1.0))
    fmt.Printf("atan2(-0.0, -1.0) => %g\n", math.Atan2(z2, -1.0))
}
$ go run nzero2.go
sqrt(0.0) => 0
sqrt(-0.0) => -0
atan2(0.0, -1.0) => 3.141592653589793
atan2(-0.0, -1.0) => -3.141592653589793

●非数

NaN は数ではないことを表す特別な値 (非数) です。一般的には 0.0 / 0.0 といった不正な演算を行うと、その結果は NaN になります。標準ライブラリ math には NaN を返す関数 NaN() と NaN を判定する関数 isNaN() が用意されています。

func NaN() float64
func IsNaN(f float64) bool
リスト : 非数 (nantest.go)

package main

import (
    "fmt"
    "math"
)

func main() {
    a := math.NaN()
    b := math.Inf(1)
    fmt.Printf("NaN = %g\n", a)
    fmt.Printf("NaN / NaN = %g\n", a / a)
    fmt.Printf("inf / inf = %g\n", b / b)
    fmt.Printf("IsNaN(NaN) = %t\n", math.IsNaN(a))
    fmt.Printf("IsNaN(inf) = %t\n", math.IsNaN(b))
}
$ go run nantest.go
NaN = NaN
NaN / NaN = NaN
inf / inf = NaN
IsNaN(NaN) = true
IsNaN(inf) = false

●Go 言語の複素数

数学では複素数 \(z\) を \(x + yi\) と表記します。x を実部、y を虚部、i を虚数単位といいます。虚数単位は 2 乗すると -1 になる数です。実部と虚部の 2 つの数値を格納するデータ構造を用意すれば、プログラミング言語でも複素数を表すことができます。Go 言語で複素数を扱うときは標準ライブラリ math/cmplx をインポートしてください。cmplx には複素数の計算に必要な定義や関数が含まれています。

複素数のデータ型は complex64 と complex128 の二種類があります。実部と虚部の数値が float32 だと complex64 になり、float64 だと complex128 になります。本稿では complex128 を使用することにします。

Go 言語は複素数 z = x + yi を x + yi と表記します。また、関数 complex() で複素数を生成することもできます。

func complex(r, i FloatType) ComplexType
func real(c ComplexType) FloatType
func imag(c ComplexType) FloatType

FloatType は実部と虚部の数値型を表します。たとえば、FloatType が float64 ならば CompelxType は complex128 になります。実部は関数 real() で、虚部は関数 imag() で求めることができます。複素数の虚部の符号を反転することを「複素共役」といいます。複素共役は cmplx の関数 Conj() で求めることができます。

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

リスト : 複素数の簡単な使用例 (ctest1.go)

package main

import (
    "fmt"
    "math/cmplx"
)

func main() {
    a := 1.0 + 2.0i
    b := 3.0 - 4.0i
    c := complex(-5.0, -6.0)
    fmt.Printf("a = %g\n", a)
    fmt.Printf("b = %g\n", b)
    fmt.Printf("c = %g\n", c)
    fmt.Printf("real(a) = %g\n", real(a))
    fmt.Printf("imag(a) = %g\n", imag(a))
    fmt.Printf("real(b) = %g\n", real(b))
    fmt.Printf("imag(b) = %g\n", imag(b))
    fmt.Printf("real(c) = %g\n", real(c))
    fmt.Printf("imag(c) = %g\n", imag(c))
    fmt.Printf("Conj(a) = %g\n", cmplx.Conj(a))
    fmt.Printf("Conj(b) = %g\n", cmplx.Conj(b))
    fmt.Printf("Conj(c) = %g\n", cmplx.Conj(c))
}
$ go run ctest1.go
a = (1+2i)
b = (3-4i)
c = (-5-6i)
real(a) = 1
imag(a) = 2
real(b) = 3
imag(b) = -4
real(c) = -5
imag(c) = -6
Conj(a) = (1-2i)
Conj(b) = (3+4i)
Conj(c) = (-5+6i)

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

絶対値と偏角は関数 cmplx.Polar() で求めることもできます。極形式 \(z = r (\cos \theta + i \sin \theta)\) で複素数を生成するときは関数 cmplx.Rect() を使うと便利です。

func Polar(x complex128) (r, θ float64)
func Rect(r, θ float64) complex128

Rect() の第 1 引数 r が絶対値、第 2 引数 \(\theta\) が偏角を表します。

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

リスト : 複素数の簡単な使用例 (ctest2.c)

package main

import (
    "fmt"
    "math"
    "math/cmplx"
)

func main() {
    z := math.Copysign(0.0, -1.0)
    x := []float64 {-1.0, -1.0, 0.0, 1.0, 1.0,  1.0,  0.0, -1.0, -1.0}
    y := []float64 { 0.0,  1.0, 1.0, 1.0, 0.0, -1.0, -1.0, -1.0, z}
    for n := 0; n < 9; n++ {
        a := complex(x[n], y[n])
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Abs(a) = %g\n", cmplx.Abs(a))
        fmt.Printf("Phase(a) = %g\n", cmplx.Phase(a))
    }
}
$ go run ctest2.go
a = (-1+0i)
Abs(a) = 1
Phase(a) = 3.141592653589793
a = (-1+1i)
Abs(a) = 1.4142135623730951
Phase(a) = 2.356194490192345
a = (0+1i)
Abs(a) = 1
Phase(a) = 1.5707963267948966
a = (1+1i)
Abs(a) = 1.4142135623730951
Phase(a) = 0.7853981633974483
a = (1+0i)
Abs(a) = 1
Phase(a) = 0
a = (1-1i)
Abs(a) = 1.4142135623730951
Phase(a) = -0.7853981633974483
a = (0-1i)
Abs(a) = 1
Phase(a) = -1.5707963267948966
a = (-1-1i)
Abs(a) = 1.4142135623730951
Phase(a) = -2.356194490192345
a = (-1-0i)
Abs(a) = 1
Phase(a) = -3.141592653589793

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

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

リスト : 複素数の使用例 (ctest3.go)

package main

import (
    "fmt"
    "math/cmplx"
)

func main() {
    a := 1.0 + 2.0i
    b := 3.0 + 4.0i
    fmt.Printf("a = %g\n", a)
    fmt.Printf("b = %g\n", b)
    fmt.Printf("a + b = %g\n", a + b)
    fmt.Printf("a - b = %g\n", a - b)
    fmt.Printf("a * b = %g\n", a * b)
    fmt.Printf("a / b = %g\n", a / b)
    fmt.Printf("a == a => %t\n", a == a)
    fmt.Printf("a != a => %t\n", a != a)
    fmt.Printf("a == b => %t\n", a == b)
    fmt.Printf("a != b => %t\n", a != b)

    c := 1e300 + 1e300i
    fmt.Printf("c = %g\n", c)
    fmt.Printf("Abs(c) = %g\n", cmplx.Abs(c))
    fmt.Printf("1 / c = %g\n", 1.0 / c)
    fmt.Printf("c * c = %g\n", c * c)
}
$ go run ctest3.go
a = (1+2i)
b = (3+4i)
a + b = (4+6i)
a - b = (-2-2i)
a * b = (-5+10i)
a / b = (0.44+0.08i)
a == a => true
a != a => false
a == b => false
a != b => true
c = (1e+300+1e+300i)
Abs(c) = 1.4142135623730952e+300
1 / c = (5e-301-5e-301i)
c * c = (NaN+Infi)

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


複素関数

●複素数の指数関数と対数関数

複素数を引数にとる指数関数はオイラー (Euler) の公式から導くことができます。

\( \begin{array}{l} e^{i\theta} = \cos \theta + i \sin \theta \quad (Euler's formula) \\ \begin{eqnarray} e^{x+iy} &=& e^x e^{iy} \\ &=& e^x (\cos y + i \sin y) \end{eqnarray} \end{array} \)

複素数の対数関数は複素数 \(z\) を絶対値 \(|z|\) と偏角 \(\theta\) を使って導くことができます。

\( \begin{array}{l} x + iy = |z| e^{i\theta} \\ \begin{eqnarray} \log_e (x + iy) &=& log_e |z| e^{i\theta} \\ &=& log_e |z| + log_e e^{i\theta} \\ &=& log_e |z| + i\theta, \quad (-\pi \leq \theta \leq \pi) \end{eqnarray} \end{array} \)

複素数 x, y のべき乗 \(x^y\) は次式で求めることができます。

\(x^y = e^{y\log x}\)

標準ライブラリ math/cmplx には、複素数に対応した関数 Exp(), Log(), Pow() が定義されています。なお、べき乗は演算子 ** でも求めることができます。

func Exp(x complex128) complex128
func Log(x complex128) complex128
func Pow(x, y complex128) complex128

簡単な使用例を示します。

リスト : 複素数の指数関数、対数関数、べき乗

package main

import (
    "fmt"
    "math"
    "math/cmplx"
)

func main() {
    pi := math.Pi
    fmt.Printf("cmplx.Exp(0 + i pi/4) = %g\n", cmplx.Exp(complex(0, pi / 4)))
    fmt.Printf("cmplx.Exp(0 + i pi/2) = %g\n", cmplx.Exp(complex(0, pi / 2)))
    fmt.Printf("cmplx.Exp(0 + i pi) = %g\n", cmplx.Exp(complex(0, pi)))
    fmt.Printf("cmplx.Exp(0 - i pi) = %g\n", cmplx.Exp(complex(0, -pi)))
    fmt.Printf("cmplx.Exp(1 + i) = %g\n", cmplx.Exp(complex(1, 1)))
    fmt.Printf("\n")

    fmt.Printf("cmplx.Log(1 + i) = %g\n", cmplx.Log(complex(1, 1)))
    fmt.Printf("cmplx.Log(1 + 0i) = %g\n", cmplx.Log(complex(1, 0)))
    fmt.Printf("cmplx.Log(0 + i) = %g\n", cmplx.Log(complex(0, 1)))
    fmt.Printf("cmplx.Log(1 - i) = %g\n", cmplx.Log(complex(1, -1)))
    fmt.Printf("cmplx.Log(1e300 + i 1e300) = %g\n", cmplx.Log(1e300 + 1e300i))
    fmt.Printf("\n")

    a := 1 + 1i;
    fmt.Printf("cmplx.Pow(1+i, 0) = %g\n", cmplx.Pow(a, 0 + 0i))
    fmt.Printf("cmplx.Pow(1+i, 1) = %g\n", cmplx.Pow(a, 1 + 0i))
    fmt.Printf("cmplx.Pow(1+i, 2) = %g\n", cmplx.Pow(a, 2 + 0i))
    fmt.Printf("cmplx.Pow(1+i, 3) = %g\n", cmplx.Pow(a, 3 + 0i))
    fmt.Printf("cmplx.Pow(1+i, 1+i) = %g\n", cmplx.Pow(a, a))
    fmt.Printf("cmplx.Pow(1+2i, 3+4i) = %g\n", cmplx.Pow(1+2i, 3+4i))
    fmt.Printf("\n")

    nz := math.Copysign(0, -1)
    fmt.Printf("cmplx.Log(-1 + 0i) = %g\n", cmplx.Log(complex(-1, 0)))
    fmt.Printf("cmplx.Log(-1 - 0i) = %g\n", cmplx.Log(complex(-1, nz)))
    fmt.Printf("cmplx.Log(-1e300 + 0i) = %g\n", cmplx.Log(complex(-1e300, 0)))
    fmt.Printf("cmplx.Log(-1e300 - 0i) = %g\n", cmplx.Log(complex(-1e300, nz)))
}
$ go run ctest4.go
cmplx.Exp(0 + i pi/4) = (0.7071067811865476+0.7071067811865475i)
cmplx.Exp(0 + i pi/2) = (6.123233995736757e-17+1i)
cmplx.Exp(0 + i pi) = (-1+1.2246467991473515e-16i)
cmplx.Exp(0 - i pi) = (-1-1.2246467991473515e-16i)
cmplx.Exp(1 + i) = (1.4686939399158851+2.2873552871788423i)

cmplx.Log(1 + i) = (0.3465735902799727+0.7853981633974483i)
cmplx.Log(1 + 0i) = (0+0i)
cmplx.Log(0 + i) = (0+1.5707963267948966i)
cmplx.Log(1 - i) = (0.3465735902799727-0.7853981633974483i)
cmplx.Log(1e300 + i 1e300) = (691.1221014884936+0.7853981633974483i)

cmplx.Pow(1+i, 0) = (1+0i)
cmplx.Pow(1+i, 1) = (1.0000000000000002+1i)
cmplx.Pow(1+i, 2) = (1.2246467991473517e-16+2.0000000000000004i)
cmplx.Pow(1+i, 3) = (-2.0000000000000004+2.0000000000000004i)
cmplx.Pow(1+i, 1+i) = (0.2739572538301211+0.5837007587586146i)
cmplx.Pow(1+2i, 3+4i) = (0.129009594074467+0.03392409290517015i)

cmplx.Log(-1 + 0i) = (0+3.141592653589793i)
cmplx.Log(-1 - 0i) = (0-3.141592653589793i)
cmplx.Log(-1e300 + 0i) = (690.7755278982137+3.141592653589793i)
cmplx.Log(-1e300 - 0i) = (690.7755278982137-3.141592653589793i)

最後の 4 つの例を見てください。関数 \(\log z \ (z = x + iy)\) は負の実軸 (\(-\infty \lt x \lt 0\)) において、\(x + 0.0i\) と \(x - 0.0i\) では値が異なります。数学では値を一つ返す関数を「一価関数」、複数の値を返す関数を「多価関数」といいます。ここで、定義域を制限することで多価関数を一価関数にみなすことを考えます。関数 \(\log z\) の場合、負の実軸を定義域から取り除けば、\(\log z\) を一価関数とみなすことができるわけです。

参考 URL 8 によると、この取り除いた領域を branch cut と呼ぶそうです。プログラミングでは branch cut を定義域から取り除くのではなく、その領域では不連続な関数とするそうです。参考文献 1 では「分枝切断線」、Python のドキュメントでは「分枝切断」と記述されています。本稿では branch cut を「分枝切断」と記述することにします。

プログラミング言語の場合、0.0 と -0.0 を区別する処理系であれば、Go 言語のように 2 つの値を区別することができます。0.0 と -0.0 を区別しない処理系では、偏角 \(\theta\) の範囲を \(-\pi \lt \theta \leq \pi\) に制限することで、\(\log z\) の返り値を (\(-\pi\) を取り除いて) 一つにすることができます。

●複素数の三角関数

複素数の三角関数の定義は、オイラーの公式から導かれる式の \(\theta\) を複素数 \(z\) に変えたものになります。

\(\sin -\theta = -\sin \theta, \ \cos -\theta = \cos \theta \) より
\( \begin{eqnarray} e^{i(-\theta)} &=& \cos -\theta + i \sin -\theta \\ &=& \cos \theta - i \sin \theta \end{eqnarray} \)

\( \begin{array}{l} e^{i\theta} + e^{-i\theta} = 2 \cos \theta \\ \cos \theta = \dfrac{e^{i\theta} + e^{-i\theta}}{2} \\ e^{i\theta} - e^{-i\theta} = 2i \sin \theta \\ \sin \theta = \dfrac{e^{i\theta} - e^{-i\theta}}{2i} \end{array} \)

\(\theta\) を複素数 z に置き換えた式が三角関数の定義になる

\( \begin{eqnarray} \sin z = \dfrac{e^{iz} - e^{-iz}}{2i} \\ \cos z = \dfrac{e^{iz} + e^{-iz}}{2} \end{eqnarray} \)

\(\sin z, \cos z\) に純虚数 \(ix\) を与えると双曲線関数 (\(\sinh x, \cosh x\)) になります。

双曲線関数の定義
\(\begin{eqnarray} \sinh x = \dfrac{e^x - e^{-x}}{2} \\ \cosh x = \dfrac{e^x + e^{-x}}{2} \end{eqnarray}\)
\(\begin{eqnarray} \sin ix &=& \dfrac{e^{iix} - e^{-iix}}{2i} \\ &=& \dfrac{e^{-x} - e^x}{2i} \times \dfrac{-i}{-i} \\ &=& i \dfrac{e^x - e^{-x}}{2} \\ &=& i \sinh x \\ \cos ix &=& \dfrac{e^{iix} + e^{-iix}}{2} \\ &=& \dfrac{e^{-x} + e^x}{2} \\ &=& \cosh x \end{eqnarray}\)

これに三角関数の加法定理 [*1] を使うと次の式が導かれます。

\(\begin{eqnarray} \sin (x + iy) &=& \sin x \cos iy + \cos x \sin iy \\ &=& \sin x \cosh y + i \cos x \sinh y \\ \cos (x + iy) &=& \cos x \cos iy - \sin x \sin iy \\ &=& \cos x \cosh y - i \sin x \sinh y \\ \tan (x + iy) &=& \dfrac{\sin 2x + \sin 2iy}{cos 2x + cos 2iy} \\ &=& \dfrac{\sin 2x + i \sinh 2y}{\cos 2x + \cosh 2y} \end{eqnarray}\)

標準ライブラリ math/cmplx に定義されている三角関数 (Sin, Cos, Tan) は複素数に対応しています。

func Sin(x complex128) complex128
func Cos(x complex128) complex128
func Tan(x complex128) complex128

簡単な使用例を示します。

リスト : 複素数の三角関数 (ctest5.c)

package main

import (
    "fmt"
    "math/cmplx"
)

func main() {
  c := []complex128 {0+1i, 0-1i, 1+1i, 1-1i}
  for n := 0; n < 4; n++ {
    a := c[n]
    b := cmplx.Sin(a)
    fmt.Printf("a = %g\n", a)
    fmt.Printf("Sin(a) = %g\n", b)
    fmt.Printf("Abs(Sin(a)) = %g\n", cmplx.Abs(b))
  }
  fmt.Println("")
  for n := 0; n < 4; n++ {
    a := c[n]
    b := cmplx.Cos(a)
    fmt.Printf("a = %g\n", a)
    fmt.Printf("Cos(a) = %g\n", b)
    fmt.Printf("Abs(Cos(a)) = %g\n", cmplx.Abs(b))
  }
  fmt.Println("")
  for n := 0; n < 4; n++ {
    a := c[n]
    b := cmplx.Tan(a)
    fmt.Printf("a = %g\n", a)
    fmt.Printf("Tan(a) = %g\n", b)
    fmt.Printf("Abs(Tan(a)) = %g\n", cmplx.Abs(b))
  }
}
$ go run ctest5.go
a = (0+1i)
Sin(a) = (0+1.1752011936438014i)
Abs(Sin(a)) = 1.1752011936438014
a = (0-1i)
Sin(a) = (0-1.1752011936438014i)
Abs(Sin(a)) = 1.1752011936438014
a = (1+1i)
Sin(a) = (1.2984575814159773+0.6349639147847361i)
Abs(Sin(a)) = 1.4453965766582497
a = (1-1i)
Sin(a) = (1.2984575814159773-0.6349639147847361i)
Abs(Sin(a)) = 1.4453965766582497

a = (0+1i)
Cos(a) = (1.5430806348152437-0i)
Abs(Cos(a)) = 1.5430806348152437
a = (0-1i)
Cos(a) = (1.5430806348152437+0i)
Abs(Cos(a)) = 1.5430806348152437
a = (1+1i)
Cos(a) = (0.8337300251311491-0.9888977057628651i)
Abs(Cos(a)) = 1.2934544550420957
a = (1-1i)
Cos(a) = (0.8337300251311491+0.9888977057628651i)
Abs(Cos(a)) = 1.2934544550420957

a = (0+1i)
Tan(a) = (0+0.761594155955765i)
Abs(Tan(a)) = 0.761594155955765
a = (0-1i)
Tan(a) = (0-0.761594155955765i)
Abs(Tan(a)) = 0.761594155955765
a = (1+1i)
Tan(a) = (0.2717525853195117+1.0839233273386948i)
Abs(Tan(a)) = 1.1174700207060706
a = (1-1i)
Tan(a) = (0.2717525853195117-1.0839233273386948i)
Abs(Tan(a)) = 1.1174700207060706
-- note --------
[*1] 三角関数の公式は引数が複素数でも成り立ちます。ただし、\(|\sin x| \leq 1, |\cos x| \leq 1\) という関係式は、x が実数だと成立しますが複素数では成立しません。

●複素数の双曲線関数

複素数の双曲線関数の定義は、実数の定義で引数 x を複素数 z に変えたものになります。

双曲線関数の定義 (z は複素数)
\(\begin{eqnarray} \sinh z = \dfrac{e^{z} - e^{-z}}{2} \\ \cosh z = \dfrac{e^{z} + e^{-z}}{2} \end{eqnarray}\)

sinh z, cosh z に純虚数 ix を与えると三角関数 (sin x, cos x) になります。

\(\begin{eqnarray} \sinh ix &=& \dfrac{e^{ix} - e^{-ix}}{2} \\ &=& \dfrac{e^{ix} - e^{-ix}}{2} \times \dfrac{i}{i} \\ &=& i \dfrac{e^{ix} - e^{-ix}}{2i} \\ &=& i \sin x \\ \cosh ix &=& \dfrac{e^{ix} + e^{ix}}{2} \\ &=& cos x \end{eqnarray}\)

これに双曲線関数の加法定理を使うと、次の式が導かれます。

双曲線関数の加法定理
\(\begin{eqnarray} \sinh(x + y) &=& \sinh x \cosh y + \cosh x \sinh y \\ \cosh(x + y) &=& \cosh x \cosh y + \sinh x \sinh y \\ \tanh(x + y) &=& \dfrac{\sinh(x + y)}{\cosh(x + y)} \\ &=& \dfrac{\sinh 2x + \sinh 2y}{\cosh 2x + \cosh 2y} \end{eqnarray}\)
\(\begin{eqnarray} \sinh(x + iy) &=& \sinh x \cos y + i \cosh x \sin y \\ \cosh(x + iy) &=& \cosh x \cos y + i \sinh x \sin y \\ \tanh(x + iy) &=& \dfrac{\sinh(x + iy)}{\cosh(x + iy)} \\ &=& \dfrac{\sinh 2x + i \sin 2y}{\cosh 2x + \cos 2y} \end{eqnarray}\)

標準ライブラリ math/cmplx に定義されている双曲線関数 (Sinh, Cosh, Tanh) は複素数に対応しています。

func Sinh(x complex128) complex128
func Cosh(x complex128) complex128
func Tanh(x complex128) complex128

簡単な使用例を示します。

リスト : 複素数の双曲線関数

package main

import (
    "fmt"
    "math/cmplx"
)

func main() {
    c := []complex128 {0+1i, 0-1i, 1+1i, 1-1i, 0+0i}
    for n := 0; n < 5; n++ {
        a := c[n]
        b := cmplx.Sinh(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Sinh(a) = %g\n", b)
    }
    fmt.Println("")
    for n := 0; n < 5; n++ {
        a := c[n]
        b := cmplx.Cosh(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Cosh(a) = %g\n", b)
    }
    fmt.Println("")
    for n := 0; n < 5; n++ {
        a := c[n]
        b := cmplx.Tanh(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Tanh(a) = %g\n", b)
    }
}
$ go run ctest6.go
a = (0+1i)
Sinh(a) = (0+0.8414709848078965i)
a = (0-1i)
Sinh(a) = (0-0.8414709848078965i)
a = (1+1i)
Sinh(a) = (0.6349639147847361+1.2984575814159773i)
a = (1-1i)
Sinh(a) = (0.6349639147847361-1.2984575814159773i)
a = (0+0i)
Sinh(a) = (0+0i)

a = (0+1i)
Cosh(a) = (0.5403023058681398+0i)
a = (0-1i)
Cosh(a) = (0.5403023058681398-0i)
a = (1+1i)
Cosh(a) = (0.8337300251311491+0.9888977057628651i)
a = (1-1i)
Cosh(a) = (0.8337300251311491-0.9888977057628651i)
a = (0+0i)
Cosh(a) = (1+0i)

a = (0+1i)
Tanh(a) = (0+1.557407724654902i)
a = (0-1i)
Tanh(a) = (0-1.557407724654902i)
a = (1+1i)
Tanh(a) = (1.0839233273386948+0.2717525853195117i)
a = (1-1i)
Tanh(a) = (1.0839233273386948-0.2717525853195117i)
a = (0+0i)
Tanh(a) = (0+0i)

●複素数の平方根

複素数 z の平方根は次の式で求めることができます。

\(z = x + iy, \ |z| = \sqrt{x^2 + y^2}, \ \theta = \arg z \ (-\pi \leq \theta \leq \pi)\) とすると

\( \sqrt{x + iy} = \begin{cases} \sqrt{|z| e^{i\theta}} = \sqrt{|z|} e^{i\theta/2} & (1) \\ \sqrt{|z| e^{i\theta + 2\pi}} = \sqrt{|z|} e^{i\theta/2 + \pi} & (2) \end{cases} \)

式 (1) を平方根の主値といいます。角度は \(2\pi\) を足すと同じ角度になるので、式 (2) がもう一つの解になります。三角関数の半角の公式を使うと、式 (1) から次の式が導かれます。

三角関数の半角の公式
\(\begin{eqnarray} \sin^2{\left(\frac{\theta}{2}\right)} = \dfrac{1 - \cos \theta}{2} \\ \cos^2{\left(\frac{\theta}{2}\right)} = \dfrac{1 + \cos \theta}{2} \end{eqnarray}\)

式 (1) を平方根の主値といいます。角度は \(2\pi\) を足すと同じ角度になるので、式 (2) がもう一つの解になります。三角関数の半角の公式を使うと、式 (1) から次の式が導かれます。

三角関数の半角の公式
\(\begin{eqnarray} \sin^2{\left(\frac{\theta}{2}\right)} = \dfrac{1 - \cos \theta}{2} \\ \cos^2{\left(\frac{\theta}{2}\right)} = \dfrac{1 + \cos \theta}{2} \end{eqnarray}\)
\(y \geq 0\) の場合
\(\begin{eqnarray} \sqrt{|z|} e^{i\theta/2} &=& \sqrt{|z|} \left(\cos{\frac{\theta}{2}} + i \sin{\frac{\theta}{2}}\right) \\ &=& \sqrt{|z|} \left(\sqrt{\frac{1 + \cos \theta}{2}}) + i \sqrt{\frac{1 - \cos \theta}{2}}\right) \\ &=& \dfrac{\sqrt{|z| + |z|\cos \theta}}{2} + i \sqrt{\frac{|z| - |z| \cos \theta}{2}} \\ &=& \sqrt{\frac{|z| + x}{2}} + i \sqrt{\frac{|z| - x}{2}}, \quad (|z|\cos \theta = x) \end{eqnarray}\)

\(y \lt 0\) の場合、虚部の符号が \(-\) になる
\( \sqrt{|z|} e^{i\theta/2} = \sqrt{\dfrac{|z| + x}{2}} - i \sqrt{\dfrac{|z| - x}{2}} \)

標準ライブラリ math/cmplx に定義されている関数 Sqrt は複素数に対応しています。

func Sqrt(x complex128) complex128

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

リスト : 複素数の平方根 (ctest7.go)

package main

import (
    "fmt"
    "math"
    "math/cmplx"
)

func main() {
    c := []complex128 {2+0i, -2+0i, 0+2i, 0-2i, 2+2i, -2-2i}
    for n := 0; n < 6; n++ {
        a := c[n]
        b := cmplx.Sqrt(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Sqrt(a) = %g\n", b)
        fmt.Printf("Sqrt(a) * Sqrt(a) = %g\n", b * b);
    }
    z := math.Copysign(0, -1)
    fmt.Printf("Sqrt(-2+0i) = %g\n", cmplx.Sqrt(complex(-2, 0)));
    fmt.Printf("Sqrt(-2-0i) = %g\n", cmplx.Sqrt(complex(-2, z)));
    fmt.Printf("Sqrt(-1e300+0i) = %g\n", cmplx.Sqrt(complex(-1e300, 0)));
    fmt.Printf("Sqrt(-1e300-0i) = %g\n", cmplx.Sqrt(complex(-1e300, z)));
}
$ go run ctest7.go
a = (2+0i)
Sqrt(a) = (1.4142135623730951+0i)
Sqrt(a) * Sqrt(a) = (2.0000000000000004+0i)
a = (-2+0i)
Sqrt(a) = (0+1.4142135623730951i)
Sqrt(a) * Sqrt(a) = (-2.0000000000000004+0i)
a = (0+2i)
Sqrt(a) = (1+1i)
Sqrt(a) * Sqrt(a) = (0+2i)
a = (0-2i)
Sqrt(a) = (1-1i)
Sqrt(a) * Sqrt(a) = (0-2i)
a = (2+2i)
Sqrt(a) = (1.5537739740300374+0.6435942529055826i)
Sqrt(a) * Sqrt(a) = (2.0000000000000004+2i)
a = (-2-2i)
Sqrt(a) = (0.6435942529055826-1.5537739740300374i)
Sqrt(a) * Sqrt(a) = (-2.0000000000000004-2i)
Sqrt(-2+0i) = (0+1.4142135623730951i)
Sqrt(-2-0i) = (0-1.4142135623730951i)
Sqrt(-1e300+0i) = (0+1e+150i)
Sqrt(-1e300-0i) = (0-1e+150i)

Sqrt は Log と同じ分枝切断を持っています。x を負の整数とすると Sqrt(x) の解は \(i \sqrt x\) になりますが、もうひとつ \(-i \sqrt x\) という解があります。

●逆三角関数

三角関数の逆関数を「逆三角関数 (inverse trigonometric function)」といいます。以下に標準ライブラリ math に定義されている逆三角関数を示します。

ここでは引数 x を実数とします。Asin x は引数 x が与えられたとき Sin w = x となる角度 w を求めます。同様に Acos x は Cos w = x となる角度 w を、Atan x は Tan x = w となる角度 w を求めます。三角関数には周期性があるので、上式を満たす角度 w は無数に存在します。つまり、逆三角関数の返り値は無数にあることになりますが、通常は一つの値を返すように範囲を制限します。これを「主値」といいます。

逆三角関数の主値を以下に示します。

\(\begin{array}{lcc} \arcsin x = w & -1 \leq x \lt 1 & -\pi/2 \leq w \leq \pi/2 \\ \arccos x = w & -1 \leq x \lt 1 & 0 \leq w \leq \pi \\ \arctan x = w & x \in \mathbb{R} & -\pi/2 \leq w \leq \pi/2 \end{array}\)

本ページでは、数式の表示に JavaScript のライブラリ MathJax を使っています。MathJax では、逆三角関数を arcsin, arccos, arctan と表示します。

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

リスト : 逆三角関数 (ctest8.go)

package main

import (
    "fmt"
    "math"
)

func main() {
    for x := -1.0; x <= 1.0; x += 0.5 {
        fmt.Printf("Asin(%g) = %g\n", x, math.Asin(x));
    }
    for x := -1.0; x <= 1.0; x += 0.5 {
        fmt.Printf("Acos(%g) = %g\n", x, math.Acos(x));
    }
    for x := -2.0; x <= 2.0; x += 1.0 {
        fmt.Printf("Atan(%g) = %g\n", x, math.Atan(x));
    }
}
$ go run ctest8.go
Asin(-1) = -1.5707963267948966
Asin(-0.5) = -0.5235987755982989
Asin(0) = 0
Asin(0.5) = 0.5235987755982989
Asin(1) = 1.5707963267948966
Acos(-1) = 3.141592653589793
Acos(-0.5) = 2.0943951023931957
Acos(0) = 1.5707963267948966
Acos(0.5) = 1.0471975511965976
Acos(1) = 0
Atan(-2) = -1.1071487177940904
Atan(-1) = -0.7853981633974483
Atan(0) = 0
Atan(1) = 0.7853981633974483
Atan(2) = 1.1071487177940904

math には関数 Atan2 も用意されています。

func Atan2(y, x float64) float64

引数 x, y は実数です。Atan2 は直交座標系においてベクトル (x, y) と x 軸との角度を求める関数です。複素平面で考えると、複素数 \(x + iy\) の偏角 \(\theta\) を求めることと同じです。返り値 (角度 \(\theta\)) の範囲は \(-\pi \leq \theta \leq \pi\) になります。

簡単な使用例を示します。

リスト : Atan2() の使用例 (ctest81.go)

package main

import (
    "fmt"
    "math"
)

func main() {
    z := math.Copysign(0, -1)
    x := []float64 {-1.0, -1.0, 0.0, 1.0, 1.0,  1.0,  0.0, -1.0, -1.0}
    y := []float64 { 0.0,  1.0, 1.0, 1.0, 0.0, -1.0, -1.0, -1.0, z}
    for n := 0; n < 9; n++ {
        fmt.Printf("Atan2(%g, %g) = %g\n", y[n], x[n], math.Atan2(y[n], x[n]));
    }
}
$ go run ctest81.go
Atan2(0, -1) = 3.141592653589793
Atan2(1, -1) = 2.356194490192345
Atan2(1, 0) = 1.5707963267948966
Atan2(1, 1) = 0.7853981633974483
Atan2(0, 1) = 0
Atan2(-1, 1) = -0.7853981633974483
Atan2(-1, 0) = -1.5707963267948966
Atan2(-1, -1) = -2.356194490192345
Atan2(-0, -1) = -3.141592653589793

●複素数の逆三角関数

複素数の逆三角関数の定義は、複素数の三角関数の定義から導くことができます。Asin z の定義は次のようになります。

\(z, w\) は複素数とする

\(\begin{array}{l} \arcsin z = w \\ z = \sin w = \dfrac{e^{iw} - e^{-iw}}{2i} \\ z \times 2ie^{iw} = \dfrac{e^{iw} - e^{-iw}}{2i} \times 2ie^{iw} \\ 2iz(e^{iw}) = (e^{iw})^2 - 1 \\ (e^{iw})^2 - 2iz(e^{iw}) - 1 = 0 \end{array}\)

\(e^{iw}\) の二次方程式と考えて解くと

\(e^{iw} = iz \pm \sqrt{1 - z^2}\)

両辺の対数をとって \(-i\) を掛け算し、平方根の主値 \(+\) を選ぶ

\(w = \arcsin z = -i \log{\left(iz + \sqrt{1 - z^2}\right)} \)

\(\arccos z, \arctan z\) は定義だけを示します。

\(\begin{array}{l} \arccos z = \dfrac{\pi}{2} - \arcsin z \\ \arctan z = i \dfrac{\log{\left(1 - iz\right)} - \log{\left(1 + iz\right)}}{2} \end{array}\)

Asin, Acos, Atan は次に示す分枝切断を持っています。

標準ライブラリ math/cmplx に定義されている逆三角関数 (Asin, Acos, Atan) は複素数に対応しています。

func Asin(x complex128) complex128
func Acos(x complex128) complex128
func Atan(x complex128) complex128

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

リスト : 複素数の逆三角関数 (ctest9.go)

package main

import (
    "fmt"
    "math"
    "math/cmplx"
)

func main() {
    z := math.Copysign(0, -1);
    c := []complex128 {2+0i, -2+0i, 0+2i, 0-2i, 2+2i, -2-2i}
    for n := 0; n < 6; n++ {
        a := c[n]
        b := cmplx.Asin(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Asin(a) = %g\n", b)
        fmt.Printf("Sin(Asin(a)) = %g\n", cmplx.Sin(b))
    }
    fmt.Println("");
    fmt.Printf("Asin(4+0.0i) = %g\n", cmplx.Asin(complex(4, 0)))
    fmt.Printf("Asin(4-0.0i) = %g\n", cmplx.Asin(complex(4, z)))
    fmt.Printf("Asin(-4+0.0i) = %g\n", cmplx.Asin(complex(-4, 0)))
    fmt.Printf("Asin(-4-0.0i) = %g\n", cmplx.Asin(complex(-4, z)))
    fmt.Println("")

    for n := 0; n < 6; n++ {
        a := c[n]
        b := cmplx.Acos(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Acos(a) = %g\n", b)
        fmt.Printf("Cos(Acos(a)) = %g\n", cmplx.Cos(b))
    }
    fmt.Println("")
    fmt.Printf("Acos(4+0.0i) = %g\n", cmplx.Acos(complex(4, 0)))
    fmt.Printf("Acos(4-0.0i) = %g\n", cmplx.Acos(complex(4, z)))
    fmt.Printf("Acos(-4+0.0i) = %g\n", cmplx.Acos(complex(-4, 0)))
    fmt.Printf("Acos(-4-0.0i) = %g\n", cmplx.Acos(complex(-4, z)))
    fmt.Println("")

    for n := 0; n < 6; n++ {
        a := c[n]
        b := cmplx.Atan(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Atan(a) = %g\n", b)
        fmt.Printf("Tan(Atan(a)) = %g\n", cmplx.Tan(b))
    }
    fmt.Println("")
    fmt.Printf("Atan(0+4i) = %g\n", cmplx.Atan(complex(0, 4)))
    fmt.Printf("Atan(-0+4i) = %g\n", cmplx.Atan(complex(z, 4)))
    fmt.Printf("Atan(0-4i) = %g\n", cmplx.Atan(complex(0, -4)))
    fmt.Printf("Atan(-0-4i) = %g\n", cmplx.Atan(complex(z, -4)))
    fmt.Println("");
}
$ go run ctest9.go
a = (2+0i)
Asin(a) = (1.5707963267948966+1.3169578969248164i)
Sin(Asin(a)) = (1.9999999999999993+1.060575238724905e-16i)
a = (-2+0i)
Asin(a) = (-1.5707963267948966+1.3169578969248164i)
Sin(Asin(a)) = (-1.9999999999999993+1.060575238724905e-16i)
a = (0+2i)
Asin(a) = (0+1.4436354751788099i)
Sin(Asin(a)) = (0+1.9999999999999987i)
a = (0-2i)
Asin(a) = (0-1.4436354751788103i)
Sin(Asin(a)) = (0-2i)
a = (2+2i)
Asin(a) = (0.7542491446980463+1.734324521487968i)
Sin(Asin(a)) = (2.000000000000003+2.000000000000002i)
a = (-2-2i)
Asin(a) = (-0.7542491446980462-1.7343245214879666i)
Sin(Asin(a)) = (-2.0000000000000004-2i)

Asin(4+0.0i) = (1.5707963267948966+2.0634370688955617i)
Asin(4-0.0i) = (1.5707963267948966-2.0634370688955608i)
Asin(-4+0.0i) = (-1.5707963267948966+2.0634370688955617i)
Asin(-4-0.0i) = (-1.5707963267948966-2.0634370688955608i)

a = (2+0i)
Acos(a) = (0-1.3169578969248164i)
Cos(Acos(a)) = (1.9999999999999993+0i)
a = (-2+0i)
Acos(a) = (3.141592653589793-1.3169578969248164i)
Cos(Acos(a)) = (-1.9999999999999993+2.12115047744981e-16i)
a = (0+2i)
Acos(a) = (1.5707963267948966-1.4436354751788099i)
Cos(Acos(a)) = (1.3691967456605042e-16+1.9999999999999991i)
a = (0-2i)
Acos(a) = (1.5707963267948966+1.4436354751788103i)
Cos(Acos(a)) = (1.3691967456605047e-16-2i)
a = (2+2i)
Acos(a) = (0.8165471820968503-1.734324521487968i)
Cos(Acos(a)) = (2.0000000000000036+2.000000000000002i)
a = (-2-2i)
Acos(a) = (2.3250454714929427+1.7343245214879666i)
Cos(Acos(a)) = (-2-2i)

Acos(4+0.0i) = (0-2.0634370688955617i)
Acos(4-0.0i) = (0+2.0634370688955608i)
Acos(-4+0.0i) = (3.141592653589793-2.0634370688955617i)
Acos(-4-0.0i) = (3.141592653589793+2.0634370688955608i)

a = (2+0i)
Atan(a) = (1.1071487177940904+0i)
Tan(Atan(a)) = (1.9999999999999996+0i)
a = (-2+0i)
Atan(a) = (-1.1071487177940904+0i)
Tan(Atan(a)) = (-1.9999999999999996+0i)
a = (0+2i)
Atan(a) = (-1.5707963267948968+0.5493061443340549i)
Tan(Atan(a)) = (4.82436794902991e-16+1.9999999999999993i)
a = (0-2i)
Atan(a) = (-1.5707963267948968-0.5493061443340549i)
Tan(Atan(a)) = (4.82436794902991e-16-1.9999999999999993i)
a = (2+2i)
Atan(a) = (1.311223269671635+0.23887786125685911i)
Tan(Atan(a)) = (1.9999999999999993+1.9999999999999991i)
a = (-2-2i)
Atan(a) = (-1.311223269671635-0.23887786125685906i)
Tan(Atan(a)) = (-1.9999999999999998-1.999999999999999i)

Atan(0+4i) = (-1.5707963267948968+0.25541281188299536i)    // 実部の符号が逆
Atan(-0+4i) = (-1.5707963267948966+0.25541281188299536i)
Atan(0-4i) = (-1.5707963267948968-0.25541281188299536i)    // 実部の符号が逆
Atan(-0-4i) = (-1.5707963267948966-0.25541281188299536i)

Go 言語 ver 1.17.4 の Atan() には分枝切断に不具合があるようです。

●逆双曲線関数

双曲線関数の逆関数を「逆双曲線関数 (inverse hyperbolic function)」といいます。標準ライブラリ math には逆双曲線関数 Asinh, Acosh, Atanh が定義されています。MathJax では逆双曲線関数を \(\sinh^{-1} x, \cosh^{-1} x, \tanh^{-1} x \) で表します。双曲線関数と逆双曲線関数の定義域と値域を示します。

\(\begin{array}{lcc} y = \sinh x & -\infty \lt x \lt \infty & -\infty \lt y \lt \infty \\ y = \cosh x & -\infty \lt x \lt \infty & 1 \leq y \lt \infty \\ y = \tanh x & -\infty \lt x \lt \infty & -1 \lt y \lt 1 \\ y = \sinh^{-1} x & -\infty \lt x \lt \infty & -\infty \lt y \lt \infty \\ y = \cosh^{-1} x & 1 \leq x \lt \infty & 0 \leq y \lt \infty \\ y = \tanh^{-1} x & -1 \lt x \lt 1 & -\infty \lt y \lt \infty \end{array}\)

x と y は実数です。x = Cosh y の逆関数 y = Acosh x を満たす y の値は 2 つありますが、ここでは \(y \geq 0\) を主値として選ぶことにします。

逆双曲線関数の定義は双曲線関数の定義から導くことができます。

\(\begin{array}{l} \sinh^{-1} x = y \\ y = \sinh x = \dfrac{e^{x} - e^{-x}}{2} \\ y \times 2e^{x} = \dfrac{e^{x} - e^{-x}}{2} \times 2e^{x} \\ 2ye^{x} = e^{2x} - 1 \\ e^{2x} - 2ye^{x} - 1 = 0 \end{array}\)

\(e^{x}\) の 2 次方程式として解く

\(e^{x} = \dfrac{2y \pm \sqrt{4y^2 + 4}}{2} = y \pm \sqrt{y^2 + 1} \)

\(e^{x} \gt 0\) だから平方根の符号に \(+\) を選んで対数をとる

\(x = \log \left(y + \sqrt{y^2 + 1}\right)\)
\(\begin{array}{l} \cosh^{-1} x = y \\ y = \cosh x = \dfrac{e^{x} + e^{-x}}{2} \\ y \times 2e^{x} = \dfrac{e^{x} + e^{-x}}{2} \times 2e^{x} \\ 2ye^{x} = e^{2x} + 1 \\ e^{2x} - 2ye^{x} + 1 = 0 \end{array}\)

\(e^{x}\) の 2 次方程式として解く

\(e^{x} = \dfrac{2y \pm \sqrt{\left(4y^2 - 4\right)}}{2} = y \pm \sqrt{y^2 - 1} \)

\(e^{x} \gt 0\) だから平方根の符号に \(+\) を選んで対数をとる

\(x = \log \left(y + \sqrt{y^2 - 1}\right), \quad (y \geq 1) \)
\(\begin{array}{l} \tanh^{-1} x = y \\ \begin{eqnarray} y &=& \tanh x \\ &=& \dfrac{\sinh x}{\cosh x} \\ &=& \dfrac{e^{x} - e^{-x}}{e^{x} + e^{-x}} \\ &=& \dfrac{e^{2x} - 1}{e^{2x} + 1} \end{eqnarray} \\ y(e^{2x} + 1) = (e^{2x} - 1) \\ (1 - y)e^{2x} = 1 + y \\ e^{2x} = \dfrac{1 + y}{1 - y} \\ x = \dfrac{1}{2} \log{\dfrac{1 + y}{1 - y}} \end{array}\)

また、次の関係式を使って Atanh から Asinh と Acosh を求めることができます。

\(\sinh^{-1} x = \tanh^{-1}{\dfrac{x}{\sqrt{x^2 + 1}}} \)

\(\cosh^{-1} x = \tanh^{-1}{\dfrac{\sqrt{1 - x^2}}{x}} \)

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

リスト : 逆双曲線関数 (ctest10.go)

package main

import (
    "fmt"
    "math"
)

func main() {
    for x := -2.0; x <= 2.0; x += 0.5 {
        fmt.Printf("Asinh(%g) = %g\n", x, math.Asinh(x))
        fmt.Printf("Sinh(Asinh(%g)) = %g\n", x, math.Sinh(math.Asinh(x)))
    }
    fmt.Println("")

    for x := 1.0; x <= 4.0; x += 0.5 {
        fmt.Printf("Acosh(%g) = %g\n", x, math.Acosh(x))
        fmt.Printf("Cosh(Acosh(%g)) = %g\n", x, math.Cosh(math.Acosh(x)))
    }
    fmt.Println("")

    for x := -0.75; x <= 0.75; x += 0.25 {
        fmt.Printf("Atanh(%g) = %g\n", x, math.Atanh(x))
        fmt.Printf("Tanh(Atanh(%g)) = %g\n", x, math.Tanh(math.Atanh(x)))
    }
}
$ go run ctest10.go
Asinh(-2) = -1.4436354751788103
Sinh(Asinh(-2)) = -2
Asinh(-1.5) = -1.1947632172871094
Sinh(Asinh(-1.5)) = -1.5
Asinh(-1) = -0.881373587019543
Sinh(Asinh(-1)) = -0.9999999999999999
Asinh(-0.5) = -0.48121182505960347
Sinh(Asinh(-0.5)) = -0.5
Asinh(0) = 0
Sinh(Asinh(0)) = 0
Asinh(0.5) = 0.48121182505960347
Sinh(Asinh(0.5)) = 0.5
Asinh(1) = 0.881373587019543
Sinh(Asinh(1)) = 0.9999999999999999
Asinh(1.5) = 1.1947632172871094
Sinh(Asinh(1.5)) = 1.5
Asinh(2) = 1.4436354751788103
Sinh(Asinh(2)) = 2

Acosh(1) = 0
Cosh(Acosh(1)) = 1
Acosh(1.5) = 0.9624236501192069
Cosh(Acosh(1.5)) = 1.5
Acosh(2) = 1.3169578969248166
Cosh(Acosh(2)) = 1.9999999999999998
Acosh(2.5) = 1.566799236972411
Cosh(Acosh(2.5)) = 2.5
Acosh(3) = 1.7627471740390859
Cosh(Acosh(3)) = 2.9999999999999996
Acosh(3.5) = 1.9248473002384139
Cosh(Acosh(3.5)) = 3.5
Acosh(4) = 2.0634370688955608
Cosh(Acosh(4)) = 4.000000000000001

Atanh(-0.75) = -0.9729550745276566
Tanh(Atanh(-0.75)) = -0.75
Atanh(-0.5) = -0.5493061443340548
Tanh(Atanh(-0.5)) = -0.49999999999999994
Atanh(-0.25) = -0.25541281188299536
Tanh(Atanh(-0.25)) = -0.25
Atanh(0) = 0
Tanh(Atanh(0)) = 0
Atanh(0.25) = 0.25541281188299536
Tanh(Atanh(0.25)) = 0.25
Atanh(0.5) = 0.5493061443340548
Tanh(Atanh(0.5)) = 0.49999999999999994
Atanh(0.75) = 0.9729550745276566
Tanh(Atanh(0.75)) = 0.75

●複素数の逆双曲線関数

複素数の逆双曲線関数の定義は、実数の定義で引数 x を複素数 z に変えたものになります。

\(\begin{array}{l} \sinh^{-1} z = \log \left(z + \sqrt{z^2 + 1}\right) \\ \begin{eqnarray} \cosh^{-1} z &=& \log \left(z + \sqrt{z^2 - 1}\right) &(1)& \\ &=& \log \left(z + \sqrt{z + 1} \sqrt{z - 1}\right) \quad &(2)& \end{eqnarray} \\ \tanh^{-1} z = \dfrac{1}{2} \log\left(\dfrac{1 + z}{1 - z}\right) = \dfrac{1}{2} \left( \log {\left( 1 + z \right)} - \log {\left(1 - z\right)} \right) \end{array}\)

参考文献, URL 7 によると、\(\cosh^{-1} z\) を式 (1) でプログラムすると「分枝切断線」が複雑になるため、他の式 (たとえば (2) など) でプログラムする処理系が多いようです。ちなみに、ANSI Common Lisp では \(\cosh^{-1} z\) を次の式で定義しています。

\( \cosh^{-1} z = 2 \log \left( \sqrt{\dfrac{z + 1}{2}} + \sqrt{\dfrac{z - 1}{2}} \right) \)

複素数の逆双曲線関数は次に示す分枝切断を持っています。

標準ヘッダファイル math/cmplx の逆双曲線関数 (Asinh, Acosh, Atanh) は複素数に対応しています。

func Asinh(x complex128) complex128
func Acosh(x complex128) complex128
func Atanh(x complex128) complex128

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

リスト : 複素数の逆双曲線関数 (ctest11.go)

package main

import (
    "fmt"
    "math"
    "math/cmplx"
)

func main() {
    c := []complex128 {2+0i, -2+0i, 0+2i, 0-2i, 2+2i, -2-2i}
    z := math.Copysign(0, -1)
    for n := 0; n < 6; n++ {
        a := c[n]
        b := cmplx.Asinh(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Asinh(a) = %g\n", b)
        fmt.Printf("Sinh(Asinh(a)) = %g\n", cmplx.Sinh(b))
    }
    fmt.Println("");
    fmt.Printf("Asinh(0.0+2.0i) = %g\n", cmplx.Asinh(complex(0, 2)))
    fmt.Printf("Asinh(-0.0+2.0i) = %g\n", cmplx.Asinh(complex(z, 2)))
    fmt.Printf("Asinh(0.0-4.0i) = %g\n", cmplx.Asinh(complex(0, -4)))
    fmt.Printf("Asinh(-0.0-4.0i) = %g\n", cmplx.Asinh(complex(z, -4)))
    fmt.Println("")

    for n := 0; n < 6; n++ {
        a := c[n]
        b := cmplx.Acosh(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Acosh(a) = %g\n", b)
        fmt.Printf("Cosh(Acosh(a)) = %g\n", cmplx.Cosh(b))
    }
    fmt.Println("")
    fmt.Printf("Acosh(0+0i) = %g\n", cmplx.Acosh(complex(0, 0)))
    fmt.Printf("Acosh(0-0i) = %g\n", cmplx.Acosh(complex(0, z)))
    fmt.Printf("Acosh(-4+0i) = %g\n", cmplx.Acosh(complex(-4, 0)))
    fmt.Printf("Acosh(-4-0i) = %g\n", cmplx.Acosh(complex(-4, z)))
    fmt.Printf("\n")

    for n := 0; n < 6; n++ {
        a := c[n]
        b := cmplx.Atanh(a)
        fmt.Printf("a = %g\n", a)
        fmt.Printf("Atanh(a) = %g\n", b)
        fmt.Printf("Tanh(Atanh(a)) = %g\n", cmplx.Tanh(b))
    }
    fmt.Println("")
    fmt.Printf("Atanh(2+0i) = %g\n", cmplx.Atanh(complex(2, 0)))
    fmt.Printf("Atanh(2-0i) = %g\n", cmplx.Atanh(complex(2, z)))
    fmt.Printf("Atanh(-4+0i) = %g\n", cmplx.Atanh(complex(-4, 0)))
    fmt.Printf("Atanh(-4-0i) = %g\n", cmplx.Atanh(complex(-4, z)))
    fmt.Println("");
}
$ go run ctest11.go
a = (2+0i)
Asinh(a) = (1.4436354751788103+0i)
Sinh(Asinh(a)) = (2+0i)
a = (-2+0i)
Asinh(a) = (-1.4436354751788099+0i)
Sinh(Asinh(a)) = (-1.9999999999999991+0i)
a = (0+2i)
Asinh(a) = (1.3169578969248166+1.5707963267948966i)
Sinh(Asinh(a)) = (1.0605752387249052e-16+1.9999999999999998i)
a = (0-2i)
Asinh(a) = (1.3169578969248166-1.5707963267948966i)
Sinh(Asinh(a)) = (1.0605752387249052e-16-1.9999999999999998i)
a = (2+2i)
Asinh(a) = (1.7343245214879666+0.7542491446980462i)
Sinh(Asinh(a)) = (2+2.0000000000000004i)
a = (-2-2i)
Asinh(a) = (-1.734324521487968-0.7542491446980463i)
Sinh(Asinh(a)) = (-2.000000000000002-2.0000000000000036i)

Asinh(0.0+2.0i) = (1.3169578969248166+1.5707963267948966i)
Asinh(-0.0+2.0i) = (-1.3169578969248164+1.5707963267948966i)
Asinh(0.0-4.0i) = (2.0634370688955608-1.5707963267948966i)
Asinh(-0.0-4.0i) = (-2.0634370688955617-1.5707963267948966i)

a = (2+0i)
Acosh(a) = (1.3169578969248164+0i)
Cosh(Acosh(a)) = (1.9999999999999993+0i)
a = (-2+0i)
Acosh(a) = (1.3169578969248164+3.141592653589793i)
Cosh(Acosh(a)) = (-1.9999999999999993+2.12115047744981e-16i)
a = (0+2i)
Acosh(a) = (1.4436354751788099+1.5707963267948966i)
Cosh(Acosh(a)) = (1.369196745660504e-16+1.9999999999999987i)
a = (0-2i)
Acosh(a) = (1.4436354751788103-1.5707963267948966i)
Cosh(Acosh(a)) = (1.3691967456605047e-16-2i)
a = (2+2i)
Acosh(a) = (1.734324521487968+0.8165471820968503i)
Cosh(Acosh(a)) = (2.0000000000000036+2.000000000000002i)
a = (-2-2i)
Acosh(a) = (1.7343245214879666-2.3250454714929427i)
Cosh(Acosh(a)) = (-2-2i)

Acosh(0+0i) = (0+1.5707963267948966i)
Acosh(0-0i) = (0-1.5707963267948966i)
Acosh(-4+0i) = (2.0634370688955617+3.141592653589793i)
Acosh(-4-0i) = (2.0634370688955608-3.141592653589793i)

a = (2+0i)
Atanh(a) = (0.5493061443340549+1.5707963267948966i)
Tanh(Atanh(a)) = (1.9999999999999993+1.8369701987210265e-16i)
a = (-2+0i)
Atanh(a) = (-0.5493061443340549+1.5707963267948966i)
Tanh(Atanh(a)) = (-1.9999999999999993+1.8369701987210265e-16i)
a = (0+2i)
Atanh(a) = (0+1.1071487177940904i)
Tanh(Atanh(a)) = (0+1.9999999999999996i)
a = (0-2i)
Atanh(a) = (0-1.1071487177940904i)
Tanh(Atanh(a)) = (0-1.9999999999999996i)
a = (2+2i)
Atanh(a) = (0.23887786125685911+1.311223269671635i)
Tanh(Atanh(a)) = (1.9999999999999998+2i)
a = (-2-2i)
Atanh(a) = (-0.23887786125685906-1.311223269671635i)
Tanh(Atanh(a)) = (-1.9999999999999991-2i)

Atanh(2+0i) = (0.5493061443340549+1.5707963267948966i)
Atanh(2-0i) = (0.5493061443340549+1.5707963267948968i)      // 虚部の符号が逆
Atanh(-4+0i) = (-0.25541281188299536+1.5707963267948966i)
Atanh(-4-0i) = (-0.25541281188299536+1.5707963267948968i)   // 虚部の符号が逆

Go 言語 ver 1.17.4 の Atanh() には分枝切断に不具合があるようです。

●参考文献, URL

  1. Guy L. Steele Jr., 『COMMON LISP 第 2 版』, 共立出版, 1991
  2. 奥村晴彦,『C言語による最新アルゴリズム事典』, 技術評論社, 1991
  3. IEEE 754 -- Wikipedia
  4. IEEE 754における負のゼロ - Wikipedia
  5. NaN - Wikipedia
  6. 逆三角関数 - Wikipedia
  7. 逆双曲線関数 - Wikipedia
  8. 逆双曲線関数と逆三角関数の branch cut | 雑記帳

Copyright (C) 2021 Makoto Hiroi
All rights reserved.

[ PrevPage | Golang | NextPage ]