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

[インデックス 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はレビュー担当者を示し、OCLCLは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)は、ffmtで指定された形式(例: '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プログラム自体が自己検証を行う形式のテストです。

  1. テストの実行方法 (// $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というメッセージを出力します。これは、テストが失敗したことを示す慣例的な方法です。
  2. Test構造体:

    type Test struct {
    	f float64;
    	in string;
    	out string;
    }
    
    • f: ソースコードに記述された浮動小数点定数(コンパイラが解釈した値)を保持します。
    • in: ソースコードに記述された浮動小数点定数の元の文字列表現です。テスト失敗時のデバッグ出力に利用されます。
    • out: ffloat64として正しく解釈された場合に期待される、strconv.ftoa64による文字列変換結果です。
  3. testsスライス: var tests = []Test { ... } このスライスには、テスト対象となる複数の浮動小数点定数とその期待値が定義されています。特に、1e23+8.5e6のような大きな数値や、100000000000000008388608のような特定の整数値がfloat64としてどのように表現されるかが検証のポイントです。outフィールドの値は、これらの数値がIEEE 754倍精度浮動小数点数として正確に表現された場合の文字列形式を示しています。

  4. 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に設定します。
    • 全てのテストケースが終了した後、okfalseであれば、panicln("bug120")を呼び出してプログラムを異常終了させます。これにより、テストスクリプトが非ゼロの終了コードを受け取り、テストの失敗を検出できます。

このテストは、コンパイラがソースコード中の浮動小数点定数を正しく内部表現に変換できることを保証するための重要な回帰テストとして機能します。

関連リンク

  • Go言語の初期のコンパイラに関する情報: Go言語の歴史やツールチェーンの進化について調べることで、6gの位置づけをより深く理解できます。
  • IEEE 754浮動小数点標準: 浮動小数点数の表現と計算における標準について学ぶことで、このバグの根本原因をより深く理解できます。
  • Go言語のstrconvパッケージのドキュメント: ftoa64関数の詳細な動作について確認できます。

参考にした情報源リンク

  • Go言語の公式ドキュメント (当時のバージョン): 6gコンパイラやstrconvパッケージに関する情報。
  • IEEE 754標準に関する技術文書や解説記事。
  • Go言語のソースコードリポジトリ(特にtest/bugsディレクトリ内の他のテストケース): Goのテストの慣例や、過去のバグ修正のパターンを理解するのに役立ちます。