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

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

このコミットは、Go言語のmathパッケージにおけるガンマ関数(Gamma)の特殊な入力値に対する挙動を、C99標準に準拠するように更新するものです。具体的には、負の整数やゼロに対するガンマ関数の戻り値を修正し、テストケースもそれに合わせて調整しています。

コミット

commit 5496e941873f2a755e387e37d965fd486e81c1ba
Author: Charles L. Dorian <cldorian@gmail.com>
Date:   Wed Apr 4 09:45:22 2012 -0400

    math: update Gamma special cases
    Match new C99 values for special cases.
    
    Fixes #2977.
    
    R=rsc, golang-dev
    CC=golang-dev
    https://golang.org/cl/5972058

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

https://github.com/golang/go/commit/5496e941873f2a755e387e37d965fd486e81c1ba

元コミット内容

math: update Gamma special cases
Match new C99 values for special cases.

Fixes #2977.

R=rsc, golang-dev
CC=golang-dev
https://golang.org/cl/5972058

変更の背景

この変更の主な背景は、Go言語のmath.Gamma関数が、C99標準で定義されているガンマ関数の特殊な入力値に対する挙動と一致していなかった点にあります。特に、負の整数やゼロに対するガンマ関数の結果が、C99の規定と異なっていました。

C99(ISO/IEC 9899:1999)は、C言語の標準規格であり、浮動小数点演算における数学関数の挙動についても厳密な定義を含んでいます。ガンマ関数(tgamma)に関して、C99は以下の特殊ケースを規定しています。

  • 引数が ±0 の場合: ±∞ を返す。
  • 引数が負の整数の場合: NaN (Not a Number) を返す。これは定義域エラー(domain error)と見なされます。
  • 引数が -∞ の場合: NaN を返す。
  • 引数が +∞ の場合: +∞ を返す。
  • 引数が NaN の場合: NaN を返す。

Goのmath.Gamma関数は、これらのC99の規定に完全に準拠していなかったため、相互運用性や予測可能性の観点から修正が必要とされました。特に、負の整数に対するガンマ関数の値は、数学的には極(pole)を持つため無限大に発散しますが、C99では「定義域エラー」としてNaNを返すことが定められています。このコミットは、この差異を解消し、Goのmath.Gamma関数がC99の期待する挙動に合致するように調整することを目的としています。

前提知識の解説

ガンマ関数 (Gamma Function, Γ(z))

ガンマ関数は、階乗関数を複素数に拡張した特殊関数です。正の整数 n に対しては Γ(n) = (n-1)! の関係が成り立ちます。例えば、Γ(1) = 0! = 1Γ(2) = 1! = 1Γ(3) = 2! = 2 となります。

ガンマ関数は、実数軸上では正の実数に対しては正の値を取り、負の実数に対しては正負を繰り返しながら振動します。しかし、非正の整数(0, -1, -2, ...)においては定義されず、極(pole)を持ちます。これらの点では関数の値が無限大に発散します。

IEEE 754 浮動小数点標準

IEEE 754は、浮動小数点数のコンピュータ上での表現と演算に関する国際標準です。この標準は、数値計算の信頼性と移植性を高めるために非常に重要です。IEEE 754では、通常の有限数に加えて、以下の特殊な値を定義しています。

  • NaN (Not a Number): 「非数」を表します。0/0、無限大 - 無限大、負の数の平方根など、数学的に未定義または表現不可能な演算の結果として生成されます。NaNは、その値が数値ではないことを示し、比較演算では常に偽となります(NaN == NaN も偽)。
  • Inf (Infinity): 「無限大」を表します。0以外の数を0で割った場合や、表現可能な最大値を超える演算結果として生成されます。正の無限大 (+Inf) と負の無限大 (-Inf) があります。
  • Signbit: 浮動小数点数の符号を示すビットです。0であれば正、1であれば負を示します。これは通常の数値だけでなく、InfやNaNにも適用されます。例えば、-0 は符号ビットが1のゼロであり、+0 とは異なる挙動を示す場合があります(特に数学関数において)。

C99 標準と数学関数

C99標準は、C言語の数学ライブラリ関数(math.h)の挙動について厳格な規定を設けています。これにより、異なるCコンパイラやプラットフォーム間での数値計算の一貫性が保証されます。ガンマ関数(tgamma)のような特殊関数についても、特定の入力値(特に特殊な浮動小数点値)に対する戻り値が明確に定義されており、これにはNaNやInfの適切な使用が含まれます。

技術的詳細

このコミットは、Goのmath.Gamma関数の実装をC99標準に合わせるために、主に以下の点を変更しています。

  1. 負の整数に対する挙動の変更:
    • 変更前: Gamma(x) は負の整数に対して ±Inf を返す可能性がありました。
    • 変更後: Gamma(x) は負の整数に対して NaN を返すように修正されました。これはC99の規定に合致します。この変更のために、isNegIntというヘルパー関数が導入され、入力値が負の整数であるかを判定するようになりました。
  2. ゼロに対する挙動の明確化:
    • 変更前: Gamma(0) の挙動が明示的に記述されていませんでしたが、おそらく ±Inf を返していました。
    • 変更後: Gamma(+0)+Inf を、Gamma(-0)-Inf を返すように明確に定義されました。これはC99の規定に合致します。Signbit(x) 関数を使用して、ゼロの符号を判定しています。
  3. 負の無限大に対する挙動の変更:
    • 変更前: Gamma(-Inf)-Inf を返していました。
    • 変更後: Gamma(-Inf)NaN を返すように修正されました。これもC99の規定に合致します。
  4. テストケースの更新:
    • src/pkg/math/all_test.go 内の gammaSC 配列が更新され、新しい特殊ケースの挙動(特に負の整数に対する NaN)を反映するようにテスト期待値が変更されました。

isNegInt関数は、与えられた浮動小数点数 x が負の整数であるかどうかを判定します。これは、x < 0 であることを確認し、さらに Modf(x) 関数(浮動小数点数を整数部と小数部に分割する関数)を使用して小数部がゼロであるかを確認することで実現されます。

これらの変更により、Goのmath.Gamma関数は、C99標準に準拠した、より予測可能で正確な挙動を示すようになりました。

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

diff --git a/src/pkg/math/all_test.go b/src/pkg/math/all_test.go
index ed66a42fb0..8cbead1ab7 100644
--- a/src/pkg/math/all_test.go
+++ b/src/pkg/math/all_test.go
@@ -1128,11 +1128,11 @@ var vfgammaSC = []float64{\n 	NaN(),\n }\n var gammaSC = []float64{\n+\tNaN(),
+\tNaN(),
 \tInf(-1),\n \tInf(1),\n \tInf(1),\n-\tInf(1),\n-\tInf(1),\n \tNaN(),\n }\n \ndiff --git a/src/pkg/math/gamma.go b/src/pkg/math/gamma.go
index 7c6f421bad..8b053cb85f 100644
--- a/src/pkg/math/gamma.go
+++ b/src/pkg/math/gamma.go
@@ -113,16 +113,23 @@ func stirling(x float64) float64 {\n // Gamma(x) returns the Gamma function of x.\n //\n // Special cases are:\n-//\tGamma(±Inf) = ±Inf\n+//\tGamma(+Inf) = +Inf\n+//\tGamma(+0) = +Inf\n+//\tGamma(-0) = -Inf\n+//\tGamma(x) = NaN for integer x < 0\n+//\tGamma(-Inf) = NaN\n //\tGamma(NaN) = NaN\n-// Large values overflow to +Inf.\n-// Zero and negative integer arguments return ±Inf.\n func Gamma(x float64) float64 {\n \tconst Euler = 0.57721566490153286060651209008240243104215933593992 // A001620\n \t// special cases\n \tswitch {\n-\tcase IsInf(x, -1) || IsNaN(x):\n-\t\treturn x\n+\tcase isNegInt(x) || IsInf(x, -1) || IsNaN(x):\n+\t\treturn NaN()\n+\tcase x == 0:\n+\t\tif Signbit(x) {\n+\t\t\treturn Inf(-1)\n+\t\t}\n+\t\treturn Inf(1)\n \tcase x < -170.5674972726612 || x > 171.61447887182298:\n \t\treturn Inf(1)\n \t}\n@@ -185,3 +192,11 @@ small:\n \t}\n \treturn z / ((1 + Euler*x) * x)\n }\n+\n+func isNegInt(x float64) bool {\n+\tif x < 0 {\n+\t\t_, xf := Modf(x)\n+\t\treturn xf == 0\n+\t}\n+\treturn false\n+}\n```

## コアとなるコードの解説

### `src/pkg/math/all_test.go` の変更

`gammaSC` 配列は、`Gamma` 関数の特殊ケースに対する期待値を定義しています。変更前は、負の整数に対応する位置で `Inf(1)` が期待されていましたが、C99の規定に合わせて `NaN()` に変更されました。これにより、負の整数入力に対する `Gamma` 関数の戻り値が `NaN` であることをテストで検証できるようになります。

```go
var gammaSC = []float64{
	NaN(), // 負の整数に対する期待値が NaN に変更
	NaN(), // 負の整数に対する期待値が NaN に変更
	Inf(-1),
	Inf(1),
	Inf(1),
	NaN(),
}

src/pkg/math/gamma.go の変更

Gamma 関数の特殊ケース処理

Gamma 関数の冒頭にある switch ステートメントが大幅に修正されました。

func Gamma(x float64) float64 {
	const Euler = 0.57721566490153286060651209008240243104215933593992 // A001620
	// special cases
	switch {
	case isNegInt(x) || IsInf(x, -1) || IsNaN(x): // 負の整数、負の無限大、NaN の場合
		return NaN() // NaN を返す
	case x == 0: // ゼロの場合
		if Signbit(x) { // 符号ビットが立っている(-0)場合
			return Inf(-1) // -Inf を返す
		}
		return Inf(1) // +Inf を返す
	case x < -170.5674972726612 || x > 171.61447887182298: // オーバーフロー範囲
		return Inf(1) // +Inf を返す
	}
	// ... (通常のガンマ関数の計算ロジック)
}
  • case isNegInt(x) || IsInf(x, -1) || IsNaN(x)::
    • isNegInt(x): 新しく導入されたヘルパー関数で、x が負の整数であるかを判定します。
    • IsInf(x, -1): x が負の無限大 (-Inf) であるかを判定します。
    • IsNaN(x): xNaN であるかを判定します。
    • これらのいずれかに該当する場合、C99の規定に従い NaN() を返します。
  • case x == 0::
    • x がゼロの場合の特殊処理です。
    • Signbit(x): x の符号ビットをチェックします。これにより、+0-0 を区別できます。
    • Signbit(x)true の場合(つまり -0 の場合)、Inf(-1)-Inf)を返します。
    • それ以外の場合(つまり +0 の場合)、Inf(1)+Inf)を返します。
  • コメントの更新:
    • Gamma 関数のドキュメントコメントも更新され、新しい特殊ケースの挙動が明示的に記述されています。

isNegInt ヘルパー関数の追加

この関数は、与えられた float64 型の数値 x が負の整数であるかどうかを判定するために追加されました。

func isNegInt(x float64) bool {
	if x < 0 { // まず負の数であることを確認
		_, xf := Modf(x) // Modf は浮動小数点数を整数部と小数部に分割する
		return xf == 0   // 小数部がゼロであれば整数
	}
	return false // 負の数でなければ負の整数ではない
}
  • if x < 0: まず、数値が負であるかを確認します。正の数やゼロは負の整数ではないため、ここで早期に false を返します。
  • _, xf := Modf(x): Modf 関数は、x を整数部と小数部に分割します。ここでは小数部 (xf) のみを使用します。
  • return xf == 0: 小数部が 0 であれば、その数値は整数であると判断できます。負の数であることと小数部がゼロであることの両方が満たされれば、負の整数であると判定されます。

これらの変更により、math.Gamma 関数はC99標準に準拠し、より堅牢で予測可能な挙動を提供するようになりました。

関連リンク

参考にした情報源リンク