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

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

このコミットは、Go言語の標準ライブラリである encoding/xml パッケージにおける、浮動小数点数(float32 および float64)のXMLマーシャリング(Goのデータ構造からXMLへの変換)に関する挙動の修正を目的としています。具体的には、strconv.FormatFloat 関数を呼び出す際に、float32float64 のビットサイズを適切に区別して渡すように変更することで、より正確なXML出力が保証されるようになります。

コミット

commit cf1f542420415826040be93bc2a252ec605a1196
Author: Vega Garcia Luis Alfonso <vegacom@gmail.com>
Date:   Mon Jan 28 12:54:27 2013 -0500

    xml: differentiate between float32 and float64 for marshalSimple
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7235045

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

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

元コミット内容

xml: differentiate between float32 and float64 for marshalSimple

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7235045

変更の背景

Goの encoding/xml パッケージは、Goの構造体をXML形式に変換(マーシャリング)する機能を提供します。この変換処理において、浮動小数点数型(float32float64)を文字列に変換する際に strconv.FormatFloat 関数が内部的に使用されます。

以前の実装では、float32float64 の両方に対して、strconv.FormatFloatbitSize 引数に一律で 64 が渡されていました。bitSize 引数は、変換対象の浮動小数点数が float32 (32ビット) なのか float64 (64ビット) なのかを示すもので、この情報に基づいて strconv.FormatFloat は適切な丸め処理や精度での文字列変換を行います。

しかし、float32 の値に対して bitSize=64 を指定すると、float32 の本来の精度を超えた情報で文字列化しようとするため、予期せぬ丸め誤差や不正確な表現が生じる可能性がありました。特に、float32 は約7桁の10進精度しか持たないのに対し、float64 は約15桁の10進精度を持ちます。この精度の違いを strconv.FormatFloat に正しく伝えることが重要でした。

このコミットは、この問題を解決し、float32float64 のそれぞれに対して、その型が持つ本来のビットサイズを strconv.FormatFloat に渡すことで、より正確で期待通りのXMLマーシャリングを実現することを目的としています。

前提知識の解説

1. Go言語の encoding/xml パッケージ

encoding/xml パッケージは、Goの構造体とXMLドキュメントの間でデータを変換するための機能を提供します。

  • マーシャリング (Marshaling): Goのデータ構造(構造体など)をXML形式のバイト列に変換するプロセスです。xml.Marshal 関数がこれを行います。
  • アンマーシャリング (Unmarshaling): XML形式のバイト列をGoのデータ構造に変換するプロセスです。xml.Unmarshal 関数がこれを行います。

2. Go言語の reflect パッケージ

reflect パッケージは、実行時にプログラムの型情報を検査したり、値を操作したりするための機能を提供します。

  • reflect.Type: Goの型の情報を表します。reflect.TypeOf(v) で値 v の型情報を取得できます。
  • reflect.Value: Goの値を表します。reflect.ValueOf(v) で値 vreflect.Value を取得できます。
  • reflect.Value.Float(): reflect.Value が浮動小数点数型の場合、その値を float64 として返します。
  • reflect.Value.Type(): reflect.Value が表す値の reflect.Type を返します。
  • reflect.Type.Bits(): 整数型または浮動小数点数型の場合、その型のビットサイズ(例: float32 なら32、float64 なら64)を返します。

3. Go言語の strconv パッケージと strconv.FormatFloat 関数

strconv パッケージは、基本的なデータ型(数値、真偽値、文字列など)と文字列の間で変換を行うための機能を提供します。 strconv.FormatFloat 関数は、浮動小数点数を文字列に変換するために使用されます。そのシグネチャは以下の通りです。

func FormatFloat(f float64, fmt byte, prec, bitSize int) string
  • f: 変換する浮動小数点数。float64 型で渡されます。
  • fmt: フォーマット指定子。
    • 'f': 固定小数点形式 (例: "123.45")
    • 'e': 指数形式 (例: "1.2345e+02")
    • 'g': fe のうち、より短く表現できる方を選択します。
  • prec: 精度。fmt'f' の場合は小数点以下の桁数、'e''g' の場合は有効数字の桁数を指定します。-1 を指定すると、必要な精度で表現されます。
  • bitSize: 変換元の浮動小数点数のビットサイズ。32 (float32) または 64 (float64) を指定します。この引数は、FormatFloat が浮動小数点数の内部表現を考慮して、正確な丸め処理を行うために非常に重要です。例えば、float32 の値を bitSize=64 でフォーマットしようとすると、float32 の精度では表現できない余分な情報まで考慮しようとして、不正確な結果を招く可能性があります。

4. 浮動小数点数の精度 (float32 vs float64)

  • float32: 単精度浮動小数点数。IEEE 754 規格に準拠し、32ビットで表現されます。約7桁の10進精度を持ちます。
  • float64: 倍精度浮動小数点数。IEEE 754 規格に準拠し、64ビットで表現されます。約15桁の10進精度を持ちます。

これらの精度の違いは、数値の表現範囲と正確性に直接影響します。XMLマーシャリングにおいて、元の数値の精度を正確に反映した文字列表現を得るためには、このビットサイズ情報を strconv.FormatFloat に正しく伝えることが不可欠です。

技術的詳細

このコミットの技術的な核心は、encoding/xml パッケージの printer 型の marshalSimple メソッドにおける strconv.FormatFloatbitSize 引数の渡し方です。

marshalSimple メソッドは、Goの基本的な型(数値、文字列など)をXMLの文字列として出力する役割を担っています。浮動小数点数型 (reflect.Float32, reflect.Float64) の場合、このメソッドは strconv.FormatFloat を呼び出して数値を文字列に変換します。

変更前のコードは以下のようになっていました。

case reflect.Float32, reflect.Float64:
    p.WriteString(strconv.FormatFloat(val.Float(), 'g', -1, 64))

ここで注目すべきは、bitSize 引数に 64 がハードコードされている点です。これは、val.Float() が常に float64 を返すため、一見すると問題ないように見えます。しかし、val.Float() が返す float64 の値は、元の reflect.Valuefloat32 であった場合、その float32 の精度で表現可能な範囲の float64 値です。

strconv.FormatFloatbitSize 引数は、元の数値が何ビットの浮動小数点数であったかを伝えるためのものです。この情報に基づいて、strconv.FormatFloat は適切な内部処理(例えば、float32 の場合は32ビットの精度で丸め処理を行う)を行います。

もし元の値が float32 であったにもかかわらず bitSize=64 を指定すると、strconv.FormatFloat はその値を float64 として扱い、float32 では表現できないような微細な誤差まで考慮して文字列化しようとします。これにより、例えば float32(0.1)0.10000000149011612 のように、本来の float32 の精度を超えた余分な桁が表示されたり、不正確な丸めが行われたりする可能性がありました。

変更後のコードは以下のようになります。

case reflect.Float32, reflect.Float64:
    p.WriteString(strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()))

この変更のポイントは、bitSize 引数に val.Type().Bits() を渡している点です。

  • val.Type(): reflect.Value が表す元のGoの型の reflect.Type を取得します。
  • val.Type().Bits(): その型が浮動小数点数型であれば、そのビットサイズ(float32 なら32、float64 なら64)を返します。

これにより、strconv.FormatFloat は、元のGoの型が float32 であれば bitSize=32 を、float64 であれば bitSize=64 を受け取ることになります。この修正によって、strconv.FormatFloat は元の型の精度を正確に考慮して文字列変換を行うため、XML出力における浮動小数点数の表現がより正確で期待通りになります。

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

変更は src/pkg/encoding/xml/marshal.go ファイルの1箇所のみです。

--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -241,7 +241,7 @@ func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) error {
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 		p.WriteString(strconv.FormatUint(val.Uint(), 10))
 	case reflect.Float32, reflect.Float64:
-		p.WriteString(strconv.FormatFloat(val.Float(), 'g', -1, 64))
+		p.WriteString(strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()))
 	case reflect.String:
 		// TODO: Add EscapeString.
 		Escape(p, []byte(val.String()))

コアとなるコードの解説

変更された行は、printer 型の marshalSimple メソッド内の switch ステートメントの一部です。

case reflect.Float32, reflect.Float64:
    p.WriteString(strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()))
  • case reflect.Float32, reflect.Float64:: この行は、現在処理しているGoのデータ型が float32 または float64 である場合に、このブロックのコードが実行されることを示しています。
  • p.WriteString(...): printer 型の WriteString メソッドを呼び出し、引数で渡された文字列をXML出力に書き込みます。
  • strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()): ここが変更の核心です。
    • val.Float(): reflect.Value から浮動小数点数(float64 型)の値を取得します。元の型が float32 であっても、このメソッドは float64 として値を返します。
    • 'g': フォーマット指定子です。strconv.FormatFloat が、固定小数点形式 ('f') と指数形式 ('e') のうち、より短く表現できる方を選択するように指示します。
    • -1: 精度指定子です。strconv.FormatFloat が、必要な精度で数値を表現するように指示します。
    • val.Type().Bits(): この部分が変更点です。 val が表す元のGoの型のビットサイズを取得します。
      • もし valfloat32 型の値を表していれば、val.Type().Bits()32 を返します。
      • もし valfloat64 型の値を表していれば、val.Type().Bits()64 を返します。

この修正により、strconv.FormatFloat は、元の浮動小数点数型が float32 であったか float64 であったかに応じて、適切なビットサイズ情報を受け取ります。これにより、strconv.FormatFloat は、元の型の精度を正確に考慮した上で、浮動小数点数を文字列に変換できるようになり、XML出力における数値の正確性が向上します。

関連リンク

参考にした情報源リンク