[インデックス 18927] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における浮動小数点数の扱いに関するバグ修正です。特に、非常に大きな負の指数を持つ浮動小数点数が正しくゼロに丸められない問題を解決します。これにより、Go言語の仕様に準拠した正確な浮動小数点演算が保証されます。
コミット
commit a43673cf8a237fa237c179aeb0862215b797b9df
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Mon Mar 24 10:10:29 2014 -0700
cmd/gc: round floats with a large negative exponent towards zero
Fixes #6902
LGTM=iant
R=iant, rsc
CC=golang-codereviews
https://golang.org/cl/78730049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a43673cf8a237fa237c179aeb0862215b797b9df
元コミット内容
cmd/gc: round floats with a large negative exponent towards zero
Fixes #6902
LGTM=iant
R=iant, rsc
CC=golang-codereviews
https://golang.org/cl/78730049
変更の背景
この変更は、Goコンパイラが非常に小さな浮動小数点定数を処理する際に発生していたバグ(Issue 6902)を修正するために行われました。具体的には、1e-10000
のような、非常に大きな負の指数を持つ浮動小数点数が、Goの浮動小数点仕様に従ってゼロに正しく丸められないという問題がありました。このような数値は、IEEE 754浮動小数点標準において「アンダーフロー」と呼ばれる現象を引き起こし、表現可能な最小の非ゼロ数よりも小さくなるため、通常はゼロとして扱われるべきです。しかし、コンパイラ内部の多倍長浮動小数点演算ライブラリ(mparith
)の処理に不備があり、この丸めが適切に行われていませんでした。この不正確な挙動は、コンパイル時に予期せぬ結果や、実行時の誤った数値比較を引き起こす可能性がありました。
前提知識の解説
- 浮動小数点数 (Floating-Point Numbers): コンピュータで実数を近似的に表現するための形式です。通常、符号部、指数部、仮数部で構成されます。
- 指数部 (Exponent): 数値の大きさを決定します。負の指数は非常に小さな数値を表します。
- 仮数部 (Mantissa/Significand): 数値の精度(有効桁数)を決定します。
- アンダーフロー (Underflow): 浮動小数点数が、表現可能な最小の非ゼロ数よりも絶対値が小さくなった場合に発生する現象です。この場合、通常はゼロに丸められます。
- IEEE 754: 浮動小数点数の表現と演算に関する国際標準です。多くのプログラミング言語やハードウェアで採用されています。
- Goコンパイラ (
cmd/gc
): Go言語の公式コンパイラの一つで、Goのソースコードを機械語に変換します。コンパイル時に定数式の評価も行います。 - 多倍長浮動小数点演算ライブラリ (
mparith
): Goコンパイラ内部で使用されるライブラリで、標準の浮動小数点型(float32
,float64
)では扱えないような、非常に大きな桁数や非常に小さな桁数の浮動小数点数を正確に計算するために使用されます。コンパイル時の定数評価などで利用されます。 dp
とex
: このコミットのコード変更箇所に登場する変数名で、それぞれdp
は仮数部の桁数(decimal places)、ex
は指数部(exponent)に関連する値を示していると推測されます。これらは多倍長浮動小数点数の内部表現における重要な要素です。
技術的詳細
このコミットの核心は、src/cmd/gc/mparith1.c
ファイル内の mpatoflt
関数における浮動小数点数の丸め処理の改善です。mpatoflt
関数は、文字列形式の浮動小数点数を内部の多倍長浮動小数点形式に変換する役割を担っています。
以前のコードでは、dp-ex
の値が short
型に収まらないほど大きい場合(つまり、非常に大きな負の指数を持つ場合)にのみ、ゼロへの丸め処理が行われていました。しかし、この条件だけでは不十分であり、dp-ex
が short
型の範囲内であっても、数値がアンダーフローするほど小さい場合にはゼロに丸める必要がありました。
新しいコードでは、以下の条件が追加されています。
if(dp-ex >= (1<<(8*sizeof(dp)-3)) || (short)(4*(dp-ex)) != 4*(dp-ex)) {
mpmovecflt(a, 0.0);
}
この変更は、以下の2つのケースを考慮しています。
dp-ex >= (1<<(8*sizeof(dp)-3))
: これは、dp-ex
が非常に大きな値になるケースを捕捉します。1<<(8*sizeof(dp)-3)
は、dp
の型が表現できる最大値に近い値を示しており、dp-ex
がこの値以上になるということは、数値が極めて小さく、アンダーフローする可能性が高いことを意味します。(short)(4*(dp-ex)) != 4*(dp-ex)
: この条件は、dp-ex
に4を掛けた値がshort
型に収まらない場合に真となります。コメントに「4 approximates least_upper_bound(log2(10))」とあるように、これは10進数の指数を2進数の指数に変換する際のスケールファクタ(約log2(10) ≈ 3.32)を考慮したものです。この条件は、dp-ex
がshort
型の範囲内であっても、その値が大きすぎるために、浮動小数点数の精度が失われ、結果的にゼロに丸めるべき状況を検出します。
これらの条件のいずれかが満たされた場合、mpmovecflt(a, 0.0)
が呼び出され、多倍長浮動小数点数 a
の値が明示的にゼロに設定されます。これにより、1e-10000
のような非常に小さな数値が正しくゼロとして扱われるようになります。
また、この修正を検証するために、test/fixedbugs/issue6902.go
という新しいテストファイルが追加されました。このテストは、var x = -1e-10000
という定数を定義し、その値がゼロと等しいことを確認することで、修正が正しく機能していることを保証します。
コアとなるコードの変更箇所
src/cmd/gc/mparith1.c
の mpatoflt
関数内:
--- a/src/cmd/gc/mparith1.c
+++ b/src/cmd/gc/mparith1.c
@@ -427,7 +427,8 @@ mpatoflt(Mpflt *a, char *as)
mppow10flt(&b, ex-dp);
mpmulfltflt(a, &b);
} else {
- if((short)(dp-ex) != dp-ex) {
+ // 4 approximates least_upper_bound(log2(10)).
+ if(dp-ex >= (1<<(8*sizeof(dp)-3)) || (short)(4*(dp-ex)) != 4*(dp-ex)) {
mpmovecflt(a, 0.0);
}
else {
test/fixedbugs/issue6902.go
の新規追加:
// 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 6902: confusing printing of large floating point constants
package main
import (
"os"
)
var x = -1e-10000
func main() {
if x != 0 {
os.Exit(1)
}
}
コアとなるコードの解説
変更された src/cmd/gc/mparith1.c
の mpatoflt
関数は、Goコンパイラが浮動小数点定数を処理する際の中心的な部分です。この関数は、文字列として表現された浮動小数点数(例: "1e-10000"
)を、コンパイラ内部で扱える多倍長浮動小数点形式に変換します。
変更前のコードでは、dp-ex
の値が short
型の範囲を超えた場合にのみ、数値がゼロに丸められるという単純なチェックが行われていました。しかし、これは不十分でした。なぜなら、dp-ex
が short
型の範囲内であっても、数値がアンダーフローするほど小さくなる可能性があるからです。
新しい条件 dp-ex >= (1<<(8*sizeof(dp)-3))
は、dp-ex
が非常に大きな正の値になるケースを捕捉します。これは、元の浮動小数点数が非常に小さな負の指数を持つことを意味し、結果として数値がゼロに非常に近いことを示唆します。1<<(8*sizeof(dp)-3)
は、dp
の型(おそらく int
または long
)が表現できる最大値に近い、非常に大きな閾値です。この閾値を超えるということは、数値がアンダーフローの領域にあることを明確に示します。
もう一つの条件 (short)(4*(dp-ex)) != 4*(dp-ex)
は、より微妙なケースを扱います。コメントにあるように、「4 approximates least_upper_bound(log2(10))」は、10進数の指数を2進数の指数に変換する際の近似係数(約3.32)を指しています。この条件は、dp-ex
に4を掛けた結果が short
型の範囲に収まらない場合に真となります。これは、dp-ex
が short
型の範囲内であっても、その値が大きすぎるために、浮動小数点数の精度が失われ、結果的にゼロに丸めるべき状況を検出するためのものです。このチェックにより、コンパイラが内部的に扱う多倍長浮動小数点数の精度限界を超えた場合に、安全にゼロに丸めることができます。
これらの条件のいずれかが満たされた場合、mpmovecflt(a, 0.0)
が呼び出され、現在の多倍長浮動小数点数 a
の値が強制的にゼロに設定されます。これにより、1e-10000
のような数値が、Goの浮動小数点仕様に従って正しくゼロとして扱われるようになります。
追加された test/fixedbugs/issue6902.go
は、この修正の回帰テストとして機能します。var x = -1e-10000
という定数を定義し、main
関数内で x != 0
である場合にプログラムが終了コード1で終了するようにしています。これは、x
が正しくゼロに丸められていることを検証するためのシンプルなテストです。このテストの追加により、将来の変更でこのバグが再発するのを防ぐことができます。
関連リンク
- Go Code Review: https://golang.org/cl/78730049
- Go Issue 6902 (詳細な情報はこのCLページに統合されています): https://golang.org/issue/6902 (このリンクは、古いGoのIssueトラッカーから新しいものへのリダイレクト、またはCLページへの直接リンクとして機能する可能性があります。)
参考にした情報源リンク
- Go Code Review System (Gerrit): https://go-review.googlesource.com/
- IEEE 754 浮動小数点数標準 (一般的な情報源): https://standards.ieee.org/standard/754-2019.html (標準自体のリンクですが、一般的な解説はWikipediaなどで見つけることができます)
- Go言語の公式ドキュメント (浮動小数点数に関する一般的な情報): https://go.dev/doc/
- Goコンパイラのソースコード (mparith1.c のコンテキスト理解のため): https://github.com/golang/go/tree/master/src/cmd/gc