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

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

このコミットは、Go言語のbignumパッケージにおける任意精度数値型(Natural, Integer, Rational)に対して、標準的な文字列変換機能(String()メソッド)と、fmtパッケージによるフォーマット機能(Format()メソッド)を実装したものです。これにより、bignumパッケージで扱われる数値が、Goの標準的な出力メカニズムとよりシームレスに連携できるようになり、デバッグや表示の利便性が向上しました。

コミット

commit 66c6b13b03215ea13d1ce57cb0f685cf7edd5cb2
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Nov 6 12:13:52 2008 -0800

    - implemented String() and Format functionality in Bignum
    - added a test
    
    R=r
    OCL=18687
    CL=18687

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

https://github.com/golang/go/commit/66c6b13b03215ea13d1ce57cb0f685cf7edd5cb2

元コミット内容

  • BignumにString()Format機能が実装されました。
  • テストが追加されました。

変更の背景

Go言語では、カスタム型を文字列として表現する際に、String()メソッドを実装することが慣習となっています。このメソッドを実装することで、fmtパッケージのPrint系関数(fmt.Println, fmt.Printfなど)がその型を自動的に文字列に変換して出力できるようになります。また、fmt.Formatterインターフェースを実装することで、より詳細なフォーマット制御(例えば、基数指定やゼロ埋めなど)が可能になります。

bignumパッケージは任意精度の数値を扱うため、これらの数値を人間が読める形式で出力する機能は非常に重要です。このコミット以前は、String(base uint)のような基数を指定するメソッドは存在しましたが、Goの標準的なStringerインターフェースやfmt.Formatterインターフェースに準拠していませんでした。

この変更の背景には、bignumパッケージがGo言語のエコシステムにより深く統合され、他の標準ライブラリやユーザーコードとの相互運用性を高めるという目的があります。これにより、bignumの数値がGoの組み込み型と同じように自然に扱えるようになり、開発者の利便性が向上します。

前提知識の解説

Go言語のfmtパッケージ

fmtパッケージは、Go言語におけるフォーマット済みI/O(入出力)を提供する標準ライブラリです。C言語のprintf/scanfに似た機能を提供し、様々な型の値を整形して出力したり、文字列から値を解析したりすることができます。

  • fmt.Print系関数: fmt.Print, fmt.Println, fmt.Printfなどがあり、引数を標準出力に出力します。
  • fmt.Sprintf関数: フォーマットされた文字列を生成し、それを返します。
  • フォーマット動詞: %d(10進数整数)、%s(文字列)、%v(デフォルトフォーマット)、%T(型名)、%b(2進数)、%o(8進数)、%x(16進数)など、様々なフォーマット動詞があります。

Stringerインターフェース

Go言語のStringerインターフェースは、以下のように定義されています。

type Stringer interface {
    String() string
}

任意の型がこのString()メソッドを実装すると、その型はStringerインターフェースを満たします。fmtパッケージのPrint系関数は、引数がStringerインターフェースを満たす場合、自動的にそのString()メソッドを呼び出して文字列表現を取得し、出力に利用します。これにより、カスタム型を人間が読める形式で簡単に表示できるようになります。

Formatterインターフェース

fmt.Formatterインターフェースは、Stringerインターフェースよりも詳細なフォーマット制御を可能にするためのインターフェースです。以下のように定義されています。

type Formatter interface {
    Format(f State, c rune)
}
  • f State: フォーマットの状態(幅、精度、フラグなど)を提供します。f.Write()を使って出力ストリームに書き込むことができます。
  • c rune: フォーマット動詞(例: 'd', 's', 'x'など)を表す文字です。

このインターフェースを実装することで、fmt.Printfのような関数でカスタムのフォーマット動詞やフラグを解釈し、それに応じた出力を行うことができます。

Goのbignumパッケージ

bignumパッケージは、Go言語で任意精度の整数(Natural, Integer)および有理数(Rational)を扱うためのライブラリです。通常のGoの組み込み整数型(int, int64など)では表現できない非常に大きな数や、浮動小数点数では精度が不足する計算を行う際に使用されます。このパッケージは、数値の加算、減算、乗算、除算、比較などの基本的な算術演算を提供します。

技術的詳細

このコミットの主要な技術的変更点は、bignumパッケージ内のNatural, Integer, Rational型に、Goの標準的なString()メソッドとfmt.Formatterインターフェースの実装を追加したことです。

  1. String(base uint)からToString(base uint)へのリネーム: 既存の基数を指定して文字列を返すメソッドString(base uint)は、Goの標準的なStringerインターフェースのString()メソッドと名前が衝突するため、ToString(base uint)にリネームされました。これにより、基数指定の機能は維持しつつ、標準インターフェースへの準拠が可能になりました。

  2. String()メソッドの追加: Natural, Integer, Rationalの各型に、引数なしのString() stringメソッドが追加されました。これらのメソッドは、内部的にToString(10)を呼び出すことで、デフォルトで10進数表現の文字列を返すように実装されています。これにより、これらの型はStringerインターフェースを満たし、fmt.Print系関数で自動的に10進数文字列として出力されるようになります。

  3. FmtBase(c int) uint関数の追加: このヘルパー関数は、fmt.FormatterインターフェースのFormatメソッド内で使用されます。フォーマット動詞c(例: 'b', 'o', 'x')を受け取り、それに対応する基数(2, 8, 16)を返します。デフォルトは10進数です。これにより、fmt.Printf%b(2進数)、%o(8進数)、%x(16進数)などのフォーマット動詞が指定された場合に、適切な基数で数値を文字列化できるようになります。

  4. Format(h Fmt.Formatter, c int)メソッドの追加: Natural, Integer, Rationalの各型に、fmt.Formatterインターフェースを満たすFormatメソッドが追加されました。このメソッドは、FmtBase関数を使ってフォーマット動詞から適切な基数を決定し、その基数でToStringメソッドを呼び出して文字列を取得します。その後、Fmt.fprintf(h, "%s", t)を使って、取得した文字列をフォーマッタの出力ストリームhに書き込みます。 コード中のコメント// BUG in 6gは、当時のGoコンパイラ(6g)における既知のバグを示唆しています。これは、コンパイラの特定のバージョンでこのコードが正しく動作しない可能性があったことを意味しますが、現在のGoバージョンでは修正されているはずです。

  5. テストの更新と追加: bignum_test.goでは、String(10)の呼び出しが新しいString()メソッドの呼び出しに更新されました。また、NatConv()関数に、fmt.sprintfを使って2進数フォーマット(%b)で数値を文字列化し、それが正しく解析できるかを検証する新しいテストケースが追加されました。

これらの変更により、bignumパッケージの数値は、Goの標準的な文字列変換およびフォーマット機能と完全に統合され、よりGoらしい(idiomatic Go)コードとして扱えるようになりました。

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

src/lib/bignum.go

--- a/src/lib/bignum.go
+++ b/src/lib/bignum.go
@@ -11,6 +11,7 @@ package Bignum
 // - Integer	signed integer numbers
 // - Rational	rational numbers
 
+import Fmt "fmt"
 
 // ----------------------------------------------------------------------------
 // Internal representation
@@ -675,7 +676,7 @@ func DivMod1(x *Natural, d Digit) (*Natural, Digit) {
 }
 
 
-func (x *Natural) String(base uint) string {
+func (x *Natural) ToString(base uint) string {
 	if len(x) == 0 {
 		return "0";
 	}
@@ -702,6 +703,27 @@ func (x *Natural) String(base uint) string {
 }
 
 
+func (x *Natural) String() string {
+	return x.ToString(10);
+}
+
+
+func FmtBase(c int) uint {
+	switch c {
+	case 'b': return 2;
+	case 'o': return 8;
+	case 'x': return 16;
+	}
+	return 10;
+}
+
+
+func (x *Natural) Format(h Fmt.Formatter, c int) {
+	t := x.ToString(FmtBase(c));  // BUG in 6g
+	Fmt.fprintf(h, "%s", t);
+}
+
+
 func HexValue(ch byte) uint {
 	d := uint(1 << LogH);
 	switch {
@@ -1092,7 +1114,7 @@ func (x *Integer) Cmp(y *Integer) int {
 }
 
 
-func (x *Integer) String(base uint) string {
+func (x *Integer) ToString(base uint) string {
 	if x.mant.IsZero() {
 		return "0";
 	}
@@ -1100,10 +1122,21 @@ func (x *Integer) String(base uint) string {
 	if x.sign {
 		s = "-";
 	}
-	return s + x.mant.String(base);
+	return s + x.mant.ToString(base);
 }
 
 	
+func (x *Integer) String() string {
+	return x.ToString(10);
+}
+
+
+func (x *Integer) Format(h Fmt.Formatter, c int) {
+	t := x.ToString(FmtBase(c));  // BUG in 6g
+	Fmt.fprintf(h, "%s", t);
+}
+
+
 // Determines base (octal, decimal, hexadecimal) if base == 0.
 // Returns the number and base.
 export func IntFromString(s string, base uint, slen *int) (*Integer, uint) {
@@ -1215,15 +1248,26 @@ func (x *Rational) Cmp(y *Rational) int {
 }
 
 
-func (x *Rational) String(base uint) string {
-	s := x.a.String(base);
+func (x *Rational) ToString(base uint) string {
+	s := x.a.ToString(base);
 	if !x.IsInt() {
-		s += "/" + x.b.String(base);
+		s += "/" + x.b.ToString(base);
 	}
 	return s;
 }
 
 
+func (x *Rational) String() string {
+	return x.ToString(10);
+}
+
+
+func (x *Rational) Format(h Fmt.Formatter, c int) {
+	t := x.ToString(FmtBase(c));  // BUG in 6g
+	Fmt.fprintf(h, "%s", t);
+}
+
+
 // Determines base (octal, decimal, hexadecimal) if base == 0.
 // Returns the number and base of the nominator.\n export func RatFromString(s string, base uint, slen *int) (*Rational, uint) {

test/bignum_test.go

--- a/test/bignum_test.go
+++ b/test/bignum_test.go
@@ -7,6 +7,7 @@
 package main
 
 import Big "bignum"
+import Fmt "fmt"
 
 const (
 	sa = "991";
@@ -71,8 +72,8 @@ func TEST(n uint, b bool) {\n func NAT_EQ(n uint, x, y *Big.Natural) {\n 	if x.Cmp(y) != 0 {\n 		println("TEST failed:", test_msg, "(", n, ")");\n-		println("x =", x.String(10));
-		println("y =", y.String(10));
+		println("x =", x.String());
+		println("y =", y.String());
 		panic();
 	}\n }\
@@ -81,8 +82,8 @@ func NAT_EQ(n uint, x, y *Big.Natural) {\n func INT_EQ(n uint, x, y *Big.Integer) {\n 	if x.Cmp(y) != 0 {\n 		println("TEST failed:", test_msg, "(", n, ")");\n-		println("x =", x.String(10));
-		println("y =", y.String(10));
+		println("x =", x.String());
+		println("y =", y.String());
 		panic();
 	}\n }\
@@ -91,8 +92,8 @@ func INT_EQ(n uint, x, y *Big.Integer) {\n func RAT_EQ(n uint, x, y *Big.Rational) {\n 	if x.Cmp(y) != 0 {\n 		println("TEST failed:", test_msg, "(", n, ")");\n-		println("x =", x.String(10));
-		println("y =", y.String(10));
+		println("x =", x.String());
+		println("y =", y.String());
 		panic();
 	}\n }\
@@ -103,9 +104,9 @@ func NatConv() {\n 	NAT_EQ(0, a, Big.Nat(991));\n 	NAT_EQ(1, b, Big.Fact(20));\n 	NAT_EQ(2, c, Big.Fact(100));\n-	TEST(3, a.String(10) == sa);\n-	TEST(4, b.String(10) == sb);\n-	TEST(5, c.String(10) == sc);\n+	TEST(3, a.String() == sa);\n+	TEST(4, b.String() == sb);\n+	TEST(5, c.String() == sc);\n 
 	test_msg = "NatConvB";\n 	var slen int;\n@@ -119,8 +120,13 @@ func NatConv() {\n 	test_msg = "NatConvC";\n 	t := c.Mul(c);\n 	for base := uint(2); base <= 16; base++ {\n-\t\tNAT_EQ(base, NatFromString(t.String(base), base, nil), t);\n+\t\tNAT_EQ(base, NatFromString(t.ToString(base), base, nil), t);\n 	}\n+\n+\ttest_msg = "NatConvD";\n+\tx := Big.Nat(100);\n+\ty, b := Big.NatFromString(Fmt.sprintf("%b", x), 2, nil);\n+\tNAT_EQ(0, y, x);\n }\
 
 
@@ -162,8 +168,8 @@ func Add(x, y *Big.Natural) *Big.Natural {\n 	z2 := y.Add(x);\n 	if z1.Cmp(z2) != 0 {\n 		println("addition not symmetric");\n-		println("x =", x.String(10));
-		println("y =", y.String(10));
+		println("x =", x.String());
+		println("y =", y.String());
 		panic();
 	}\n 	return z1;\n@@ -197,20 +203,20 @@ func Mul(x, y *Big.Natural) *Big.Natural {\n 	z2 := y.Mul(x);\n 	if z1.Cmp(z2) != 0 {\n 		println("multiplication not symmetric");\n-		println("x =", x.String(10));
-		println("y =", y.String(10));
+		println("x =", x.String());
+		println("y =", y.String());
 		panic();
 	}\n 	if !x.IsZero() && z1.Div(x).Cmp(y) != 0 {\n 		println("multiplication/division not inverse (A)");\n-		println("x =", x.String(10));
-		println("y =", y.String(10));
+		println("x =", x.String());
+		println("y =", y.String());
 		panic();
 	}\n 	if !y.IsZero() && z1.Div(y).Cmp(x) != 0 {\n 		println("multiplication/division not inverse (B)");\n-		println("x =", x.String(10));
-		println("y =", y.String(10));
+		println("x =", x.String());
+		println("y =", y.String());
 		panic();
 	}\n 	return z1;\

コアとなるコードの解説

src/lib/bignum.go

  • import Fmt "fmt": fmtパッケージをFmtというエイリアスでインポートしています。これは、Goの初期のコードベースでよく見られた慣習です。
  • func (x *Natural) ToString(base uint) string: Natural型(符号なし任意精度整数)の文字列変換メソッドが、StringからToStringにリネームされました。これは、Goの標準的なStringerインターフェースのString()メソッドとの名前の衝突を避けるためです。このメソッドは引き続き、指定された基数で数値を文字列に変換する機能を提供します。IntegerRational型についても同様のリネームが行われています。
  • func (x *Natural) String() string: Natural型に、GoのStringerインターフェースを満たすString()メソッドが追加されました。このメソッドは、内部的にx.ToString(10)を呼び出し、常に10進数表現の文字列を返します。これにより、fmt.Println(myNatural)のように呼び出すだけで、自然に10進数で出力されるようになります。IntegerRational型にも同様のメソッドが追加されています。
  • func FmtBase(c int) uint: この関数は、fmt.FormatterインターフェースのFormatメソッド内で使用されるヘルパー関数です。cはフォーマット動詞(例: 'b', 'o', 'x')を表す文字です。switch文を使って、これらの動詞に対応する基数(2, 8, 16)を返します。どの動詞にも一致しない場合は、デフォルトの10を返します。
  • func (x *Natural) Format(h Fmt.Formatter, c int): Natural型に、fmt.Formatterインターフェースを満たすFormatメソッドが追加されました。
    • t := x.ToString(FmtBase(c))FmtBase関数を使って、与えられたフォーマット動詞cから適切な基数を取得し、その基数でToStringメソッドを呼び出して数値を文字列に変換します。
    • Fmt.fprintf(h, "%s", t)fmt.Fprintf関数(ここではFmt.fprintfとしてエイリアスされている)を使って、変換された文字列tを、フォーマッタの出力ストリームhに書き込みます。%sは文字列として出力するためのフォーマット動詞です。
    • // BUG in 6g:このコメントは、当時のGoコンパイラ(6g)における既知のバグを示しています。これは、特定のコンパイラバージョンでこのコードが期待通りに動作しない可能性があったことを示唆していますが、現代のGoコンパイラでは問題ないはずです。IntegerRational型にも同様のFormatメソッドが追加されています。

test/bignum_test.go

  • import Fmt "fmt": テストファイルでもfmtパッケージをFmtとしてインポートしています。
  • println("x =", x.String());: NAT_EQ, INT_EQ, RAT_EQなどのテストヘルパー関数内で、デバッグ出力のために使用されていたx.String(10)の呼び出しが、新しく追加されたx.String()メソッドの呼び出しに置き換えられています。これにより、テストコードもGoの標準的なStringerインターフェースの利用に準拠しています。
  • TEST(3, a.String() == sa);: NatConv()関数内のテストケースで、a.String(10)a.String()に置き換えられています。これは、Stringerインターフェースの実装が正しく機能していることを確認するためです。
  • NAT_EQ(base, NatFromString(t.ToString(base), base, nil), t);: NatConv()関数内のループで、t.String(base)t.ToString(base)に置き換えられています。これは、基数指定の文字列変換メソッドのリネームに対応したものです。
  • 新しいテストケースの追加: NatConv()関数に以下の新しいテストケースが追加されました。
    	test_msg = "NatConvD";
    	x := Big.Nat(100);
    	y, b := Big.NatFromString(Fmt.sprintf("%b", x), 2, nil);
    	NAT_EQ(0, y, x);
    
    このテストは、Big.Nat(100)というNatural型の数値を作成し、Fmt.sprintf("%b", x)を使ってその2進数表現の文字列を取得します。%bfmt.FormatterインターフェースのFormatメソッドが正しく2進数にフォーマットできるかをテストします。その後、Big.NatFromStringを使ってその2進数文字列を再度Natural型に変換し、元の数値xと等しいことをNAT_EQで検証しています。これにより、FormatメソッドとNatFromStringの連携が正しく機能していることを確認しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(fmtパッケージ、StringerFormatterインターフェースに関する記述)
  • Go言語のbignumパッケージのソースコード(コミット前後の比較)
  • Go言語におけるカスタム型の文字列変換に関する一般的な慣習とベストプラクティスに関する情報