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

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

このコミットは、Go言語のmath/bigパッケージに、任意精度有理数型であるRat(Rational Number)のための汎用エンコーディングインターフェース(encoding.TextMarshalerおよびencoding.TextUnmarshaler)のサポートを追加するものです。これにより、big.Rat型の値をテキスト形式で簡単にシリアライズおよびデシリアライズできるようになり、特にJSONやXMLなどのデータフォーマットを介したデータ転送が容易になります。

コミット

commit eea28f6701ac888d5613470a88d09b634efc1d75
Author: Michael T. Jones <mtj@google.com>
Date:   Thu Feb 13 08:42:19 2014 -0800

    math/big: add support for general encoding interfaces
    TextMarshaller and TextUnmarshaller to ease transport of
    unlimited precision rational numbers.
    
    Fixes #7287.
    
    Consists of encode and decode functions and two test
    functions, one using JSON and one using XML. Each
    verifies round trips for integers (rationals with
    denominator == 1) and for fractional vaues.
    
    LGTM=gri
    R=gri, cookieo9, bradfitz, mtj
    CC=golang-codereviews
    https://golang.org/cl/61180043

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

https://github.com/golang/go/commit/eea28f6701ac888d5613470a88d09b634efc1d75

元コミット内容

このコミットの元の内容は、math/bigパッケージのRat型に、encoding.TextMarshalerencoding.TextUnmarshalerインターフェースのサポートを追加することです。これにより、無制限精度の有理数をテキスト形式で簡単に転送できるようになります。具体的には、エンコード関数とデコード関数、そしてJSONとXMLを使用した2つのテスト関数が含まれており、それぞれ整数(分母が1の有理数)と分数でのラウンドトリップ(エンコードとデコードが正しく行われること)を検証しています。

変更の背景

この変更の背景には、Go言語のmath/bigパッケージが提供する任意精度数値型(Int, Float, Ratなど)を、一般的なデータシリアライゼーションフォーマット(JSON, XMLなど)で扱う際の利便性の向上が挙げられます。特にRat型のような複雑な構造を持つ型を、標準的なエンコーディングメカニズムで扱えるようにすることで、開発者はカスタムのシリアライゼーションロジックを記述することなく、これらの数値をネットワーク経由で送信したり、ファイルに保存したりすることが可能になります。

コミットメッセージにFixes #7287とありますが、Goの公式リポジトリでこのIssue番号が見つからないため、内部的なIssue番号であるか、あるいは別の文脈での参照である可能性があります。しかし、この変更がユーザーからの要望や、より広範なGoエコシステムでのデータ交換のニーズに応えるものであることは明らかです。

前提知識の解説

Go言語のencodingパッケージとインターフェース

Go言語の標準ライブラリには、データエンコーディングとデコーディングのための強力なencodingパッケージ群が含まれています。これらのパッケージは、特定のデータフォーマット(例: encoding/json, encoding/xml, encoding/gobなど)に特化していますが、共通のインターフェースを介して動作します。

  • encoding.TextMarshalerインターフェース: このインターフェースは、任意の型をテキスト形式に変換するためのMarshalText() ([]byte, error)メソッドを定義します。このメソッドを実装することで、その型はJSONの文字列フィールドやXMLのテキストコンテンツとして自動的にエンコードされるようになります。

  • encoding.TextUnmarshalerインターフェース: このインターフェースは、テキスト形式のデータを任意の型に変換するためのUnmarshalText(text []byte) errorメソッドを定義します。このメソッドを実装することで、その型はJSONの文字列フィールドやXMLのテキストコンテンツから自動的にデコードされるようになります。

これらのインターフェースを実装することで、開発者はカスタムのシリアライゼーションロジックを記述することなく、Goの標準的なエンコーディング/デコーディングメカニズムを利用して、複雑なデータ型を扱うことができます。

math/bigパッケージとRat

math/bigパッケージは、Go言語で任意精度の算術演算を行うための型を提供します。これは、標準のGoの数値型(int, float64など)が持つ精度や範囲の制限を超える計算が必要な場合に非常に有用です。

  • big.Rat: big.Ratは、任意精度の有理数を表す型です。有理数は、整数を分子と分母で表される分数(例: 1/2, 3/4, -5/3)です。big.Ratは、分母が1の場合には整数も表現できます。この型は、金融計算、科学技術計算、暗号学など、高い精度が要求される分野で利用されます。

JSONとXML

  • JSON (JavaScript Object Notation): 軽量なデータ交換フォーマットであり、人間が読み書きしやすく、機械が解析しやすいという特徴があります。WebアプリケーションのAPIなどで広く利用されています。

  • XML (Extensible Markup Language): 構造化されたデータを表現するためのマークアップ言語です。JSONよりも冗長ですが、より厳密なスキーマ定義や複雑なデータ構造の表現が可能です。

これらのフォーマットは、異なるシステム間でデータを交換する際の標準的な手段であり、Goのencoding/jsonencoding/xmlパッケージによってサポートされています。

技術的詳細

このコミットの主要な技術的詳細は、math/big.Rat型がencoding.TextMarshalerencoding.TextUnmarshalerインターフェースを実装した点にあります。

MarshalText()の実装

Rat型のMarshalText()メソッドは、Ratの値をその文字列表現(例: "1/2", "3", "-5/3")に変換します。これは、Rat型が既に持っているRatString()メソッドを利用することで実現されています。RatString()は、Ratの値を標準的な分数形式の文字列として返します。MarshalText()は、この文字列をバイトスライスに変換して返します。

これにより、encoding/jsonencoding/xmlなどのパッケージがRat型の値をエンコードする際に、自動的にその文字列表現を使用するようになります。例えば、JSONにエンコードされると、{"value": "1/2"}のように文字列として表現されます。

UnmarshalText()の実装

Rat型のUnmarshalText()メソッドは、テキスト形式のバイトスライスを受け取り、それをRat型の値にパースします。この実装では、Rat型が既に持っているSetString()メソッドを利用しています。SetString()は、文字列を受け取り、それをRatの値として設定します。パースが成功した場合はnilエラーを返し、失敗した場合はfmt.Errorfを使用してエラーを返します。

これにより、JSONやXMLからデコードする際に、文字列として表現された有理数をRat型に正しく変換できるようになります。例えば、JSONの{"value": "1/2"}という文字列から、対応するbig.Ratオブジェクトが生成されます。

テストの追加

コミットには、JSONとXMLの両方でRat型のエンコード/デコードが正しく機能するかを検証するテスト関数が含まれています。これらのテストは、様々な整数値と分数値を対象に、エンコード後にデコードし、元の値とデコードされた値が一致するか(ラウンドトリップが成功するか)を確認します。これにより、実装の正確性と堅牢性が保証されます。

特に、ratNumsratDenomsというスライスで定義された、非常に大きな数値や小さな数値、正負の数値、そして分母が1(整数)の場合とそうでない場合の両方を網羅するテストデータが用意されており、広範なケースでの動作が検証されています。

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

src/pkg/math/big/rat.go

// MarshalText implements the encoding.TextMarshaler interface
func (r *Rat) MarshalText() (text []byte, err error) {
	return []byte(r.RatString()), nil
}

// UnmarshalText implements the encoding.TextUnmarshaler interface
func (r *Rat) UnmarshalText(text []byte) error {
	if _, ok := r.SetString(string(text)); !ok {
		return fmt.Errorf("math/big: cannot unmarshal %q into a *big.Rat", text)
	}
	return nil
}

src/pkg/math/big/rat_test.go

import (
	"encoding/json"
	"encoding/xml"
	// ...
)

var ratNums = []string{
	"-141592653589793238462643383279502884197169399375105820974944592307816406286",
	"-1415926535897932384626433832795028841971",
	"-141592653589793",
	"-1",
	"0",
	"1",
	"141592653589793",
	"1415926535897932384626433832795028841971",
	"141592653589793238462643383279502884197169399375105820974944592307816406286",
}

var ratDenoms = []string{
	"1",
	"718281828459045",
	"7182818284590452353602874713526624977572",
	"718281828459045235360287471352662497757247093699959574966967627724076630353",
}

func TestRatJSONEncoding(t *testing.T) {
	for _, num := range ratNums {
		for _, denom := range ratDenoms {
			var tx Rat
			tx.SetString(num + "/" + denom)
			b, err := json.Marshal(&tx)
			if err != nil {
				t.Errorf("marshaling of %s failed: %s", &tx, err)
				continue
			}
			var rx Rat
			if err := json.Unmarshal(b, &rx); err != nil {
				t.Errorf("unmarshaling of %s failed: %s", &tx, err)
				continue
			}
			if rx.Cmp(&tx) != 0 {
				t.Errorf("JSON encoding of %s failed: got %s want %s", &tx, &rx, &tx)
			}
		}
	}
}

func TestRatXMLEncoding(t *testing.T) {
	for _, num := range ratNums {
		for _, denom := range ratDenoms {
			var tx Rat
			tx.SetString(num + "/" + denom)
			b, err := xml.Marshal(&tx)
			if err != nil {
				t.Errorf("marshaling of %s failed: %s", &tx, err)
				continue
			}
			var rx Rat
			if err := xml.Unmarshal(b, &rx); err != nil {
				t.Errorf("unmarshaling of %s failed: %s", &tx, err)
				continue
			}
			if rx.Cmp(&tx) != 0 {
				t.Errorf("XML encoding of %s failed: got %s want %s", &tx, &rx, &tx)
			}
		}
	}
}

コアとなるコードの解説

rat.goの変更

  • MarshalText()メソッド: このメソッドは*Ratレシーバを持ち、encoding.TextMarshalerインターフェースを実装します。内部ではr.RatString()を呼び出してRatの文字列表現を取得し、それを[]byteに変換して返します。エラーは発生しないため、nilを返します。これにより、Rat型がJSONやXMLにエンコードされる際に、その文字列表現が使用されるようになります。

  • UnmarshalText()メソッド: このメソッドは*Ratレシーバを持ち、encoding.TextUnmarshalerインターフェースを実装します。引数として[]byte形式のテキストデータを受け取ります。このテキストデータをstringに変換し、r.SetString()メソッドに渡してRatの値を設定しようと試みます。SetString()は、文字列が有効な有理数形式でない場合にfalseを返すため、その場合はfmt.Errorfを使ってエラーメッセージを生成し、返します。これにより、テキスト形式の有理数文字列からRat型へのデコードが可能になります。

rat_test.goの変更

  • encoding/jsonencoding/xmlのインポート: テストのために、これらのパッケージが新しくインポートされています。

  • ratNumsratDenoms: テストに使用される分子と分母の文字列スライスが定義されています。これらは、非常に大きな数値、小さな数値、正負の数値、そして整数(分母が"1")と分数を含む多様なケースをカバーしています。

  • TestRatJSONEncoding()関数: このテスト関数は、ratNumsratDenomsの全ての組み合わせに対して以下の処理を行います。

    1. Ratオブジェクトtxを生成し、SetString()で分子と分母の組み合わせを設定します。
    2. json.Marshal(&tx)を呼び出してtxをJSONバイトスライスにエンコードします。エンコードに失敗した場合はエラーを報告します。
    3. 新しいRatオブジェクトrxを生成し、json.Unmarshal(b, &rx)を呼び出してエンコードされたJSONバイトスライスをrxにデコードします。デコードに失敗した場合はエラーを報告します。
    4. rx.Cmp(&tx) != 0で、元のtxとデコードされたrxが等しいか(比較結果が0でないか)を比較します。等しくない場合は、JSONエンコーディング/デコーディングのラウンドトリップが失敗したとしてエラーを報告します。
  • TestRatXMLEncoding()関数: このテスト関数はTestRatJSONEncoding()とほぼ同じロジックですが、encoding/xmlパッケージを使用してXMLエンコーディング/デコーディングのラウンドトリップを検証します。

これらのテストは、Rat型がencoding.TextMarshalerencoding.TextUnmarshalerインターフェースを正しく実装し、JSONとXMLの両方で期待通りに動作することを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特にsrc/pkg/math/big/rat.gosrc/pkg/math/big/rat_test.go)
  • JSON (JavaScript Object Notation) 公式サイト
  • XML (Extensible Markup Language) W3C勧告
  • Go言語のencodingインターフェースに関する一般的な情報源