[インデックス 15000] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/xml パッケージにおける、浮動小数点数(float32 および float64)のXMLマーシャリング(Goのデータ構造からXMLへの変換)に関する挙動の修正を目的としています。具体的には、strconv.FormatFloat 関数を呼び出す際に、float32 と float64 のビットサイズを適切に区別して渡すように変更することで、より正確な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形式に変換(マーシャリング)する機能を提供します。この変換処理において、浮動小数点数型(float32 と float64)を文字列に変換する際に strconv.FormatFloat 関数が内部的に使用されます。
以前の実装では、float32 と float64 の両方に対して、strconv.FormatFloat の bitSize 引数に一律で 64 が渡されていました。bitSize 引数は、変換対象の浮動小数点数が float32 (32ビット) なのか float64 (64ビット) なのかを示すもので、この情報に基づいて strconv.FormatFloat は適切な丸め処理や精度での文字列変換を行います。
しかし、float32 の値に対して bitSize=64 を指定すると、float32 の本来の精度を超えた情報で文字列化しようとするため、予期せぬ丸め誤差や不正確な表現が生じる可能性がありました。特に、float32 は約7桁の10進精度しか持たないのに対し、float64 は約15桁の10進精度を持ちます。この精度の違いを strconv.FormatFloat に正しく伝えることが重要でした。
このコミットは、この問題を解決し、float32 と float64 のそれぞれに対して、その型が持つ本来のビットサイズを 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)で値vのreflect.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':fとeのうち、より短く表現できる方を選択します。
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.FormatFloat の bitSize 引数の渡し方です。
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.Value が float32 であった場合、その float32 の精度で表現可能な範囲の float64 値です。
strconv.FormatFloat の bitSize 引数は、元の数値が何ビットの浮動小数点数であったかを伝えるためのものです。この情報に基づいて、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の型のビットサイズを取得します。- もし
valがfloat32型の値を表していれば、val.Type().Bits()は32を返します。 - もし
valがfloat64型の値を表していれば、val.Type().Bits()は64を返します。
- もし
この修正により、strconv.FormatFloat は、元の浮動小数点数型が float32 であったか float64 であったかに応じて、適切なビットサイズ情報を受け取ります。これにより、strconv.FormatFloat は、元の型の精度を正確に考慮した上で、浮動小数点数を文字列に変換できるようになり、XML出力における数値の正確性が向上します。
関連リンク
- Go言語
encoding/xmlパッケージドキュメント: https://pkg.go.dev/encoding/xml - Go言語
reflectパッケージドキュメント: https://pkg.go.dev/reflect - Go言語
strconvパッケージドキュメント: https://pkg.go.dev/strconv - Go言語の
float32とfloat64について: https://go.dev/blog/go-floating-point
参考にした情報源リンク
- https://golang.org/cl/7235045 (元のGo Gerritの変更リスト)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEd1PBoz7HML7M2OBTzpaMyGMOKuPbsp8qfLDc3I78oTync6xG_Myjaeg_xV1c9jvZV5KzlLzqWyvGdvLEQL8Sx7ooggY50y7Wywog_QN3namIDKbdmUAlPyB-gAa7Wl45mDURkAA_Buk3DPGAg7UM1
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG6hGuRsYO2rgN8wC7uvGG4pdCyqPIhtpr5OVCsqhbi8PC5aepSIui5OYCmPgo9hJA3dLGh2anTeYK1JweRMHl_lIDuV1zdzNLFUMX1WSKs725lpgIkmoSfnt2GZTRRGIkGAojShmjm_5P_bC6jIw==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFFPjSJAqvyfDtzvoXEzfQJ1AayWSlguHLUAdsOIq1UQVfxdqAu0BSdhqu7CTYnI25fh5FyjvNxLx0ELNPlZnKJbDM0CKGaUlzdPjdpyqyYOgqsjjWL53W3Vy2epqhkLgfHSfGDjhHG0nIY
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHF0kgdVEw7oDmx99wyWYyeRfESsMKGOUr4NqiSg83DqpBx_4Wk_kAEfOhGkMoYdYYXhgncZGvOS6bEeOs8OBBpIDU6kI2WujHH70biA-NHA4DyiZFtO2RqiVkDDAdDZypD0Nhz7K3puAvEeTugzHgGGAChYT7nXfGbGDT3mnmfRV6ZLVtKaIDKpW3owuwTxMHw
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEFIUEckQ1PkZWUutbR7f_2pNA1iWeK9VV7AqfXhIp4lX1uAznnnB7qxJnJxHNSb83Z_ogQgJZOdZCm1Y_EEszUyWPMU6Z1WJjblJZIuF-zHqv1Uvnc5dRFSqI=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFBvBxk21kcg5riy0H0t1MAcjj8uxECiRXxK5ZqkBZ-mbIOW1zQ5ycv5nJyGATi4WcvE1fpFWXAqSTFlDswFAVsMn2cI51NZQliv1wTvINKBbGqrCISHZu7S4xLp-d5Ijl-PRJUc6GB84CHJ9r3SFjpZIrQbSA15rO8MQkFRk4TJkUN6sjFKyKsDKWyX8yX_bloaECS8rSOt0BVWu8=