[インデックス 18898] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における多倍長浮動小数点数演算ライブラリ(mparith
)の改善に関するものです。具体的には、浮動小数点定数の指数部がオーバーフローまたはアンダーフローするケースを適切に処理するための変更が導入されました。これにより、過度に大きな浮動小数点定数はコンパイルエラーとなり、過度に小さな浮動小数点定数はゼロに丸められるようになります。これは、Go言語の定数評価における数値の正確性と堅牢性を向上させるための重要な修正です。
コミット
commit cb502775107ce5f6f22e9b47c9c77300859864b4
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Wed Mar 19 05:48:00 2014 +0100
cmd/gc: check exponent overflow and underflow in mparith
A too large float constant is an error.
A too small float constant is rounded to zero.
Fixes #7419
Update #6902
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/76730046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cb502775107ce5f6f22e9b47c9c77300859864b4
元コミット内容
Goコンパイラ(cmd/gc
)において、多倍長浮動小数点数演算ライブラリ(mparith
)で指数部のオーバーフローとアンダーフローをチェックするようにしました。
- 過度に大きな浮動小数点定数はエラーとなります。
- 過度に小さな浮動小数点定数はゼロに丸められます。
これはIssue #7419を修正し、Issue #6902を更新します。
変更の背景
Go言語のコンパイラは、ソースコード中の定数をコンパイル時に評価します。この際、浮動小数点定数、特に非常に大きな値や非常に小さな値を扱う場合、その数値がターゲットとなる浮動小数点形式(通常はIEEE 754倍精度浮動小数点数)で表現できる範囲を超えることがあります。
このコミットが修正する主な問題は、以下の2つのシナリオです。
- 指数部のオーバーフロー: 非常に大きな浮動小数点定数(例:
1e+1000000
)が指定された場合、その指数部が表現可能な最大値を超えてしまう問題です。これまでのコンパイラでは、このようなケースが適切にエラーとして検出されず、予期せぬ動作や誤った数値として扱われる可能性がありました。 - 指数部のアンダーフロー: 非常に小さな浮動小数点定数(例:
1e-1000000
)が指定された場合、その指数部が表現可能な最小値よりも小さくなってしまう問題です。IEEE 754標準では、このような「非正規化数」や「ゼロへのフラッシュ」といった概念がありますが、コンパイラがこれを適切に処理しないと、誤った値として扱われたり、実行時の挙動が予測不能になったりする可能性がありました。特に、Issue #7419では、Goコンパイラが非常に小さな浮動小数点定数をゼロに丸めるべきところで、誤った値として扱ってしまう挙動が報告されていました。
これらの問題を修正することで、Goコンパイラは浮動小数点定数の扱いにおいて、より堅牢で予測可能な挙動を提供するようになります。これにより、開発者は数値の精度に関する予期せぬバグに遭遇するリスクが低減されます。
前提知識の解説
浮動小数点数表現 (IEEE 754)
現代のコンピュータにおける浮動小数点数は、ほとんどの場合、IEEE 754標準に従って表現されます。この標準では、数値を以下の3つの要素で表現します。
- 符号 (Sign): 数値が正か負かを示す1ビット。
- 指数部 (Exponent): 数値のスケール(桁の大きさ)を示す部分。基数(通常は2)の何乗かを表現します。
- 仮数部 (Mantissa/Significand): 数値の有効数字を示す部分。
例えば、倍精度浮動小数点数(float64
)では、64ビットのうち1ビットが符号、11ビットが指数部、52ビットが仮数部に割り当てられます。
- 正規化数 (Normalized Numbers): 仮数部が1.xxxx...の形式で表現される通常の浮動小数点数。
- 非正規化数 (Denormalized Numbers): 非常にゼロに近い数値を表現するために、仮数部の先頭の暗黙の1を0として扱う数値。これにより、表現可能な最小の正規化数よりもさらに小さな値を表現できますが、精度は低下します。
- 無限大 (Infinity): オーバーフローの結果など、表現可能な最大値を超える場合に用いられます。
- NaN (Not a Number): 不定形な演算結果(例: 0/0, 無限大-無限大)を表します。
オーバーフローとアンダーフロー
- オーバーフロー (Overflow): 計算結果が、そのデータ型で表現できる最大値を超えてしまう状態です。浮動小数点数の場合、指数部が表現可能な最大値を超えると発生し、通常は無限大として扱われるか、エラーとなります。
- アンダーフロー (Underflow): 計算結果が、そのデータ型で表現できる最小の非ゼロ値よりも小さくなってしまう状態です。浮動小数点数の場合、指数部が表現可能な最小値よりも小さくなると発生します。これは、非正規化数として表現されるか、あるいは「ゼロへのフラッシュ (flush to zero)」としてゼロに丸められることがあります。
Goコンパイラ (cmd/gc)
cmd/gc
は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルの過程で、ソースコード中の定数(数値リテラルなど)は、実行時ではなくコンパイル時に評価されます。この評価には、高い精度を必要とする多倍長演算ライブラリが使用されることがあります。
多倍長浮動小数点演算 (mparith)
mparith
は、Goコンパイラ内部で使用される多倍長(multiple-precision)算術ライブラリの一部です。通常のfloat64
型よりも高い精度で浮動小数点数を扱うことができます。これは、コンパイル時に定数を評価する際に、丸め誤差を最小限に抑え、正確な結果を得るために重要です。mparith
は、Mpflt
という構造体で浮動小数点数を表現し、その中に指数部(exp
)や仮数部(val
)などの情報を保持しています。
技術的詳細
このコミットの核心は、mparith
ライブラリにおける浮動小数点数の指数部(exp
)の管理を強化することです。特に、新しい関数mpsetexp
が導入され、既存の浮動小数点演算関数がこのmpsetexp
を介して指数部を設定するように変更されました。
mpsetexp
関数の役割
mpsetexp
関数は、Mpflt
型の浮動小数点数と、設定しようとする新しい指数値exp
を受け取ります。この関数は以下のロジックで指数部を処理します。
- 指数部の範囲チェック:
(short)exp != exp
という条件で、exp
がshort
型で表現できる範囲を超えているかをチェックします。mparith
内部では、指数部がshort
型で管理されているため、このチェックはオーバーフローを検出するために重要です。0x7fff
はshort
型の最大値(32767)に相当します。- オーバーフローの場合:
exp > 0
であれば、設定しようとしている指数が正の方向に大きすぎることを意味します。この場合、yyerror("float constant is too large")
を呼び出してコンパイルエラーを報告し、a->exp
を0x7fff
(最大値)に設定します。 - アンダーフローの場合:
exp <= 0
であれば、設定しようとしている指数が負の方向に小さすぎることを意味します。この場合、mpmovecflt(a, 0)
を呼び出して、浮動小数点数をゼロに丸めます。
- オーバーフローの場合:
- 正常な場合:
exp
がshort
型で表現可能な範囲内であれば、a->exp = exp
として指数部を直接設定します。
mpsetexp
の統合
mpsetexp
関数は、mparith
ライブラリ内の複数の浮動小数点演算関数に統合されました。これにより、指数部が変更される可能性のあるすべての操作で、自動的にオーバーフローとアンダーフローのチェックが行われるようになります。
mpatoflt
(文字列から浮動小数点数への変換): 文字列リテラルから浮動小数点数を解析する際に、指数部が計算されます。この計算結果をmpsetexp
に渡すことで、初期値の段階でオーバーフロー/アンダーフローがチェックされます。特に、dp-ex
がshort
の範囲を超える場合にゼロに丸める処理が追加されました。mpnorm
(正規化): 浮動小数点数を正規化する際に指数部が調整されます。この調整後の指数部がmpsetexp
に渡されます。mpaddfltflt
(加算),mpmulfltflt
(乗算),mpdivfltflt
(除算): これらの演算では、結果の指数部が計算されます。計算された指数部がmpsetexp
に渡され、結果の数値が表現可能かどうかが検証されます。mpgetflt
(浮動小数点数値の取得): 浮動小数点数を取得する際に、仮数部のシフトに伴って指数部が調整されます。この調整もmpsetexp
を介して行われます。
Issue #7419の修正
Issue #7419は、1e-779137
のような非常に小さな浮動小数点定数が、Goコンパイラによって正しくゼロに丸められないという問題でした。このコミットによって導入されたmpsetexp
関数は、指数部が小さすぎる場合に数値をゼロに丸めるロジックを含んでいます。test/fixedbugs/issue7419.go
で追加されたテストは、1e-779137
と1e-779138
という2つの非常に小さな定数が、コンパイル時に正しくゼロとして評価されることを検証しています。
コアとなるコードの変更箇所
このコミットでは、主に以下の4つのファイルが変更されました。
-
src/cmd/gc/go.h
:mpsetexp
関数のプロトタイプ宣言が追加されました。
--- a/src/cmd/gc/go.h +++ b/src/cmd/gc/go.h @@ -1255,6 +1255,7 @@ void tmpmovecflt(Mpflt *a, double c); void tmpmulfltflt(Mpflt *a, Mpflt *b); void tmpnegflt(Mpflt *a); void tmpnorm(Mpflt *a); +void mpsetexp(Mpflt *a, int exp); int mptestflt(Mpflt *a); int sigfig(Mpflt *a);
-
src/cmd/gc/mparith1.c
:mpatoflt
関数内で、指数部を設定する箇所がmpsetexp
の呼び出しに置き換えられました。- 特に、
dp-ex
がshort
の範囲を超える場合に、a
をゼロに丸めるための条件分岐が追加されました。
--- a/src/cmd/gc/mparith1.c +++ b/src/cmd/gc/mparith1.c @@ -416,7 +416,7 @@ mpatoflt(Mpflt *a, char *as) if(eb) { if(dp) goto bad; - a->exp += ex; + mpsetexp(a, a->exp+ex); goto out; } @@ -427,8 +427,13 @@ mpatoflt(Mpflt *a, char *as) mppow10flt(&b, ex-dp); mpmulfltflt(a, &b); } else { - mppow10flt(&b, dp-ex); - mpdivfltflt(a, &b); + if((short)(dp-ex) != dp-ex) { + tmpmovecflt(a, 0.0); + } + else { + mppow10flt(&b, dp-ex); + mpdivfltflt(a, &b); + } } }
-
src/cmd/gc/mparith3.c
:mpsetexp
関数の実装が追加されました。mpnorm
,mpaddfltflt
,mpmulfltflt
,mpdivfltflt
,mpgetflt
といった複数の関数内で、指数部を直接設定していた箇所がmpsetexp
の呼び出しに置き換えられました。
--- a/src/cmd/gc/mparith3.c +++ b/src/cmd/gc/mparith3.c @@ -22,6 +22,27 @@ sigfig(Mpflt *a) return i+1; } +/* + * sets the exponent. + * a too large exponent is an error. + * a too small exponent rounds the number to zero. + */ +void +mpsetexp(Mpflt *a, int exp) { + if((short)exp != exp) { + if(exp > 0) { + yyerror("float constant is too large"); + a->exp = 0x7fff; + } + else { + tmpmovecflt(a, 0); + } + } + else { + a->exp = exp; + } +} + /* * shifts the leading non-zero * word of the number to Mpnorm @@ -60,7 +81,7 @@ mpnorm(Mpflt *a) } mpshiftfix(&a->val, s); - a->exp -= s; + mpsetexp(a, a->exp-s); } /// implements float arihmetic @@ -95,7 +116,7 @@ mpaddfltflt(Mpflt *a, Mpflt *b) if(s < 0) { // b is larger, shift a right mpshiftfix(&a->val, s); - a->exp -= s; + mpsetexp(a, a->exp-s); mpaddfixfix(&a->val, &b->val, 0); goto out; } @@ -131,7 +152,7 @@ mpmulfltflt(Mpflt *a, Mpflt *b) } mpmulfract(&a->val, &b->val); - a->exp = (a->exp + b->exp) + Mpscale*Mpprec - Mpscale - 1; + mpsetexp(a, (a->exp + b->exp) + Mpscale*Mpprec - Mpscale - 1); tmpnorm(a); if(Mpdebug) @@ -171,7 +192,7 @@ mpdivfltflt(Mpflt *a, Mpflt *b) // divide mpdivfract(&a->val, &c.val); - a->exp = (a->exp-c.exp) - Mpscale*(Mpprec-1) + 1; + mpsetexp(a, (a->exp-c.exp) - Mpscale*(Mpprec-1) + 1); tmpnorm(a); if(Mpdebug) @@ -199,7 +220,10 @@ mpgetflt(Mpflt *a) while((a->val.a[Mpnorm-1] & Mpsign) == 0) { mpshiftfix(&a->val, 1); - a->exp -= 1; + mpsetexp(a, a->exp-1); // can set 'a' to zero + s = sigfig(a); + if(s == 0) + return 0; } // the magic numbers (64, 63, 53, 10, -1074) are
-
test/fixedbugs/issue7419.go
:- Issue #7419の修正を検証するための新しいテストファイルが追加されました。
- 非常に小さな浮動小数点定数
1e-779137
と1e-779138
が、コンパイル時にゼロとして評価されることを確認します。
// run // Copyright 2014 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. // Issue 7419: odd behavior for float constants underflowing to 0 package main import ( "os" ) var x = 1e-779137 var y = 1e-779138 func main() { if x != 0 { os.Exit(1) } if y != 0 { os.Exit(2) } }
コアとなるコードの解説
mpsetexp
関数の詳細
mpsetexp
関数は、mparith
ライブラリにおける浮動小数点数の指数部を安全に設定するためのゲートウェイとして機能します。
void
mpsetexp(Mpflt *a, int exp) {
if((short)exp != exp) { // expがshort型で表現できる範囲を超えているかチェック
if(exp > 0) { // 正の方向に大きすぎる場合(オーバーフロー)
yyerror("float constant is too large"); // コンパイルエラーを報告
a->exp = 0x7fff; // 指数部を最大値に設定
}
else { // 負の方向に小さすぎる場合(アンダーフロー)
tmpmovecflt(a, 0); // 数値をゼロに丸める
}
}
else { // 正常な範囲内であれば、そのまま設定
a->exp = exp;
}
}
if((short)exp != exp)
: この条件は、int
型のexp
がshort
型にキャストされた後、元のint
型の値と異なる場合に真となります。これは、exp
がshort
型の表現範囲(通常-32768から32767)を超えていることを意味します。yyerror("float constant is too large")
: Goコンパイラのエラー報告関数です。これにより、コンパイル時にユーザーにエラーメッセージが表示されます。a->exp = 0x7fff;
:0x7fff
は16進数で32767であり、符号付き16ビット整数(short
)の最大値です。オーバーフローの場合、指数部をこの最大値に設定することで、内部的な整合性を保ちつつ、エラー状態を示します。tmpmovecflt(a, 0);
:Mpflt
型のa
をゼロに設定する関数です。アンダーフローの場合、数値をゼロに丸めるというポリシーに従います。
各関数でのmpsetexp
の適用
mpatoflt
: 文字列から浮動小数点数を解析する際、指数部ex
が計算されます。以前はa->exp += ex;
と直接加算していましたが、mpsetexp(a, a->exp+ex);
とすることで、加算結果がオーバーフロー/アンダーフローしないかチェックされるようになりました。また、dp-ex
(小数点以下の桁数と指数部の差)がshort
の範囲を超えるような極端なアンダーフローの場合も、明示的にゼロに丸める処理が追加されました。mpnorm
: 浮動小数点数を正規化する際、仮数部をシフトした分だけ指数部を調整します。この調整もmpsetexp
を介して行われることで、正規化の過程で指数部が範囲外になることを防ぎます。mpmulfltflt
/mpdivfltflt
: 乗算や除算では、結果の指数部はオペランドの指数部の合計または差に基づいて計算されます。これらの計算結果もmpsetexp
に渡されることで、演算結果が表現可能な範囲に収まるように保証されます。mpgetflt
: 浮動小数点数を取得する際、内部表現から最終的な値に変換する過程で指数部が調整されることがあります。この調整もmpsetexp
を通じて行われ、特にゼロへの丸め処理が適切に適用されるようになりました。sigfig(a)
が0になる(有効数字がなくなる)場合にreturn 0;
とするロジックも追加され、より堅牢になっています。
test/fixedbugs/issue7419.go
のテスト内容
このテストは、Go言語のソースコードに記述された非常に小さな浮動小数点定数が、コンパイル時に正しくゼロとして扱われることを検証します。
var x = 1e-779137
var y = 1e-779138
func main() {
if x != 0 {
os.Exit(1) // xが0でなければエラー終了
}
if y != 0 {
os.Exit(2) // yが0でなければエラー終了
}
}
1e-779137
や1e-779138
といった数値は、標準的なfloat64
型で表現できる最小の非ゼロ値(約4.9e-324
)よりもはるかに小さいため、アンダーフローが発生し、ゼロに丸められるべきです。このテストは、コンパイラがこの丸め処理を正しく行っていることを確認することで、Issue #7419で報告されたバグが修正されたことを保証します。
関連リンク
- Go Issue #7419: cmd/gc: odd behavior for float constants underflowing to 0
- Go Issue #6902: cmd/gc: float constant precision
- https://github.com/golang/go/issues/6902
- このIssueは、浮動小数点定数の精度に関するより広範な議論を含んでおり、本コミットはその一部としてアンダーフロー/オーバーフローの挙動を改善しています。
- Go Change List 76730046:
参考にした情報源リンク
- IEEE 754 浮動小数点数:
- 浮動小数点数:
- オーバーフロー (数値):
- アンダーフロー (数値):
- Go言語のコンパイラに関する一般的な情報:
- Goの公式ドキュメントやブログ記事など、
cmd/gc
の内部構造や定数評価に関する詳細な技術記事があれば、より深い理解に役立ちます。
- Goの公式ドキュメントやブログ記事など、