[インデックス 13135] ファイルの概要
このコミットは、Go言語の標準ライブラリ math/big
パッケージにおいて、Int
型(任意精度整数)に対するJSONのマーシャリング(Goのデータ構造からJSONへの変換)およびアンマーシャリング(JSONからGoのデータ構造への変換)のサポートを追加するものです。これにより、math/big.Int
型の値をJSONデータとして容易に扱うことができるようになります。
コミット
commit 13a59b8c6d092075c0ff9d6ec3b5d43b6ead7b39
Author: Robert Griesemer <gri@golang.org>
Date: Tue May 22 17:20:37 2012 -0700
math/big: implement JSON un/marshaling support for Ints
Also: simplified some existing tests.
No support for Rats for now because the precision-preserving
default notation (fractions of the form a/b) is not a valid
JSON value.
Fixes #3657.
R=golang-dev, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/6211079
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/13a59b8c6d092075c0ff9d6ec3b5d43b6ead7b39
元コミット内容
math/big
パッケージの Int
型にJSONのマーシャリングとアンマーシャリングのサポートを実装しました。
既存のテストも一部簡素化しました。
Rat
型(任意精度有理数)については、現時点ではサポートしていません。これは、精度を維持するデフォルトの表記(a/b形式の分数)が有効なJSON値ではないためです。
Issue #3657 を修正します。
変更の背景
この変更の背景には、Go言語の math/big
パッケージで提供される任意精度整数型 Int
を、Webサービスやデータ交換で広く利用されるJSON形式でシリアライズ・デシリアライズしたいというニーズがありました。Goの encoding/json
パッケージは、Goのデータ構造とJSONの間で変換を行うための標準的な方法を提供しますが、カスタム型がJSONとの間で適切に変換されるためには、json.Marshaler
および json.Unmarshaler
インターフェースを実装する必要があります。
コミットメッセージにある Fixes #3657
は、この機能追加が特定のバグ報告や機能要望に対応するものであることを示しています。Issue #3657 は、math/big.Int
型がJSONにマーシャリングできないという問題提起であったと推測されます。任意精度整数は、通常の int
や int64
では表現できない非常に大きな数値を扱う場合に不可欠であり、これらの数値をJSONとして送受信する機能は、金融アプリケーション、暗号通貨、科学計算など、高精度な数値計算を必要とする多くのGoアプリケーションにとって重要です。
また、Rat
型(任意精度有理数)についてはJSONサポートが見送られています。これは、Rat
型の標準的な文字列表現が "a/b" のような分数形式であり、これがJSONの有効な数値形式ではないためです。JSONは数値に対して厳格な形式(整数または浮動小数点数)を要求するため、分数形式は直接JSON数値として表現できません。このため、Rat
型のJSONサポートには、別の表現形式(例: 文字列としての "a/b"、または分子と分母を別々のフィールドとして持つオブジェクト)を検討する必要があり、このコミットでは Int
型に焦点を当てています。
前提知識の解説
1. Go言語の math/big
パッケージ
math/big
パッケージは、Go言語で任意精度の数値演算を行うための型と関数を提供します。これは、標準のGoの組み込み型(int
, int64
, float64
など)では表現できない、非常に大きな整数、有理数、または浮動小数点数を扱う必要がある場合に利用されます。
*big.Int
: 任意精度の整数型です。メモリが許す限り、どんなに大きな整数でも表現できます。*big.Rat
: 任意精度の有理数型です。分子と分母に*big.Int
を使用して、正確な分数値を表現します。*big.Float
: 任意精度の浮動小数点数型です。
これらの型は、通常の数値型とは異なり、値のコピーではなくポインタとして扱われることが一般的です。
2. JSON (JavaScript Object Notation)
JSONは、人間が読み書きしやすく、機械が解析・生成しやすいデータ交換フォーマットです。WebアプリケーションやAPIで広く利用されています。JSONのデータ型には、オブジェクト、配列、文字列、数値、真偽値(true/false)、nullがあります。
- 数値: JSONの数値は、整数または浮動小数点数として表現されます。指数表記も可能です。JSONの仕様では、数値の精度や範囲は特に定められていませんが、一般的にはIEEE 754倍精度浮動小数点数に準拠した実装が多いため、非常に大きな整数や高精度な小数を直接表現するには限界があります。
3. Go言語の encoding/json
パッケージ
encoding/json
パッケージは、Goのデータ構造とJSONの間で変換を行うための機能を提供します。
- マーシャリング (Marshaling): Goのデータ構造をJSONバイト列に変換するプロセスです。
json.Marshal()
関数を使用します。 - アンマーシャリング (Unmarshaling): JSONバイト列をGoのデータ構造に変換するプロセスです。
json.Unmarshal()
関数を使用します。
カスタム型がJSONとの間で適切に変換されるためには、以下のインターフェースを実装することが一般的です。
-
json.Marshaler
インターフェース:type Marshaler interface { MarshalJSON() ([]byte, error) }
このインターフェースを実装することで、型自身がJSONへの変換方法を定義できます。
MarshalJSON
メソッドは、JSON形式のバイトスライスとエラーを返します。 -
json.Unmarshaler
インターフェース:type Unmarshaler interface { UnmarshalJSON([]byte) error }
このインターフェースを実装することで、型自身がJSONからの変換方法を定義できます。
UnmarshalJSON
メソッドは、JSONバイトスライスを受け取り、その内容を自身のフィールドにデコードします。
math/big.Int
のような任意精度整数は、標準のJSON数値型では表現しきれない可能性があるため、通常はJSON文字列として表現されます。例えば、123456789012345678901234567890
のような大きな整数は、JSONでは "123456789012345678901234567890"
のように文字列として扱われるのが一般的です。このコミットもこのアプローチを採用しています。
4. Go言語の encoding/gob
パッケージ
encoding/gob
パッケージは、Goのデータ構造をGoプログラム間でエンコード/デコードするための形式を提供します。これはJSONとは異なり、Go固有のバイナリ形式であり、Goプログラム間の通信や永続化に適しています。このコミットでは、既存の gob
エンコーディングテストを簡素化し、そのテストケースをJSONエンコーディングテストでも再利用しています。
技術的詳細
このコミットの主要な変更点は、math/big.Int
型に MarshalJSON
と UnmarshalJSON
メソッドを追加し、encoding/json
パッケージとの互換性を持たせたことです。
-
MarshalJSON
メソッドの実装 (src/pkg/math/big/int.go
):func (x *Int) MarshalJSON() ([]byte, error)
として定義されています。- このメソッドは、
*big.Int
の値をJSONバイトスライスに変換します。 - 実装は非常にシンプルで、
x.String()
を呼び出してInt
の文字列表現を取得し、それを[]byte
に変換して返します。 - これにより、
*big.Int
の値はJSONでは"12345"
のような文字列として表現されます。これは、JSONの数値型が任意精度をサポートしないため、大きな整数を正確に表現するための一般的なアプローチです。
-
UnmarshalJSON
メソッドの実装 (src/pkg/math/big/int.go
):func (z *Int) UnmarshalJSON(x []byte) error
として定義されています。- このメソッドは、JSONバイトスライスを
*big.Int
の値に変換します。 - 入力された
[]byte
をstring(x)
で文字列に変換し、z.SetString(string(x), 0)
を呼び出してInt
の値を設定します。SetString
の第2引数0
は、基数を自動的に検出することを示します(例: "0x" プレフィックスがあれば16進数、そうでなければ10進数)。 SetString
が失敗した場合(例: 無効な数値文字列が入力された場合)、fmt.Errorf
を使用してエラーを返します。TODO(gri): get rid of the []byte/string conversions
というコメントがあり、これは将来的に[]byte
から直接Int
を設定する、より効率的な方法を検討する余地があることを示唆しています。
-
テストの追加と簡素化 (
src/pkg/math/big/int_test.go
,src/pkg/math/big/rat_test.go
):encoding/json
パッケージがインポートされました。- 既存の
gobEncodingTests
変数がencodingTests
にリネームされ、負の数を含むより広範なテストケースが追加されました。これにより、gob
とjson
の両方のエンコーディングテストで同じデータセットを再利用できるようになりました。 TestIntGobEncoding
およびTestRatGobEncoding
のテストロジックが簡素化されました。特に、負の数や分数(Rat
の場合)を生成するための複雑なループが削除され、encodingTests
に直接それらのケースが含まれるようになりました。TestIntJSONEncoding
という新しいテスト関数が追加されました。このテストは、encodingTests
の各文字列に対して*big.Int
を作成し、それをJSONにマーシャリングし、再度アンマーシャリングして、元の値と一致するかどうかを検証します。これにより、JSONのエンコード/デコード機能が正しく動作することが保証されます。
この変更により、math/big.Int
型はGoの encoding/json
パッケージの標準的なメカニズムを通じて、JSONデータとしてシームレスに扱えるようになりました。
コアとなるコードの変更箇所
src/pkg/math/big/int.go
@@ -894,3 +894,19 @@ func (z *Int) GobDecode(buf []byte) error {
z.abs = z.abs.setBytes(buf[1:])
return nil
}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (x *Int) MarshalJSON() ([]byte, error) {
+ // TODO(gri): get rid of the []byte/string conversions
+ return []byte(x.String()), nil
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (z *Int) UnmarshalJSON(x []byte) error {
+ // TODO(gri): get rid of the []byte/string conversions
+ _, ok := z.SetString(string(x), 0)
+ if !ok {
+ return fmt.Errorf("math/big: cannot unmarshal %s into a *big.Int", x)
+ }
+ return nil
+}
src/pkg/math/big/int_test.go
@@ -8,6 +8,7 @@ import (
"bytes"
"encoding/gob"
"encoding/hex"
+ "encoding/json" // 追加
"fmt"
"math/rand"
"testing"
@@ -1368,8 +1369,12 @@ func TestModInverse(t *testing.T) {
}
}
-// used by TestIntGobEncoding and TestRatGobEncoding
-var gobEncodingTests = []string{ // gobEncodingTests から encodingTests にリネーム
+var encodingTests = []string{
+ "-539345864568634858364538753846587364875430589374589", // 負の大きな数
+ "-678645873", // 負の数
+ "-100",
+ "-2",
+ "-1",
"0",
"1",
"2",
@@ -1383,26 +1388,37 @@ func TestIntGobEncoding(t *testing.T) {
var medium bytes.Buffer
enc := gob.NewEncoder(&medium)
dec := gob.NewDecoder(&medium)
- for i, test := range gobEncodingTests { // gobEncodingTests から encodingTests に変更
- for j := 0; j < 2; j++ { // 負の数を生成するループを削除
- medium.Reset() // empty buffer for each test case (in case of failures)
- stest := test
- if j != 0 {
- // negative numbers
- stest = "-" + test
- }
- var tx Int
- tx.SetString(stest, 10)
- if err := enc.Encode(&tx); err != nil {
- t.Errorf("#%d%c: encoding failed: %s", i, 'a'+j, err)
- }
- var rx Int
- if err := dec.Decode(&rx); err != nil {
- t.Errorf("#%d%c: decoding failed: %s", i, 'a'+j, err)
- }
- if rx.Cmp(&tx) != 0 {
- t.Errorf("#%d%c: transmission failed: got %s want %s", i, 'a'+j, &rx, &tx)
- }
+ for _, test := range encodingTests { // 簡素化されたループ
+ medium.Reset()
+ var tx Int
+ tx.SetString(test, 10)
+ if err := enc.Encode(&tx); err != nil {
+ t.Errorf("encoding of %s failed: %s", &tx, err)
+ }
+ var rx Int
+ if err := dec.Decode(&rx); err != nil {
+ t.Errorf("decoding of %s failed: %s", &tx, err)
+ }
+ if rx.Cmp(&tx) != 0 {
+ t.Errorf("transmission of %s failed: got %s want %s", &tx, &rx, &tx)
+ }
+ }
+}
+
+// TestIntJSONEncoding 新しいテスト関数
+func TestIntJSONEncoding(t *testing.T) {
+ for _, test := range encodingTests {
+ var tx Int
+ tx.SetString(test, 10)
+ b, err := json.Marshal(&tx)
+ if err != nil {
+ t.Errorf("marshaling of %s failed: %s", &tx, err)
+ }
+ var rx Int
+ if err := json.Unmarshal(b, &rx); err != nil {
+ t.Errorf("unmarshaling of %s failed: %s", &tx, err)
+ }
+ if rx.Cmp(&tx) != 0 {
+ t.Errorf("JSON encoding of %s failed: got %s want %s", &tx, &rx, &tx)
}
}
}
src/pkg/math/big/rat_test.go
@@ -387,30 +387,19 @@ func TestRatGobEncoding(t *testing.T) {
var medium bytes.Buffer
enc := gob.NewEncoder(&medium)
dec := gob.NewDecoder(&medium)
- for i, test := range gobEncodingTests { // gobEncodingTests から encodingTests に変更
- for j := 0; j < 4; j++ { // 負の数や分数を生成するループを削除
- medium.Reset() // empty buffer for each test case (in case of failures)
- stest := test
- if j&1 != 0 {
- // negative numbers
- stest = "-" + test
- }
- if j%2 != 0 {
- // fractions
- stest = stest + "." + test
- }
- var tx Rat
- tx.SetString(stest)
- if err := enc.Encode(&tx); err != nil {
- t.Errorf("#%d%c: encoding failed: %s", i, 'a'+j, err)
- }
- var rx Rat
- if err := dec.Decode(&rx); err != nil {
- t.Errorf("#%d%c: decoding failed: %s", i, 'a'+j, err)
- }
- if rx.Cmp(&tx) != 0 {
- t.Errorf("#%d%c: transmission failed: got %s want %s", i, 'a'+j, &rx, &tx)
- }
+ for _, test := range encodingTests { // 簡素化されたループ
+ medium.Reset()
+ var tx Rat
+ tx.SetString(test + ".14159265") // 固定の小数部を追加
+ if err := enc.Encode(&tx); err != nil {
+ t.Errorf("encoding of %s failed: %s", &tx, err)
+ }
+ var rx Rat
+ if err := dec.Decode(&rx); err != nil {
+ t.Errorf("decoding of %s failed: %s", &tx, err)
+ }
+ if rx.Cmp(&tx) != 0 {
+ t.Errorf("transmission of %s failed: got %s want %s", &tx, &rx, &tx)
}
}
}
コアとなるコードの解説
src/pkg/math/big/int.go
の変更
-
MarshalJSON
メソッド: このメソッドはjson.Marshaler
インターフェースを実装しており、*big.Int
型の値をJSON形式に変換する際の挙動を定義します。return []byte(x.String()), nil
という行がその核心です。これは、*big.Int
のString()
メソッド(数値の10進数文字列表現を返す)を呼び出し、その結果の文字列をバイトスライスに変換して返します。 これにより、例えばbig.NewInt(12345)
はJSONでは"12345"
という文字列として表現されます。これは、JSONの数値型が任意精度をサポートしないため、大きな整数を正確に表現するための標準的な方法です。TODO(gri): get rid of the []byte/string conversions
というコメントは、文字列への変換を介さずに直接バイトスライスを生成する、より効率的な方法がある可能性を示唆しています。 -
UnmarshalJSON
メソッド: このメソッドはjson.Unmarshaler
インターフェースを実装しており、JSONバイトスライスを*big.Int
型の値に変換する際の挙動を定義します。_, ok := z.SetString(string(x), 0)
という行がその核心です。これは、入力されたJSONバイトスライスx
をGoの文字列に変換し、その文字列を*big.Int
のSetString
メソッドに渡して値を設定します。SetString
の第2引数0
は、入力文字列の基数(10進数、16進数など)を自動的に検出するように指示します。if !ok { return fmt.Errorf(...) }
の部分は、SetString
が失敗した場合(例えば、JSONが有効な数値文字列を含んでいない場合)に適切なエラーを返すためのエラーハンドリングです。 こちらもTODO(gri): get rid of the []byte/string conversions
というコメントがあり、効率化の余地が示されています。
src/pkg/math/big/int_test.go
および src/pkg/math/big/rat_test.go
の変更
encoding/json
のインポート:int_test.go
にimport "encoding/json"
が追加され、JSON関連のテストが可能になりました。encodingTests
変数の導入: 以前はgobEncodingTests
という名前だった変数がencodingTests
にリネームされ、負の数や非常に大きな負の数を含む、より包括的なテストケースが追加されました。これにより、gob
とjson
の両方のエンコーディングテストで同じデータセットを共有できるようになり、テストの保守性が向上しました。- 既存テストの簡素化:
TestIntGobEncoding
とTestRatGobEncoding
の内部ロジックが簡素化されました。以前はテストケース内で負の数や分数(Rat
の場合)を動的に生成していましたが、encodingTests
にそれらのケースが直接含まれるようになったため、テストコードがより読みやすく、簡潔になりました。 TestIntJSONEncoding
の追加: この新しいテスト関数は、*big.Int
のJSONマーシャリングとアンマーシャリングが正しく機能するかを検証します。json.Marshal(&tx)
で*big.Int
をJSONバイトスライスに変換します。json.Unmarshal(b, &rx)
でそのJSONバイトスライスを新しい*big.Int
に変換します。rx.Cmp(&tx) != 0
で、元の値tx
とアンマーシャリングされた値rx
が等しいかどうかを比較します。Cmp
メソッドは、2つのInt
の比較結果を返します(0であれば等しい)。- エラーが発生した場合や値が一致しない場合に、詳細なエラーメッセージを出力します。
これらの変更により、math/big.Int
はGoの標準的なJSONエンコーディング/デコーディングメカニズムに完全に統合され、開発者は *big.Int
を他のGoのデータ構造と同様にJSONとして扱うことができるようになりました。
関連リンク
- Go issue #3657: https://code.google.com/p/go/issues/detail?id=3657 (古いGoogle Codeのリンクですが、コミットメッセージに記載されています)
- Gerrit Change-Id:
https://golang.org/cl/6211079
(GoのコードレビューシステムGerritの変更リスト)
参考にした情報源リンク
- Go言語
math/big
パッケージのドキュメント: https://pkg.go.dev/math/big - Go言語
encoding/json
パッケージのドキュメント: https://pkg.go.dev/encoding/json - JSON (JavaScript Object Notation) 公式サイト: https://www.json.org/json-ja.html
- Go言語の
json.Marshaler
とjson.Unmarshaler
インターフェースに関する解説記事 (一般的な情報):- Go by Example: JSON: https://gobyexample.com/json
- A Guide to JSON in Go: https://www.alexedwards.net/blog/a-go-guide-to-json
- Custom JSON Unmarshaling in Go: https://www.calhoun.io/custom-json-unmarshaling-in-go/
- Go言語の
encoding/gob
パッケージのドキュメント: https://pkg.go.dev/encoding/gob - IEEE 754 (浮動小数点数に関する標準): https://ja.wikipedia.org/wiki/IEEE_754 (JSONの数値表現の背景知識として)