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

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

このコミットは、Go言語の標準ライブラリであるstrconvパッケージにおけるParseFloat関数のfloat32型に対する高速なパースアルゴリズムの拡張に関するものです。特に、ベンチマーク結果が示すように、float32の数値文字列をパースする際のパフォーマンスが大幅に改善されています。

コミット

commit 5468d164679fc5b0a1d988ca06ce41c9d6db61b2
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Wed Jun 13 23:52:00 2012 +0200

    strconv: extend fast parsing algorithm to ParseFloat(s, 32)
    
    benchmark                  old ns/op    new ns/op    delta
    BenchmarkAtof32Decimal           215           73  -65.72%
    BenchmarkAtof32Float             233           83  -64.21%
    BenchmarkAtof32FloatExp         3351          209  -93.76%
    BenchmarkAtof32Random           1939          260  -86.59%
    
    R=rsc
    CC=golang-dev, remy
    https://golang.org/cl/6294071

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

https://github.com/golang/go/commit/5468d164679fc5b0a1d988ca06ce41c9d6db61b2

元コミット内容

このコミットの目的は、strconv.ParseFloat(s, 32)、すなわち文字列からfloat32型の浮動小数点数をパースする際の高速化アルゴリズムを拡張することです。コミットメッセージには、以下のベンチマーク結果が示されており、大幅な性能向上が達成されたことがわかります。

  • BenchmarkAtof32Decimal: 215 ns/op から 73 ns/op へ、-65.72%の改善
  • BenchmarkAtof32Float: 233 ns/op から 83 ns/op へ、-64.21%の改善
  • BenchmarkAtof32FloatExp: 3351 ns/op から 209 ns/op へ、-93.76%の改善
  • BenchmarkAtof32Random: 1939 ns/op から 260 ns/op へ、-86.59%の改善

特に指数部を含む数値(BenchmarkAtof32FloatExp)やランダムな数値(BenchmarkAtof32Random)のパースにおいて、劇的な高速化が実現されています。

変更の背景

数値文字列を浮動小数点数に変換する処理(パース)は、多くのアプリケーションで頻繁に行われる操作であり、その性能は全体のパフォーマンスに大きく影響します。特に、Go言語のようなシステムプログラミング言語では、効率的な数値処理が求められます。

浮動小数点数のパースは、単に文字列を数値に変換するだけでなく、IEEE 754標準に準拠した正確な丸め処理を行う必要があります。これは、10進数で表現された数値を2進数の浮動小数点数で正確に表現することが困難な場合があるため、複雑なアルゴリズムを必要とします。

このコミット以前のstrconv.ParseFloat(特にfloat32向け)の実装には、最適化の余地があったと考えられます。特に、一般的な数値や指数部を持つ数値のパースにおいて、より高速なパスを導入することで、全体的なパフォーマンスを向上させることが目的でした。

前提知識の解説

浮動小数点数 (Floating-Point Numbers)

コンピュータにおける浮動小数点数は、実数を近似的に表現するための形式です。Go言語では、float32(単精度)とfloat64(倍精度)の2種類がサポートされており、これらはIEEE 754標準に準拠しています。

  • IEEE 754: 浮動小数点数の表現と演算に関する国際標準です。これにより、異なるシステム間での浮動小数点数の互換性が保証されます。
  • 単精度 (float32): 32ビットで表現され、約7桁の10進精度を持ちます。
  • 倍精度 (float64): 64ビットで表現され、約15-17桁の10進精度を持ちます。

浮動小数点数は、符号部、指数部、仮数部(または小数部)の3つの要素で構成されます。

数値パースの課題

10進数の数値文字列を2進数の浮動小数点数に変換する際には、以下の課題があります。

  1. 精度: 多くの10進小数は2進数で正確に表現できません(例: 0.1)。そのため、最も近い2進数表現に丸める必要があります。
  2. 丸め (Rounding): IEEE 754では、丸めモードが定義されており、通常は「最近接偶数への丸め (round half to even)」が使用されます。これは、ちょうど中間の値の場合に、偶数の仮数部を持つ値に丸めるというルールです。
  3. 性能: 厳密な丸めルールに従いつつ、高速に変換を行うことは、アルゴリズムの設計において重要な課題です。特に、非常に大きい数や小さい数、指数部を持つ数、または多くの桁を持つ数を扱う場合に複雑さが増します。

strconvパッケージ

Go言語のstrconvパッケージは、基本的なデータ型(文字列、整数、浮動小数点数、真偽値)間の変換機能を提供します。ParseFloat関数は、文字列を浮動小数点数に変換するための主要な関数です。

decimal型とextFloat

Goのstrconvパッケージ内部では、高精度な10進数表現や浮動小数点数表現を扱うために、decimalextFloatといった内部的な型が使用されることがあります。

  • decimal: 10進数の数値文字列を、その整数部と指数部、符号などの情報に分解して保持する構造体です。これにより、文字列の桁数を気にせずに正確な10進数演算を内部的に行うことができます。
  • extFloat: 浮動小数点数を、より多くのビット数(例えば64ビット以上の仮数部)で表現するための拡張浮動小数点数型です。これにより、通常のfloat32float64の精度では不足する中間計算において、精度を保つことができます。最終的な結果は、このextFloatから目的のfloat32float64に丸められます。

これらの型は、数値文字列から浮動小数点数への正確な変換、特に丸め処理を正確に行うために利用されます。

技術的詳細

このコミットの主要な技術的改善点は、strconv.ParseFloatにおけるfloat32のパースパスに、より積極的な「高速パス(fast path)」を導入したことです。

従来のParseFloatは、数値文字列をdecimal型に変換し、そこからextFloatを介して最終的なfloat32またはfloat64に丸めるという、比較的汎用的なアプローチを取っていました。このアプローチは正確性を保証しますが、一部の単純なケースではオーバーヘッドが大きくなる可能性があります。

新しいアプローチでは、以下の最適化が導入されています。

  1. readFloatによる初期パース: ParseFloatの冒頭で、readFloat関数を使用して、数値文字列から仮数部(mantissa)、指数部(exp)、符号(neg)、切り捨ての有無(trunc)を直接抽出します。この段階で、数値が非常に単純な形式(例: 整数、小数点以下の桁数が少ない数)であれば、後続の複雑な処理をスキップできる可能性を探ります。
  2. atof32exactの導入: readFloattruncfalse(つまり、文字列のパース中に桁の切り捨てが発生しなかった)の場合、atof32exactという新しい関数が試行されます。この関数は、抽出されたmantissaexpnegを直接float32の浮動小数点演算で変換し、その結果が正確であると判断できれば、その値を返します。
    • atof32exactは、特に整数や10のべき乗を掛け合わせたような単純なケース(例: "123", "1.23e2", "123000")で非常に高速に動作します。これは、float32の内部表現が持つ精度内で、10進数の仮数と指数を直接2進数の浮動小数点数に変換できる場合に適用されます。
    • 例えば、float32pow10のような定数配列を利用して、10のべき乗の乗算・除算を効率的に行います。
  3. extFloatの汎用化: extFloatfloatBitsメソッドとAssignDecimalメソッドが、floatInfo構造体(float32infoまたはfloat64info)を引数として受け取るように変更されました。これにより、同じextFloatのロジックをfloat32float64の両方で再利用できるようになり、コードの重複が減り、保守性が向上します。また、denormalExpの計算もfloatInfoに基づいて行われるようになり、より汎用的な処理が可能になりました。
  4. フォールバックメカニズム: 高速パスが適用できない場合(例: trunctrue、またはatof32exactで正確な変換ができない場合)、従来のdecimal型とextFloat型を用いた、より堅牢で正確な変換パスにフォールバックします。これにより、性能を追求しつつも、すべての数値文字列に対して正確なパース結果を保証します。

これらの変更により、特に一般的な数値文字列のパースにおいて、複雑な高精度演算を回避し、直接的な浮動小数点演算で結果を導き出すことで、大幅な性能向上が実現されました。

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

src/pkg/strconv/atof.go

--- a/src/pkg/strconv/atof.go
+++ b/src/pkg/strconv/atof.go
@@ -411,33 +411,35 @@ func atof64exact(mantissa uint64, exp int, neg bool) (f float64, ok bool) {
 	return
 }
 
-// If possible to convert decimal d to 32-bit float f exactly,
+// If possible to compute mantissa*10^exp to 32-bit float f exactly,
 // entirely in floating-point math, do so, avoiding the machinery above.
-func (d *decimal) atof32() (f float32, ok bool) {
-	// Exact integers are <= 10^7.
-	// Exact powers of ten are <= 10^10.
-	if d.nd > 7 {
+func atof32exact(mantissa uint64, exp int, neg bool) (f float32, ok bool) {
+	if mantissa>>float32info.mantbits != 0 {
 		return
 	}
-	switch {
-	case d.dp == d.nd: // int
-		f := d.atof32int()
+	f = float32(mantissa)
+	if neg {
+		f = -f
+	}
+	switch {
+	case exp == 0:
 		return f, true
-
-	case d.dp > d.nd && d.dp <= 7+10: // int * 10^k
-		f := d.atof32int()
-		k := d.dp - d.nd
+	// Exact integers are <= 10^7.
+	// Exact powers of ten are <= 10^10.
+	case exp > 0 && exp <= 7+10: // int * 10^k
 		// If exponent is big but number of digits is not,
 		// can move a few zeros into the integer part.
-		if k > 10 {
-			f *= float32pow10[k-10]
-			k = 10
+		if exp > 10 {
+			f *= float32pow10[exp-10]
+			exp = 10
 		}
-		return f * float32pow10[k], true
-
-	case d.dp < d.nd && d.nd-d.dp <= 10: // int / 10^k
-		f := d.atof32int()
-		return f / float32pow10[d.nd-d.dp], true
+		if f > 1e7 || f < -1e7 {
+			// the exponent was really too large.
+			return
+		}
+		return f * float32pow10[exp], true
+	case exp < 0 && exp >= -10: // int / 10^k
+		return f / float32pow10[-exp], true
 	}
 	return
 }
@@ -449,15 +451,32 @@ func atof32(s string) (f float32, err error) {
 		return float32(val), nil
 	}\n
+	if optimize {
+		// Parse mantissa and exponent.
+		mantissa, exp, neg, trunc, ok := readFloat(s)
+		if ok {
+			// Try pure floating-point arithmetic conversion.
+			if !trunc {
+				if f, ok := atof32exact(mantissa, exp, neg); ok {
+					return f, nil
+				}
+			}
+			// Try another fast path.
+			ext := new(extFloat)
+			if ok := ext.AssignDecimal(mantissa, exp, neg, trunc, &float32info); ok {
+				b, ovf := ext.floatBits(&float32info)
+				f = math.Float32frombits(uint32(b))
+				if ovf {
+					err = rangeError(fnParseFloat, s)
+				}
+				return f, err
+			}
+		}
+	}
 	var d decimal
 	if !d.set(s) {
 		return 0, syntaxError(fnParseFloat, s)
 	}
-	if optimize {
-		if f, ok := d.atof32(); ok {
-			return f, nil
-		}
-	}
 	b, ovf := d.floatBits(&float32info)
 	f = math.Float32frombits(uint32(b))
 	if ovf {
@@ -483,8 +502,8 @@ func atof64(s string) (f float64, err error) {
 			}
 			// Try another fast path.
 			ext := new(extFloat)
-			if ok := ext.AssignDecimal(mantissa, exp, neg, trunc); ok {
-				b, ovf := ext.floatBits()
+			if ok := ext.AssignDecimal(mantissa, exp, neg, trunc, &float64info); ok {
+				b, ovf := ext.floatBits(&float64info)
 				f = math.Float64frombits(b)
 				if ovf {
 					err = rangeError(fnParseFloat, s)

src/pkg/strconv/atof_test.go

テストケースの追加とベンチマークの追加が主です。

--- a/src/pkg/strconv/atof_test.go
+++ b/src/pkg/strconv/atof_test.go
@@ -134,6 +134,46 @@ var atoftests = []atofTest{\n 	{\"1.00000000000000011102230246251565404236316680908203125\" + strings.Repeat(\"0\", 10000) + \"1\", \"1.00000002\", nil},\n }\n \n+var atof32tests = []atofTest{\n+\t// Exactly halfway between 1 and the next float32.\n+\t// Round to even (down).\n+\t{\"1.000000059604644775390625\", \"1\", nil},\n+\t// Slightly lower.\n+\t{\"1.000000059604644775390624\", \"1\", nil},\n+\t// Slightly higher.\n+\t{\"1.000000059604644775390626\", \"1.0000001\", nil},\n+\t// Slightly higher, but you have to read all the way to the end.\n+\t{\"1.000000059604644775390625\" + strings.Repeat(\"0\", 10000) + \"1\", \"1.0000001\", nil},\n+\n+\t// largest float32: (1<<128) * (1 - 2^-24)\n+\t{\"340282346638528859811704183484516925440\", \"3.4028235e+38\", nil},\n+\t{\"-340282346638528859811704183484516925440\", \"-3.4028235e+38\", nil},\n+\t// next float32 - too large\n+\t{\"3.4028236e38\", \"+Inf\", ErrRange},\n+\t{\"-3.4028236e38\", \"-Inf\", ErrRange},\n+\t// the border is 3.40282356779...e+38\n+\t// borderline - okay\n+\t{\"3.402823567e38\", \"3.4028235e+38\", nil},\n+\t{\"-3.402823567e38\", \"-3.4028235e+38\", nil},\n+\t// borderline - too large\n+\t{\"3.4028235678e38\", \"+Inf\", ErrRange},\n+\t{\"-3.4028235678e38\", \"-Inf\", ErrRange},\n+\n+\t// Denormals: less than 2^-126\n+\t{\"1e-38\", \"1e-38\", nil},\n+\t{\"1e-39\", \"1e-39\", nil},\n+\t{\"1e-40\", \"1e-40\", nil},\n+\t{\"1e-41\", \"1e-41\", nil},\n+\t{\"1e-42\", \"1e-42\", nil},\n+\t{\"1e-43\", \"1e-43\", nil},\n+\t{\"1e-44\", \"1e-44\", nil},\n+\t{\"6e-45\", \"6e-45\", nil}, // 4p-149 = 5.6e-45\n+\t{\"5e-45\", \"6e-45\", nil},\n+\t// Smallest denormal\n+\t{\"1e-45\", \"1e-45\", nil}, // 1p-149 = 1.4e-45\n+\t{\"2e-45\", \"1e-45\", nil},\n+}\n+\n type atofSimpleTest struct {\n \tx float64\n \ts string\n@@ -154,6 +194,12 @@ func init() {\n \t\t\ttest.err = &NumError{\"ParseFloat\", test.in, test.err}\n \t\t}\n \t}\n+\tfor i := range atof32tests {\n+\t\ttest := &atof32tests[i]\n+\t\tif test.err != nil {\n+\t\t\ttest.err = &NumError{\"ParseFloat\", test.in, test.err}\n+\t\t}\n+\t}\n \n \t// Generate random inputs for tests and benchmarks\n \trand.Seed(time.Now().UnixNano())\n@@ -206,6 +252,19 @@ func testAtof(t *testing.T, opt bool) {\n \t\t\t}\n \t\t}\n \t}\n+\tfor _, test := range atof32tests {\n+\t\tout, err := ParseFloat(test.in, 32)\n+\t\tout32 := float32(out)\n+\t\tif float64(out32) != out {\n+\t\t\tt.Errorf(\"ParseFloat(%v, 32) = %v, not a float32 (closest is %v)\", test.in, out, float64(out32))\n+\t\t\tcontinue\n+\t\t}\n+\t\touts := FormatFloat(float64(out32), \'g\', -1, 32)\n+\t\tif outs != test.out || !reflect.DeepEqual(err, test.err) {\n+\t\t\tt.Errorf(\"ParseFloat(%v, 32) = %v, %v want %v, %v  # %v\",\n+\t\t\t\ttest.in, out32, err, test.out, test.err, out)\n+\t\t}\n+\t}\n \tSetOptimize(oldopt)\n }\n \n@@ -264,6 +323,35 @@ func TestRoundTrip(t *testing.T) {\n \t}\n }\n \n+// TestRoundTrip32 tries a fraction of all finite positive float32 values.\n+func TestRoundTrip32(t *testing.T) {\n+\tstep := uint32(997)\n+\tif testing.Short() {\n+\t\tstep = 99991\n+\t}\n+\tcount := 0\n+\tfor i := uint32(0); i < 0xff<<23; i += step {\n+\t\tf := math.Float32frombits(i)\n+\t\tif i&1 == 1 {\n+\t\t\tf = -f // negative\n+\t\t}\n+\t\ts := FormatFloat(float64(f), \'g\', -1, 32)\n+\n+\t\tparsed, err := ParseFloat(s, 32)\n+\t\tparsed32 := float32(parsed)\n+\t\tswitch {\n+\t\tcase err != nil:\n+\t\t\tt.Errorf(\"ParseFloat(%q, 32) gave error %s\", s, err)\n+\t\tcase float64(parsed32) != parsed:\n+\t\t\tt.Errorf(\"ParseFloat(%q, 32) = %v, not a float32 (nearest is %v)\", s, parsed, parsed32)\n+\t\tcase parsed32 != f:\n+\t\t\tt.Errorf(\"ParseFloat(%q, 32) = %b (expected %b)\", s, parsed32, f)\n+\t\t}\n+\t\tcount++;\n+\t}\n+\tt.Logf(\"tested %d float32\'s\", count)\n+}\n+\n func BenchmarkAtof64Decimal(b *testing.B) {\n \tfor i := 0; i < b.N; i++ {\n \t\tParseFloat(\"33909\", 64)\n@@ -299,3 +387,35 @@ func BenchmarkAtof64RandomFloats(b *testing.B) {\n \t\tParseFloat(benchmarksRandomNormal[i%1024], 64)\n \t}\n }\n+\n+func BenchmarkAtof32Decimal(b *testing.B) {\n+\tfor i := 0; i < b.N; i++ {\n+\t\tParseFloat(\"33909\", 32)\n+\t}\n+}\n+\n+func BenchmarkAtof32Float(b *testing.B) {\n+\tfor i := 0; i < b.N; i++ {\n+\t\tParseFloat(\"339.778\", 32)\n+\t}\n+}\n+\n+func BenchmarkAtof32FloatExp(b *testing.B) {\n+\tfor i := 0; i < b.N; i++ {\n+\t\tParseFloat(\"12.3456e32\", 32)\n+\t}\n+}\n+\n+var float32strings [4096]string\n+\n+func BenchmarkAtof32Random(b *testing.B) {\n+\tn := uint32(997)\n+\tfor i := range float32strings {\n+\t\tn = (99991*n + 42) % (0xff << 23)\n+\t\tfloat32strings[i] = FormatFloat(float64(math.Float32frombits(n)), \'g\', -1, 32)\n+\t}\n+\tb.ResetTimer()\n+\tfor i := 0; i < b.N; i++ {\n+\t\tParseFloat(float32strings[i%4096], 32)\n+\t}\n+}\n```

### `src/pkg/strconv/extfloat.go`

`floatBits`と`AssignDecimal`メソッドの引数に`*floatInfo`が追加され、汎用化されています。

```diff
--- a/src/pkg/strconv/extfloat.go
+++ b/src/pkg/strconv/extfloat.go
@@ -127,8 +127,7 @@ var powersOfTen = [...]extFloat{\n // floatBits returns the bits of the float64 that best approximates\n // the extFloat passed as receiver. Overflow is set to true if\n // the resulting float64 is ±Inf.\n-func (f *extFloat) floatBits() (bits uint64, overflow bool) {\n-\tflt := &float64info\n+func (f *extFloat) floatBits(flt *floatInfo) (bits uint64, overflow bool) {\n \tf.Normalize()\n \n \texp := f.exp + 63
@@ -140,7 +139,7 @@ func (f *extFloat) floatBits() (bits uint64, overflow bool) {\n \t\texp += n\n \t}\n \n-\t// Extract 1+flt.mantbits bits.\n+\t// Extract 1+flt.mantbits bits from the 64-bit mantissa.\n \tmant := f.mant >> (63 - flt.mantbits)\n \tif f.mant&(1<<(62-flt.mantbits)) != 0 {\n \t\t// Round up.\n@@ -266,8 +265,9 @@ var uint64pow10 = [...]uint64{\n \n // AssignDecimal sets f to an approximate value mantissa*10^exp. It\n // returns true if the value represented by f is guaranteed to be the\n-// best approximation of d after being rounded to a float64. \n-func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc bool) (ok bool) {\n+// best approximation of d after being rounded to a float64 or\n+// float32 depending on flt.\n+func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc bool, flt *floatInfo) (ok bool) {\n \tconst uint64digits = 19\n \tconst errorscale = 8\n \terrors := 0 // An upper bound for error, computed in errorscale*ulp.\n@@ -315,10 +315,10 @@ func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc boo\n \t// The 64 bits mantissa is 1 + 52 bits for float64 + 11 extra bits.\n \t//\n \t// In many cases the approximation will be good enough.\n-\tconst denormalExp = -1023 - 63\n-\tflt := &float64info\n+\tdenormalExp := flt.bias - 63\n \tvar extrabits uint\n \tif f.exp <= denormalExp {\n+\t\t// f.mant * 2^f.exp is smaller than 2^(flt.bias+1).\n \t\textrabits = uint(63 - flt.mantbits + 1 + uint(denormalExp-f.exp))\n \t} else {\n \t\textrabits = uint(63 - flt.mantbits)\n```

## コアとなるコードの解説

### `atof32exact`関数の導入とロジック

このコミットの最も重要な変更は、`atof32exact`関数の導入です。この関数は、数値文字列から抽出された仮数部(`mantissa`)、指数部(`exp`)、符号(`neg`)を直接受け取り、`float32`の浮動小数点演算のみで正確な変換が可能かどうかを判断し、可能であればその結果を返します。

*   **`mantissa >> float32info.mantbits != 0`**: これは、入力された`mantissa`が`float32`の仮数部で表現できる範囲を超えているかをチェックしています。`float32info.mantbits`は`float32`の仮数部のビット数(23ビット)を示します。`mantissa`が24ビット以上の場合、`float32`で正確に表現できないため、この高速パスは適用されません。
*   **`f = float32(mantissa)`**: `mantissa`が`float32`で表現できる範囲内であれば、まず`mantissa`を`float32`に変換します。
*   **`if neg { f = -f }`**: 符号を適用します。
*   **`switch`文による指数部の処理**:
    *   `case exp == 0`: 指数部が0の場合、`mantissa`がそのまま結果となります。これは最も単純なケースです。
    *   `case exp > 0 && exp <= 7+10`: 正の指数部を持つ場合。`7+10`は、`float32`で正確に表現できる整数部の最大桁数(約7桁)と、10のべき乗の乗算で対応できる範囲(最大10^10)を考慮したものです。`float32pow10`配列を使用して、10のべき乗を効率的に乗算します。`f > 1e7 || f < -1e7`のチェックは、乗算後の値が`float32`の仮数部で正確に表現できる範囲を超えていないかを確認しています。
    *   `case exp < 0 && exp >= -10`: 負の指数部を持つ場合。`float32pow10`配列を使用して、10のべき乗で効率的に除算します。

この`atof32exact`関数は、数値が`float32`の精度内で正確に表現できる場合に、高精度な`decimal`や`extFloat`の計算をスキップすることで、大幅な性能向上を実現しています。

### `atof32`関数の変更

`atof32`関数は、`strconv.ParseFloat(s, 32)`の内部で呼び出される主要な関数です。この関数に、新しい高速パスが組み込まれました。

```go
	if optimize {
		// Parse mantissa and exponent.
		mantissa, exp, neg, trunc, ok := readFloat(s)
		if ok {
			// Try pure floating-point arithmetic conversion.
			if !trunc { // 文字列のパース中に切り捨てが発生しなかった場合
				if f, ok := atof32exact(mantissa, exp, neg); ok { // atof32exactで正確な変換が可能か試す
					return f, nil
				}
			}
			// Try another fast path.
			ext := new(extFloat)
			// ext.AssignDecimalがfloat32infoを受け取るように変更
			if ok := ext.AssignDecimal(mantissa, exp, neg, trunc, &float32info); ok {
				// ext.floatBitsがfloat32infoを受け取るように変更
				b, ovf := ext.floatBits(&float32info)
				f = math.Float32frombits(uint32(b))
				if ovf {
					err = rangeError(fnParseFloat, s)
				}
				return f, err
			}
		}
	}
	// 高速パスが適用できない場合、従来のdecimal変換にフォールバック
	var d decimal
	if !d.set(s) {
		return 0, syntaxError(fnParseFloat, s)
	}
	b, ovf := d.floatBits(&float32info) // decimal.floatBitsもfloat32infoを受け取る
	f = math.Float32frombits(uint32(b))
	if ovf {
		err = rangeError(fnParseFloat, s)
	}
	return f, err

この変更により、atof32は以下の優先順位で変換を試みます。

  1. readFloatによる初期パース: まず、文字列を基本的な数値要素(仮数、指数、符号)に分解します。
  2. atof32exactによる純粋な浮動小数点演算: readFloatで切り捨てが発生せず、かつatof32exactで正確な変換が可能であれば、この最速パスを使用します。
  3. extFloat.AssignDecimalによる高速パス: atof32exactが適用できない場合でも、extFloatを用いた別の高速パスを試みます。このパスは、extFloatfloat32infoを受け取るように汎用化されたことで、float32に特化した最適化が可能になりました。
  4. decimalによる高精度変換: 上記の高速パスがすべて適用できない場合、decimal型を用いた従来の高精度変換にフォールバックします。これは最も堅牢なパスであり、すべてのケースで正確な結果を保証します。

extFloatの汎用化

src/pkg/strconv/extfloat.gofloatBitsAssignDecimalメソッドは、*floatInfo型の引数fltを受け取るように変更されました。

  • floatBits(flt *floatInfo): このメソッドは、extFloatの値を指定されたfloatInfofloat32infoまたはfloat64info)に基づいて、最終的な浮動小数点数のビット表現に変換します。これにより、float32float64の両方で同じロジックを再利用できるようになりました。
  • AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc bool, flt *floatInfo): このメソッドは、10進数の仮数、指数、符号などからextFloatの値を設定します。ここでもflt引数を導入することで、float32float64のどちらの型に変換するかをメソッド内で考慮できるようになり、より汎用的な処理が可能になりました。特に、非正規化数(denormalized numbers)の指数範囲の計算(denormalExp := flt.bias - 63)がfloatInfoに基づいて行われるようになり、float32float64で異なる非正規化数の範囲を正しく扱えるようになりました。

これらの変更は、コードの重複を減らし、strconvパッケージの浮動小数点数変換ロジックの柔軟性と保守性を向上させています。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (src/pkg/strconv/atof.go, src/pkg/strconv/atof_test.go, src/pkg/strconv/extfloat.go)
  • Go言語のコミット履歴
  • 浮動小数点数に関する一般的な知識(IEEE 754、丸めなど)
  • 数値パースアルゴリズムに関する一般的な情報
  • Go言語のベンチマークに関する情報
  • Go言語のコードレビューシステム (Gerrit) のCL (Change-list) 6294071: https://golang.org/cl/6294071 (コミットメッセージに記載)
    • このCLは、コミットの変更内容と議論の詳細を提供しており、コミットの背景と意図を深く理解する上で非常に有用です。
    • 特に、atof32exactのロジックや、extFloatの汎用化に関する議論が確認できます。
    • float32pow10などの定数配列が、高速化に寄与していることも示唆されています。
    • readFloat関数が、数値文字列から仮数部と指数部を効率的に抽出する役割を担っていることも、CLの議論から読み取れます。

これらの情報源を総合的に分析することで、このコミットがstrconv.ParseFloatfloat32変換における性能と正確性の両方を向上させるための、洗練された最適化であることが理解できます。