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

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

このコミットは、Go言語の標準ライブラリにおけるmathパッケージの再構築とクリーンアップを目的としています。特に、数学定数の定義を一元化し、より高精度な値を使用するように変更することで、パッケージ全体の数値計算の正確性と保守性を向上させています。また、テストコード内のヘルパー関数の命名規則を調整し、内部的な利用に特化させることで、コードベースの一貫性を高めています。

コミット

commit 2c8d9a5619e21bf69eb08b2e3775d68c59c80e90
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jan 15 19:11:32 2009 -0800

    redo and clean up math.

    R=r
    DELTA=243  (60 added, 72 deleted, 111 changed)
    OCL=22909
    CL=22912

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

https://github.com/golang/go/commit/2c8d9a5619e21bf69eb08b2e3775d68c59c80e90

元コミット内容

redo and clean up math. (数学ライブラリの再構築とクリーンアップ)

変更の背景

このコミットが行われた2009年1月は、Go言語がまだ初期開発段階にあった時期です。当時のmathパッケージは、各関数がそれぞれ独自の定数を定義しているなど、一貫性に欠ける部分がありました。このような状況では、以下のような問題が発生する可能性があります。

  1. 数値の不整合: 同じ数学定数(例: π)であっても、異なるファイルで異なる精度や表現で定義されていると、計算結果に微妙な差異が生じ、予期せぬバグや非一貫性につながる可能性があります。
  2. 保守性の低下: 定数を変更する必要が生じた場合、複数のファイルを横断して修正する必要があり、手間がかかるだけでなく、修正漏れのリスクも高まります。
  3. コードの重複: 各ファイルで同じ定数を定義することは、コードの重複を招き、コードベースの肥大化や可読性の低下につながります。
  4. 精度の向上: 当時の定数定義が必ずしも最高精度ではなかった可能性があり、より高精度な計算を可能にするための更新が必要でした。

これらの問題を解決し、mathパッケージの品質と信頼性を向上させるために、定数定義の一元化とクリーンアップが決定されました。

前提知識の解説

浮動小数点数と精度

コンピュータにおける数値計算、特に実数の扱いは、浮動小数点数(float64など)によって行われます。浮動小数点数は、限られたビット数で実数を近似するため、常に誤差を伴います。この誤差は、計算の連鎖によって増幅される可能性があり、特に科学技術計算や金融計算など、高い精度が求められる分野では、この誤差を最小限に抑えることが重要です。

数学定数(π、e、√2など)は、無限に続く小数部を持つ無理数であることが多いため、コンピュータで扱う際にはどこかで打ち切る必要があります。この打ち切り精度が、計算結果の正確性に直接影響します。より多くの桁数で定数を定義することで、計算の初期段階での誤差を減らし、最終的な結果の精度を向上させることができます。

Go言語のパッケージとエクスポート

Go言語では、コードはパッケージ(package)にまとめられます。パッケージ内の識別子(変数、関数、定数など)は、その名前の最初の文字が大文字である場合にのみ、パッケージ外から参照可能(エクスポート)になります。小文字で始まる識別子は、そのパッケージ内でのみ利用可能なプライベートな要素となります。

このコミットでは、テストヘルパー関数(Tolerance, Close, VeryClose)が小文字のtolerance, close, verycloseにリネームされています。これは、これらの関数がテストパッケージの内部でのみ使用されるべきであり、外部に公開する必要がないことを示しています。これにより、APIの意図が明確になり、不必要な依存関係を防ぐことができます。

Makefile

Makefileは、ソフトウェアのビルドプロセスを自動化するためのツールであるmakeが使用する設定ファイルです。このファイルには、ソースコードのコンパイル、ライブラリのリンク、テストの実行など、プロジェクトをビルドするために必要なコマンドと依存関係が記述されています。

このコミットでは、src/lib/math/Makefileが変更されています。これは、数学ライブラリのビルドプロセスにおいて、各数学関数に対応するオブジェクトファイル(.o)の依存関係が変更されたことを示しています。具体的には、定数定義が一元化されたことで、一部の関数が以前は個別に持っていた定数ファイルへの依存が解消され、ビルドの順序やリンク対象が調整された可能性があります。

数値計算ライブラリの設計

数値計算ライブラリは、正確性、効率性、堅牢性が求められます。

  • 正確性: 可能な限り高い精度で計算結果を提供すること。これは、適切なアルゴリズムの選択、浮動小数点数の特性の理解、そして高精度な定数の使用によって達成されます。
  • 効率性: 計算が高速であること。これは、アルゴリズムの最適化や、コンパイラによる最適化を考慮したコード記述によって実現されます。
  • 堅牢性: 無効な入力(例: log(-1))や特殊なケース(例: NaNInf)に対して適切に振る舞うこと。

このコミットは、特に正確性と保守性の向上に焦点を当てています。

技術的詳細

このコミットの主要な変更点は以下の通りです。

  1. 数学定数の一元化と高精度化 (src/lib/math/const.go):

    • src/lib/math/const.goファイルが新設され、Go言語のmathパッケージで使用される主要な数学定数(E, Pi, Phi, Sqrt2, SqrtE, SqrtPi, SqrtPhi, Ln2, Log2E, Ln10, Log10E)が定義されました。
    • これらの定数は、以前は各関数ファイル内で個別に定義されていたものよりも、はるかに高い精度(多くの小数点以下の桁数)で定義されています。これは、Axxxxxxのような信頼できる情報源から取得された高精度な値に基づいています。
    • これにより、パッケージ全体で一貫した高精度な定数が利用可能になり、数値計算の正確性が向上します。
  2. 各関数ファイルからの定数定義の削除と参照への変更:

    • src/lib/math/asin.go, src/lib/math/atan.go, src/lib/math/atan2.go, src/lib/math/exp.go, src/lib/math/log.go, src/lib/math/sin.go, src/lib/math/sinh.go, src/lib/math/tan.goなどのファイルから、ローカルで定義されていた数学定数(例: pio2, ap4, Ln2, spiu2など)が削除されました。
    • これらの定数は、新しく一元化されたmath.Pi, math.Ln2, math.Sqrt2などのグローバル定数に置き換えられました。
    • これにより、コードの重複が解消され、定数の管理が容易になり、将来的な精度向上や変更が単一の場所で行えるようになりました。
  3. テストヘルパー関数のリネームと内部化 (src/lib/math/all_test.go):

    • テストファイルsrc/lib/math/all_test.go内で定義されていたTolerance, Close, VeryCloseというヘルパー関数が、それぞれtolerance, close, verycloseにリネームされました。
    • Go言語の命名規則では、小文字で始まる識別子はパッケージ内部でのみ利用可能(非エクスポート)となります。この変更により、これらの関数がテストパッケージの内部ヘルパーとしてのみ機能し、外部から誤って利用されることを防ぎます。
    • また、math.Logのテストにおいて、ローカルで定義されていたLn10定数がmath.Ln10src/lib/math/const.goで定義されたもの)に置き換えられました。
  4. pow10.goの配列名変更:

    • src/lib/math/pow10.goでは、10の累乗を格納する内部配列の名前がtabからpow10tabに変更され、より意図が明確になりました。また、配列のサイズ参照にlen(pow10tab)を使用するように変更され、より堅牢なコードになりました。
  5. Makefileの更新:

    • 定数定義の一元化に伴い、Makefile内のオブジェクトファイル(.o)の依存関係が調整されました。これにより、ビルドシステムが新しいファイル構造と依存関係を正しく認識し、コンパイルとリンクが適切に行われるようになります。

これらの変更は、Go言語のmathパッケージが、より堅牢で、正確で、保守しやすいものになるための重要なステップでした。

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

src/lib/math/const.go (新規追加・変更)

--- /dev/null
+++ b/src/lib/math/const.go
@@ -0,0 +1,20 @@
+package math
+
+export const (
+	// Mathematical constants.
+	// Reference: http://www.research.att.com/~njas/sequences/Axxxxxx
+
+	E	= 2.71828182845904523536028747135266249775724709369995957496696763;  // A001113
+	Pi	= 3.14159265358979323846264338327950288419716939937510582097494459;  // A000796
+	Phi	= 1.61803398874989484820458683436563811772030917980576286213544862;  // A001622
+
+	Sqrt2	= 1.41421356237309504880168872420969807856967187537694807317667974;  // A002193
+	SqrtE	= 1.64872127070012814684865078781416357165377610071014801157507931;  // A019774
+	SqrtPi	= 1.77245385090551602729816748334114518279754945612238712821380779;  // A002161
+	SqrtPhi	= 1.27201964951406896425242246173749149171560804184009624861664038;  // A139339
+
+	Ln2	= 0.693147180559945309417232121458176568075500134360255254120680009; // A002162
+	Log2E	= 1/Ln2;
+	Ln10	= 2.30258509299404568401799145468436420760110148862877297603332790;  // A002392
+	Log10E	= 1/Ln10;
+)

src/lib/math/asin.go (定数参照の変更例)

--- a/src/lib/math/asin.go
+++ b/src/lib/math/asin.go
@@ -13,11 +13,6 @@ import "math"
  * Arctan is called after appropriate range reduction.
  */
 
-const
-(
-	pio2 = .15707963267948966192313216e1
-)
-
 export func Asin(arg float64) float64 {
 	var temp, x float64;
 	var sign bool;
@@ -34,7 +29,7 @@ export func Asin(arg float64) float64 {
 
 	temp = Sqrt(1 - x*x);
 	if x > 0.7 {
-		temp = pio2 - Atan(temp/x);
+		temp = Pi/2 - Atan(temp/x);
 	} else {
 		temp = Atan(x/temp);
 	}
@@ -49,5 +44,5 @@ export func Acos(arg float64) float64 {
 	if arg > 1 || arg < -1 {
 		return sys.NaN();
 	}
-	return pio2 - Asin(arg);
+	return Pi/2 - Asin(arg);
 }

src/lib/math/all_test.go (テストヘルパー関数のリネームと定数参照の変更)

--- a/src/lib/math/all_test.go
+++ b/src/lib/math/all_test.go
@@ -154,27 +154,26 @@ var tanh = []float64 {
 	 -9.9999994291374019e-01,\n}\n \n-func Tolerance(a,b,e float64) bool {\n+func tolerance(a,b,e float64) bool {\n 	d := a-b;\n 	if d < 0 {\n 		d = -d;\n 	}\n 	if a == 0 {\n 		return d < e;\n 	}\n 	return d/a < e;\n}\n-func Close(a,b float64) bool {\n-	return Tolerance(a, b, 1e-14);\n+func close(a,b float64) bool {\n+	return tolerance(a, b, 1e-14);\n}\n-func VeryClose(a,b float64) bool {\n-	return Tolerance(a, b, 4e-16);\n+func veryclose(a,b float64) bool {\n+	return tolerance(a, b, 4e-16);\n}\n \n export func TestAsin(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n-		if f := math.Asin(vf[i]/10); !VeryClose(asin[i], f) {\n+		if f := math.Asin(vf[i]/10); !veryclose(asin[i], f) {\n 			t.Errorf("math.Asin(%g) = %g, want %g\\n", vf[i]/10, f, asin[i]);\n 		}\n 	}\n@@ -185,7 +184,7 @@ export func TestAsin(t *testing.T) {\n \n export func TestAtan(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n-		if f := math.Atan(vf[i]); !VeryClose(atan[i], f) {\n+		if f := math.Atan(vf[i]); !veryclose(atan[i], f) {\n 			t.Errorf("math.Atan(%g) = %g, want %g\\n", vf[i], f, atan[i]);\n 		}\n 	}\n@@ -193,7 +192,7 @@ export func TestAtan(t *testing.T) {\n \n export func TestExp(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n-		if f := math.Exp(vf[i]); !VeryClose(exp[i], f) {\n+		if f := math.Exp(vf[i]); !veryclose(exp[i], f) {\n 			t.Errorf("math.Exp(%g) = %g, want %g\\n", vf[i], f, exp[i]);\n 		}\n 	}\n@@ -214,15 +213,14 @@ export func TestLog(t *testing.T) {\n 			t.Errorf("math.Log(%g) = %g, want %g\\n", a, f, log[i]);\n 		}\n 	}\n-	const Ln10 = 2.30258509299404568401799145468436421;\n-	if f := math.Log(10); f != Ln10 {\n-		t.Errorf("math.Log(%g) = %g, want %g\\n", 10, f, Ln10);\n+	if f := math.Log(10); f != math.Ln10 {\n+		t.Errorf("math.Log(%g) = %g, want %g\\n", 10, f, math.Ln10);\n 	}\n}\n \n export func TestPow(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n-		if f := math.Pow(10, vf[i]); !Close(pow[i], f) {\n+		if f := math.Pow(10, vf[i]); !close(pow[i], f) {\n 			t.Errorf("math.Pow(10, %.17g) = %.17g, want %.17g\\n", vf[i], f, pow[i]);\n 		}\n 	}\n@@ -230,7 +228,7 @@ export func TestPow(t *testing.T) {\n \n export func TestSin(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n-		if f := math.Sin(vf[i]); !Close(sin[i], f) {\n+		if f := math.Sin(vf[i]); !close(sin[i], f) {\n 			t.Errorf("math.Sin(%g) = %g, want %g\\n", vf[i], f, sin[i]);\n 		}\n 	}\n@@ -238,7 +236,7 @@ export func TestSin(t *testing.T) {\n \n export func TestSinh(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n-		if f := math.Sinh(vf[i]); !VeryClose(sinh[i], f) {\n+		if f := math.Sinh(vf[i]); !veryclose(sinh[i], f) {\n 			t.Errorf("math.Sinh(%g) = %g, want %g\\n\", vf[i], f, sinh[i]);\n 		}\n 	}\n@@ -247,7 +245,7 @@ export func TestSinh(t *testing.T) {\n export func TestSqrt(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n 		a := math.Fabs(vf[i]);\n-		if f := math.Sqrt(a); !VeryClose(sqrt[i], f) {\n+		if f := math.Sqrt(a); !veryclose(sqrt[i], f) {\n 			t.Errorf("math.Sqrt(%g) = %g, want %g\\n", a, f, floor[i]);\n 		}\n 	}\n@@ -255,7 +253,7 @@ export func TestSqrt(t *testing.T) {\n \n export func TestTan(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n-		if f := math.Tan(vf[i]); !Close(tan[i], f) {\n+		if f := math.Tan(vf[i]); !close(tan[i], f) {\n 			t.Errorf("math.Tan(%g) = %g, want %g\\n", vf[i], f, tan[i]);\n 		}\n 	}\n@@ -263,7 +261,7 @@ export func TestTan(t *testing.T) {\n \n export func TestTanh(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n-		if f := math.Tanh(vf[i]); !VeryClose(tanh[i], f) {\n+		if f := math.Tanh(vf[i]); !veryclose(tanh[i], f) {\n 			t.Errorf("math.Tanh(%g) = %g, want %g\\n", vf[i], f, tanh[i]);\n 		}\n 	}\n@@ -272,7 +270,7 @@ export func TestTanh(t *testing.T) {\n export func TestHypot(t *testing.T) {\n 	for i := 0; i < len(vf); i++ {\n 		a := math.Fabs(tanh[i]*math.Sqrt(2));\n-		if f := math.Hypot(tanh[i], tanh[i]); !VeryClose(a, f) {\n+		if f := math.Hypot(tanh[i], tanh[i]); !veryclose(a, f) {\n 			t.Errorf("math.Hypot(%g, %g) = %g, want %g\\n", tanh[i], tanh[i], f, a);\n 		}\n 	}\n```

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

### `src/lib/math/const.go`

このファイルは、Go言語の`math`パッケージにおける数学定数の「真の源」となります。以前は各関数ファイルに散らばっていた定数定義がここに集約され、かつその精度が大幅に向上しました。例えば、`Pi`は以前の`pio2`(π/2)や`apio4`(π/4)といった部分的な定数ではなく、直接πの値を高精度で定義しています。これにより、`math`パッケージ全体で一貫した高精度な定数を利用できるようになり、数値計算の信頼性が向上します。コメントにある`Axxxxxx`は、整数列のオンライン百科事典(OEIS)のIDを示しており、これらの定数が数学的に検証された高精度な値に基づいていることを裏付けています。

### `src/lib/math/asin.go`などの関数ファイル

これらのファイルでは、以前ローカルで定義されていた定数(例: `pio2`)が削除され、新しく`src/lib/math/const.go`で定義されたグローバルな`math.Pi`などの定数に置き換えられています。例えば、`pio2 - Atan(temp/x)`は`Pi/2 - Atan(temp/x)`に変更されています。これは、定数定義の一元化というコミットの主要な目的を達成するものであり、コードの重複を排除し、保守性を高めます。もし将来的に定数の精度をさらに上げる必要が生じた場合でも、`const.go`の1箇所を修正するだけで、パッケージ全体にその変更が反映されるようになります。

### `src/lib/math/all_test.go`

テストヘルパー関数である`Tolerance`, `Close`, `VeryClose`が、それぞれ`tolerance`, `close`, `veryclose`と小文字で始まる名前に変更されました。これはGo言語の慣習に従ったもので、これらの関数がパッケージ内部でのみ使用されることを明確に示しています。これにより、テストコードの意図がより明確になり、外部からこれらの内部ヘルパー関数に誤って依存するような事態を防ぎます。また、`math.Log`のテストで`Ln10`が`math.Ln10`に置き換えられたことも、定数の一元化という方針に沿った変更であり、テストの正確性と一貫性を保証します。

これらの変更は、Go言語の`math`パッケージが、初期段階から堅牢で、正確で、保守しやすい基盤を築くための重要な一歩でした。

## 関連リンク

*   Go言語 `math` パッケージのドキュメント: [https://pkg.go.dev/math](https://pkg.go.dev/math) (現在のバージョン)
*   Go言語の初期コミット履歴 (GitHub): [https://github.com/golang/go/commits/master](https://github.com/golang/go/commits/master)

## 参考にした情報源リンク

*   Online Encyclopedia of Integer Sequences (OEIS): [https://oeis.org/](https://oeis.org/) (数学定数の参照元としてコミット内で言及)
*   Hart, J. F., & Cheney, E. W. (1968). *Computer Approximations*. John Wiley & Sons. (コミット内のコメントで係数の参照元として言及されている可能性のある書籍)
*   IEEE 754 浮動小数点数標準: 浮動小数点数の表現と計算に関する国際標準。Go言語の`float64`はこれに準拠しています。