[インデックス 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
インターフェースの実装を追加したことです。
-
String(base uint)
からToString(base uint)
へのリネーム: 既存の基数を指定して文字列を返すメソッドString(base uint)
は、Goの標準的なStringer
インターフェースのString()
メソッドと名前が衝突するため、ToString(base uint)
にリネームされました。これにより、基数指定の機能は維持しつつ、標準インターフェースへの準拠が可能になりました。 -
String()
メソッドの追加:Natural
,Integer
,Rational
の各型に、引数なしのString() string
メソッドが追加されました。これらのメソッドは、内部的にToString(10)
を呼び出すことで、デフォルトで10進数表現の文字列を返すように実装されています。これにより、これらの型はStringer
インターフェースを満たし、fmt.Print
系関数で自動的に10進数文字列として出力されるようになります。 -
FmtBase(c int) uint
関数の追加: このヘルパー関数は、fmt.Formatter
インターフェースのFormat
メソッド内で使用されます。フォーマット動詞c
(例:'b'
,'o'
,'x'
)を受け取り、それに対応する基数(2, 8, 16)を返します。デフォルトは10進数です。これにより、fmt.Printf
で%b
(2進数)、%o
(8進数)、%x
(16進数)などのフォーマット動詞が指定された場合に、適切な基数で数値を文字列化できるようになります。 -
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バージョンでは修正されているはずです。 -
テストの更新と追加:
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()
メソッドとの名前の衝突を避けるためです。このメソッドは引き続き、指定された基数で数値を文字列に変換する機能を提供します。Integer
とRational
型についても同様のリネームが行われています。func (x *Natural) String() string
:Natural
型に、GoのStringer
インターフェースを満たすString()
メソッドが追加されました。このメソッドは、内部的にx.ToString(10)
を呼び出し、常に10進数表現の文字列を返します。これにより、fmt.Println(myNatural)
のように呼び出すだけで、自然に10進数で出力されるようになります。Integer
とRational
型にも同様のメソッドが追加されています。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コンパイラでは問題ないはずです。Integer
とRational
型にも同様の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進数表現の文字列を取得します。%b
はfmt.Formatter
インターフェースのFormat
メソッドが正しく2進数にフォーマットできるかをテストします。その後、Big.NatFromString
を使ってその2進数文字列を再度Natural
型に変換し、元の数値x
と等しいことをNAT_EQ
で検証しています。これにより、Format
メソッドとNatFromString
の連携が正しく機能していることを確認しています。
関連リンク
参考にした情報源リンク
- Go言語の公式ドキュメント(
fmt
パッケージ、Stringer
、Formatter
インターフェースに関する記述) - Go言語の
bignum
パッケージのソースコード(コミット前後の比較) - Go言語におけるカスタム型の文字列変換に関する一般的な慣習とベストプラクティスに関する情報