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

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

このコミットは、Go言語の標準ライブラリstrconvパッケージにおける浮動小数点数と文字列間の変換処理、特にfloat32型に対するGrisu3アルゴリズムの拡張と、浮動小数点数の正規化処理の改善に焦点を当てています。これにより、浮動小数点数の文字列変換(ftoa)および文字列から浮動小数点数への変換(atof)のパフォーマンスが向上しています。

コミット

commit d6147d8102b095caac3267f9864a4025650c43f8
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Tue Jul 10 07:44:23 2012 +0200

    strconv: extend Grisu3 algorithm to float32.
    
    Also improve extfloat.Normalize to obtain a modest performance
    gain in parsing, and add a shortcut path for exact integers.
    
    benchmark                              old ns/op    new ns/op    delta
    BenchmarkAtof64Decimal                        73           73   -0.54%
    BenchmarkAtof64Float                          91           91   -0.54%
    BenchmarkAtof64FloatExp                      198          180   -9.09%
    BenchmarkAtof64Big                           307          308   +0.33%
    
    BenchmarkAtof32Decimal                        72           72   +0.42%
    BenchmarkAtof32Float                          83           83   -0.72%
    BenchmarkAtof32FloatExp                      212          186  -12.26%
    BenchmarkAtof32Random                        262          250   -4.58%
    
    BenchmarkAppendFloatDecimal                  474          305  -35.65%
    BenchmarkAppendFloat                         497          489   -1.61%
    BenchmarkAppendFloatExp                      493          483   -2.03%
    BenchmarkAppendFloatNegExp                   481          481   +0.00%
    BenchmarkAppendFloatBig                      667          652   -2.25%
    
    BenchmarkAppendFloat32Integer                338          307   -9.17%
    BenchmarkAppendFloat32ExactFraction          364          439  +20.60%
    BenchmarkAppendFloat32Point                 1299          490  -62.28%
    BenchmarkAppendFloat32Exp                   2593          489  -81.14%
    BenchmarkAppendFloat32NegExp                5116          481  -90.60%
    
    R=rsc, r
    CC=golang-dev, remy
    https://golang.org/cl/6303087

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

https://github.com/golang/go/commit/d6147d8102b095caac3267f9864a4025650c43f8

元コミット内容

このコミットは、Go言語のstrconvパッケージにおいて、以下の主要な変更を導入しています。

  1. Grisu3アルゴリズムのfloat32への拡張: 以前はfloat64にのみ適用されていたGrisu3アルゴリズムが、float32型にも適用されるようになりました。Grisu3は、浮動小数点数を最短かつ正確な10進数文字列に変換するための効率的なアルゴリズムです。
  2. extfloat.Normalize関数の改善: 浮動小数点数の内部表現を正規化するextfloat.Normalize関数の実装が最適化されました。これにより、浮動小数点数のパース(文字列から数値への変換)において、わずかながらパフォーマンスの向上が見られます。
  3. 正確な整数値に対するショートカットパスの追加: 浮動小数点数が正確な整数値を表す場合、その変換処理を高速化するための特別なパスが追加されました。

これらの変更は、ベンチマーク結果に示されているように、特にfloat32の文字列変換(AppendFloat32*ベンチマーク)において劇的なパフォーマンス向上をもたらしています。

変更の背景

浮動小数点数を10進数文字列に変換する処理は、コンピュータサイエンスにおいて長年の課題でした。特に、IEEE 754浮動小数点数標準に準拠しつつ、最短かつ正確な10進数表現を得ることは複雑です。従来のアルゴリズムは、精度を保証するために多くの計算を必要とするか、あるいは最短表現を保証できないというトレードオフがありました。

Grisu3アルゴリズムは、この問題に対する効率的な解決策の一つとして提案されました。これは、浮動小数点数の内部表現(2進数)から直接、最短かつ正確な10進数表現を生成することを目指します。Go言語のstrconvパッケージでは、既にfloat64に対してGrisu3が採用されていましたが、このコミット以前はfloat32には適用されていませんでした。

このコミットの背景には、float32の文字列変換性能の改善と、浮動小数点数処理全般の効率化という目的があります。特に、Webアプリケーションやデータ処理など、浮動小数点数の文字列変換が頻繁に行われるシナリオにおいて、パフォーマンスのボトルネックを解消することが期待されます。また、extfloat.Normalizeの改善や正確な整数値のショートカットパスは、浮動小数点数の内部処理の効率を高め、全体的なパフォーマンス向上に寄与します。

前提知識の解説

1. IEEE 754 浮動小数点数標準

Go言語のfloat32float64は、IEEE 754標準に準拠しています。この標準は、浮動小数点数を符号、指数部、仮数部の3つの要素で表現します。

  • 符号 (Sign): 数値が正か負かを示す1ビット。
  • 指数部 (Exponent): 数値の大きさを表す部分。バイアス形式で表現され、実際の指数にオフセットが加算されています。
  • 仮数部 (Mantissa/Significand): 数値の精度を表す部分。正規化された浮動小数点数では、常に先頭に暗黙の1ビット(implicit leading bit)があると仮定されます。

float32 (単精度) は32ビットで、符号1ビット、指数部8ビット、仮数部23ビット(暗黙の1ビットを含めると24ビット)で構成されます。 float64 (倍精度) は64ビットで、符号1ビット、指数部11ビット、仮数部52ビット(暗黙の1ビットを含めると53ビット)で構成されます。

2. 浮動小数点数の正規化 (Normalization)

浮動小数点数の正規化とは、仮数部の最上位ビットが常に1になるように調整するプロセスです。これにより、仮数部の精度を最大限に活用し、同じ数値を複数の異なる表現で表す「冗長性」を排除します。例えば、0.00101 * 2^51.01 * 2^2 と正規化されます。非正規化数(denormalized numbers)は、アンダーフローを処理するために正規化されない特別なケースです。

extfloat.Normalize関数は、この正規化処理を担当し、仮数部を左シフトして最上位ビットをセットし、それに応じて指数部を調整します。

3. Grisu3 アルゴリズム

Grisu3は、浮動小数点数を最短かつ正確な10進数文字列に変換するためのアルゴリズムです。その主な特徴は以下の通りです。

  • 最短表現: 浮動小数点数を10進数に変換する際、元の浮動小数点数を一意に識別できる最小桁数の10進数表現を生成します。例えば、0.1は内部的には正確に表現できないため、0.1000000000000000055511151231257827021181583404541015625のような2進数表現になりますが、Grisu3はこれを0.1と出力します。
  • 正確性: 変換された10進数文字列を再度浮動小数点数に変換すると、元の浮動小数点数と完全に一致することを保証します。
  • 効率性: 他の正確な変換アルゴリズム(例: Dragon4)と比較して、Grisu3は一般的に高速です。これは、浮動小数点数の内部表現を直接操作し、多倍長整数演算を最小限に抑えることで実現されます。Grisu3は、特定の範囲の浮動小数点数に対しては非常に高速ですが、全ての浮動小数点数に対応できるわけではありません。対応できない場合は、より汎用的なアルゴリズム(例: Dragon4の変種)にフォールバックします。

4. strconvパッケージ

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

技術的詳細

strconv/decimal.goの変更

decimal.goでは、decimal構造体のAssignメソッド内で使用されるバッファのサイズが[50]byteから[24]byteに縮小されました。

--- a/src/pkg/strconv/decimal.go
+++ b/src/pkg/strconv/decimal.go
@@ -79,7 +79,7 @@ func trim(a *decimal) {
 
 // Assign v to a.
 func (a *decimal) Assign(v uint64) {
-	var buf [50]byte
+	var buf [24]byte
 
 	// Write reversed decimal in buf.
 	n := 0

この変更は、uint64の最大値(約1.8 x 10^19)が10進数で20桁であるため、その文字列表現を格納するのに50バイトは過剰であり、24バイトで十分であるという最適化です。これにより、メモリ使用量がわずかに削減されます。

strconv/extfloat.goの変更

AssignComputeBoundsの変更

AssignComputeBounds関数のシグネチャと実装が大きく変更されました。

--- a/src/pkg/strconv/extfloat.go
+++ b/src/pkg/strconv/extfloat.go
@@ -190,29 +190,24 @@ func (f *extFloat) Assign(x float64) {
 	f.exp -= 64
 }
 
-// AssignComputeBounds sets f to the value of x and returns
+// AssignComputeBounds sets f to the floating point value
+// defined by mant, exp and precision given by flt. It returns
 // lower, upper such that any number in the closed interval
-// [lower, upper] is converted back to x.\n-func (f *extFloat) AssignComputeBounds(x float64) (lower, upper extFloat) {
-\t// Special cases.\n-\tbits := math.Float64bits(x)\n-\tflt := &float64info\n-\tneg := bits>>(flt.expbits+flt.mantbits) != 0\n-\texpBiased := int(bits>>flt.mantbits) & (1<<flt.expbits - 1)\n-\tmant := bits & (uint64(1)<<flt.mantbits - 1)\n-\n-\tif expBiased == 0 {\n-\t\t// denormalized.\n-\t\tf.mant = mant\n-\t\tf.exp = 1 + flt.bias - int(flt.mantbits)\n-\t} else {\n-\t\tf.mant = mant | 1<<flt.mantbits\n-\t\tf.exp = expBiased + flt.bias - int(flt.mantbits)\n-\t}\n+// [lower, upper] is converted back to the same floating point number.
+func (f *extFloat) AssignComputeBounds(mant uint64, exp int, neg bool, flt *floatInfo) (lower, upper extFloat) {
+\tf.mant = mant
+\tf.exp = exp - int(flt.mantbits)
 \tf.neg = neg
+\tif f.exp <= 0 && mant == (mant>>uint(-f.exp))<<uint(-f.exp) {
+\t\t// An exact integer
+\t\tf.mant >>= uint(-f.exp)
+\t\tf.exp = 0
+\t\treturn *f, *f
+\t}
+\texpBiased := exp - flt.bias
 \n \tupper = extFloat{mant: 2*f.mant + 1, exp: f.exp - 1, neg: f.neg}
-\tif mant != 0 || expBiased == 1 {\n+\tif mant != 1<<flt.mantbits || expBiased == 1 {
 \t\tlower = extFloat{mant: 2*f.mant - 1, exp: f.exp - 1, neg: f.neg}
 \t} else {\n \t\tlower = extFloat{mant: 4*f.mant - 1, exp: f.exp - 2, neg: f.neg}
@@ -222,20 +217,38 @@ func (f *extFloat) AssignComputeBounds(x float64) (lower, upper extFloat) {

変更前はfloat64型のxを受け取っていましたが、変更後はmant (仮数部), exp (指数部), neg (符号), flt (floatInfo構造体) といった浮動小数点数の構成要素を直接受け取るようになりました。これにより、math.Float64bitsを呼び出してビットを分解するオーバーヘッドがなくなります。

さらに重要なのは、正確な整数値に対するショートカットパスが追加されたことです。

	if f.exp <= 0 && mant == (mant>>uint(-f.exp))<<uint(-f.exp) {
		// An exact integer
		f.mant >>= uint(-f.exp)
		f.exp = 0
		return *f, *f
	}

このコードは、浮動小数点数が正確な整数を表すかどうかをチェックします。f.exp <= 0は、指数部が0以下であることを意味し、これは数値が1以下の整数(または非常に小さい非整数)であることを示唆します。mant == (mant>>uint(-f.exp))<<uint(-f.exp)は、仮数部が指数部によって示されるシフト量で正確に割り切れる(つまり、小数部がない)ことを確認します。もし条件が満たされれば、その数値は正確な整数であり、f.mantを適切にシフトしてf.expを0に設定し、lowerupperの境界をf自身に設定して、以降の複雑な境界計算をスキップします。これにより、整数値の変換が高速化されます。

Normalize関数の改善

Normalize関数は、仮数部を正規化するための処理です。

--- a/src/pkg/strconv/extfloat.go
+++ b/src/pkg/strconv/extfloat.go
@@ -222,20 +217,38 @@ func (f *extFloat) AssignComputeBounds(x float64) (lower, upper extFloat) {
 
 // Normalize normalizes f so that the highest bit of the mantissa is
 // set, and returns the number by which the mantissa was left-shifted.
-func (f *extFloat) Normalize() uint {
-\tif f.mant == 0 {\n \t\treturn 0
-\t}\n-\texp_before := f.exp\n-\tfor f.mant < (1 << 55) {\n-\t\tf.mant <<= 8\n-\t\tf.exp -= 8\n+\tshift = uint(f.exp - exp)
+\tf.mant, f.exp = mant, exp
+\treturn
 }
-\tfor f.mant < (1 << 63) {\n-\t\tf.mant <<= 1\n-\t\tf.exp -= 1\n+\tshift = uint(f.exp - exp)
+\tf.mant, f.exp = mant, exp
+\treturn
 }
-\treturn uint(exp_before - f.exp)\n+\tshift = uint(f.exp - exp)
+\tf.mant, f.exp = mant, exp
+\treturn
 }
 
 // Multiply sets f to the product f*g: the result is correctly rounded,
@@ -390,6 +403,12 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool {
 	\td.dp = 0
 	\td.neg = f.neg
 	}\n+\tif f.exp == 0 && *lower == *f && *lower == *upper {
+\t\t// an exact integer.
+\t\td.Assign(f.mant)
+\t\td.neg = f.neg
+\t\treturn true
+\t}
 	const minExp = -60
 	const maxExp = -32
 	upper.Normalize()

変更前は、仮数部を8ビットずつ、その後1ビットずつ左シフトして正規化していました。変更後は、より効率的な「バイナリサーチ」のようなシフト戦略を採用しています。

	if mant>>(64-32) == 0 {
		mant <<= 32
		exp -= 32
	}
	if mant>>(64-16) == 0 {
		mant <<= 16
		exp -= 16
	}
	if mant>>(64-8) == 0 {
		mant <<= 8
		exp -= 8
	}
	if mant>>(64-4) == 0 {
		mant <<= 4
		exp -= 4
	}
	if mant>>(64-2) == 0 {
		mant <<= 2
		exp -= 2
	}
	if mant>>(64-1) == 0 {
		mant <<m 1
		exp -= 1
	}

この新しいアプローチでは、まず32ビット、次に16ビット、8ビット、4ビット、2ビット、1ビットと、大きなシフト量から順にチェックし、必要に応じてシフトを行います。これにより、仮数部が63ビット目にセットされるまでのシフト回数を大幅に削減し、正規化処理を高速化します。

ShortestDecimalの変更

ShortestDecimal関数にも、正確な整数値に対するショートカットパスが追加されました。

--- a/src/pkg/strconv/extfloat.go
+++ b/src/pkg/strconv/extfloat.go
@@ -390,6 +403,12 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool {
 	\td.dp = 0
 	\td.neg = f.neg
 	}\n+\tif f.exp == 0 && *lower == *f && *lower == *upper {
+\t\t// an exact integer.
+\t\td.Assign(f.mant)
+\t\td.neg = f.neg
+\t\treturn true
+\t}
 	const minExp = -60
 	const maxExp = -32
 	upper.Normalize()

これはAssignComputeBoundsで追加されたショートカットと連携しており、もし浮動小数点数が正確な整数であり、その境界が自身と一致する場合(つまり、その浮動小数点数が一意の整数を表す場合)、直接decimal型に変換して処理を終了します。これにより、Grisu3アルゴリズムの複雑な計算をスキップし、パフォーマンスを向上させます。

strconv/ftoa.goの変更

ftoa.goでは、genericFtoa関数内でGrisu3アルゴリズムを適用する条件が変更されました。

--- a/src/pkg/strconv/ftoa.go
+++ b/src/pkg/strconv/ftoa.go
@@ -104,10 +104,10 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte {
 	\td := new(decimal)
 	\tif shortest {
 	\t\tok := false
-\t\t\tif optimize && bitSize == 64 {
+\t\t\tif optimize {
 	\t\t\t// Try Grisu3 algorithm.
 	\t\t\tf := new(extFloat)
-\t\t\t\tlower, upper := f.AssignComputeBounds(val)
+\t\t\t\tlower, upper := f.AssignComputeBounds(mant, exp, neg, flt)
 	\t\t\tok = f.ShortestDecimal(d, &lower, &upper)
 	\t\t}
 	\t\tif !ok {

以前はoptimize && bitSize == 64という条件でGrisu3が適用されていましたが、bitSize == 64の条件が削除され、単にoptimizeが真であればGrisu3が試行されるようになりました。これにより、float32bitSize == 32)に対してもGrisu3アルゴリズムが適用されるようになり、コミットメッセージの「extend Grisu3 algorithm to float32」が実現されました。

また、f.AssignComputeBounds(val)の呼び出しが、f.AssignComputeBounds(mant, exp, neg, flt)に変更されました。これは、extfloat.goAssignComputeBoundsのシグネチャが変更されたことに対応するものです。

strconv/ftoa_test.goの変更

テストファイルでは、ベンチマーク関数がリファクタリングされ、float32に対する新しいベンチマークが追加されました。

--- a/src/pkg/strconv/ftoa_test.go
+++ b/src/pkg/strconv/ftoa_test.go
@@ -203,37 +203,23 @@ func BenchmarkFormatFloatBig(b *testing.B) {
 	}\n }\n \n-func BenchmarkAppendFloatDecimal(b *testing.B) {
-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, 33909, \'g\', -1, 64)
-\t}\n-}\n-\n-func BenchmarkAppendFloat(b *testing.B) {
-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, 339.7784, \'g\', -1, 64)
-\t}\n-}\n-\n-func BenchmarkAppendFloatExp(b *testing.B) {
-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, -5.09e75, \'g\', -1, 64)
-\t}\n-}\n-\n-func BenchmarkAppendFloatNegExp(b *testing.B) {
-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, -5.11e-95, \'g\', -1, 64)
-\t}\n-}\n-\n+func benchmarkAppendFloat(b *testing.B, f float64, fmt byte, prec, bitSize int) {
+\tdst := make([]byte, 30)
+\tfor i := 0; i < b.N; i++ {
+\t\tAppendFloat(dst[:0], f, fmt, prec, bitSize)
+\t}\n }\n \n+func BenchmarkAppendFloatDecimal(b *testing.B) { benchmarkAppendFloat(b, 33909, \'g\', -1, 64) }\n+func BenchmarkAppendFloat(b *testing.B)        { benchmarkAppendFloat(b, 339.7784, \'g\', -1, 64) }\n+func BenchmarkAppendFloatExp(b *testing.B)     { benchmarkAppendFloat(b, -5.09e75, \'g\', -1, 64) }\n+func BenchmarkAppendFloatNegExp(b *testing.B)  { benchmarkAppendFloat(b, -5.11e-95, \'g\', -1, 64) }\n func BenchmarkAppendFloatBig(b *testing.B) {\n-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, 123456789123456789123456789, \'g\', -1, 64)
-\t}\n+\tbenchmarkAppendFloat(b, 123456789123456789123456789, \'g\', -1, 64)
 }\n+\n+func BenchmarkAppendFloat32Integer(b *testing.B)       { benchmarkAppendFloat(b, 33909, \'g\', -1, 32) }\n+func BenchmarkAppendFloat32ExactFraction(b *testing.B) { benchmarkAppendFloat(b, 3.375, \'g\', -1, 32) }\n+func BenchmarkAppendFloat32Point(b *testing.B)         { benchmarkAppendFloat(b, 339.7784, \'g\', -1, 32) }\n+func BenchmarkAppendFloat32Exp(b *testing.B)           { benchmarkAppendFloat(b, -5.09e25, \'g\', -1, 32) }\n+func BenchmarkAppendFloat32NegExp(b *testing.B)        { benchmarkAppendFloat(b, -5.11e-25, \'g\', -1, 32) }\n```
複数の`BenchmarkAppendFloat*`関数が、共通のヘルパー関数`benchmarkAppendFloat`を使用するように変更されました。これにより、ベンチマークコードの重複が削減され、可読性と保守性が向上します。

最も重要なのは、`float32`に対する新しいベンチマークが追加されたことです。
*   `BenchmarkAppendFloat32Integer`
*   `BenchmarkAppendFloat32ExactFraction`
*   `BenchmarkAppendFloat32Point`
*   `BenchmarkAppendFloat32Exp`
*   `BenchmarkAppendFloat32NegExp`

これらのベンチマークは、`float32`の様々なケース(整数、正確な分数、小数点を含む数値、指数表記の数値)における`AppendFloat`のパフォーマンスを測定するために導入されました。コミットメッセージのベンチマーク結果が示すように、これらの新しいベンチマークは、`float32`の文字列変換において大幅なパフォーマンス向上を示しています。特に、`BenchmarkAppendFloat32Exp`と`BenchmarkAppendFloat32NegExp`では80%以上の改善が見られ、Grisu3アルゴリズムの`float32`への適用が非常に効果的であったことを裏付けています。

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

このコミットのコアとなる変更は、主に以下のファイルと関数に集中しています。

1.  **`src/pkg/strconv/extfloat.go`**:
    *   `AssignComputeBounds`関数のシグネチャ変更と、正確な整数値に対するショートカットパスの追加。
    *   `Normalize`関数の正規化ロジックの最適化(バイナリサーチのようなシフト戦略)。
    *   `ShortestDecimal`関数における正確な整数値のショートカットパスの追加。

2.  **`src/pkg/strconv/ftoa.go`**:
    *   `genericFtoa`関数内で、Grisu3アルゴリズムの適用条件から`bitSize == 64`の制約を削除し、`float32`にも適用可能にした点。
    *   `AssignComputeBounds`の呼び出し引数の変更。

これらの変更が連携して、浮動小数点数の文字列変換の精度とパフォーマンスを向上させています。

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

### `extfloat.go`の`Normalize`関数(変更後)

```go
func (f *extFloat) Normalize() (shift uint) {
	mant, exp := f.mant, f.exp
	if mant == 0 {
		return 0
	}
	// Fast path for common shifts
	if mant>>(64-32) == 0 {
		mant <<= 32
		exp -= 32
	}
	if mant>>(64-16) == 0 {
		mant <<= 16
		exp -= 16
	}
	if mant>>(64-8) == 0 {
		mant <<= 8
		exp -= 8
	}
	if mant>>(64-4) == 0 {
		mant <<= 4
		exp -= 4
	}
	if mant>>(64-2) == 0 {
		mant <<= 2
		exp -= 2
	}
	if mant>>(64-1) == 0 {
		mant <<= 1
		exp -= 1
	}
	shift = uint(f.exp - exp) // Calculate total shift amount
	f.mant, f.exp = mant, exp
	return
}

このNormalize関数は、extFloat構造体の仮数部f.mantを正規化します。正規化とは、仮数部の最上位ビット(63ビット目、0-indexed)が1になるように左シフトすることです。

  1. mant, exp := f.mant, f.exp: 現在の仮数部と指数部をローカル変数にコピーします。
  2. if mant == 0 { return 0 }: 仮数部が0の場合は、正規化の必要がないため0を返します。
  3. 高速パス(バイナリサーチのようなシフト):
    • if mant>>(64-32) == 0: 仮数部を32ビット右シフトした結果が0の場合、仮数部の最上位ビットはまだ64-32=32ビット目より下にあることを意味します。この場合、仮数部を32ビット左シフトし、指数部を32減らします。
    • 同様に、16ビット、8ビット、4ビット、2ビット、1ビットと、より小さいシフト量でチェックとシフトを繰り返します。この一連の条件分岐により、仮数部が効率的に正規化されます。例えば、仮数部が非常に小さい場合でも、一度に大きなシフトを行うことで、ループ処理よりも少ないステップで正規化が完了します。
  4. shift = uint(f.exp - exp): 正規化によって指数部がどれだけ変化したか(つまり、仮数部がどれだけ左シフトされたか)を計算します。
  5. f.mant, f.exp = mant, exp: 正規化された仮数部と指数部をextFloat構造体に書き戻します。
  6. return: シフト量を返します。

この最適化された正規化処理は、浮動小数点数のパースや文字列変換において、内部的なビット操作の効率を高め、全体的なパフォーマンス向上に貢献します。

ftoa.gogenericFtoa関数(関連部分)

func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte {
	// ... (前略) ...
	d := new(decimal)
	if shortest {
		ok := false
		if optimize { // Changed from optimize && bitSize == 64
			// Try Grisu3 algorithm.
			f := new(extFloat)
			// lower, upper := f.AssignComputeBounds(val) // Old call
			lower, upper := f.AssignComputeBounds(mant, exp, neg, flt) // New call
			ok = f.ShortestDecimal(d, &lower, &upper)
		}
		if !ok {
			// Fallback to slower but always correct algorithm (e.g., Dragon4 variant)
			// ... (後略) ...
		}
	}
	// ... (後略) ...
}

このコードスニペットは、浮動小数点数を最短の10進数文字列に変換するロジックの一部です。

  1. if shortest: 最短表現が必要な場合にこのブロックに入ります。
  2. if optimize: 最適化が有効な場合にGrisu3アルゴリズムを試行します。
    • 変更点: 以前はoptimize && bitSize == 64という条件があり、Grisu3はfloat64にのみ適用されていました。このコミットによりbitSize == 64の条件が削除されたため、float32bitSize == 32)に対してもGrisu3が試行されるようになりました。
  3. f := new(extFloat): 拡張浮動小数点数表現のためのextFloatインスタンスを作成します。
  4. lower, upper := f.AssignComputeBounds(mant, exp, neg, flt): 浮動小数点数の値から、その値を一意に識別できる10進数表現の範囲(下限lowerと上限upper)を計算します。ここで、extfloat.goで変更された新しいシグネチャのAssignComputeBoundsが呼び出されます。
  5. ok = f.ShortestDecimal(d, &lower, &upper): Grisu3アルゴリズムを使用して、extFloatから最短の10進数表現を計算し、decimal構造体dに格納します。成功すればoktrueになります。
  6. if !ok: Grisu3が適用できない(または失敗した)場合、より汎用的な(しかし遅い)アルゴリズムにフォールバックします。

この変更により、float32の文字列変換においてもGrisu3の高速な処理が利用できるようになり、特に指数部を持つ数値や小数点以下の桁数が多い数値の変換で顕著なパフォーマンス向上が実現されました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/pkg/strconvディレクトリ)
  • コミットメッセージとベンチマーク結果
  • Grisu3アルゴリズムに関する学術論文
  • IEEE 754浮動小数点数標準に関する資料
  • 浮動小数点数の正規化に関する一般的な情報
  • Go言語のベンチマークに関するドキュメントI have generated the detailed technical explanation in Markdown format, including all the requested sections and adhering to the specified language and detail level. I have also incorporated information about the Grisu3 algorithm, floating-point representation, and normalization based on my knowledge and the context of the commit.

I will now output the explanation to standard output.

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

このコミットは、Go言語の標準ライブラリ`strconv`パッケージにおける浮動小数点数と文字列間の変換処理、特に`float32`型に対するGrisu3アルゴリズムの拡張と、浮動小数点数の正規化処理の改善に焦点を当てています。これにより、浮動小数点数の文字列変換(`ftoa`)および文字列から浮動小数点数への変換(`atof`)のパフォーマンスが向上しています。

## コミット

commit d6147d8102b095caac3267f9864a4025650c43f8 Author: Rémy Oudompheng oudomphe@phare.normalesup.org Date: Tue Jul 10 07:44:23 2012 +0200

strconv: extend Grisu3 algorithm to float32.

Also improve extfloat.Normalize to obtain a modest performance
gain in parsing, and add a shortcut path for exact integers.

benchmark                              old ns/op    new ns/op    delta
BenchmarkAtof64Decimal                        73           73   -0.54%
BenchmarkAtof64Float                          91           91   -0.54%
BenchmarkAtof64FloatExp                      198          180   -9.09%
BenchmarkAtof64Big                           307          308   +0.33%

BenchmarkAtof32Decimal                        72           72   +0.42%
BenchmarkAtof32Float                          83           83   -0.72%
BenchmarkAtof32FloatExp                      212          186  -12.26%
BenchmarkAtof32Random                        262          250   -4.58%

BenchmarkAppendFloatDecimal                  474          305  -35.65%
BenchmarkAppendFloat                         497          489   -1.61%
BenchmarkAppendFloatExp                      493          483   -2.03%
BenchmarkAppendFloatNegExp                   481          481   +0.00%
BenchmarkAppendFloatBig                      667          652   -2.25%

BenchmarkAppendFloat32Integer                338          307   -9.17%
BenchmarkAppendFloat32ExactFraction          364          439  +20.60%
BenchmarkAppendFloat32Point                 1299          490  -62.28%
BenchmarkAppendFloat32Exp                   2593          489  -81.14%
BenchmarkAppendFloat32NegExp                5116          481  -90.60%

R=rsc, r
CC=golang-dev, remy
https://golang.org/cl/6303087

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

[https://github.com/golang/go/commit/d6147d8102b095caac3267f9864a4025650c43f8](https://github.com/golang/go/commit/d6147d8102b095caac3267f9864a4025650c43f8)

## 元コミット内容

このコミットは、Go言語の`strconv`パッケージにおいて、以下の主要な変更を導入しています。

1.  **Grisu3アルゴリズムの`float32`への拡張**: 以前は`float64`にのみ適用されていたGrisu3アルゴリズムが、`float32`型にも適用されるようになりました。Grisu3は、浮動小数点数を最短かつ正確な10進数文字列に変換するための効率的なアルゴリズムです。
2.  **`extfloat.Normalize`関数の改善**: 浮動小数点数の内部表現を正規化する`extfloat.Normalize`関数の実装が最適化されました。これにより、浮動小数点数のパース(文字列から数値への変換)において、わずかながらパフォーマンスの向上が見られます。
3.  **正確な整数値に対するショートカットパスの追加**: 浮動小数点数が正確な整数値を表す場合、その変換処理を高速化するための特別なパスが追加されました。

これらの変更は、ベンチマーク結果に示されているように、特に`float32`の文字列変換(`AppendFloat32*`ベンチマーク)において劇的なパフォーマンス向上をもたらしています。

## 変更の背景

浮動小数点数を10進数文字列に変換する処理は、コンピュータサイエンスにおいて長年の課題でした。特に、IEEE 754浮動小数点数標準に準拠しつつ、最短かつ正確な10進数表現を得ることは複雑です。従来のアルゴリズムは、精度を保証するために多くの計算を必要とするか、あるいは最短表現を保証できないというトレードオフがありました。

Grisu3アルゴリズムは、この問題に対する効率的な解決策の一つとして提案されました。これは、浮動小数点数の内部表現(2進数)から直接、最短かつ正確な10進数表現を生成することを目指します。Go言語の`strconv`パッケージでは、既に`float64`に対してGrisu3が採用されていましたが、このコミット以前は`float32`には適用されていませんでした。

このコミットの背景には、`float32`の文字列変換性能の改善と、浮動小数点数処理全般の効率化という目的があります。特に、Webアプリケーションやデータ処理など、浮動小数点数の文字列変換が頻繁に行われるシナリオにおいて、パフォーマンスのボトルネックを解消することが期待されます。また、`extfloat.Normalize`の改善や正確な整数値のショートカットパスは、浮動小数点数の内部処理の効率を高め、全体的なパフォーマンス向上に寄与します。

## 前提知識の解説

### 1. IEEE 754 浮動小数点数標準

Go言語の`float32`と`float64`は、IEEE 754標準に準拠しています。この標準は、浮動小数点数を符号、指数部、仮数部の3つの要素で表現します。

*   **符号 (Sign)**: 数値が正か負かを示す1ビット。
*   **指数部 (Exponent)**: 数値の大きさを表す部分。バイアス形式で表現され、実際の指数にオフセットが加算されています。
*   **仮数部 (Mantissa/Significand)**: 数値の精度を表す部分。正規化された浮動小数点数では、常に先頭に暗黙の1ビット(implicit leading bit)があると仮定されます。

`float32` (単精度) は32ビットで、符号1ビット、指数部8ビット、仮数部23ビット(暗黙の1ビットを含めると24ビット)で構成されます。
`float64` (倍精度) は64ビットで、符号1ビット、指数部11ビット、仮数部52ビット(暗黙の1ビットを含めると53ビット)で構成されます。

### 2. 浮動小数点数の正規化 (Normalization)

浮動小数点数の正規化とは、仮数部の最上位ビットが常に1になるように調整するプロセスです。これにより、仮数部の精度を最大限に活用し、同じ数値を複数の異なる表現で表す「冗長性」を排除します。例えば、`0.00101 * 2^5` は `1.01 * 2^2` と正規化されます。非正規化数(denormalized numbers)は、アンダーフローを処理するために正規化されない特別なケースです。

`extfloat.Normalize`関数は、この正規化処理を担当し、仮数部を左シフトして最上位ビットをセットし、それに応じて指数部を調整します。

### 3. Grisu3 アルゴリズム

Grisu3は、浮動小数点数を最短かつ正確な10進数文字列に変換するためのアルゴリズムです。その主な特徴は以下の通りです。

*   **最短表現**: 浮動小数点数を10進数に変換する際、元の浮動小数点数を一意に識別できる最小桁数の10進数表現を生成します。例えば、`0.1`は内部的には正確に表現できないため、`0.1000000000000000055511151231257827021181583404541015625`のような2進数表現になりますが、Grisu3はこれを`0.1`と出力します。
*   **正確性**: 変換された10進数文字列を再度浮動小数点数に変換すると、元の浮動小数点数と完全に一致することを保証します。
*   **効率性**: 他の正確な変換アルゴリズム(例: Dragon4)と比較して、Grisu3は一般的に高速です。これは、浮動小数点数の内部表現を直接操作し、多倍長整数演算を最小限に抑えることで実現されます。Grisu3は、特定の範囲の浮動小数点数に対しては非常に高速ですが、全ての浮動小数点数に対応できるわけではありません。対応できない場合は、より汎用的なアルゴリズム(例: Dragon4の変種)にフォールバックします。

### 4. `strconv`パッケージ

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

## 技術的詳細

### `strconv/decimal.go`の変更

`decimal.go`では、`decimal`構造体の`Assign`メソッド内で使用されるバッファのサイズが`[50]byte`から`[24]byte`に縮小されました。
```diff
--- a/src/pkg/strconv/decimal.go
+++ b/src/pkg/strconv/decimal.go
@@ -79,7 +79,7 @@ func trim(a *decimal) {
 
 // Assign v to a.
 func (a *decimal) Assign(v uint64) {
-	var buf [50]byte
+	var buf [24]byte
 
 	// Write reversed decimal in buf.
 	n := 0

この変更は、uint64の最大値(約1.8 x 10^19)が10進数で20桁であるため、その文字列表現を格納するのに50バイトは過剰であり、24バイトで十分であるという最適化です。これにより、メモリ使用量がわずかに削減されます。

strconv/extfloat.goの変更

AssignComputeBoundsの変更

AssignComputeBounds関数のシグネチャと実装が大きく変更されました。

--- a/src/pkg/strconv/extfloat.go
+++ b/src/pkg/strconv/extfloat.go
@@ -190,29 +190,24 @@ func (f *extFloat) Assign(x float64) {
 	f.exp -= 64
 }
 
-// AssignComputeBounds sets f to the value of x and returns
+// AssignComputeBounds sets f to the floating point value
+// defined by mant, exp and precision given by flt. It returns
 // lower, upper such that any number in the closed interval
-// [lower, upper] is converted back to x.\n-func (f *extFloat) AssignComputeBounds(x float64) (lower, upper extFloat) {
-\t// Special cases.\n-\tbits := math.Float64bits(x)\n-\tflt := &float64info\n-\tneg := bits>>(flt.expbits+flt.mantbits) != 0\n-\texpBiased := int(bits>>flt.mantbits) & (1<<flt.expbits - 1)\n-\tmant := bits & (uint64(1)<<flt.mantbits - 1)\n-\n-\tif expBiased == 0 {\n-\t\t// denormalized.\n-\t\tf.mant = mant\n-\t\tf.exp = 1 + flt.bias - int(flt.mantbits)\n-\t} else {\n-\t\tf.mant = mant | 1<<flt.mantbits\n-\t\tf.exp = expBiased + flt.bias - int(flt.mantbits)\n-\t}\n+// [lower, upper] is converted back to the same floating point number.
+func (f *extFloat) AssignComputeBounds(mant uint64, exp int, neg bool, flt *floatInfo) (lower, upper extFloat) {
+\tf.mant = mant
+\tf.exp = exp - int(flt.mantbits)
 \tf.neg = neg
+\tif f.exp <= 0 && mant == (mant>>uint(-f.exp))<<uint(-f.exp) {
+\t\t// An exact integer
+\t\tf.mant >>= uint(-f.exp)
+\t\tf.exp = 0
+\t\treturn *f, *f
+\t}
+\texpBiased := exp - flt.bias
 \n \tupper = extFloat{mant: 2*f.mant + 1, exp: f.exp - 1, neg: f.neg}
-\tif mant != 0 || expBiased == 1 {\n+\tif mant != 1<<flt.mantbits || expBiased == 1 {
 \t\tlower = extFloat{mant: 2*f.mant - 1, exp: f.exp - 1, neg: f.neg}
 \t} else {\n \t\tlower = extFloat{mant: 4*f.mant - 1, exp: f.exp - 2, neg: f.neg}
@@ -222,20 +217,38 @@ func (f *extFloat) AssignComputeBounds(x float64) (lower, upper extFloat) {

変更前はfloat64型のxを受け取っていましたが、変更後はmant (仮数部), exp (指数部), neg (符号), flt (floatInfo構造体) といった浮動小数点数の構成要素を直接受け取るようになりました。これにより、math.Float64bitsを呼び出してビットを分解するオーバーヘッドがなくなります。

さらに重要なのは、正確な整数値に対するショートカットパスが追加されたことです。

	if f.exp <= 0 && mant == (mant>>uint(-f.exp))<<uint(-f.exp) {
		// An exact integer
		f.mant >>= uint(-f.exp)
		f.exp = 0
		return *f, *f
	}

このコードは、浮動小数点数が正確な整数を表すかどうかをチェックします。f.exp <= 0は、指数部が0以下であることを意味し、これは数値が1以下の整数(または非常に小さい非整数)であることを示唆します。mant == (mant>>uint(-f.exp))<<uint(-f.exp)は、仮数部が指数部によって示されるシフト量で正確に割り切れる(つまり、小数部がない)ことを確認します。もし条件が満たされれば、その数値は正確な整数であり、f.mantを適切にシフトしてf.expを0に設定し、lowerupperの境界をf自身に設定して、以降の複雑な境界計算をスキップします。これにより、整数値の変換が高速化されます。

Normalize関数の改善

Normalize関数は、仮数部を正規化するための処理です。

--- a/src/pkg/strconv/extfloat.go
+++ b/src/pkg/strconv/extfloat.go
@@ -222,20 +217,38 @@ func (f *extFloat) AssignComputeBounds(x float64) (lower, upper extFloat) {
 
 // Normalize normalizes f so that the highest bit of the mantissa is
 // set, and returns the number by which the mantissa was left-shifted.
-func (f *extFloat) Normalize() uint {
-\tif f.mant == 0 {\n \t\treturn 0
-\t}\n-\texp_before := f.exp\n-\tfor f.mant < (1 << 55) {\n-\t\tf.mant <<= 8\n-\t\tf.exp -= 8\n+\tshift = uint(f.exp - exp)
+\tf.mant, f.exp = mant, exp
+\treturn
 }
-\tfor f.mant < (1 << 63) {\n-\t\tf.mant <<= 1\n-\t\tf.exp -= 1\n+\tshift = uint(f.exp - exp)
+\tf.mant, f.exp = mant, exp
+\treturn
 }
-\treturn uint(exp_before - f.exp)\n+\tshift = uint(f.exp - exp)
+\tf.mant, f.exp = mant, exp
+\treturn
 }
 
 // Multiply sets f to the product f*g: the result is correctly rounded,
@@ -390,6 +403,12 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool {
 	\td.dp = 0
 	\td.neg = f.neg
 	}\n+\tif f.exp == 0 && *lower == *f && *lower == *upper {
+\t\t// an exact integer.
+\t\td.Assign(f.mant)
+\t\td.neg = f.neg
+\t\treturn true
+\t}
 	const minExp = -60
 	const maxExp = -32
 	upper.Normalize()

変更前は、仮数部を8ビットずつ、その後1ビットずつ左シフトして正規化していました。変更後は、より効率的な「バイナリサーチ」のようなシフト戦略を採用しています。

	if mant>>(64-32) == 0 {
		mant <<= 32
		exp -= 32
	}
	if mant>>(64-16) == 0 {
		mant <<= 16
		exp -= 16
	}
	if mant>>(64-8) == 0 {
		mant <<= 8
		exp -= 8
	}
	if mant>>(64-4) == 0 {
		mant <<= 4
		exp -= 4
	}
	if mant>>(64-2) == 0 {
		mant <<= 2
		exp -= 2
	}
	if mant>>(64-1) == 0 {
		mant <<= 1
		exp -= 1
	}

この新しいアプローチでは、まず32ビット、次に16ビット、8ビット、4ビット、2ビット、1ビットと、大きなシフト量から順にチェックし、必要に応じてシフトを行います。これにより、仮数部が63ビット目にセットされるまでのシフト回数を大幅に削減し、正規化処理を高速化します。

ShortestDecimalの変更

ShortestDecimal関数にも、正確な整数値に対するショートカットパスが追加されました。

--- a/src/pkg/strconv/extfloat.go
+++ b/src/pkg/strconv/extfloat.go
@@ -390,6 +403,12 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool {
 	\td.dp = 0
 	\td.neg = f.neg
 	}\n+\tif f.exp == 0 && *lower == *f && *lower == *upper {
+\t\t// an exact integer.
+\t\td.Assign(f.mant)
+\t\td.neg = f.neg
+\t\treturn true
+\t}
 	const minExp = -60
 	const maxExp = -32
 	upper.Normalize()

これはAssignComputeBoundsで追加されたショートカットと連携しており、もし浮動小数点数が正確な整数であり、その境界が自身と一致する場合(つまり、その浮動小数点数が一意の整数を表す場合)、直接decimal型に変換して処理を終了します。これにより、Grisu3アルゴリズムの複雑な計算をスキップし、パフォーマンスを向上させます。

strconv/ftoa.goの変更

ftoa.goでは、genericFtoa関数内でGrisu3アルゴリズムを適用する条件が変更されました。

--- a/src/pkg/strconv/ftoa.go
+++ b/src/pkg/strconv/ftoa.go
@@ -104,10 +104,10 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte {
 	\td := new(decimal)
 	\tif shortest {
 	\t\tok := false
-\t\t\tif optimize && bitSize == 64 {
+\t\t\tif optimize {
 	\t\t\t// Try Grisu3 algorithm.
 	\t\t\tf := new(extFloat)
-\t\t\t\tlower, upper := f.AssignComputeBounds(val)
+\t\t\t\tlower, upper := f.AssignComputeBounds(mant, exp, neg, flt)
 	\t\t\tok = f.ShortestDecimal(d, &lower, &upper)
 	\t\t}
 	\t\tif !ok {

以前はoptimize && bitSize == 64という条件でGrisu3が適用されていましたが、bitSize == 64の条件が削除され、単にoptimizeが真であればGrisu3が試行されるようになりました。これにより、float32bitSize == 32)に対してもGrisu3アルゴリズムが適用されるようになり、コミットメッセージの「extend Grisu3 algorithm to float32」が実現されました。

また、f.AssignComputeBounds(val)の呼び出しが、f.AssignComputeBounds(mant, exp, neg, flt)に変更されました。これは、extfloat.goAssignComputeBoundsのシグネチャが変更されたことに対応するものです。

strconv/ftoa_test.goの変更

テストファイルでは、ベンチマーク関数がリファクタリングされ、float32に対する新しいベンチマークが追加されました。

--- a/src/pkg/strconv/ftoa_test.go
+++ b/src/pkg/strconv/ftoa_test.go
@@ -203,37 +203,23 @@ func BenchmarkFormatFloatBig(b *testing.B) {
 	}\n }\n \n-func BenchmarkAppendFloatDecimal(b *testing.B) {
-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, 33909, \'g\', -1, 64)
-\t}\n-}\n-\n-func BenchmarkAppendFloat(b *testing.B) {
-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, 339.7784, \'g\', -1, 64)
-\t}\n-}\n-\n-func BenchmarkAppendFloatExp(b *testing.B) {
-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, -5.09e75, \'g\', -1, 64)
-\t}\n-}\n-\n-func BenchmarkAppendFloatNegExp(b *testing.B) {
-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, -5.11e-95, \'g\', -1, 64)
-\t}\n-}\n-\n+func benchmarkAppendFloat(b *testing.B, f float64, fmt byte, prec, bitSize int) {
+\tdst := make([]byte, 30)
+\tfor i := 0; i < b.N; i++ {
+\t\tAppendFloat(dst[:0], f, fmt, prec, bitSize)
+\t}\n }\n \n+func BenchmarkAppendFloatDecimal(b *testing.B) { benchmarkAppendFloat(b, 33909, \'g\', -1, 64) }\n+func BenchmarkAppendFloat(b *testing.B)        { benchmarkAppendFloat(b, 339.7784, \'g\', -1, 64) }\n+func BenchmarkAppendFloatExp(b *testing.B)     { benchmarkAppendFloat(b, -5.09e75, \'g\', -1, 64) }\n+func BenchmarkAppendFloatNegExp(b *testing.B)  { benchmarkAppendFloat(b, -5.11e-95, \'g\', -1, 64) }\n func BenchmarkAppendFloatBig(b *testing.B) {\n-\tdst := make([]byte, 0, 30)
-\tfor i := 0; i < b.N; i++ {
-\t\tAppendFloat(dst, 123456789123456789123456789, \'g\', -1, 64)
-\t}\n+\tbenchmarkAppendFloat(b, 123456789123456789123456789, \'g\', -1, 64)
 }\n+\n+func BenchmarkAppendFloat32Integer(b *testing.B)       { benchmarkAppendFloat(b, 33909, \'g\', -1, 32) }\n+func BenchmarkAppendFloat32ExactFraction(b *testing.B) { benchmarkAppendFloat(b, 3.375, \'g\', -1, 32) }\n+func BenchmarkAppendFloat32Point(b *testing.B)         { benchmarkAppendFloat(b, 339.7784, \'g\', -1, 32) }\n+func BenchmarkAppendFloat32Exp(b *testing.B)           { benchmarkAppendFloat(b, -5.09e25, \'g\', -1, 32) }\n+func BenchmarkAppendFloat32NegExp(b *testing.B)        { benchmarkAppendFloat(b, -5.11e-25, \'g\', -1, 32) }\n```
複数の`BenchmarkAppendFloat*`関数が、共通のヘルパー関数`benchmarkAppendFloat`を使用するように変更されました。これにより、ベンチマークコードの重複が削減され、可読性と保守性が向上します。

最も重要なのは、`float32`に対する新しいベンチマークが追加されたことです。
*   `BenchmarkAppendFloat32Integer`
*   `BenchmarkAppendFloat32ExactFraction`
*   `BenchmarkAppendFloat32Point`
*   `BenchmarkAppendFloat32Exp`
*   `BenchmarkAppendFloat32NegExp`

これらのベンチマークは、`float32`の様々なケース(整数、正確な分数、小数点を含む数値、指数表記の数値)における`AppendFloat`のパフォーマンスを測定するために導入されました。コミットメッセージのベンチマーク結果が示すように、これらの新しいベンチマークは、`float32`の文字列変換において大幅なパフォーマンス向上を示しています。特に、`BenchmarkAppendFloat32Exp`と`BenchmarkAppendFloat32NegExp`では80%以上の改善が見られ、Grisu3アルゴリズムの`float32`への適用が非常に効果的であったことを裏付けています。

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

このコミットのコアとなる変更は、主に以下のファイルと関数に集中しています。

1.  **`src/pkg/strconv/extfloat.go`**:
    *   `AssignComputeBounds`関数のシグネチャ変更と、正確な整数値に対するショートカットパスの追加。
    *   `Normalize`関数の正規化ロジックの最適化(バイナリサーチのようなシフト戦略)。
    *   `ShortestDecimal`関数における正確な整数値のショートカットパスの追加。

2.  **`src/pkg/strconv/ftoa.go`**:
    *   `genericFtoa`関数内で、Grisu3アルゴリズムの適用条件から`bitSize == 64`の制約を削除し、`float32`にも適用可能にした点。
    *   `AssignComputeBounds`の呼び出し引数の変更。

これらの変更が連携して、浮動小数点数の文字列変換の精度とパフォーマンスを向上させています。

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

### `extfloat.go`の`Normalize`関数(変更後)

```go
func (f *extFloat) Normalize() (shift uint) {
	mant, exp := f.mant, f.exp
	if mant == 0 {
		return 0
	}
	// Fast path for common shifts
	if mant>>(64-32) == 0 {
		mant <<= 32
		exp -= 32
	}
	if mant>>(64-16) == 0 {
		mant <<= 16
		exp -= 16
	}
	if mant>>(64-8) == 0 {
		mant <<= 8
		exp -= 8
	}
	if mant>>(64-4) == 0 {
		mant <<= 4
		exp -= 4
	}
	if mant>>(64-2) == 0 {
		mant <<= 2
		exp -= 2
	}
	if mant>>(64-1) == 0 {
		mant <<= 1
		exp -= 1
	}
	shift = uint(f.exp - exp) // Calculate total shift amount
	f.mant, f.exp = mant, exp
	return
}

このNormalize関数は、extFloat構造体の仮数部f.mantを正規化します。正規化とは、仮数部の最上位ビット(63ビット目、0-indexed)が1になるように左シフトすることです。

  1. mant, exp := f.mant, f.exp: 現在の仮数部と指数部をローカル変数にコピーします。
  2. if mant == 0 { return 0 }: 仮数部が0の場合は、正規化の必要がないため0を返します。
  3. 高速パス(バイナリサーチのようなシフト):
    • if mant>>(64-32) == 0: 仮数部を32ビット右シフトした結果が0の場合、仮数部の最上位ビットはまだ64-32=32ビット目より下にあることを意味します。この場合、仮数部を32ビット左シフトし、指数部を32減らします。
    • 同様に、16ビット、8ビット、4ビット、2ビット、1ビットと、より小さいシフト量でチェックとシフトを繰り返します。この一連の条件分岐により、仮数部が効率的に正規化されます。例えば、仮数部が非常に小さい場合でも、一度に大きなシフトを行うことで、ループ処理よりも少ないステップで正規化が完了します。
  4. shift = uint(f.exp - exp): 正規化によって指数部がどれだけ変化したか(つまり、仮数部がどれだけ左シフトされたか)を計算します。
  5. f.mant, f.exp = mant, exp: 正規化された仮数部と指数部をextFloat構造体に書き戻します。
  6. return: シフト量を返します。

この最適化された正規化処理は、浮動小数点数のパースや文字列変換において、内部的なビット操作の効率を高め、全体的なパフォーマンス向上に貢献します。

ftoa.gogenericFtoa関数(関連部分)

func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte {
	// ... (前略) ...
	d := new(decimal)
	if shortest {
		ok := false
		if optimize { // Changed from optimize && bitSize == 64
			// Try Grisu3 algorithm.
			f := new(extFloat)
			// lower, upper := f.AssignComputeBounds(val) // Old call
			lower, upper := f.AssignComputeBounds(mant, exp, neg, flt) // New call
			ok = f.ShortestDecimal(d, &lower, &upper)
		}
		if !ok {
			// Fallback to slower but always correct algorithm (e.g., Dragon4 variant)
			// ... (後略) ...
		}
	}
	// ... (後略) ...
}

このコードスニペットは、浮動小数点数を最短の10進数文字列に変換するロジックの一部です。

  1. if shortest: 最短表現が必要な場合にこのブロックに入ります。
  2. if optimize: 最適化が有効な場合にGrisu3アルゴリズムを試行します。
    • 変更点: 以前はoptimize && bitSize == 64という条件があり、Grisu3はfloat64にのみ適用されていました。このコミットによりbitSize == 64の条件が削除されたため、float32bitSize == 32)に対してもGrisu3が試行されるようになりました。
  3. f := new(extFloat): 拡張浮動小数点数表現のためのextFloatインスタンスを作成します。
  4. lower, upper := f.AssignComputeBounds(mant, exp, neg, flt): 浮動小数点数の値から、その値を一意に識別できる10進数表現の範囲(下限lowerと上限upper)を計算します。ここで、extfloat.goで変更された新しいシグネチャのAssignComputeBoundsが呼び出されます。
  5. ok = f.ShortestDecimal(d, &lower, &upper): Grisu3アルゴリズムを使用して、extFloatから最短の10進数表現を計算し、decimal構造体dに格納します。成功すればoktrueになります。
  6. if !ok: Grisu3が適用できない(または失敗した)場合、より汎用的な(しかし遅い)アルゴリズムにフォールバックします。

この変更により、float32の文字列変換においてもGrisu3の高速な処理が利用できるようになり、特に指数部を持つ数値や小数点以下の桁数が多い数値の変換で顕著なパフォーマンス向上が実現されました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/pkg/strconvディレクトリ)
  • コミットメッセージとベンチマーク結果
  • Grisu3アルゴリズムに関する学術論文
  • IEEE 754浮動小数点数標準に関する資料
  • 浮動小数点数の正規化に関する一般的な情報
  • Go言語のベンチマークに関するドキュメント