[インデックス 1265] ファイルの概要
このコミットは、Goコンパイラ(gc
)における浮動小数点定数の取り扱い、特にfloat64
またはfloat32
のコンテキストで使用される際の丸め(truncating)に関する修正を導入しています。Go言語のコンパイラが、ソースコード中の浮動小数点リテラルを内部表現から実際のfloat32
またはfloat64
型に変換する際に、IEEE 754標準に準拠した正確な丸めが行われるように改善されています。
コミット
commit 89996e1fb1b3834746340570c2d1b48ef9fc1b9b
Author: Ken Thompson <ken@golang.org>
Date: Tue Dec 2 17:03:47 2008 -0800
truncating of float constants when
used in float64 or float32 contexts
R=r
OCL=20297
CL=20297
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/89996e1fb1b3834746340570c2d1b48ef9fc1b9b
元コミット内容
浮動小数点定数がfloat64
またはfloat32
コンテキストで使用される際の丸め処理。
変更の背景
Go言語のコンパイラは、ソースコード中の数値リテラルを内部的に高精度な多倍長演算(multi-precision arithmetic)で扱います。これにより、コンパイル時の計算精度を最大限に保ち、最終的な型への変換時にのみ精度を落とすことができます。しかし、このコミット以前は、高精度で表現された浮動小数点リテラルが、最終的にfloat32
(単精度)やfloat64
(倍精度)といった固定精度型に変換される際に、IEEE 754浮動小数点標準に準拠した適切な丸め処理が行われていなかった可能性があります。
特に、float32
やfloat64
はそれぞれ24ビット(隠れビットを含め23ビットの仮数部)と53ビット(隠れビットを含め52ビットの仮数部)の精度しか持たないため、高精度なリテラルをこれらの型に代入する際には、余分な精度を適切に切り捨てる(丸める)必要があります。この丸めが不適切だと、コンパイル時と実行時で浮動小数点値の挙動が異なったり、期待される精度が得られなかったりする問題が発生します。
このコミットは、このような浮動小数点リテラルの型変換における精度管理と丸め処理の正確性を向上させることを目的としています。
前提知識の解説
Goコンパイラ (gc
)
Go言語の公式コンパイラはgc
と呼ばれ、Goのソースコードを機械語に変換する役割を担っています。gc
は、型チェック、最適化、コード生成など、コンパイルの様々な段階で動作します。このコミットで変更されているsrc/cmd/gc/const.c
やsrc/cmd/gc/mparith3.c
は、gc
の内部で定数処理や多倍長演算を司る部分です。
浮動小数点数 (IEEE 754)
現代のコンピュータにおける浮動小数点数の標準はIEEE 754です。Go言語のfloat32
とfloat64
もこの標準に準拠しています。
float32
(単精度): 32ビットで表現され、約7桁の10進精度を持ちます。仮数部(significand)は23ビット(隠れビットを含め24ビット)、指数部は8ビットです。float64
(倍精度): 64ビットで表現され、約15-17桁の10進精度を持ちます。仮数部は52ビット(隠れビットを含め53ビット)、指数部は11ビットです。
IEEE 754では、数値が表現可能な範囲を超えたり、精度が足りない場合に丸め(rounding)が必要になります。標準で推奨される丸めモードの一つに「最近接偶数への丸め(round half to even)」があります。これは、ちょうど中間にある値(例: 2.5, 3.5)を丸める際に、結果が偶数になる方へ丸めるというルールです。
多倍長演算 (Multi-precision Arithmetic)
多倍長演算とは、コンピュータのネイティブなワードサイズ(例: 64ビット)よりも大きな精度で数値を扱うための技術です。Goコンパイラは、ソースコード中の数値リテラルを解析する際に、この多倍長演算ライブラリ(mp
プレフィックスの関数群)を使用して、可能な限り高い精度で中間値を保持します。これにより、コンパイル時の計算誤差を最小限に抑え、最終的な型への変換時にのみ必要な精度に丸めることができます。
コンパイラの定数処理
コンパイラは、ソースコード中の定数(リテラル)を特別な方法で扱います。例えば、const x = 0.1 + 0.2
のような式は、コンパイル時に計算され、その結果が定数として埋め込まれます。この際、中間計算は高精度で行われ、最終的な定数の型(例: float64
)に合わせて丸められます。
技術的詳細
このコミットの主要な変更点は、src/cmd/gc/const.c
にtruncfltlit
という新しい関数が導入され、既存のconvlit
関数内でこのtruncfltlit
が呼び出されるようになったことです。また、src/cmd/gc/mparith3.c
内のmpgetflt
関数も、多倍長浮動小数点数をdouble
型に変換する際の丸めロジックが改善されています。
truncfltlit
関数の役割
truncfltlit
関数は、多倍長浮動小数点リテラル(Mpflt *fv
)を、指定された型(Type *t
、float32
またはfloat64
)の精度に丸める役割を担います。
-
TFLOAT64
の場合:d = mpgetflt(fv);
で多倍長浮動小数点数fv
をC言語のdouble
型に変換します。mpmovecflt(fv, d);
でそのdouble
値を再び多倍長浮動小数点数fv
に戻します。この往復変換により、double
の精度(IEEE 754倍精度)に丸められます。
-
TFLOAT32
の場合:d = mpgetflt(fv);
でfv
をdouble
に変換します。f = d;
でdouble
をC言語のfloat
型にキャストします。これにより、float
の精度(IEEE 754単精度)に丸められます。d = f;
でfloat
を再びdouble
にキャストします。これは、mpmovecflt
がdouble
を引数に取るための中間ステップです。mpmovecflt(fv, d);
でそのdouble
値をfv
に戻します。この一連の操作により、float32
の精度に丸められます。
この関数は、コンパイラが内部的に使用する多倍長浮動小数点表現と、ターゲットとなるGoの浮動小数点型(float32
/float64
)との間の精度ギャップを埋めるための重要なブリッジとなります。
mpgetflt
関数の改善
mpgetflt
関数は、多倍長浮動小数点数(Mpflt
)をC言語のdouble
型に変換する際に使用されます。このコミットでは、特に以下の点が改善されています。
-
正規化ループの条件変更:
while((a->val.a[Mpnorm-1] & (1L<<(Mpscale-1))) == 0)
からwhile((a->val.a[Mpnorm-1] & Mpsign) == 0)
へ変更されました。これは、多倍長数の最上位ビット(符号ビット)をチェックして正規化を行うロジックの修正であり、より正確な正規化を保証します。 -
IEEE 754固有の丸め処理の明示と修正: 以前のコードには、
// should do this in multi precision
というコメントがあり、多倍長精度で丸めを行うべきという意図が示唆されていました。今回の変更では、mpgetflt
内でdouble
への変換時にIEEE 754の「最近接偶数への丸め(round half to even)」に近いロジックが導入されています。if((v&0x3ffULL) != 0x200ULL || (v&0x400) != 0)
の条件で、v += 0x200ULL;
(丸め)が行われます。これは、下位10ビット(0x3ffULL
)と次のビット(0x400
)を考慮して、丸めが必要かどうかを判断し、必要であれば0x200ULL
(2の9乗、つまり10ビット目の位置)を加算して丸めを行います。コメントが// round toward even
に変更されており、IEEE 754の標準的な丸めモードに近づける意図が明確になっています。v &= ~0x3ffULL;
がv >>= 10;
に変更されました。これは、下位10ビットをマスクでクリアする代わりに、単純に右に10ビットシフトすることで、不要な精度を切り捨てる操作です。f = ldexp(f, Mpnorm*Mpscale + a->exp - 63);
がf = ldexp(f, Mpnorm*Mpscale + a->exp - 53);
に変更されました。53
はdouble
の仮数部のビット数(隠れビットを含む)であり、この変更により、指数部の調整がdouble
の正確な表現に合致するようになりました。以前の63
は誤りであったか、異なる中間表現を想定していた可能性があります。
これらの変更により、Goコンパイラが浮動小数点リテラルを処理する際の精度管理と丸め処理が、よりIEEE 754標準に準拠し、予測可能な挙動を示すようになりました。
コアとなるコードの変更箇所
src/cmd/gc/const.c
-
truncfltlit
関数の追加:void truncfltlit(Mpflt *fv, Type *t) { double d; float f; if(t == T) return; // convert large precision literal floating // into limited precision (float64 or float32) // botch -- this assumes that compiler fp // has same precision as runtime fp switch(t->etype) { case TFLOAT64: d = mpgetflt(fv); mpmovecflt(fv, d); break; case TFLOAT32: d = mpgetflt(fv); f = d; d = f; mpmovecflt(fv, d); break; } }
-
convlit
関数内でのtruncfltlit
の呼び出し追加:- 整数リテラルから浮動小数点リテラルへの変換後:
// ... n->val.u.fval = fv; // fv is newly allocated Mpflt mpmovefixflt(fv, xv); n->val.ctype = CTFLT; truncfltlit(fv, t); // ADDED // ...
- 浮動小数点リテラルから別の浮動小数点型への変換後:
// ... // Old commented out block for truncation removed // replaced by: truncfltlit(fv, t); // ADDED // ...
- 整数リテラルから浮動小数点リテラルへの変換後:
-
convlit1
関数(ret
ラベル付近)でのtruncfltlit
の呼び出し追加:// ... } else if(wl == Wlitfloat) { n->val.u.fval = fval; truncfltlit(fval, n->type); // ADDED } // ...
src/cmd/gc/mparith3.c
-
mpgetflt
関数内の正規化ループ条件の変更:// Old: while((a->val.a[Mpnorm-1] & (1L<<(Mpscale-1))) == 0) { // New: while((a->val.a[Mpnorm-1] & Mpsign) == 0) { // MODIFIED mpshiftfix(&a->val, 1); a->exp -= 1; }
-
mpgetflt
関数内の丸めロジックと指数調整の変更:// ... // New comment: // the magic numbers (64, 63, 53, 10) are // IEEE specific. this should be done machine // independently or in the 6g half of the compiler // ... // Old: if((v&0x3ffULL) != 0x200ULL || (v&0x400) != 0) // Old: v += 0x200ULL; // round // Old: v &= ~0x3ffULL; // New: if((v&0x3ffULL) != 0x200ULL || (v&0x400) != 0) v += 0x200ULL; // round toward even // MODIFIED COMMENT v >>= 10; // MODIFIED f = (double)(v); // Old: f = ldexp(f, Mpnorm*Mpscale + a->exp - 63); // New: f = ldexp(f, Mpnorm*Mpscale + a->exp - 53); // MODIFIED // ...
コアとなるコードの解説
このコミットの核心は、Goコンパイラが浮動小数点リテラルを扱う際の精度管理を、より厳密にIEEE 754標準に準拠させる点にあります。
src/cmd/gc/const.c
に追加されたtruncfltlit
関数は、コンパイラ内部で高精度に保持されている浮動小数点リテラル(Mpflt
型)を、最終的にGoのfloat32
またはfloat64
型に変換する際の「丸めゲートウェイ」として機能します。この関数は、C言語のネイティブなdouble
型やfloat
型へのキャストを利用することで、コンパイラが動作するシステム(通常はIEEE 754準拠)の浮動小数点演算ユニットに丸め処理を委ねています。これにより、Goコンパイラ自身が複雑な丸めロジックを実装する代わりに、既存の信頼性の高いハードウェア/ソフトウェアの浮動小数点処理を利用できるという利点があります。
src/cmd/gc/mparith3.c
のmpgetflt
関数への変更は、多倍長浮動小数点数からCのdouble
型への変換自体を改善するものです。特に、仮数部のビットシフト(v >>= 10;
)と指数部の調整(- 53
)は、double
型が持つ53ビットの仮数部(隠れビットを含む)に正確に合わせるためのものです。また、「最近接偶数への丸め」を意図したロジックの修正は、IEEE 754標準で推奨される丸めモードに準拠することで、浮動小数点演算の予測可能性と正確性を高めます。
これらの変更が組み合わさることで、Go言語のソースコードに記述された浮動小数点リテラルは、コンパイル時に適切な精度で丸められ、実行時の挙動がより一貫性を持つようになります。これは、数値計算の正確性が求められるアプリケーションにおいて非常に重要です。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- IEEE 754浮動小数点標準に関する情報: https://standards.ieee.org/standard/754-2019.html (標準自体は有料ですが、概要はWikipediaなどで参照可能)
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- IEEE 754 - Wikipedia: https://ja.wikipedia.org/wiki/IEEE_754
- 浮動小数点数 - Wikipedia: https://ja.wikipedia.org/wiki/%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0
- 多倍長整数 - Wikipedia: https://ja.wikipedia.org/wiki/%E5%A4%9A%E5%80%8D%E9%95%B7%E6%95%B4%E6%95%B0 (多倍長浮動小数点数も同様の概念)
- Goコンパイラの内部構造に関する一般的な情報源 (Goのブログやカンファレンストークなど)