[インデックス 1270] ファイルの概要
このコミットは、Go言語の標準ライブラリstrconv
パッケージにおける浮動小数点数変換の挙動を修正するものです。具体的には、文字列"-0"
を浮動小数点数に変換した際に、IEEE 754標準で定義されている「負のゼロ(-0)」を正しく返すように変更されました。また、初期のGoコンパイラ(6g
)における特定の浮動小数点演算のバグに対するワークアラウンドも含まれています。
コミット
commit 32a1ee85b1929b85f7b1fd9c0acc4673aec4a443
Author: Russ Cox <rsc@golang.org>
Date: Wed Dec 3 13:29:13 2008 -0800
Make strconv.atof("-0") return -0
and update test.
R=iant
DELTA=11 (3 added, 1 deleted, 7 changed)
OCL=20350
CL=20362
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/32a1ee85b1929b85f7b1fd9c0acc4673aec4a443
元コミット内容
strconv.atof("-0")
が-0
を返すようにし、テストを更新。
変更の背景
このコミットの主な背景には、浮動小数点数の国際標準であるIEEE 754における「符号付きゼロ(Signed Zero)」の概念があります。IEEE 754では、正のゼロ(+0)と負のゼロ(-0)が区別されます。数学的には両者ともゼロですが、特定の浮動小数点演算(例えば、ゼロによる除算)においては異なる結果を生じることがあります。
Go言語の初期段階において、strconv
パッケージのatof
(ParseFloat
の前身にあたる関数群)が文字列"-0"
を解析する際に、単なる0
(正のゼロ)を返していました。これはIEEE 754標準に準拠しておらず、浮動小数点演算の正確性を損なう可能性がありました。このコミットは、この不正確な挙動を修正し、標準に則った-0
を返すようにすることで、Go言語の数値計算の信頼性を向上させることを目的としています。
また、コミットにはf = -f;
をf *= -1;
に変更する部分が含まれており、これにはBUG work around 6g f = -f.
というコメントが付されています。これは、当時のGoコンパイラ(6g
)が単項の負演算子(-
)を最適化する際に、特定の状況下で浮動小数点数の符号付きゼロのセマンティクスを正しく扱えないバグが存在したため、そのバグを回避するためのワークアラウンドとして導入されました。
前提知識の解説
IEEE 754 浮動小数点標準
IEEE 754は、コンピュータにおける浮動小数点数の表現と演算に関する国際標準です。この標準は、数値の精度、範囲、および特殊な値(無限大、非数NaN、そして符号付きゼロ)の扱いを定義しています。
- 符号付きゼロ(Signed Zero): IEEE 754では、正のゼロ(+0)と負のゼロ(-0)が存在します。これらは、数値としては同じゼロですが、符号ビットが異なります。
- +0: 符号ビットが0
- -0: 符号ビットが1
この区別は、例えば
1 / +0 = +Infinity
、1 / -0 = -Infinity
のように、特定の演算結果に影響を与えます。また、アンダーフローの結果としてゼロになった場合など、計算の履歴を保持する役割も持ちます。
Go言語のstrconv
パッケージ
strconv
パッケージは、Go言語の標準ライブラリの一部であり、基本的なデータ型(文字列、整数、浮動小数点数、真偽値)間の変換機能を提供します。例えば、ParseFloat
関数は文字列を浮動小数点数に変換し、FormatFloat
関数は浮動小数点数を文字列に変換します。このコミットで修正されたatof
関数は、ParseFloat
の初期の実装の一部、またはその基盤となる関数でした。
浮動小数点数の内部表現
浮動小数点数は通常、符号ビット、指数部、仮数部(または分数部)の3つの部分で構成されます。
- 符号ビット: 数値が正か負かを示す(0が正、1が負)。
- 指数部: 数値の大きさを表す。
- 仮数部: 数値の有効桁数を表す。 符号付きゼロは、仮数部がすべてゼロで、指数部が最小値(正規化されていないゼロ)であり、符号ビットのみが異なるという形で表現されます。
技術的詳細
このコミットの技術的詳細は、主にsrc/lib/strconv/atof.go
内のDecimalToFloatBits
関数と、DecimalToFloat64Int
/DecimalToFloat32Int
関数の変更に集約されます。
DecimalToFloatBits
関数におけるゼロ値の処理
DecimalToFloatBits
関数は、10進数表現の数値を浮動小数点数のビット表現に変換する役割を担っています。変更前は、入力された10進数d
がゼロ(d.nd == 0
)の場合、単純にreturn 0, false
としていました。これは、結果として正のゼロのビット表現(すべてのビットが0)を返していました。
変更後は、ゼロの場合の処理がより詳細になりました。
// Zero is always a special case.
if d.nd == 0 {
- return 0, false
+ mant = 0;
+ exp = flt.bias;
+ goto out;
}
この変更により、d.nd == 0
(数値がゼロ)の場合でも、mant
(仮数部)を0
に、exp
(指数部)をflt.bias
(浮動小数点形式のバイアス値)に明示的に設定し、goto out
で関数の最終的なビット生成部分にジャンプするようにしました。これにより、neg
(負の符号)フラグがtrue
の場合には、最終的なビット表現に負の符号が反映され、-0
が正しく生成されるようになります。これは、IEEE 754の符号付きゼロのセマンティクスに準拠するための重要な変更です。
DecimalToFloat64Int
およびDecimalToFloat32Int
関数におけるf = -f
の変更
これらの関数は、整数部分を浮動小数点数に変換する際に使用されます。変更前は、負の数である場合にf = -f;
という単項の負演算子を使用していました。
if neg {
- f = -f;
+ f *= -1; // BUG work around 6g f = -f.
}
この変更は、f = -f;
をf *= -1;
に置き換えるものです。コメントにあるように、これは当時のGoコンパイラ6g
のバグに対するワークアラウンドです。6g
コンパイラは、f = -f
のような単項の負演算を最適化する際に、特に浮動小数点数の符号付きゼロのケースで、符号ビットを正しく反転させない、あるいは他の予期せぬ挙動を引き起こす可能性がありました。f *= -1
という乗算による負号の適用は、コンパイラが異なるコードパスを生成するため、このバグを回避することができました。これは、コンパイラの成熟度がまだ低かったGo言語の初期段階における典型的な問題解決策の一つです。
コアとなるコードの変更箇所
diff --git a/src/lib/strconv/atof.go b/src/lib/strconv/atof.go
index 9345b99396..8869e2032c 100644
--- a/src/lib/strconv/atof.go
+++ b/src/lib/strconv/atof.go
@@ -110,14 +110,16 @@ var powtab = []int{\n }\n \n func DecimalToFloatBits(neg bool, d *Decimal, trunc bool, flt *FloatInfo) (b uint64, overflow bool) {\n+\tvar exp int;\n+\tvar mant uint64;\n+\n \t// Zero is always a special case.\n \tif d.nd == 0 {\n-\t\treturn 0, false
+\t\tmant = 0;
+\t\texp = flt.bias;
+\t\tgoto out;
\t}\n \n-\tvar exp int;\n-\tvar mant uint64;\n-\
\t// Obvious overflow/underflow.\n \t// These bounds are for 64-bit floats.\n \t// Will have to change if we want to support 80-bit floats in the future.\n@@ -212,7 +214,7 @@ func DecimalToFloat64Int(neg bool, d *Decimal) float64 {\n \t\tf = f*10 + float64(d.d[i] - \'0\');\n \t}\n \tif neg {\n-\t\tf = -f;\n+\t\tf *= -1;\t// BUG work around 6g f = -f.\n \t}\n \treturn f;\n }\n@@ -223,7 +225,7 @@ func DecimalToFloat32Int(neg bool, d *Decimal) float32 {\n \t\tf = f*10 + float32(d.d[i] - \'0\');\n \t}\n \tif neg {\n-\t\tf = -f;\n+\t\tf *= -1;\t// BUG work around 6g f = -f.\n \t}\n \treturn f;\n }\ndiff --git a/src/lib/strconv/atof_test.go b/src/lib/strconv/atof_test.go
index cf4603f810..ab4fcd1462 100644
--- a/src/lib/strconv/atof_test.go
+++ b/src/lib/strconv/atof_test.go
@@ -32,7 +32,7 @@ var tests = []Test {\n \tTest{ \"100000000000000016777215\", \"1.0000000000000001e+23\", nil },\n \tTest{ \"100000000000000016777216\", \"1.0000000000000003e+23\", nil },\n \tTest{ \"-1\", \"-1\", nil },\n-\tTest{ \"-0\", \"0\", nil },\n+\tTest{ \"-0\", \"-0\", nil },\n \tTest{ \"1e-20\", \"1e-20\", nil },\n \tTest{ \"625e-3\", \"0.625\", nil },\n \n```
## コアとなるコードの解説
### `src/lib/strconv/atof.go`
1. **`DecimalToFloatBits`関数の変更**:
* `var exp int; var mant uint64;`の宣言が、`if d.nd == 0`ブロックの前に移動されました。これは、ゼロの特殊ケース処理においてもこれらの変数が適切に初期化され、`goto out`によって最終的なビット生成ロジックに渡されることを保証するためです。
* ゼロ値(`d.nd == 0`)の処理が`return 0, false`から、`mant = 0; exp = flt.bias; goto out;`に変更されました。この変更により、入力がゼロである場合でも、浮動小数点数の仮数部と指数部が明示的に設定され、関数の後半で符号ビットが考慮された最終的なビット表現が生成されるようになります。これにより、`neg`(負の符号)が`true`であれば、`-0`のビット表現が正しく生成されます。
2. **`DecimalToFloat64Int`および`DecimalToFloat32Int`関数の変更**:
* `if neg { f = -f; }`が`if neg { f *= -1; // BUG work around 6g f = -f. }`に変更されました。これは、前述の通り、当時の`6g`コンパイラにおける単項負演算子のバグを回避するためのワークアラウンドです。`f *= -1`とすることで、コンパイラが異なるコードを生成し、符号付きゼロを含む浮動小数点数の符号反転が正しく行われるようにします。
### `src/lib/strconv/atof_test.go`
* テストケース`Test{ "-0", "0", nil },`が`Test{ "-0", "-0", nil },`に変更されました。これは、`strconv.atof("-0")`が期待通り`-0`を返すようになったことを検証するためのテストの更新です。このテストの変更は、コミットの主要な目的が達成されたことを明確に示しています。
これらの変更は、Go言語がIEEE 754浮動小数点標準に厳密に準拠し、数値計算の正確性と信頼性を向上させるための初期の取り組みの一環です。また、コンパイラのバグに対する実用的なワークアラウンドも示しており、Go言語開発の初期段階における課題と解決策の一端を垣間見ることができます。
## 関連リンク
* IEEE 754 - Wikipedia: [https://ja.wikipedia.org/wiki/IEEE_754](https://ja.wikipedia.org/wiki/IEEE_754)
* Go言語 `strconv`パッケージ ドキュメント: [https://pkg.go.dev/strconv](https://pkg.go.dev/strconv) (現在のドキュメント)
## 参考にした情報源リンク
* Go言語のコミット履歴 (GitHub): [https://github.com/golang/go/commits/master](https://github.com/golang/go/commits/master)
* IEEE 754に関する一般的な情報源 (Web検索)
* Go言語の初期のコンパイラに関する情報 (Web検索)