Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18933] ファイルの概要

このコミットは、Go言語の標準ライブラリmath/cmplxパッケージにおけるPow関数の挙動を修正するものです。具体的には、Pow(0, x)(0のx乗)の計算において、xが特定の値を取る場合の振る舞いを、math.Pow(実数版のべき乗関数)との一貫性を保ちつつ、数学的に正しい定義に近づけることを目的としています。

コミット

commit a9014ba4150b782ee10ab532752f97a7df26846e
Author: Rob Pike <r@golang.org>
Date:   Tue Mar 25 11:25:20 2014 +1100

    math/cmplx: define Pow(0, x) for problematic values of x.
    Currently it's always zero, but that is inconsistent with math.Pow
    and also plain wrong.
    This is a proposal for how it should be defined.
    Fixes #7583.
    
    LGTM=rsc
    R=golang-codereviews, iant, gobot, rsc
    CC=golang-codereviews
    https://golang.org/cl/76940044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a9014ba4150b782ee10ab532752f97a7df26846e

元コミット内容

このコミットは、math/cmplxパッケージのPow関数において、xが0である場合のべき乗計算の定義を修正するものです。これまでの実装では、Pow(0, x)は常に0を返していましたが、これは実数におけるmath.Powの挙動と矛盾しており、また数学的にも不正確なケースが存在しました。このコミットは、これらの「問題のあるxの値」に対するPow(0, x)の振る舞いを、より正確かつ一貫性のあるものにするための提案と実装を含んでいます。

具体的には、以下のケースが考慮されています。

  • Pow(0, ±0)1+0i を返す。
  • Pow(0, c)real(c) < 0 の場合、imag(c) がゼロならば Inf+0i を返し、そうでなければ Inf+Inf i を返す。

この変更は、Issue #7583を修正するものです。

変更の背景

Go言語の標準ライブラリmath/cmplxは複素数演算を提供しますが、その中のPow(x, y)関数(xのy乗)には、xが0である場合の挙動に不整合がありました。特に、Pow(0, y)が常に0を返すという現在の実装は、実数におけるmath.Pow(0, y)の定義と異なり、また複素数におけるべき乗の数学的定義とも乖離していました。

例えば、実数においては0^0 = 1と定義されることが多く、また0の負のべき乗は無限大に発散します。しかし、cmplx.Pow(0, 0)0を返し、cmplx.Pow(0, -1)0を返すという状況は、ユーザーにとって直感的ではなく、バグの原因となる可能性がありました。

このコミットは、このような不整合を解消し、cmplx.Powがより堅牢で、数学的に正確な結果を返すようにすることを目的としています。これにより、math.Powとの一貫性が向上し、複素数演算を利用するプログラムの信頼性が高まります。

前提知識の解説

複素数とべき乗

複素数z = a + biaは実部、bは虚部、iは虚数単位)のべき乗z^wは、実数のべき乗よりも複雑な定義を持ちます。一般的に、z^w = exp(w * log(z))として定義されます。ここでexpは複素指数関数、logは複素対数関数です。

0のべき乗の特殊性

実数における0のべき乗は、いくつかの特殊なケースがあります。

  • 0^0: これは不定形ですが、文脈によって1と定義されることが多いです(例: 二項定理、テイラー展開など)。
  • 0^x (x > 0): 0となります。
  • 0^x (x < 0): 1/0^(-x)となり、無限大に発散します。

複素数の場合も、これらの実数の概念が拡張されます。特に、0を底とする複素数のべき乗は、実部と虚部の両方を考慮する必要があります。

IEEE 754浮動小数点標準とNaN/Inf

Go言語のmathおよびcmplxパッケージは、IEEE 754浮動小数点標準に準拠しています。この標準では、数値計算における特殊な結果を表現するために、NaN(Not a Number)やInf(Infinity、無限大)といった特殊な値が定義されています。

  • math.Inf(1): 正の無限大
  • math.Inf(-1): 負の無限大
  • complex(math.Inf(1), 0): 実部が正の無限大、虚部が0の複素数。
  • Inf(): cmplxパッケージで定義されている、実部と虚部が両方とも正の無限大の複素数(complex(math.Inf(1), math.Inf(1)))。

これらの特殊な値は、数学的な発散や未定義の操作の結果を表現するために使用されます。

技術的詳細

このコミットの主要な変更点は、src/pkg/math/cmplx/pow.go内のPow関数に、xが0である場合の特殊な処理を追加したことです。

変更前のPow関数は、xの絶対値(modulus)が0の場合に、単純にcomplex(0, 0)を返していました。

func Pow(x, y complex128) complex128 {
	modulus := Abs(x)
	if modulus == 0 {
		return complex(0, 0) // This was the problematic line
	}
	// ...
}

変更後、x == 0(またはx == -0)の場合に、yの実部rと虚部iに基づいて、以下のように分岐するロジックが追加されました。

  1. r == 0 (yの実部が0の場合):

    • このケースは、Pow(0, ±0)のような状況に対応します。数学的な慣例に従い、11+0i)を返します。これは実数における0^0 = 1の定義と一致します。
  2. r < 0 (yの実部が負の場合):

    • これは0の負のべき乗に相当し、結果は無限大に発散します。
    • i == 0 (yの虚部が0の場合):
      • complex(math.Inf(1), 0)を返します。これは実部が正の無限大、虚部が0の複素数です。実数における0の負のべき乗が正の無限大になることと一致します。
    • i != 0 (yの虚部が0でない場合):
      • Inf()を返します。Inf()complex(math.Inf(1), math.Inf(1))を返すヘルパー関数です。複素数の負のべき乗で虚部が非ゼロの場合、結果は実部と虚部の両方が無限大になることがあります。
  3. r > 0 (yの実部が正の場合):

    • これは0の正のべき乗に相当し、結果は0となります。

この新しいロジックにより、cmplx.Pow(0, y)の挙動が、math.Pow(0, y)の挙動とより密接に一致し、複素数におけるべき乗の数学的定義に沿ったものになります。

また、src/pkg/math/cmplx/cmath_test.goには、これらの特殊ケースを検証するための新しいテストケースが追加されています。

	// Special cases for Pow(0, c).
	var zero = complex(0, 0)
	zeroPowers := [][2]complex128{
		{0, 1 + 0i}, // Pow(0, 0) should be 1
		{1.5, 0 + 0i}, // Pow(0, positive) should be 0
		{-1.5, complex(math.Inf(0), 0)}, // Pow(0, negative real) should be Inf+0i
		{-1.5 + 1.5i, Inf()}, // Pow(0, negative real + non-zero imag) should be Inf+Inf i
	}
	for _, zp := range zeroPowers {
		if f := Pow(zero, zp[0]); f != zp[1] {
			t.Errorf("Pow(%g, %g) = %g, want %g", zero, zp[0], f, zp[1])
		}
	}

このテストケースは、上記の新しい定義が正しく実装されていることを確認します。

コアとなるコードの変更箇所

src/pkg/math/cmplx/pow.go

--- a/src/pkg/math/cmplx/pow.go
+++ b/src/pkg/math/cmplx/pow.go
@@ -43,7 +43,25 @@ import "math"
 //    IEEE      -10,+10     30000       9.4e-15     1.5e-15
 
 // Pow returns x**y, the base-x exponential of y.
+// For generalized compatiblity with math.Pow:
+// Pow(0, ±0) returns 1+0i
+// Pow(0, c) for real(c)<0 returns Inf+0i if imag(c) is zero, otherwise Inf+Inf i.
 func Pow(x, y complex128) complex128 {
+\tif x == 0 { // Guaranteed also true for x == -0.
+\t\tr, i := real(y), imag(y)
+\t\tswitch {\n+\t\tcase r == 0:\n+\t\t\treturn 1\n+\t\tcase r < 0:\n+\t\t\tif i == 0 {\n+\t\t\t\treturn complex(math.Inf(1), 0)\n+\t\t\t}\n+\t\t\treturn Inf()\n+\t\tcase r > 0:\n+\t\t\treturn 0\n+\t\t}\n+\t\tpanic(\"not reached\")
+\t}
 	modulus := Abs(x)
 	if modulus == 0 {
 		return complex(0, 0)

src/pkg/math/cmplx/cmath_test.go

--- a/src/pkg/math/cmplx/cmath_test.go
+++ b/src/pkg/math/cmplx/cmath_test.go
@@ -656,6 +656,19 @@ func TestPolar(t *testing.T) {
 	}\n }\n func TestPow(t *testing.T) {
+\t// Special cases for Pow(0, c).
+\tvar zero = complex(0, 0)
+\tzeroPowers := [][2]complex128{
+\t\t{0, 1 + 0i},
+\t\t{1.5, 0 + 0i},
+\t\t{-1.5, complex(math.Inf(0), 0)},
+\t\t{-1.5 + 1.5i, Inf()},
+\t}
+\tfor _, zp := range zeroPowers {\n+\t\tif f := Pow(zero, zp[0]); f != zp[1] {\n+\t\t\tt.Errorf("Pow(%g, %g) = %g, want %g", zero, zp[0], f, zp[1])
+\t\t}\n+\t}
 \tvar a = complex(3.0, 3.0)\n \tfor i := 0; i < len(vc); i++ {\n \t\tif f := Pow(a, vc[i]); !cSoclose(pow[i], f, 4e-15) {

コアとなるコードの解説

src/pkg/math/cmplx/pow.go の変更

Pow関数の冒頭に、x0である場合の新しい条件分岐が追加されました。

  • if x == 0 { ... }: この条件は、入力xが複素数0+0iである場合に真となります。x == -0の場合も同様に処理されます。
  • r, i := real(y), imag(y): yの複素数を実部rと虚部iに分解します。
  • switch { ... }: yの実部rの値に基づいて、以下の3つのケースに分岐します。
    • case r == 0:: yの実部が0の場合。例えばy = 0+0i0)やy = 0+1i(純虚数)など。この場合、Pow(0, y)11+0i)を返します。これは0^0 = 1の慣例を拡張したものです。
    • case r < 0:: yの実部が負の場合。例えばy = -1+0iy = -2+3iなど。これは0の負のべき乗に相当し、結果は無限大に発散します。
      • if i == 0: yの虚部が0の場合(例: y = -1+0i)。実数における0の負のべき乗と同様に、complex(math.Inf(1), 0)(正の無限大の実部と0の虚部)を返します。
      • else: yの虚部が0でない場合(例: y = -1+1i)。この場合、Inf()(実部と虚部が両方とも正の無限大)を返します。これは複素数の特性によるものです。
    • case r > 0:: yの実部が正の場合。例えばy = 1+0iy = 2-1iなど。この場合、Pow(0, y)00+0i)を返します。これは実数における0の正のべき乗と同様です。
  • panic("not reached"): この行は、上記のswitch文がすべての可能なケースを網羅しているため、通常は到達しないことを示します。もし到達した場合は、予期せぬ状態であることを示唆します。

この変更により、Pow(0, y)の挙動がより正確かつ予測可能になり、math.Powとの一貫性が保たれます。

src/pkg/math/cmplx/cmath_test.go の変更

TestPow関数内に、Pow(0, c)の特殊ケースを検証するための新しいテストブロックが追加されました。

  • var zero = complex(0, 0): 複素数0を定義します。
  • zeroPowers := [][2]complex128{ ... }: テストケースの配列を定義します。各要素は[入力y, 期待される出力]のペアです。
    • {0, 1 + 0i}: Pow(0, 0)1+0iを返すことをテストします。
    • {1.5, 0 + 0i}: Pow(0, 1.5)(正の実部)が0+0iを返すことをテストします。
    • {-1.5, complex(math.Inf(0), 0)}: Pow(0, -1.5)(負の実部、虚部0)がInf+0iを返すことをテストします。math.Inf(0)math.Inf(1)と同じく正の無限大を返します。
    • {-1.5 + 1.5i, Inf()}: Pow(0, -1.5 + 1.5i)(負の実部、非ゼロ虚部)がInf+Inf iを返すことをテストします。
  • for _, zp := range zeroPowers { ... }: 各テストケースをループで回し、Pow関数を呼び出して結果を検証します。
  • t.Errorf(...): 期待される結果と異なる場合にエラーメッセージを出力します。

これらのテストケースは、pow.goで実装された新しいロジックが正しく機能していることを保証します。

関連リンク

参考にした情報源リンク