[インデックス 14604] ファイルの概要
このコミットは、Go言語の標準ライブラリfmt
パッケージにおけるバグ修正に関するものです。具体的には、fmt.Print
やfmt.Sprintf
などの関数で複素数型のスライス([]complex64
や[]complex128
)を%v
バーブ(デフォルトフォーマット)で出力する際に、不適切なプラス記号(+
)が付与される問題を修正しています。
コミット
commit 07cc05864c958fcacf9b880263072c66c4040415
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 11 11:49:41 2012 -0500
fmt: fix %v of complex slice
Fixes #4525.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6929049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/07cc05864c958fcacf9b880263072c66c4040415
元コミット内容
fmt: fix %v of complex slice
このコミットは、fmt
パッケージが複素数スライスを%v
フォーマットで処理する際のバグを修正します。具体的には、複素数のフォーマット中に設定されたplus
フラグが、スライス内の後続の要素に誤って影響を与え、不必要なプラス記号が付与される問題に対処しています。
変更の背景
Go言語のfmt
パッケージは、様々なデータ型を文字列にフォーマットするための強力な機能を提供します。このパッケージは、内部的にフォーマットの状態を管理するためのfmt
構造体(format.go
内で定義されているfmt
型)を持っています。この構造体には、数値の符号(プラス記号の表示)を制御するplus
というフラグが含まれています。
問題は、複素数(complex64
やcomplex128
)をフォーマットする際に発生していました。複素数は実部と虚部から構成され、例えば1+2i
のように表現されます。fmt
パッケージは、複素数の虚部をフォーマットする際に、その虚部が正の値であっても常にプラス記号を明示的に表示するために、一時的にplus
フラグをtrue
に設定していました。
この一時的なplus
フラグの変更が、複素数のスライス(例: []complex64{1, 2, 3}
)を%v
でフォーマットする際に問題を引き起こしました。スライス内の最初の複素数をフォーマットした後、plus
フラグがtrue
に設定されたままになっていました。その結果、スライス内の次の複素数をフォーマットする際に、本来不要なプラス記号(例: +2+0i
)が先頭に付与されてしまうというバグがありました。期待される出力は[(1+0i) (2+0i) (3+0i)]
であるにもかかわらず、バグのあるバージョンでは[(1+0i) (+2+0i) (+3+0i)]
のような出力になっていました。
このコミットは、このplus
フラグの「状態漏れ」を修正し、複素数スライスが正しくフォーマットされるようにすることを目的としています。
前提知識の解説
Go言語のfmt
パッケージ
fmt
パッケージは、Go言語におけるI/Oフォーマットを実装するためのパッケージです。C言語のprintf
やscanf
に似た機能を提供し、様々なデータ型を整形して出力したり、文字列からデータを読み取ったりすることができます。
- フォーマットバーブ:
%v
は、Goの値をデフォルトのフォーマットで出力するためのバーブです。構造体やスライスなど、あらゆる型に適用できます。他にも、%d
(整数)、%s
(文字列)、%f
(浮動小数点数)などがあります。 - フラグ: フォーマットバーブには、出力の挙動を制御するフラグを付加できます。
+
フラグ: 数値の符号を常に表示します(例:+10
,-5
)。複素数では、虚部が正の場合に+
を表示します(例:1+2i
)。#
フラグ: 別のフォーマットを使用します(例: 16進数に0x
を付加)。-
フラグ: 左寄せにします。0
フラグ: ゼロ埋めします。
fmt
パッケージの内部構造 (format.go
)
fmt
パッケージの内部では、fmt
という構造体がフォーマット処理の状態を管理しています。この構造体は、出力バッファ、現在のフォーマットバーブ、各種フラグ(plus
, space
, sharp
など)、幅、精度などの情報を含んでいます。
type fmt struct {
buf *buffer
arg interface{} // value being formatted
fmt rune // format verb (e.g. 'v', 's', 'd')
plus bool // + flag
space bool // ' ' flag
sharp bool // # flag
zero bool // 0 flag
minus bool // - flag
width int // width
prec int // precision
// ...
}
fmt
構造体のメソッドが、特定の型のフォーマットを担当します。例えば、fmt_c64
はcomplex64
型を、fmt_c128
はcomplex128
型をフォーマットします。
複素数型 (complex64
, complex128
)
Go言語には、複素数を扱うための組み込み型complex64
(実部と虚部がfloat32
)とcomplex128
(実部と虚部がfloat64
)があります。これらは、real()
関数とimag()
関数を使って実部と虚部を取得できます。
技術的詳細
このバグは、fmt
構造体のplus
フラグが、複素数のフォーマット処理中に一時的に変更され、その変更が元の状態に復元されないまま次のフォーマット処理に引き継がれてしまう「状態漏れ」によって発生していました。
fmt
パッケージが複素数をフォーマットする際、特に虚部をフォーマットするロジックにおいて、虚部が正の値であっても常に+
記号を表示するために、f.plus = true
という行がありました。これは、例えば1+2i
という複素数において、虚部の2i
を+2i
として表示するために必要な処理です。
しかし、このf.plus = true
の変更後、複素数のフォーマットが完了した際に、plus
フラグが元の値に戻されていませんでした。
// src/pkg/fmt/format.go (修正前、抜粋)
func (f *fmt) fmt_c64(v complex64, verb rune) {
f.buf.WriteByte('(')
r := real(v)
for i := 0; ; i++ {
// ... 実部をフォーマット ...
f.buf.WriteByte('+') // 虚部の前に + を出力
f.plus = true // ここでplusフラグがtrueに設定される
r = imag(v)
}
f.buf.Write(irparenBytes)
}
もし、この複素数がスライスの一部としてフォーマットされる場合、最初の複素数の処理が終わった後もf.plus
はtrue
のままになります。スライス内の次の要素(例えば、complex64(2)
)がフォーマットされる際、fmt
パッケージはf.plus
がtrue
であると判断し、その値の前に不必要な+
記号を付与してしまいます。結果として、+2+0i
のような誤った出力が生成されていました。
この修正は、複素数のフォーマット処理の開始時にplus
フラグの現在の状態を保存し、処理の終了時にその保存された状態にplus
フラグを復元することで、この状態漏れを防ぎます。
コアとなるコードの変更箇所
このコミットでは、主に2つのファイルが変更されています。
-
src/pkg/fmt/fmt_test.go
:- 新しいテストケースが追加されました。これは、
complex64
とcomplex128
のスライスを%v
でフォーマットした際の期待される出力を検証するものです。 - 追加されたテストケース:
// Complex fmt used to leave the plus flag set for future entries in the array // causing +2+0i and +3+0i instead of 2+0i and 3+0i. {"%v", []complex64{1, 2, 3}, "[(1+0i) (2+0i) (3+0i)]"}, {"%v", []complex128{1, 2, 3}, "[(1+0i) (2+0i) (3+0i)]"},
- これらのテストは、修正前のバグを再現し、修正後に正しく動作することを確認するために重要です。
- 新しいテストケースが追加されました。これは、
-
src/pkg/fmt/format.go
:fmt
構造体のfmt_c64
メソッドとfmt_c128
メソッドに修正が加えられました。fmt_c64
メソッドの変更:--- a/src/pkg/fmt/format.go +++ b/src/pkg/fmt/format.go @@ -428,6 +428,7 @@ func (f *fmt) fmt_fb32(v float32) { f.formatFloat(float64(v), 'b', 0, 32) } func (f *fmt) fmt_c64(v complex64, verb rune) { f.buf.WriteByte('(') r := real(v) + oldPlus := f.plus // plusフラグの現在の状態を保存 for i := 0; ; i++ { // ... f.plus = true r = imag(v) } + f.plus = oldPlus // 保存した状態にplusフラグを復元 f.buf.Write(irparenBytes) }
fmt_c128
メソッドの変更:fmt_c64
と同様の修正が適用されました。--- a/src/pkg/fmt/format.go +++ b/src/pkg/fmt/format.go @@ -454,6 +456,7 @@ func (f *fmt) fmt_c64(v complex64, verb rune) { func (f *fmt) fmt_c128(v complex128, verb rune) { f.buf.WriteByte('(') r := real(v) + oldPlus := f.plus // plusフラグの現在の状態を保存 for i := 0; ; i++ { // ... f.plus = true r = imag(v) } + f.plus = oldPlus // 保存した状態にplusフラグを復元 f.buf.Write(irparenBytes) }
コアとなるコードの解説
この修正の核心は、fmt_c64
およびfmt_c128
関数内でf.plus
フラグの状態を適切に管理することです。
-
oldPlus := f.plus
:- 複素数のフォーマット処理が始まる直前に、
fmt
構造体の現在のplus
フラグの状態をoldPlus
という変数に保存しています。これは、この関数が呼び出される前のplus
フラグがどのような状態であったかを記憶するためです。 - この
plus
フラグは、例えばfmt.Printf("%+v", 10)
のように、呼び出し元で+
フラグが指定されている場合にtrue
になっている可能性があります。
- 複素数のフォーマット処理が始まる直前に、
-
f.plus = true
:- 複素数の虚部をフォーマットする際に、虚部が正の値であっても
+
記号を明示的に表示するために、一時的にf.plus
をtrue
に設定します。これは、複素数の標準的な文字列表現(例:1+2i
)に合わせるためのものです。
- 複素数の虚部をフォーマットする際に、虚部が正の値であっても
-
f.plus = oldPlus
:- 複素数のフォーマット処理が完了した後、
f.plus
フラグを、関数が呼び出される前の元の状態(oldPlus
に保存されていた値)に復元しています。 - この行が追加されたことで、複素数のフォーマット中に一時的に変更された
plus
フラグが、その後のフォーマット処理(特にスライス内の次の要素のフォーマット)に影響を与えることがなくなりました。これにより、スライス内の後続の複素数に不必要な+
記号が付与されるバグが解消されます。
- 複素数のフォーマット処理が完了した後、
このシンプルな変更により、fmt
パッケージの内部状態管理が改善され、複素数スライスのフォーマットが期待通りに行われるようになりました。
関連リンク
- Go言語の
fmt
パッケージに関する公式ドキュメント: https://pkg.go.dev/fmt - このコミットが修正したとされるIssue:
#4525
(ただし、GoのIssue Trackerで直接この番号のIssueを見つけることはできませんでした。これは内部的な追跡番号であるか、または古いIssueがアーカイブされている可能性があります。)
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/07cc05864c958fcacf9b880263072c66c4040415
- Go言語の
fmt
パッケージのソースコード (src/pkg/fmt/format.go
): https://github.com/golang/go/blob/master/src/fmt/format.go (現在のバージョン) - Go言語の
fmt
パッケージのテストコード (src/pkg/fmt/fmt_test.go
): https://github.com/golang/go/blob/master/src/fmt/fmt_test.go (現在のバージョン) - Go言語の複素数型に関するドキュメント: https://go.dev/ref/spec#Complex_numbers