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

[インデックス 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を設定することがあります。
  • MpintMpflt: Goコンパイラ内部で使用される多倍長整数および多倍長浮動小数点数の構造体です。これらは、コンパイル時の定数評価において、標準のC言語の型では表現できない精度や範囲の数値を扱うために利用されます。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. strtodの導入による文字列から浮動小数点数への変換の改善:

    • src/cmd/gc/mparith1.cmpatoflt関数(文字列を多倍長浮動小数点数Mpfltに変換する関数)において、従来の複雑な手動パースロジックが削除され、C標準ライブラリのstrtod関数が導入されました。
    • strtodは、IEEE 754標準に準拠した正確な浮動小数点数変換を提供するため、Goコンパイラが文字列リテラルから浮動小数点定数を解析する際の精度と堅牢性が大幅に向上しました。
    • strtodの呼び出し後にはerrnoをチェックし、オーバーフローが発生した場合にはMpflt構造体のovfフラグを設定するようになりました。これにより、数値変換時のエラーハンドリングが明確化されました。
  2. 多倍長整数から浮動小数点数への新しい変換パスの追加:

    • src/cmd/gc/go.hdouble mpgetfixflt(Mpint *a);という新しい関数宣言が追加されました。
    • src/cmd/gc/mparith2.cmpgetfixfltの実装が追加されました。この関数は、Mpint(多倍長整数)をdoubleに変換します。
      • 従来のmpgetfix関数はvlonglong long)を返すため、非常に大きなMpintvlongの範囲を超えると精度が失われたりオーバーフローしたりする可能性がありました。
      • mpgetfixfltは、Mpintを直接doubleに変換するのではなく、まずsnprint関数を使ってMpintを文字列にフォーマットし(%Bフォーマット指定子を使用)、その文字列をstrtod関数でdoubleに変換するという間接的なアプローチを取っています。
      • このアプローチにより、vlongの中間表現を介さずに、Mpintの持つ精度を可能な限りdoubleに反映させることが可能になります。特に、vlongの範囲を超える大きな整数が浮動小数点数に変換される際に、より正確な結果が得られるようになります。
    • src/cmd/gc/mparith1.cmpmovefixflt関数が、mpgetfixの代わりに新しく追加されたmpgetfixfltを使用するように変更されました。
  3. テストケースの追加と修正:

    • test/bugs/bug120.goに、1e23+83886091e23+1のような、大きな浮動小数点定数の精度問題を検証する新しいテストケースが追加されました。これらのテストケースは、float64の精度限界と、コンパイラがこれらの定数をどのように処理するかを浮き彫りにします。
    • test/const.goでは、fhugefhuge_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を文字列に変換し、strtoddoubleに変換します。

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コンパイラが浮動小数点定数を扱う際の精度と堅牢性を向上させることにあります。

  1. mpatofltにおけるstrtodの採用:

    • 以前のmpatoflt関数は、文字列から浮動小数点数を解析するために独自のロジックを使用していました。このような手動での解析は、特にエッジケースや丸め処理においてバグを導入しやすい傾向があります。
    • strtodは、C標準ライブラリの一部として広くテストされ、IEEE 754浮動小数点標準に厳密に準拠しています。これを採用することで、Goコンパイラは文字列リテラル(例: 3.14, 1e23)をMpfltに変換する際の正確性を外部の信頼できる実装に委ねることができ、コンパイラ自身のコードベースを簡素化しつつ、より正確な結果を保証します。
    • errnoのチェックは、strtodがオーバーフローやアンダーフローなどのエラーを検出した場合に、コンパイラが適切に反応できるようにするために重要です。
  2. mpgetfixfltによるMpintからdoubleへの安全な変換:

    • Mpintは任意の精度の整数を表現できますが、これを直接vlong(64ビット整数)に変換してからdoubleにする場合、Mpintvlongの最大値を超えるような非常に大きな数値であると、中間変換で情報が失われる可能性がありました。
    • mpgetfixfltは、Mpintをまず文字列に変換し、その文字列をstrtoddoubleに変換するという戦略を取ります。この「文字列を介した変換」は、一見非効率に見えますが、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言語の初期の設計に関する議論やドキュメント。