[インデックス 11830] ファイルの概要
このコミットは、Go言語の標準ライブラリであるstrconv
パッケージにおける浮動小数点数変換(ParseFloat
およびFormatFloat
)の堅牢性を高めるためのテスト追加です。特に、特定の環境(Darwin/386ビルダー)で発生した、浮動小数点数のラウンドトリップ変換(文字列化して再度数値に戻す処理)における不整合を再現するためのテストケースが追加されています。この問題はGo Issue 2917として報告されており、浮動小数点演算ユニット(FPU)の精度設定(64ビットと80ビット)の違いに起因する可能性が指摘されています。
コミット
commit f7a3683928fcfdcc0c7432b7d553b18627e40a50
Author: Russ Cox <rsc@golang.org>
Date: Sun Feb 12 23:24:54 2012 -0500
strconv: add tests for issue 2917
Cannot reproduce the failure locally,
but add explicit test in case some other
machine can.
Fixes #2917 (for now).
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5651071
---
src/pkg/strconv/atof_test.go | 34 ++++++++++++++++++++++++++++++++++\n 1 file changed, 34 insertions(+)
diff --git a/src/pkg/strconv/atof_test.go b/src/pkg/strconv/atof_test.go
index 72cea49256..1da8c84d55 100644
--- a/src/pkg/strconv/atof_test.go
+++ b/src/pkg/strconv/atof_test.go
@@ -226,6 +226,40 @@ func TestAtofRandom(t *testing.T) {
t.Logf("tested %d random numbers", len(atofRandomTests))
}
+var roundTripCases = []struct {
+\tf float64
+\ts string
+}{
+\t// Issue 2917.
+\t// A Darwin/386 builder failed on AtofRandom with this case.
+\t{8865794286000691 << 39, "4.87402195346389e+27"},
+\t{8865794286000692 << 39, "4.8740219534638903e+27"},
+}\n
+func TestRoundTrip(t *testing.T) {
+\tfor _, tt := range roundTripCases {
+\t\told := SetOptimize(false)\n\t\ts := FormatFloat(tt.f, 'g', -1, 64)\n\t\tif s != tt.s {\n\t\t\tt.Errorf("no-opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s)\n\t\t}\n\t\tf, err := ParseFloat(tt.s, 64)\n\t\tif f != tt.f || err != nil {\n\t\t\tt.Errorf("no-opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f)\n\t\t}\n\t\tSetOptimize(true)\n\t\ts = FormatFloat(tt.f, 'g', -1, 64)\n\t\tif s != tt.s {\n\t\t\tt.Errorf("opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s)\n\t\t}\n\t\tf, err = ParseFloat(tt.s, 64)\n\t\tif f != tt.f || err != nil {\n\t\t\tt.Errorf("opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f)\n\t\t}\n\t\tSetOptimize(old)\n\t}\n}\n+\n func BenchmarkAtof64Decimal(b *testing.B) {
\tfor i := 0; i < b.N; i++ {
\t\tParseFloat("33909", 64)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f7a3683928fcfdcc0c7432b7d553b18627e40a50
元コミット内容
strconv: add tests for issue 2917
Cannot reproduce the failure locally,
but add explicit test in case some other
machine can.
Fixes #2917 (for now).
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5651071
変更の背景
このコミットは、Go言語のstrconv
パッケージにおける浮動小数点数変換のバグ、具体的にはGo Issue 2917に対応するために行われました。この問題は、特にDarwin/386アーキテクチャのビルド環境でAtofRandom
テストが失敗するという形で顕在化しました。
根本的な原因は、浮動小数点演算ユニット(FPU)の精度設定の違いにありました。一部のシステムでは、FPUが80ビットのレジスタを使用して浮動小数点計算を行うのに対し、Goランタイムは64ビットのレジスタに最適化されているか、それを前提としていました。オペレーティングシステムがスレッドを80ビット精度で初期化し、GoランタイムがFPUの制御ワードを適切に調整しない場合、strconv.ParseFloat
による浮動小数点数変換が不正確になる可能性がありました。
コミットメッセージにあるように、この問題はコミット作成者のローカル環境では再現できませんでしたが、他のマシンで発生する可能性を考慮し、明示的なテストケースを追加することで、将来的な回帰を防ぐことを目的としています。
前提知識の解説
strconv
パッケージ: Go言語の標準ライブラリの一つで、基本的なデータ型(数値、真偽値など)と文字列との間の変換機能を提供します。ParseFloat
は文字列を浮動小数点数に、FormatFloat
は浮動小数点数を文字列に変換します。- 浮動小数点数(Floating-Point Numbers): コンピュータで実数を近似的に表現するための形式です。IEEE 754標準が広く用いられており、Go言語の
float64
型は通常この標準の倍精度浮動小数点数(64ビット)に準拠しています。 - 浮動小数点演算ユニット(FPU: Floating-Point Unit): CPUの一部で、浮動小数点数の計算を専門に行うハードウェアです。FPUは、加算、減算、乗算、除算などの基本的な演算だけでなく、より複雑な数学関数も実行できます。
- FPUの精度設定: FPUは、計算に使用する内部レジスタの精度を設定できる場合があります。一般的なのは64ビット(倍精度)ですが、一部のIntel x87 FPUなどでは80ビットの拡張精度モードをサポートしています。この精度設定が、ソフトウェアが期待する精度と異なる場合に、計算結果のわずかな差異や丸め誤差の蓄積により、問題が発生することがあります。
- ラウンドトリップ変換(Round-Trip Conversion): あるデータ型から別のデータ型に変換し、その後元のデータ型に戻す処理のことです。この際、元の値と変換後の値が完全に一致することが期待されます。浮動小数点数の場合、
float64 -> string -> float64
の変換で元のfloat64
値が正確に再現されることが重要です。
技術的詳細
Go Issue 2917の核心は、strconv.ParseFloat
が特定の環境下で、文字列から浮動小数点数への変換時に不正確な結果を返す可能性があったことです。これは、FPUの精度設定がGoランタイムの期待と異なる場合に発生しました。
具体的には、FPUが80ビット精度で動作している場合、中間計算やレジスタへの値のロード・ストア時に64ビットの倍精度浮動小数点数とは異なる丸めが行われることがあります。これにより、FormatFloat
で文字列化された浮動小数点数が、ParseFloat
で元の数値に正確に戻らない、いわゆる「ラウンドトリップ問題」が発生しました。
このコミットで追加されたテストケースは、この特定のシナリオを捕捉することを目的としています。roundTripCases
に定義された浮動小数点数とそれに対応する文字列は、Darwin/386ビルダーで実際に問題を引き起こした値です。これらの値は非常に大きく、浮動小数点数の精度が問われる境界値に近いものです。
テストでは、SetOptimize(false)
とSetOptimize(true)
を切り替えることで、strconv
パッケージ内部の最適化の有無にかかわらず、ラウンドトリップ変換が正しく行われることを確認しています。これは、最適化の有無がFPUの挙動に影響を与える可能性を考慮しているためと考えられます。
8865794286000691 << 39
のような表記は、ビットシフト演算子を使って非常に大きな整数を表現し、それが浮動小数点数に変換される際の挙動をテストしています。このような極端な値は、浮動小数点数の内部表現におけるエッジケースを突くのに役立ちます。
コアとなるコードの変更箇所
変更はsrc/pkg/strconv/atof_test.go
ファイルに集中しており、以下の部分が追加されています。
roundTripCases
という新しいテストケースのスライスが定義されています。これは、float64
値とその期待される文字列表現のペアを含んでいます。TestRoundTrip
という新しいテスト関数が追加されています。
コアとなるコードの解説
roundTripCases
var roundTripCases = []struct {
f float64
s string
}{
// Issue 2917.
// A Darwin/386 builder failed on AtofRandom with this case.
{8865794286000691 << 39, "4.87402195346389e+27"},
{8865794286000692 << 39, "4.8740219534638903e+27"},
}
このスライスは、Go Issue 2917で問題が報告された具体的な浮動小数点数とその文字列表現を定義しています。
8865794286000691 << 39
は、8865794286000691
を左に39ビットシフトした整数値をfloat64
型として扱います。これは非常に大きな数値となり、浮動小数点数の精度が厳しく問われるケースです。
TestRoundTrip
関数
func TestRoundTrip(t *testing.T) {
for _, tt := range roundTripCases {
old := SetOptimize(false) // 最適化を無効にする
s := FormatFloat(tt.f, 'g', -1, 64)
if s != tt.s {
t.Errorf("no-opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s)
}
f, err := ParseFloat(tt.s, 64)
if f != tt.f || err != nil {
t.Errorf("no-opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f)
}
SetOptimize(true) // 最適化を有効にする
s = FormatFloat(tt.f, 'g', -1, 64)
if s != tt.s {
t.Errorf("opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s)
}
f, err = ParseFloat(tt.s, 64)
if f != tt.f || err != nil {
t.Errorf("opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f)
}
SetOptimize(old) // 元の最適化設定に戻す
}
}
このテスト関数は、roundTripCases
内の各ペアに対して以下のラウンドトリップ変換を検証します。
-
最適化無効でのテスト:
SetOptimize(false)
を呼び出し、strconv
パッケージ内部の最適化を一時的に無効にします。これは、最適化の有無がFPUの挙動に影響を与える可能性を考慮しているためです。FormatFloat(tt.f, 'g', -1, 64)
でfloat64
値を文字列に変換し、期待される文字列tt.s
と一致するか確認します。ParseFloat(tt.s, 64)
でその文字列を再度float64
に変換し、元のtt.f
と一致するか、エラーが発生しないかを確認します。
-
最適化有効でのテスト:
SetOptimize(true)
を呼び出し、最適化を有効に戻します。- 同様に
FormatFloat
とParseFloat
のラウンドトリップ変換を検証します。
-
設定の復元:
- 最後に
SetOptimize(old)
を呼び出し、テスト開始前の最適化設定に戻します。
- 最後に
このテストの目的は、特定の浮動小数点数が文字列に変換され、その後再び数値に変換されたときに、元の数値と完全に一致するかどうかを確認することです。特に、FPUの精度問題が原因で発生する可能性のある微細な誤差を検出することを意図しています。
関連リンク
- Go Issue 2917: https://go.dev/issue/2917 (このコミットが修正対象としているGoのIssueトラッカーの項目)
- Gerrit Change-ID:
https://golang.org/cl/5651071
(GoプロジェクトのコードレビューシステムGerritにおけるこの変更のリンク)
参考にした情報源リンク
- Go Vulnerability Database - GO-2024-2917 (FPUの精度問題に関する一般的な情報): https://go.dev/security/vuln/GO-2024-2917.html
- 注: このリンクは直接Issue 2917を指すものではありませんが、FPUの精度問題がGoの浮動小数点数処理に影響を与える可能性について言及しているため、参考情報として記載しました。