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

[インデックス 1269] ファイルの概要

このコミットは、Go言語のコンパイラ(gc)における多倍長浮動小数点数(mpflt)の扱いに関する重要な改善を導入しています。具体的には、正確な多倍長浮動小数点定数のインポート/エクスポート機能の追加と、そのための新しい構文 decimal_int ("p" | "P") [ "+" | "-" ] decimal_int の導入が主な変更点です。この新しい構文は、decimal1 * 2^decimal2 という形式で値を表現し、浮動小数点数を正確にバイナリ形式で指定できるようにします。

コミット

commit f8797daa9fd783b0edda749cc668bcef9282d2fc
Author: Ken Thompson <ken@golang.org>
Date:   Wed Dec 3 13:17:26 2008 -0800

    import/export of exact mp floating constants
    new syntax for exact mp floating constants
         decimal_int ( "p" | "P" ) [ "+" | "-" ] decimal_int
         the value is decimal1 * 2^decimal2
    
    R=r
    OCL=20357
    CL=20357

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f8797daa9fd783b0edda749cc668bcef9282d2fc

元コミット内容

import/export of exact mp floating constants
new syntax for exact mp floating constants
     decimal_int ( "p" | "P" ) [ "+" | "-" ] decimal_int
     the value is decimal1 * 2^decimal2

変更の背景

この変更の背景には、Go言語のコンパイラが浮動小数点数を扱う際の精度と表現力の向上が挙げられます。従来の浮動小数点定数の表現では、IEEE 754倍精度浮動小数点数(double)に変換される際に精度が失われる可能性がありました。特に、コンパイル時に正確な浮動小数点定数を扱う必要がある場合、この精度損失は問題となります。

新しい構文 decimal_int ("p" | "P") [ "+" | "-" ] decimal_int は、数値 decimal12decimal2 乗でスケーリングするという、バイナリ浮動小数点数の正確な表現を可能にします。これは、特に科学技術計算や金融計算など、厳密な精度が求められる分野において、コンパイラが浮動小数点定数をより正確に処理できるようにするために不可欠な機能です。

また、この変更は、コンパイラが生成するバイナリコード間で多倍長浮動小数点定数を正確にインポート/エクスポートする能力を向上させます。これにより、異なるコンパイルユニット間で浮動小数点定数の値が正確に保持され、予測可能な動作が保証されます。

前提知識の解説

多倍長浮動小数点数 (Multi-Precision Floating-Point Numbers)

多倍長浮動小数点数とは、通常の floatdouble 型が持つ固定されたビット数よりも多くのビットを使用して、より高い精度と広い範囲の数値を表現できる浮動小数点数のことです。Go言語のコンパイラ内部では、Mpflt (Multi-Precision Float) という構造体がこれに該当し、mparith ライブラリがその演算を担っています。これにより、コンパイル時に数値計算の精度を最大限に高めることができます。

浮動小数点数の表現

一般的な浮動小数点数は、以下の形式で表現されます。

符号部 × 仮数部 × 基数^指数部

ここで、基数は通常2(バイナリ浮動小数点数)または10(10進浮動小数点数)です。IEEE 754標準では基数2が用いられます。

新しい構文 decimal_int ("p" | "P") [ "+" | "-" ] decimal_int は、この浮動小数点数の表現をより直接的に、かつ正確に指定するためのものです。

  • decimal_int: 仮数部(またはその一部)に相当する10進整数。
  • "p" または "P": 指数部が2のべき乗であることを示す記号。これは、C言語の printf フォーマット指定子における %a (hexadecimal floating-point) に似た概念で、バイナリ指数を明示します。
  • [ "+" | "-" ] decimal_int: 指数部(2のべき乗の指数)に相当する10進整数。

この構文により、例えば 1.5p+31.5 * 2^3 = 1.5 * 8 = 12.0 を意味し、3p-13 * 2^-1 = 3 * 0.5 = 1.5 を意味します。これにより、浮動小数点数をバイナリ形式で正確に指定することが可能になります。

字句解析 (Lexical Analysis)

字句解析(レキシングまたはスキャニングとも呼ばれる)は、コンパイラの最初のフェーズです。ソースコードを読み込み、意味のある最小単位である「トークン」に分割します。例えば、123 は数値トークン、+ は演算子トークン、if はキーワードトークンとなります。このコミットでは、新しい浮動小数点定数の構文を認識するために、字句解析器(lex.c)が変更されています。

技術的詳細

このコミットは、Goコンパイラの以下の主要なコンポーネントに影響を与えています。

  1. 字句解析器 (src/cmd/gc/lex.c):

    • 既存の浮動小数点数解析ロジックに、'p' または 'P' の文字を検出する新しいパス (casep) が追加されました。
    • 'p' または 'P' の後に続く符号(+ または -)と指数部(10進整数)を正しく解析するように変更されています。
    • これにより、123p+45 のような新しい形式の浮動小数点定数をトークンとして認識できるようになります。
  2. 多倍長算術ライブラリ (src/cmd/gc/mparith1.c):

    • mpatoflt 関数(文字列から多倍長浮動小数点数への変換)が拡張され、新しい p 形式の指数部を処理できるようになりました。
    • eb (binary point) という新しいフラグが導入され、p 形式の指数部が検出されたかどうかを追跡します。
    • p 形式の指数部が指定された場合、従来の10進指数部(e 形式)とは異なるロジックで、2のべき乗によるスケーリングが a->exp += ex; によって直接適用されます。
    • Fconv 関数(多倍長浮動小数点数を文字列にフォーマットする関数)も変更され、新しい p 形式で出力するように修正されました。これにより、正確な多倍長浮動小数点定数をエクスポートする際に、そのバイナリ表現が保持されます。具体的には、仮数部を正規化し、末尾のゼロビットを除去して、%Bp+%d または %Bp-%d の形式で出力します。
  3. ヘッダーファイル (src/cmd/gc/go.h):

    • mparith3.c に関連する新しい関数 sigfigmpnorm のプロトタイプが追加されました。sigfig は有効桁数を取得し、mpnorm は多倍長浮動小数点数を正規化するために使用されます。
    • mpnorm のプロトタイプが重複していたため、一つ削除されています。
  4. エクスポート処理 (src/cmd/gc/export.c):

    • dumpexportconst 関数において、浮動小数点定数をエクスポートする際のフォーマットが %.17e から %F に変更されました。この %F は、mparith1.c で定義された Fconv 関数によって処理され、新しい p 形式で出力されるようになります。これにより、エクスポートされる浮動小数点定数の精度が向上します。

これらの変更により、Goコンパイラは、ソースコード内で指定された正確な多倍長浮動小数点定数を、そのバイナリ表現を保持したまま内部で処理し、他のコンパイルユニットにエクスポートできるようになります。

コアとなるコードの変更箇所

src/cmd/gc/export.c

--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -105,7 +105,7 @@ dumpexportconst(Sym *s)
 		Bprint(bout, "0x%llux\n", n->val.u.bval);
 		break;
 	case CTFLT:
-		Bprint(bout, "%.17e\n", mpgetflt(n->val.u.fval));
+		Bprint(bout, "%F\n", n->val.u.fval);
 		break;
 	case CTSTR:
 		Bprint(bout, "\"%Z\"\n", n->val.u.sval);

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -567,6 +567,8 @@ void
 mpshiftfix(Mpint *a, int s);
 /*
  *	mparith3.c
  */
+int	sigfig(Mpflt *a);
+void	mpnorm(Mpflt *a);
 void	tmpmovefltflt(Mpflt *a, Mpflt *b);
 void	tmpmovecflt(Mpflt *a, double f);
 int	mptestflt(Mpflt *a);
@@ -576,7 +578,6 @@ void	mpdivfltflt(Mpflt *a, Mpflt *b);
 void	mpnegflt(Mpflt *a);
 double	mpgetflt(Mpflt *a);
 int	Fconv(Fmt*);
-void	mpnorm(Mpflt *a);

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -742,6 +742,8 @@ dc:
 		goto casedot;
 	if(c == 'e' || c == 'E')
 		goto casee;
+	if(c == 'p' || c == 'P')
+		goto casep;
 
 ncu:
 	*cp = 0;
@@ -780,6 +782,22 @@ casee:
 		*cp++ = c;
 		c = getc();
 	}
+	goto caseout;
+
+casep:
+	*cp++ = 'p';
+	c = getc();
+	if(c == '+' || c == '-') {
+		*cp++ = c;
+		c = getc();
+	}
+	if(!isdigit(c))
+		yyerror("malformed fp constant exponent");
+	while(isdigit(c)) {
+		*cp++ = c;
+		c = getc();
+	}
+	goto caseout;
 
 caseout:
 	*cp = 0;

src/cmd/gc/mparith1.c

--- a/src/cmd/gc/mparith1.c
+++ b/src/cmd/gc/mparith1.c
@@ -194,13 +194,14 @@ void
 mpatoflt(Mpflt *a, char *as)
 {
 	Mpflt b;
-	int dp, c, f, ef, ex, zer;
+	int dp, c, f, ef, ex, eb, zer;
 	char *s;
 
 	s = as;
 	dp = 0;		/* digits after decimal point */
 	f = 0;		/* sign */
 	ex = 0;		/* exponent */
+	eb = 0;		/* binary point */
 	zer = 1;	/* zero */
 
 	mpmovecflt(a, 0.0);
@@ -239,6 +240,10 @@ mpatoflt(Mpflt *a, char *as)
 			dp++;
 			continue;
 
+		case 'P':
+		case 'p':
+			eb = 1;
+
 		case 'E':
 		case 'e':
 			ex = 0;
@@ -266,6 +271,13 @@ mpatoflt(Mpflt *a, char *as)
 		break;
 	}
 
+	if(eb) {
+		if(dp)
+			goto bad;
+		a->exp += ex;
+		goto out;
+	}
+
 	if(dp)
 		dp--;
 	if(mpcmpfltc(a, 0.0) != 0) {
@@ -277,6 +289,8 @@ mpatoflt(Mpflt *a, char *as)
 			mpdivfltflt(a, &b);
 		}
 	}
+
+out:
 	if(f)
 		mpnegflt(a);
 	return;
@@ -407,12 +421,30 @@ int
 Fconv(Fmt *fp)
 {
 	char buf[500];
-	Mpflt *fval;
+	Mpflt *fvp, fv;
+
+	fvp = va_arg(fp->args, Mpflt*);
+	if(sigfig(fvp) == 0) {
+		snprint(buf, sizeof(buf), "0p+0");
+		goto out;
+	}
+	fv = *fvp;
+
+	while(fv.val.a[0] == 0) {
+		mpshiftfix(&fv.val, -Mpscale);
+		fv.exp += Mpscale;
+	}
+	while((fv.val.a[0]&1) == 0) {
+		mpshiftfix(&fv.val, -1);
+		fv.exp += 1;
+	}
 
-	fval = va_arg(fp->args, Mpflt*);
-	if(fval->exp >= 0)
-		snprint(buf, sizeof(buf), "(%B*2^%d)", &fval->val, fval->exp);
-	else
-		snprint(buf, sizeof(buf), "(%B/2^%d)", &fval->val, -fval->exp);
+	if(fv.exp >= 0) {
+		snprint(buf, sizeof(buf), "%Bp+%d", &fv.val, fv.exp);
+		goto out;
+	}
+	snprint(buf, sizeof(buf), "%Bp-%d", &fv.val, -fv.exp);
+
+out:
 	return fmtstrcpy(fp, buf);
 }

コアとなるコードの解説

src/cmd/gc/lex.c の変更

lex.c の変更は、字句解析器が新しい浮動小数点定数の構文を認識するためのものです。

	if(c == 'p' || c == 'P')
		goto casep;

この行は、現在の文字 c'p' または 'P' である場合に、新しい casep ラベルにジャンプするように指示しています。これにより、字句解析器は通常の10進浮動小数点数(e または E を含む)とは異なる、バイナリ指数を持つ浮動小数点数を処理する準備をします。

casep:
	*cp++ = 'p';
	c = getc();
	if(c == '+' || c == '-') {
		*cp++ = c;
		c = getc();
	}
	if(!isdigit(c))
		yyerror("malformed fp constant exponent");
	while(isdigit(c)) {
		*cp++ = c;
		c = getc();
	}
	goto caseout;

casep ブロックでは、まず 'p' または 'P' をバッファに格納します。次に、オプションの符号(+ または -)を読み込み、その後に続く10進数字のシーケンスを指数部として読み込みます。数字でない文字が来た場合や、数字が全くない場合はエラー (yyerror) を報告します。これにより、123p+45 のような形式が正しく解析されます。

src/cmd/gc/mparith1.c の変更

mparith1.c の変更は、多倍長浮動小数点数の文字列変換とフォーマットに関するものです。

mpatoflt 関数の変更

mpatoflt 関数は、文字列から多倍長浮動小数点数への変換を行います。

	int dp, c, f, ef, ex, eb, zer;

eb (binary point) という新しい変数が追加されました。これは、入力文字列に p または P が含まれているかどうかを示すフラグとして使用されます。

		case 'P':
		case 'p':
			eb = 1;

入力文字列の解析中に 'P' または 'p' が検出された場合、eb フラグが 1 に設定されます。

	if(eb) {
		if(dp)
			goto bad;
		a->exp += ex;
		goto out;
	}

eb1 の場合(つまり、p 形式の指数部が指定された場合)、小数点(dp)が存在しないことを確認します。もし存在すれば、それは不正な形式 (goto bad) です。その後、解析された指数 ex を多倍長浮動小数点数 a の指数部 a->exp に直接加算します。これは、p 形式の指数が2のべき乗の指数として扱われるためです。

Fconv 関数の変更

Fconv 関数は、多倍長浮動小数点数を文字列にフォーマットするために使用されます。

	Mpflt *fvp, fv;

	fvp = va_arg(fp->args, Mpflt*);
	if(sigfig(fvp) == 0) {
		snprint(buf, sizeof(buf), "0p+0");
		goto out;
	}
	fv = *fvp;

	while(fv.val.a[0] == 0) {
		mpshiftfix(&fv.val, -Mpscale);
		fv.exp += Mpscale;
	}
	while((fv.val.a[0]&1) == 0) {
		mpshiftfix(&fv.val, -1);
		fv.exp += 1;
	}

この部分では、まず引数から Mpflt ポインタを取得し、その値を fv にコピーします。sigfig 関数で有効桁数が0の場合は "0p+0" と出力します。 次に、fv の仮数部 fv.val を正規化します。fv.val.a[0] == 0 のループは、仮数部の最上位ワードが0である限り、Mpscale ビットだけ右シフトし、指数部を Mpscale だけ増やします。これは、仮数部を正規化して先頭のゼロを取り除く操作です。 while((fv.val.a[0]&1) == 0) のループは、仮数部の最下位ビットが0である限り(つまり偶数である限り)、1ビットだけ右シフトし、指数部を1だけ増やします。これは、仮数部を正規化して末尾のゼロビットを取り除く操作であり、最も簡潔なバイナリ表現を得るために重要です。

	if(fv.exp >= 0) {
		snprint(buf, sizeof(buf), "%Bp+%d", &fv.val, fv.exp);
		goto out;
	}
	snprint(buf, sizeof(buf), "%Bp-%d", &fv.val, -fv.exp);

正規化された fv の指数部 fv.exp が0以上の場合、%Bp+%d の形式で出力します。%B は多倍長整数を10進数で出力するためのフォーマット指定子です。指数部が負の場合、%Bp-%d の形式で出力します。これにより、123p+45123p-45 のような、正確なバイナリ浮動小数点定数の文字列表現が生成されます。

src/cmd/gc/export.c の変更

 	case CTFLT:
-		Bprint(bout, "%.17e\n", mpgetflt(n->val.u.fval));
+		Bprint(bout, "%F\n", n->val.u.fval);
 		break;

この変更は、浮動小数点定数をエクスポートする際のフォーマットを変更しています。以前は %.17e を使用して倍精度浮動小数点数として出力していましたが、これは精度損失の可能性がありました。新しい %F フォーマットは、mparith1.c で定義された Fconv 関数を呼び出し、多倍長浮動小数点数を新しい p 形式で正確に文字列化します。これにより、エクスポートされる定数の精度が保証されます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/gc ディレクトリ)
  • 多倍長算術に関する一般的な知識
  • コンパイラの字句解析に関する一般的な知識
  • C言語の printf フォーマット指定子に関する情報 (特に %a について)