[インデックス 1143] ファイルの概要
このコミットは、Goコンパイラ(gc
)における浮動小数点数の扱い、特に大きな数値の精度と変換に関するバグ(bug120
)の修正に焦点を当てています。具体的には、多倍長整数(Mpint
)から浮動小数点数への変換ロジックの改善と、文字列から浮動小数点数への変換に標準ライブラリ関数strtod
を導入することで、数値の精度とオーバーフロー処理を向上させています。
コミット
commit a1585b676bb13a36847beda61b94d32433d96715
Author: Russ Cox <rsc@golang.org>
Date: Mon Nov 17 13:58:45 2008 -0800
fix the easy parts of bug120
R=r,ken
DELTA=66 (52 added, 3 deleted, 11 changed)
OCL=19386
CL=19389
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a1585b676bb13a36847beda61b94d32433d96715
元コミット内容
fix the easy parts of bug120
R=r,ken
DELTA=66 (52 added, 3 deleted, 11 changed)
OCL=19386
CL=19389
変更の背景
このコミットは、Goコンパイラにおける浮動小数点数の精度問題、特にbug120
として知られるバグの一部を修正するために行われました。bug120
は、Go言語の初期段階において、コンパイラが大きな浮動小数点定数を正確に処理できないという問題でした。例えば、1e23
のような非常に大きな数値が、float64
の精度限界やコンパイラ内部の数値処理ロジックの不備により、期待される値と異なる値に丸められたり、オーバーフローしたりする現象が発生していました。
特に、1e23+8388608
のような、float64
で正確に表現できるはずの数値が、コンパイラの内部処理で誤った結果になることが問題視されていました。これは、コンパイラが多倍長整数(Mpint
)や多倍長浮動小数点数(Mpflt
)を扱う際に、中間的な変換や計算で精度が失われることが原因でした。このコミットは、これらの問題のうち「簡単な部分」を修正し、より正確な数値処理を実現することを目的としています。
前提知識の解説
- Goコンパイラ(
gc
): Go言語の公式コンパイラの一つで、Goのソースコードを機械語に変換します。初期のGoコンパイラはC言語で書かれており、数値演算ライブラリも独自に実装されていました。 - 多倍長演算(Multi-precision arithmetic): コンピュータの標準的なデータ型(例:
int64
,float64
)で扱える範囲を超える大きな数値を扱うための演算手法です。Goコンパイラ内部では、コンパイル時に定数計算を行うために多倍長整数(Mpint
)や多倍長浮動小数点数(Mpflt
)が使用されます。 float64
(IEEE 754 double-precision): 64ビットの浮動小数点数形式で、Go言語のfloat64
型に相当します。約15〜17桁の10進精度を持ち、非常に広い範囲の数値を表現できますが、全ての数値を正確に表現できるわけではなく、丸め誤差が発生することがあります。strtod
: C標準ライブラリ関数の一つで、文字列をdouble
型の浮動小数点数に変換します。この関数は、国際的な標準(IEEE 754)に準拠した正確な変換を行うことが期待されます。errno
: C言語でシステムコールやライブラリ関数がエラーを報告するために使用されるグローバル変数です。strtod
のような関数は、変換エラー(例: オーバーフロー)が発生した場合にerrno
を設定することがあります。Mpint
とMpflt
: Goコンパイラ内部で使用される多倍長整数および多倍長浮動小数点数の構造体です。これらは、コンパイル時の定数評価において、標準のC言語の型では表現できない精度や範囲の数値を扱うために利用されます。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
strtod
の導入による文字列から浮動小数点数への変換の改善:src/cmd/gc/mparith1.c
のmpatoflt
関数(文字列を多倍長浮動小数点数Mpflt
に変換する関数)において、従来の複雑な手動パースロジックが削除され、C標準ライブラリのstrtod
関数が導入されました。strtod
は、IEEE 754標準に準拠した正確な浮動小数点数変換を提供するため、Goコンパイラが文字列リテラルから浮動小数点定数を解析する際の精度と堅牢性が大幅に向上しました。strtod
の呼び出し後にはerrno
をチェックし、オーバーフローが発生した場合にはMpflt
構造体のovf
フラグを設定するようになりました。これにより、数値変換時のエラーハンドリングが明確化されました。
-
多倍長整数から浮動小数点数への新しい変換パスの追加:
src/cmd/gc/go.h
にdouble mpgetfixflt(Mpint *a);
という新しい関数宣言が追加されました。src/cmd/gc/mparith2.c
にmpgetfixflt
の実装が追加されました。この関数は、Mpint
(多倍長整数)をdouble
に変換します。- 従来の
mpgetfix
関数はvlong
(long long
)を返すため、非常に大きなMpint
がvlong
の範囲を超えると精度が失われたりオーバーフローしたりする可能性がありました。 mpgetfixflt
は、Mpint
を直接double
に変換するのではなく、まずsnprint
関数を使ってMpint
を文字列にフォーマットし(%B
フォーマット指定子を使用)、その文字列をstrtod
関数でdouble
に変換するという間接的なアプローチを取っています。- このアプローチにより、
vlong
の中間表現を介さずに、Mpint
の持つ精度を可能な限りdouble
に反映させることが可能になります。特に、vlong
の範囲を超える大きな整数が浮動小数点数に変換される際に、より正確な結果が得られるようになります。
- 従来の
src/cmd/gc/mparith1.c
のmpmovefixflt
関数が、mpgetfix
の代わりに新しく追加されたmpgetfixflt
を使用するように変更されました。
-
テストケースの追加と修正:
test/bugs/bug120.go
に、1e23+8388609
や1e23+1
のような、大きな浮動小数点定数の精度問題を検証する新しいテストケースが追加されました。これらのテストケースは、float64
の精度限界と、コンパイラがこれらの定数をどのように処理するかを浮き彫りにします。test/const.go
では、fhuge
とfhuge_1
という非常に近い値を持つfloat64
定数が、float64
の精度では区別できないことを明示するために、アサーションが>
から==
に変更され、コメントが追加されました。test/convlit.go
では、float
のオーバーフローテストがfloat32
に限定され、エラーメッセージもより正確なものに修正されました。test/fmt_test.go
では、float64
のフォーマット出力の期待値が微調整され、より正確な丸めが反映されています。test/golden.out
は、これらの変更によって発生するコンパイラの出力(エラーメッセージやテスト結果)の変更を反映するために更新されました。
これらの変更は、Goコンパイラが浮動小数点定数をより正確に解析し、内部で処理できるようにするための重要なステップです。特に、strtod
の利用は、数値変換の正確性を外部の信頼できる実装に委ねることで、コンパイラ自身の複雑な数値解析ロジックを簡素化し、バグのリスクを低減する効果があります。
コアとなるコードの変更箇所
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -558,6 +558,7 @@ void mprshfixfix(Mpint *a, Mpint *b);
void mpxorfixfix(Mpint *a, Mpint *b);
void mpcomfix(Mpint *a);
vlong mpgetfix(Mpint *a);
+double mpgetfixflt(Mpint *a);
/*
* mparith3.c
mpgetfixflt
関数の宣言が追加されました。これは多倍長整数をdouble
に変換するための新しいインターフェースです。
src/cmd/gc/mparith1.c
--- a/src/cmd/gc/mparith1.c
+++ b/src/cmd/gc/mparith1.c
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+#include <u.h>
+#include <errno.h>
#include "go.h"
/// uses arithmetic
@@ -149,7 +151,7 @@ mpcomfix(Mpint *a)
void
mpmovefixflt(Mpflt *a, Mpint *b)
{
- mpmovecflt(a, mpgetfix(b));
+ mpmovecflt(a, mpgetfixflt(b));
}
void
@@ -200,6 +202,15 @@ mpatoflt(Mpflt *a, char *as)
{
int dp, c, f, ef, ex, zer;
char *s;
+ double f64;
+
+ /* until Mpflt is really mp, use strtod to get rounding right */
+ errno = 0;
+ f64 = strtod(as, &s);
+ mpmovecflt(a, f64);
+ if(errno != 0)
+ a->ovf = 1;
+ return;
s = as;
dp = 0; /* digits after decimal point */
@@ -279,14 +290,14 @@ mpatoflt(Mpflt *a, char *as)
return;
bad:
- warn("set ovf in mpatof");
+ warn("set ovf in mpatof: %s", as);
mpmovecflt(a, 0.0);
}
//
// fixed point input
// required syntax is [+-][0[x]]d*
-//
+//
void
mpatofix(Mpint *a, char *as)
{
mpatoflt
関数がstrtod
を使用するように変更され、mpmovefixflt
が新しいmpgetfixflt
関数を呼び出すようになりました。
src/cmd/gc/mparith2.c
--- a/src/cmd/gc/mparith2.c
+++ b/src/cmd/gc/mparith2.c
@@ -459,6 +459,17 @@ mpgetfix(Mpint *a)
return v;
}
+double
+mpgetfixflt(Mpint *a)
+{
+ // answer might not fit in intermediate vlong, so format
+ // to string and then let the string routine convert.
+ char buf[1000];
+
+ snprint(buf, sizeof buf, "%B", a);
+ return strtod(buf, nil);
+}
+
void
mpmovecfix(Mpint *a, vlong c)
{
mpgetfixflt
関数の実装が追加されました。Mpint
を文字列に変換し、strtod
でdouble
に変換します。
test/bugs/bug120.go
--- a/test/bugs/bug120.go
+++ b/test/bugs/bug120.go
@@ -19,8 +19,21 @@ var tests = []Test {
Test{ 456.7, "456.7", "456.7" },
Test{ 1e23+8.5e6, "1e23+8.5e6", "1.0000000000000001e+23" },
Test{ 100000000000000008388608, "100000000000000008388608", "1.0000000000000001e+23" },
+\tTest{ 1e23+8388609, "1e23+8388609", "1.0000000000000001e+23" },
+\n+\t// "x" = the floating point value from converting the string x.
+\t// These are exactly representable in 64-bit floating point:
+\t// 1e23-8388608
+\t// 1e23+8388608
+\t// The former has an even mantissa, so "1e23" rounds to 1e23-8388608.
+\t// If "1e23+8388608" is implemented as "1e23" + "8388608",
+\t// that ends up computing 1e23-8388608 + 8388608 = 1e23,
+\t// which rounds back to 1e23-8388608.
+\t// The correct answer, of course, would be "1e23+8388608" = 1e23+8388608.
+\t// This is not going to be correct until 6g has multiprecision floating point.
+\t// A simpler case is "1e23+1", which should also round to 1e23+8388608.
Test{ 1e23+8.388608e6, "1e23+8.388608e6", "1.0000000000000001e+23" },
-\tTest{ 1e23+8.388609e6, "1e23+8.388609e6", "1.0000000000000001e+23" },
+\tTest{ 1e23+1, "1e23+1", "1.0000000000000001e+23" },
}\n \n func main() {\n@@ -30,6 +43,12 @@ func main() {\n \t\tv := strconv.ftoa64(t.f, 'g', -1);\n \t\tif v != t.out {\n \t\t\tprintln("Bad float64 const:", t.in, "want", t.out, "got", v);\n+\t\t\tx, overflow, ok := strconv.atof64(t.out);\n+\t\t\tif !ok {\n+\t\t\t\tpanicln("bug120: strconv.atof64", t.out);\n+\t\t\t}\n+\t\t\tprintln("\\twant exact:", strconv.ftoa64(x, 'g', 1000));\n+\t\t\tprintln("\\tgot exact: ", strconv.ftoa64(t.f, 'g', 1000));\n \t\t\tok = false;\n \t\t}\n \t}\ndiff --git a/test/const.go b/test/const.go
bug120
のテストケースが追加され、デバッグ出力が強化されました。
コアとなるコードの解説
このコミットの核心は、Goコンパイラが浮動小数点定数を扱う際の精度と堅牢性を向上させることにあります。
-
mpatoflt
におけるstrtod
の採用:- 以前の
mpatoflt
関数は、文字列から浮動小数点数を解析するために独自のロジックを使用していました。このような手動での解析は、特にエッジケースや丸め処理においてバグを導入しやすい傾向があります。 strtod
は、C標準ライブラリの一部として広くテストされ、IEEE 754浮動小数点標準に厳密に準拠しています。これを採用することで、Goコンパイラは文字列リテラル(例:3.14
,1e23
)をMpflt
に変換する際の正確性を外部の信頼できる実装に委ねることができ、コンパイラ自身のコードベースを簡素化しつつ、より正確な結果を保証します。errno
のチェックは、strtod
がオーバーフローやアンダーフローなどのエラーを検出した場合に、コンパイラが適切に反応できるようにするために重要です。
- 以前の
-
mpgetfixflt
によるMpint
からdouble
への安全な変換:Mpint
は任意の精度の整数を表現できますが、これを直接vlong
(64ビット整数)に変換してからdouble
にする場合、Mpint
がvlong
の最大値を超えるような非常に大きな数値であると、中間変換で情報が失われる可能性がありました。mpgetfixflt
は、Mpint
をまず文字列に変換し、その文字列をstrtod
でdouble
に変換するという戦略を取ります。この「文字列を介した変換」は、一見非効率に見えますが、Mpint
の持つ完全な精度情報を文字列として保持し、strtod
にその解析を任せることで、vlong
の範囲に縛られることなく、double
で表現可能な最も近い値に正確に丸めることを可能にします。これは、特に100000000000000008388608
のような、float64
で正確に表現できるがvlong
では表現できない大きな整数定数を扱う場合に不可欠です。
これらの変更は、Go言語が数値リテラルをコンパイル時に正確に評価し、期待される浮動小数点動作を提供するための基盤を強化します。特に、科学計算や金融計算など、浮動小数点数の精度が重要となるアプリケーションにおいて、コンパイラが生成するバイナリの信頼性を高めることに貢献します。
関連リンク
- Go言語の初期のバグトラッカーやメーリングリストのアーカイブに
bug120
に関する詳細情報がある可能性があります。 - IEEE 754 浮動小数点標準に関するドキュメント。
参考にした情報源リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- C言語の
strtod
関数のドキュメント(例:man strtod
) - IEEE 754 浮動小数点標準に関する一般的な情報源(例: Wikipedia)
- Go言語の初期の設計に関する議論やドキュメント。