[インデックス 1139] ファイルの概要
このコミットは、Go言語の初期のコンパイラである6g
における浮動小数点定数の扱いに存在したバグを修正するためのテストケースを追加するものです。具体的には、6g
コンパイラが特定の浮動小数点定数を正しく処理できない問題に対処しています。
コミット
commit f333f4685cc667dda0be6ecd5500ff8fa10f4a2a
Author: Russ Cox <rsc@golang.org>
Date: Mon Nov 17 12:33:49 2008 -0800
floating point constant errors in 6g
R=r
OCL=19379
CL=19379
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f333f4685cc667dda0be6ecd5500ff8fa10f4a2a
元コミット内容
floating point constant errors in 6g
このコミットメッセージは、6g
コンパイラにおける浮動小数点定数のエラーに関する修正であることを簡潔に示しています。R=r
はレビュー担当者を示し、OCL
とCL
はGoプロジェクトの内部的な変更リスト番号を指します。
変更の背景
Go言語の初期開発段階において、コンパイラ(特に6g
)はまだ成熟しておらず、様々なバグが存在していました。このコミットは、その中でも浮動小数点数の定数表現に関する特定のバグに対処するために作成されました。
浮動小数点数は、コンピュータ内部で近似値として表現されることが多く、その精度や丸め処理は非常に複雑です。コンパイラがソースコード中の浮動小数点定数をバイナリコードに変換する際、その変換プロセスに誤りがあると、予期せぬ計算結果や比較の不一致を引き起こす可能性があります。
このバグは、6g
コンパイラが特定の大きな浮動小数点定数(例: 1e23+8.5e6
)を正しく内部表現に変換できない、あるいはその変換結果が期待される値と異なるという問題を示唆しています。このような問題は、科学技術計算や金融計算など、高い精度が要求されるアプリケーションにおいて致命的な影響を及ぼすため、早期の修正が不可欠でした。
このコミットは、そのバグを再現し、将来的な回帰を防ぐためのテストケースtest/bugs/bug120.go
を追加することで、コンパイラの堅牢性を向上させることを目的としています。
前提知識の解説
1. 6g
コンパイラ
6g
は、Go言語の初期に存在したコンパイラの一つで、主に64ビットアーキテクチャ(x86-64)をターゲットとしていました。Go言語のツールチェーンは、当初、アセンブラ(6a
)、リンカ(6l
)、コンパイラ(6g
)といった形で提供されており、6g
はその中核を担うコンパイラでした。Go言語の進化とともに、これらのコンパイラはより汎用的なgo tool compile
などに統合されていきましたが、このコミットが作成された2008年当時は、6g
が主要なコンパイラでした。
2. 浮動小数点数 (Floating-Point Numbers)
浮動小数点数は、実数をコンピュータ上で表現するための形式です。IEEE 754標準が広く用いられており、Go言語のfloat64
型は通常、この標準の倍精度浮動小数点数(64ビット)に準拠しています。
浮動小数点数は、以下の形式で表現されます。
符号部 (sign) × 仮数部 (mantissa) × 基数 (base)^指数部 (exponent)
コンピュータの内部では、仮数部と指数部が有限のビット数で表現されるため、全ての実数を正確に表現することはできません。特に、無限小数や非常に大きな数、非常に小さな数は近似値として扱われます。この近似処理や丸め処理が、浮動小数点計算における「誤差」の主な原因となります。
3. 浮動小数点定数のコンパイル時評価
プログラミング言語において、ソースコード中に直接記述された数値(例: 123.5
, 1e23
)は「定数」と呼ばれます。コンパイラは、これらの定数をプログラムの実行前に適切なバイナリ形式に変換します。浮動小数点定数の場合、コンパイラはソースコード中の文字列表現(例: "123.5"
)を、IEEE 754形式などの内部的な浮動小数点表現に変換する必要があります。
この変換プロセスにおいて、コンパイラの実装によっては、特定の定数で精度が失われたり、誤った値に丸められたりするバグが発生することがあります。特に、非常に桁数の多い数や、2のべき乗で正確に表現できない数(例: 0.1)は、このような問題を引き起こしやすい傾向があります。
4. strconv
パッケージとftoa64
関数
Go言語の標準ライブラリであるstrconv
パッケージは、基本的なデータ型(数値、真偽値など)と文字列との間の変換を提供します。このコミットで登場するstrconv.ftoa64
関数は、float64
型の値を文字列に変換するための関数です。
ftoa64(f float64, fmt byte, prec int)
は、f
をfmt
で指定された形式(例: 'g'
は一般的な形式)とprec
で指定された精度で文字列に変換します。この関数は、コンパイラが内部的に保持する浮動小数点定数の値が、期待通りに文字列として表現されるかを確認するために利用されています。もしコンパイラが定数を誤って解釈していれば、ftoa64
で文字列化した結果も期待値と異なるはずです。
技術的詳細
このコミットが修正しようとしている問題は、6g
コンパイラがソースコード中の特定の浮動小数点定数を、その正確な倍精度浮動小数点表現に変換する際に誤りを犯していたという点にあります。
test/bugs/bug120.go
ファイル内のtests
スライスに定義されているテストケースは、この問題を具体的に示しています。
var tests = []Test {
Test{ 123.5, "123.5", "123.5" },
Test{ 456.7, "456.7", "456.7" },
Test{ 1e23+8.5e6, "1e23+8.5e6", "1.0000000000000001e+23" },
Test{ 100000000000000008388608, "100000000000000008388608", "1.0000000000000001e+23" },
Test{ 1e23+8.388608e6, "1e23+8.388608e6", "1.0000000000000001e+23" },
Test{ 1e23+8.388609e6, "1e23+8.388609e6", "1.0000000000000001e+23" },
}
ここで注目すべきは、特に大きな数値や、1e23
のような指数表記を含む数値です。
1e23+8.5e6
100000000000000008388608
(これは1e23 + 8388608
、つまり1e23 + 2^23
に相当する非常に大きな整数)1e23+8.388608e6
(1e23 + 2^23
)1e23+8.388609e6
(1e23 + 2^23 + 1
に近い値)
これらの数値は、float64
の精度限界に近い、あるいはその境界線上で丸め誤差が発生しやすい値です。out
フィールドに指定されている"1.0000000000000001e+23"
という文字列は、これらの大きな数値がfloat64
として表現された際の正確な値(またはそれに最も近い表現可能な値)を示しています。
コンパイラがこれらの定数を処理する際、例えば1e23+8.5e6
というソースコード上のリテラルを、内部のfloat64
値に変換する過程で、期待される1.0000000000000001e+23
とは異なる値に変換してしまっていたと考えられます。このテストは、strconv.ftoa64
を使って、コンパイラが解釈したfloat64
値を再度文字列に変換し、それが期待されるout
文字列と一致するかどうかを検証することで、このバグを検出します。
もしコンパイラが定数を誤って解釈していれば、t.f
(コンパイラが解釈した定数)をftoa64
で文字列化した結果v
が、期待されるt.out
と一致せず、テストが失敗する仕組みです。このテストの追加により、6g
コンパイラがこれらの浮動小数点定数を正しく処理するようになったことが保証されます。
コアとなるコードの変更箇所
このコミットでは、test/bugs/bug120.go
という新しいファイルが追加されています。
--- /dev/null
+++ b/test/bugs/bug120.go
@@ -0,0 +1,39 @@
+// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: bug120
+//
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+package main
+
+import "strconv";
+
+type Test struct {
+ f float64;
+ in string;
+ out string;
+}
+
+var tests = []Test {
+ Test{ 123.5, "123.5", "123.5" },
+ Test{ 456.7, "456.7", "456.7" },
+ Test{ 1e23+8.5e6, "1e23+8.5e6", "1.0000000000000001e+23" },
+ Test{ 100000000000000008388608, "100000000000000008388608", "1.0000000000000001e+23" },
+ Test{ 1e23+8.388608e6, "1e23+8.388608e6", "1.0000000000000001e+23" },
+ Test{ 1e23+8.388609e6, "1e23+8.388609e6", "1.0000000000000001e+23" },
+}
+
+func main() {
+ ok := true;
+ for i := 0; i < len(tests); i++ {
+ t := tests[i];
+ v := strconv.ftoa64(t.f, 'g', -1);
+ if v != t.out {
+ println("Bad float64 const:", t.in, "want", t.out, "got", v);
+ ok = false;
+ }
+ }
+ if !ok {
+ panicln("bug120");
+ }
+}
コアとなるコードの解説
追加されたbug120.go
ファイルは、Go言語のテストフレームワークを使用せず、Goプログラム自体が自己検証を行う形式のテストです。
-
テストの実行方法 (
// $G $D/$F.go ...
): ファイルの先頭にあるコメント行は、このテストの実行方法を示しています。$G $D/$F.go
:6g
コンパイラ($G
)を使って、現在のファイル($F.go
)をコンパイルします。$L $F.$A
:6l
リンカ($L
)を使って、コンパイルされたオブジェクトファイル($F.$A
)をリンクし、実行可能ファイルを作成します。./$A.out
: 作成された実行可能ファイルを実行します。|| echo BUG: bug120
: もし実行が失敗(非ゼロ終了コード)した場合、BUG: bug120
というメッセージを出力します。これは、テストが失敗したことを示す慣例的な方法です。
-
Test
構造体:type Test struct { f float64; in string; out string; }
f
: ソースコードに記述された浮動小数点定数(コンパイラが解釈した値)を保持します。in
: ソースコードに記述された浮動小数点定数の元の文字列表現です。テスト失敗時のデバッグ出力に利用されます。out
:f
がfloat64
として正しく解釈された場合に期待される、strconv.ftoa64
による文字列変換結果です。
-
tests
スライス:var tests = []Test { ... }
このスライスには、テスト対象となる複数の浮動小数点定数とその期待値が定義されています。特に、1e23+8.5e6
のような大きな数値や、100000000000000008388608
のような特定の整数値がfloat64
としてどのように表現されるかが検証のポイントです。out
フィールドの値は、これらの数値がIEEE 754倍精度浮動小数点数として正確に表現された場合の文字列形式を示しています。 -
main
関数:func main() { ok := true; for i := 0; i < len(tests); i++ { t := tests[i]; v := strconv.ftoa64(t.f, 'g', -1); // コンパイラが解釈したfを文字列に変換 if v != t.out { // 期待値と比較 println("Bad float64 const:", t.in, "want", t.out, "got", v); ok = false; } } if !ok { panicln("bug120"); // 失敗した場合、パニックを起こす } }
main
関数は、tests
スライス内の各Test
エントリをループで処理します。- 各テストケースについて、
t.f
(コンパイラがソースコードから解釈した浮動小数点値)をstrconv.ftoa64
関数を使って文字列に変換します。'g'
フォーマットは、必要に応じて指数表記と通常の表記を切り替える一般的な形式です。-1
の精度は、ftoa64
が最適な精度を自動的に選択することを意味します。 - 変換された文字列
v
が、Test
構造体で定義された期待される出力t.out
と一致するかどうかを比較します。 - もし一致しない場合、エラーメッセージ(元の入力、期待値、実際の結果)を出力し、
ok
フラグをfalse
に設定します。 - 全てのテストケースが終了した後、
ok
がfalse
であれば、panicln("bug120")
を呼び出してプログラムを異常終了させます。これにより、テストスクリプトが非ゼロの終了コードを受け取り、テストの失敗を検出できます。
このテストは、コンパイラがソースコード中の浮動小数点定数を正しく内部表現に変換できることを保証するための重要な回帰テストとして機能します。
関連リンク
- Go言語の初期のコンパイラに関する情報: Go言語の歴史やツールチェーンの進化について調べることで、
6g
の位置づけをより深く理解できます。 - IEEE 754浮動小数点標準: 浮動小数点数の表現と計算における標準について学ぶことで、このバグの根本原因をより深く理解できます。
- Go言語の
strconv
パッケージのドキュメント:ftoa64
関数の詳細な動作について確認できます。
参考にした情報源リンク
- Go言語の公式ドキュメント (当時のバージョン):
6g
コンパイラやstrconv
パッケージに関する情報。 - IEEE 754標準に関する技術文書や解説記事。
- Go言語のソースコードリポジトリ(特に
test/bugs
ディレクトリ内の他のテストケース): Goのテストの慣例や、過去のバグ修正のパターンを理解するのに役立ちます。