Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14604] ファイルの概要

このコミットは、Go言語の標準ライブラリfmtパッケージにおけるバグ修正に関するものです。具体的には、fmt.Printfmt.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というフラグが含まれています。

問題は、複素数(complex64complex128)をフォーマットする際に発生していました。複素数は実部と虚部から構成され、例えば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言語のprintfscanfに似た機能を提供し、様々なデータ型を整形して出力したり、文字列からデータを読み取ったりすることができます。

  • フォーマットバーブ: %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_c64complex64型を、fmt_c128complex128型をフォーマットします。

複素数型 (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.plustrueのままになります。スライス内の次の要素(例えば、complex64(2))がフォーマットされる際、fmtパッケージはf.plustrueであると判断し、その値の前に不必要な+記号を付与してしまいます。結果として、+2+0iのような誤った出力が生成されていました。

この修正は、複素数のフォーマット処理の開始時にplusフラグの現在の状態を保存し、処理の終了時にその保存された状態にplusフラグを復元することで、この状態漏れを防ぎます。

コアとなるコードの変更箇所

このコミットでは、主に2つのファイルが変更されています。

  1. src/pkg/fmt/fmt_test.go:

    • 新しいテストケースが追加されました。これは、complex64complex128のスライスを%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)]"},
      
    • これらのテストは、修正前のバグを再現し、修正後に正しく動作することを確認するために重要です。
  2. 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フラグの状態を適切に管理することです。

  1. oldPlus := f.plus:

    • 複素数のフォーマット処理が始まる直前に、fmt構造体の現在のplusフラグの状態をoldPlusという変数に保存しています。これは、この関数が呼び出される前のplusフラグがどのような状態であったかを記憶するためです。
    • このplusフラグは、例えばfmt.Printf("%+v", 10)のように、呼び出し元で+フラグが指定されている場合にtrueになっている可能性があります。
  2. f.plus = true:

    • 複素数の虚部をフォーマットする際に、虚部が正の値であっても+記号を明示的に表示するために、一時的にf.plustrueに設定します。これは、複素数の標準的な文字列表現(例: 1+2i)に合わせるためのものです。
  3. f.plus = oldPlus:

    • 複素数のフォーマット処理が完了した後、f.plusフラグを、関数が呼び出される前の元の状態(oldPlusに保存されていた値)に復元しています。
    • この行が追加されたことで、複素数のフォーマット中に一時的に変更されたplusフラグが、その後のフォーマット処理(特にスライス内の次の要素のフォーマット)に影響を与えることがなくなりました。これにより、スライス内の後続の複素数に不必要な+記号が付与されるバグが解消されます。

このシンプルな変更により、fmtパッケージの内部状態管理が改善され、複素数スライスのフォーマットが期待通りに行われるようになりました。

関連リンク

  • Go言語のfmtパッケージに関する公式ドキュメント: https://pkg.go.dev/fmt
  • このコミットが修正したとされるIssue: #4525 (ただし、GoのIssue Trackerで直接この番号のIssueを見つけることはできませんでした。これは内部的な追跡番号であるか、または古いIssueがアーカイブされている可能性があります。)

参考にした情報源リンク