[インデックス 17831] ファイルの概要
このコミットは、Go言語の標準ライブラリ time
パッケージにおける ParseDuration
関数のバグ修正に関するものです。具体的には、32ビットアーキテクチャ上で、小数点以下9桁を超える精度を持つ時間文字列をパースする際に発生するオーバーフローの問題を解決しています。
コミット
commit d220c9957ccc4f2985849ee2e8c7e49fb20f04fe
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Oct 22 18:33:05 2013 -0400
time: fix ParseDuration overflow when given more than 9 digits on 32-bit arch
Fixes #6617.
R=golang-dev, rsc, r
CC=golang-dev
https://golang.org/cl/15080043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d220c9957ccc4f2985849ee2e8c7e49fb20f04fe
元コミット内容
time: fix ParseDuration overflow when given more than 9 digits on 32-bit arch
Fixes #6617.
このコミットは、time
パッケージの ParseDuration
関数が、32ビットアーキテクチャ上で小数点以下9桁を超える時間文字列を解析する際に発生するオーバーフローを修正します。関連する問題として #6617 が挙げられています。
変更の背景
time.ParseDuration
関数は、"1h30m" や "1.5s" のような文字列形式の期間を time.Duration
型に変換するために使用されます。この関数は、秒以下の非常に細かい時間(ナノ秒など)も正確に解析できる必要があります。
問題は、32ビットシステムにおいて、小数点以下の桁数が非常に多い(9桁を超える)時間文字列を処理する際に発生していました。具体的には、小数点以下の部分を計算するために使用される scale
変数が int
型で宣言されており、これが非常に大きな値になるとオーバーフローを引き起こしていました。例えば、"0.3333333333333333333h" のような文字列を解析する際、scale
が 10^19
のような値になる可能性があり、これは32ビット整数の最大値(約 2 * 10^9
)をはるかに超えてしまいます。結果として、誤った期間が計算されたり、パースエラーが発生したりする可能性がありました。
このバグは、特に高精度な時間計測や、外部システムから受け取った時間文字列を正確に処理する必要があるアプリケーションにおいて、深刻な問題となり得ました。
前提知識の解説
time.Duration
型: Go言語のtime
パッケージで定義されている型で、ナノ秒単位で時間を表す整数型です。これにより、時間の加算や減算、比較などを安全かつ効率的に行えます。time.ParseDuration
関数: 文字列形式の期間(例: "1h30m", "500ms", "1.5s")をtime.Duration
型に変換する関数です。この関数は、数値部分と単位(ns, us, ms, s, m, h)を解析し、最終的なDuration
値を計算します。小数点以下の値もサポートしており、例えば "1.5s" は1秒500ミリ秒として解析されます。- 浮動小数点数 (float64): 倍精度浮動小数点数であり、非常に広い範囲の数値を表現でき、小数点以下の精度も高く保てます。Go言語では
float64
が標準的な浮動小数点数型です。 - 整数型 (int): 整数値を扱う型です。Go言語の
int
型は、実行環境のアーキテクチャ(32ビットまたは64ビット)によってサイズが異なります。32ビットシステムでは32ビット整数、64ビットシステムでは64ビット整数として扱われます。32ビット整数の最大値は約20億(2^31 - 1
)です。 - オーバーフロー: 変数に格納できる最大値を超えた値を代入しようとしたときに発生する現象です。整数型の場合、オーバーフローが発生すると、値が予期せぬ小さな値になったり、負の値になったりすることがあります。
- 32ビットアーキテクチャと64ビットアーキテクチャ: CPUのレジスタやメモリアドレスの幅を示すものです。32ビットシステムでは、一度に処理できるデータの量が32ビットに制限され、整数の最大値も32ビットで表現できる範囲に限られます。一方、64ビットシステムでは、より大きな整数値やメモリアドレスを直接扱うことができます。
技術的詳細
このコミットの核心は、ParseDuration
関数内で小数点以下の部分を計算する際に使用される scale
変数の型を int
から float64
に変更した点にあります。
ParseDuration
関数は、時間文字列を解析する際に、数値部分と単位を分離し、それぞれの部分を Duration
型に変換して合計します。小数点以下の部分を処理する際には、例えば "0.5s" の "0.5" のように、小数点以下の桁数に応じて適切なスケールファクタを乗算または除算する必要があります。
元のコードでは、このスケールファクタを計算する scale
変数が int
型でした。小数点以下の桁数が増えるにつれて、scale
の値は 10^1
, 10^2
, 10^3
... と増加していきます。32ビットシステムでは、int
型が32ビット整数として扱われるため、scale
が 10^10
(100億) を超えるような値になると、オーバーフローが発生してしまいます。これにより、正確なスケールファクタが計算できなくなり、結果として ParseDuration
が誤った値を返したり、エラーになったりしていました。
この修正では、scale
変数を float64
型に変更することで、このオーバーフローの問題を解決しています。float64
は倍精度浮動小数点数であり、非常に大きな数値(約 1.8 * 10^308
まで)を表現できるため、小数点以下の桁数が非常に多くなっても scale
がオーバーフローすることはありません。これにより、ParseDuration
は32ビットアーキテクチャ上でも、小数点以下9桁を超える高精度な時間文字列を正確に解析できるようになりました。
また、float64(x) / float64(scale)
の計算も float64(x) / scale
に変更されていますが、これは scale
が既に float64
であるため、冗長な型変換を削除したものです。
コアとなるコードの変更箇所
変更は src/pkg/time/format.go
と src/pkg/time/time_test.go
の2つのファイルにわたります。
src/pkg/time/format.go
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -1204,11 +1204,11 @@ func ParseDuration(s string) (Duration, error) {
if err != nil {
return 0, errors.New("time: invalid duration " + orig)
}
- scale := 1
+ scale := 1.0
for n := pl - len(s); n > 0; n-- {
scale *= 10
}
- g += float64(x) / float64(scale)
+ g += float64(x) / scale
post = pl != len(s)
}
if !pre && !post {
src/pkg/time/time_test.go
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -1318,6 +1318,8 @@ var parseDurationTests = []struct {
{"39h9m14.425s", true, 39*Hour + 9*Minute + 14*Second + 425*Millisecond},
// large value
{"52763797000ns", true, 52763797000 * Nanosecond},
+ // more than 9 digits after decimal point, see http://golang.org/issue/6617
+ {"0.3333333333333333333h", true, 20 * Minute},
// errors
{"", false, 0},
コアとなるコードの解説
src/pkg/time/format.go
の変更
scale := 1
からscale := 1.0
への変更:- 元のコードでは
scale
がint
型として初期化されていました。これにより、scale *= 10
のループで値が大きくなりすぎると、32ビットシステムでオーバーフローが発生していました。 scale := 1.0
とすることで、scale
はfloat64
型として初期化されます。これにより、scale
が非常に大きな値になってもオーバーフローすることなく、正確な浮動小数点数として値を保持できるようになります。
- 元のコードでは
g += float64(x) / float64(scale)
からg += float64(x) / scale
への変更:scale
が既にfloat64
型になったため、float64(scale)
という冗長な型変換が不要になりました。これはコードの簡潔化と、意図がより明確になるための変更です。
これらの変更により、ParseDuration
関数は、小数点以下の桁数が多く、scale
が大きな値になる場合でも、正確な浮動小数点演算を使用して期間を計算できるようになりました。
src/pkg/time/time_test.go
の変更
- 新しいテストケースの追加:
{"0.3333333333333333333h", true, 20 * Minute}
という新しいテストケースが追加されました。- このテストケースは、小数点以下に非常に多くの桁数を持つ時間文字列(19桁)を
ParseDuration
が正しく解析できることを検証します。 0.3333333333333333333h
は、正確には1/3
時間に相当し、これは20分
です。このテストケースは、修正が正しく機能し、高精度な時間文字列が期待通りに20 * Minute
として解析されることを保証します。- コメント
// more than 9 digits after decimal point, see http://golang.org/issue/6617
は、このテストケースが修正対象の問題(#6617)に直接関連していることを示しています。
このテストケースの追加は、バグが修正されたことを確認するだけでなく、将来の回帰を防ぐための重要なステップです。
関連リンク
- Go言語の
time
パッケージのドキュメント: https://pkg.go.dev/time - Go言語の
time.ParseDuration
関数のドキュメント: https://pkg.go.dev/time#ParseDuration
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/d220c9957ccc4f2985849ee2e8c7e49fb20f04fe
- Go言語の公式Issueトラッカー (ただし、Issue #6617は公開リポジトリでは見つかりませんでした。これは内部的なトラッキング番号であるか、非常に古いIssueである可能性があります。)
- Go言語のソースコード (timeパッケージ): https://github.com/golang/go/tree/master/src/time
- Go言語の浮動小数点数に関するドキュメントや記事 (一般的な情報源)
- 32ビット/64ビットアーキテクチャにおける数値表現に関する一般的な情報源