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

[インデックス 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内の各ペアに対して以下のラウンドトリップ変換を検証します。

  1. 最適化無効でのテスト:

    • SetOptimize(false)を呼び出し、strconvパッケージ内部の最適化を一時的に無効にします。これは、最適化の有無がFPUの挙動に影響を与える可能性を考慮しているためです。
    • FormatFloat(tt.f, 'g', -1, 64)float64値を文字列に変換し、期待される文字列tt.sと一致するか確認します。
    • ParseFloat(tt.s, 64)でその文字列を再度float64に変換し、元のtt.fと一致するか、エラーが発生しないかを確認します。
  2. 最適化有効でのテスト:

    • SetOptimize(true)を呼び出し、最適化を有効に戻します。
    • 同様にFormatFloatParseFloatのラウンドトリップ変換を検証します。
  3. 設定の復元:

    • 最後に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の浮動小数点数処理に影響を与える可能性について言及しているため、参考情報として記載しました。