[インデックス 12938] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtime
パッケージ内のテストコードにおける浮動小数点数表現の修正に関するものです。具体的には、Date
関数の引数として渡されるナノ秒の値を計算する際に、gccgo
コンパイラでの浮動小数点数の精度向上によって発生した問題に対処しています。
コミット
commit 426bf361316ab495a29c9e0a05da6236894dd392
Author: Ian Lance Taylor <iant@golang.org>
Date: Mon Apr 23 15:46:54 2012 -0700
time: change float expression to ensure it is an integer
When I increased the number of bits that gccgo uses for
untyped floats, the expression 0.52*1e9 was no longer
integral. This patch fixes that.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6113043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/426bf361316ab495a29c9e0a05da6236894dd392
元コミット内容
time: change float expression to ensure it is an integer
When I increased the number of bits that gccgo uses for untyped floats, the expression 0.52*1e9 was no longer integral. This patch fixes that.
変更の背景
この変更の背景には、Go言語のコンパイラの一つであるgccgo
における浮動小数点数の内部表現の変更があります。コミットメッセージによると、gccgo
が「型なし浮動小数点数(untyped floats)」に使用するビット数を増やした際に、0.52 * 1e9
という式の結果がもはや整数ではなくなってしまった、という問題が発生しました。
time
パッケージのDate
関数は、秒の小数点以下の部分をナノ秒単位で受け取ります。例えば、0.52
秒は0.52 * 1e9
ナノ秒、つまり520,000,000
ナノ秒として表現されるべきです。しかし、gccgo
の浮動小数点数表現の変更により、この計算結果が厳密な整数値(例: 520000000.0000000000000001
のような値)ではなくなったため、テストが失敗する可能性が出てきました。
この問題は、浮動小数点数の計算が常に厳密な結果を保証しないという性質に起因します。特に、0.52
のような有限の小数であっても、二進数表現では無限小数となり、特定の精度で丸められる際に誤差が生じることがあります。gccgo
が内部的に使用するビット数を増やしたことで、この誤差が以前よりも顕著になり、整数であるべき値がわずかにずれてしまう状況が発生したと考えられます。
このコミットは、このようなコンパイラの内部的な浮動小数点数処理の変更に起因する問題を回避し、Date
関数に渡されるナノ秒の値が常に正確な整数であることを保証するために行われました。
前提知識の解説
1. Go言語のtime
パッケージ
Go言語の標準ライブラリであるtime
パッケージは、時刻の表現、操作、フォーマット、および時間間隔の測定を提供します。
time.Time
型: 特定の時点を表します。time.Date
関数: 指定された年、月、日、時、分、秒、ナノ秒、およびロケーション情報からtime.Time
オブジェクトを構築します。引数nsec
はナノ秒単位で、int
型を受け取ります。
2. 浮動小数点数と精度
コンピュータにおける浮動小数点数(Floating-point number)は、実数を近似的に表現するための形式です。IEEE 754などの標準に基づいて表現されますが、多くの実数は二進数で正確に表現できないため、計算には常に丸め誤差が伴う可能性があります。
例えば、0.1
という十進数は二進数では無限小数になるため、コンピュータ内部では近似値として表現されます。この近似値が連鎖的な計算によって誤差を拡大させることがあります。
3. 1e9
とは
1e9
は科学的記数法(exponential notation)で1 × 10^9
、つまり1,000,000,000
を意味します。これはGo言語を含む多くのプログラミング言語で浮動小数点数のリテラルとして使用できます。time
パッケージでは、秒をナノ秒に変換するためにこの値がよく使われます(1秒 = 10^9ナノ秒)。
4. 型なし浮動小数点数(Untyped Floats)
Go言語には「型なし定数(untyped constants)」という概念があります。これは、リテラル値(例: 0.52
, 1e9
, 520
)が、特定の型に束縛される前に、より高い精度で表現されることを意味します。
例えば、0.52
は最初は型なし浮動小数点数として扱われ、その後に変数に代入されたり、関数に渡されたりする際に、その変数の型や関数の引数の型(例: float64
)に変換されます。この変換の際に、精度が失われる可能性があります。
gccgo
が型なし浮動小数点数に使用するビット数を増やしたということは、内部的な計算精度が向上したことを意味しますが、それがかえって特定の計算で「厳密な整数」であるべき値がわずかにずれるという副作用を生んだと考えられます。これは、より高い精度で計算された結果が、最終的に整数型に変換される際に、わずかな誤差が原因で丸めが意図しない方向に働く可能性があるためです。
5. gccgo
コンパイラ
Go言語には主に2つの公式コンパイラがあります。
gc
(Go compiler): Goチームが開発している主要なコンパイラで、Go言語で書かれています。gccgo
: GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。C/C++など他の言語と同様にGCCの最適化やバックエンドを利用できます。
このコミットはgccgo
特有の問題に対処しており、gc
コンパイラでは同様の問題が発生しなかった可能性があります。これは、両コンパイラが浮動小数点数の内部表現や最適化戦略において異なる実装を持っているためです。
技術的詳細
問題の根源は、0.52 * 1e9
という浮動小数点数演算の結果が、gccgo
の特定のバージョン(型なし浮動小数点数のビット数を増やした後)で、厳密な整数値である520,000,000
ではなく、例えば519,999,999.9999999999999999
や520,000,000.0000000000000001
のような、ごくわずかな誤差を含む値になったことです。
time.Date
関数のnsec
引数はint
型を期待します。Go言語では、浮動小数点数を整数型に変換する際、小数部分は切り捨てられます。
- もし
0.52 * 1e9
が519,999,999.999...
となった場合、int
に変換されると519,999,999
となり、期待される520,000,000
とは異なる値になります。 - もし
0.52 * 1e9
が520,000,000.000...1
となった場合、int
に変換されると520,000,000
となり、結果的には正しい値になりますが、これは偶然に依存します。
この問題を解決するために、コミットでは0.52 * 1e9
という浮動小数点数演算を、520 * 1e6
という整数演算に近い形に変更しています。
-
0.52 * 1e9
0.52
は浮動小数点数リテラル。1e9
も浮動小数点数リテラル。- これらの乗算は浮動小数点数演算として実行され、前述の丸め誤差が生じる可能性があります。
-
520 * 1e6
520
は整数リテラル。1e6
は1,000,000
を意味する浮動小数点数リテラルですが、この文脈では520
と乗算されることで、結果的に520,000,000
という厳密な整数値が期待されます。1e6
は1,000,000
であり、0.52 * 1e9
と同じ520,000,000
という結果を得るために、0.52
を520
に、1e9
を1e6
に調整しています。- この変更により、
520
という整数と1e6
という1,000,000
の乗算は、浮動小数点数演算ではあるものの、より「正確な」整数結果を生成しやすくなります。特に、520
は正確に二進数で表現できる整数であり、1,000,000
も同様です。これらの乗算は、0.52
のような無限小数になりうる値を含む計算よりも、丸め誤差の影響を受けにくい傾向があります。
この修正は、浮動小数点数の計算における誤差の性質を理解し、それを回避するための実用的なアプローチを示しています。特に、整数であるべき値は、可能な限り整数演算に近い形で表現することが重要です。
コアとなるコードの変更箇所
変更はsrc/pkg/time/time_test.go
ファイル内のjsonTests
というテストデータ構造体配列の一箇所です。
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -805,7 +805,7 @@ var jsonTests = []struct {
time Time
json string
}{
- {Date(9999, 4, 12, 23, 20, 50, .52*1e9, UTC), `"9999-04-12T23:20:50.52Z"`},
+ {Date(9999, 4, 12, 23, 20, 50, 520*1e6, UTC), `"9999-04-12T23:20:50.52Z"`},
{Date(1996, 12, 19, 16, 39, 57, 0, Local), `"1996-12-19T16:39:57-08:00"`},
{Date(0, 1, 1, 0, 0, 0, 1, FixedZone("", 1*60)), `"0000-01-01T00:00:00.000000001+00:01"`},
}
コアとなるコードの解説
変更された行は、jsonTests
配列の最初の要素です。このテストケースは、特定のtime.Time
オブジェクトがJSON形式で正しくシリアライズされることを検証しています。
元のコード:
{Date(9999, 4, 12, 23, 20, 50, .52*1e9, UTC), `"9999-04-12T23:20:50.52Z"`},
ここで、Date
関数の第7引数(ナノ秒)に.52*1e9
が渡されています。これは0.52 * 1,000,000,000
を意味し、期待される値は520,000,000
ナノ秒です。
修正後のコード:
{Date(9999, 4, 12, 23, 20, 50, 520*1e6, UTC), `"9999-04-12T23:20:50.52Z"`},
修正では、ナノ秒の計算式が520*1e6
に変更されました。これは520 * 1,000,000
を意味し、結果はやはり520,000,000
ナノ秒です。
この変更の意図は、前述の通り、0.52
という浮動小数点数を含む計算がgccgo
で丸め誤差を生じる可能性があったため、より整数に近い形での計算に置き換えることで、ナノ秒の値が常に正確な整数としてDate
関数に渡されるようにすることです。520
と1e6
(1,000,000
)はどちらも正確に二進数で表現できるため、これらの乗算は0.52
を含む乗算よりも浮動小数点数誤差の影響を受けにくいと考えられます。
この修正はテストコード内で行われていますが、これはtime.Date
関数自体の動作を保証するためのものであり、間接的にtime
パッケージの堅牢性を高めることに貢献しています。
関連リンク
- Go言語
time
パッケージのドキュメント: https://pkg.go.dev/time - Go言語の型なし定数に関する公式ブログ記事 (英語): https://go.dev/blog/constants
- GCCGoプロジェクトページ: https://gcc.gnu.org/onlinedocs/gccgo/
参考にした情報源リンク
- コミットメッセージと差分情報:
/home/orange/Project/comemo/commit_data/12938.txt
- GitHub上のコミットページ: https://github.com/golang/go/commit/426bf361316ab495a29c9e0a05da6236894dd392
- Go言語の浮動小数点数に関する一般的な知識
gccgo
コンパイラに関する一般的な知識- 浮動小数点数演算の精度に関する一般的な知識
time.Date
関数のシグネチャと引数の意味