[インデックス 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=