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

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

このコミットは、Go言語の標準ライブラリであるfmtパッケージにおける、符号付き浮動小数点数のゼロパディングに関するバグ修正です。具体的には、正の浮動小数点数に対して符号(+)を付けてゼロパディングを行う際に、期待される出力が得られない問題を解決しています。

コミット

commit 5ad5b7a551b30d27d7af00e3e981014f4acd8bd4
Author: Felix Geisendörfer <haimuiba@gmail.com>
Date:   Thu Dec 12 06:40:16 2013 -0800

    fmt: Fix signed zero-padding for positive floats
    
    Space padding still has the same issue, I will send a separate patch for that
    if this one gets accepted.
    Fixes #6856.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/35660043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/5ad5b7a551b30d27d7af00e3e981014f4acd8bd4

元コミット内容

fmt: Fix signed zero-padding for positive floats

このコミットは、fmtパッケージにおいて、正の浮動小数点数に対する符号付きゼロパディングの不具合を修正します。コミットメッセージには、スペースパディングについても同様の問題があるが、このパッチが受け入れられれば別途修正パッチを送ると記載されています。また、Fixes #6856とあり、GoのIssueトラッカー上の問題6856を修正するものであることが示されています。ただし、Goの公式IssueトラッカーではIssue 6856は確認できませんでした。

変更の背景

Go言語のfmtパッケージは、C言語のprintfのような書式指定出力機能を提供します。浮動小数点数を整形して出力する際、%f%eなどの動詞(verb)を使用し、さらにフラグ(+ 0など)や幅、精度を指定することで、出力形式を細かく制御できます。

このコミットが修正しようとしている問題は、特に+フラグ(常に符号を出力)と0フラグ(ゼロパディング)を組み合わせて正の浮動小数点数を出力する場合に発生していました。例えば、%+07.2fという書式指定で1.0を出力しようとすると、期待される+001.00のような出力ではなく、パディングが正しく適用されない、あるいは符号が正しく扱われないといった不具合があったと考えられます。

元の実装では、正の数に対して+フラグが指定された場合、符号の追加とパディングの処理順序に問題があった可能性があります。具体的には、符号が追加される前にパディングの幅が計算されてしまい、結果としてゼロが不足したり、符号がパディングの対象外になったりする挙動が考えられます。

前提知識の解説

Go言語のfmtパッケージ

fmtパッケージは、Go言語における基本的な書式指定I/O機能を提供します。fmt.Printf関数は、指定された書式文字列に基づいて引数を整形し、標準出力に出力します。書式文字列は、通常の文字列と、%で始まる「書式指定子(verb)」から構成されます。

浮動小数点数の書式指定

fmtパッケージで浮動小数点数を整形する際には、主に以下の書式指定子が使用されます。

  • %f: 小数点以下の桁数を指定して浮動小数点数を表示します(例: 3.14159)。
  • %e: 科学表記で浮動小数点数を表示します(例: 3.14159e+00)。
  • %g: 値に応じて%fまたは%eの短い方を選択して表示します。

フラグ

書式指定子には、出力の挙動を制御するためのフラグを付加できます。

  • +フラグ: 数値の符号(正の場合は+、負の場合は-)を常に表示します。
  • (スペース)フラグ: 正の数値の前にスペースを挿入します。+フラグと同時に指定された場合は+フラグが優先されます。
  • 0フラグ: 数値の幅を指定した場合に、左側をスペースではなくゼロでパディングします。

幅と精度

  • : 出力される文字列全体の最小幅を指定します。指定された幅に満たない場合、デフォルトではスペースでパディングされます。
  • 精度: 浮動小数点数の場合、小数点以下の桁数を指定します。

例: fmt.Printf("%+07.2f", 1.0)

  • +: 符号を常に表示
  • 0: ゼロパディング
  • 7: 最小幅7文字
  • .2: 小数点以下2桁
  • f: 浮動小数点数形式

この場合、1.0+1.00となり、幅7文字にゼロパディングされるため、期待される出力は+001.00となります。

技術的詳細

fmtパッケージの内部では、format.goファイル内のfmt構造体が書式設定のロジックを管理しています。浮動小数点数の書式設定は、(*fmt).formatFloatメソッドによって行われます。

このメソッドは、浮動小数点数の値、書式指定子(verb)、精度、幅などの情報を受け取り、それに基づいて文字列を生成します。元の実装では、正の浮動小数点数に対して+フラグが指定された場合、符号の追加処理がパディング処理の前に適切に行われていなかったと考えられます。

具体的には、formatFloatメソッド内で、数値の文字列表現が生成された後、その文字列に対してパディング処理が行われます。この際、f.plus+フラグが指定されているか)が真の場合に、符号+を文字列の先頭に追加する処理が行われます。しかし、この符号の追加が、幅の計算やゼロパディングの適用よりも後に行われていたか、あるいは符号が追加されることで幅の計算がずれてしまうようなロジックになっていた可能性があります。

コミットの変更点を見ると、f.plusが真の場合に、まずf.buf.WriteByte('+')でバッファに+を書き込み、その後f.wid--で幅を1減らしています。これは、符号が1文字分の幅を占めることを考慮し、残りの幅に対してパディングを行うための修正です。これにより、符号がパディングの計算に正しく含まれるようになり、期待通りのゼロパディングが実現されます。

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

diff --git a/src/pkg/fmt/fmt_test.go b/src/pkg/fmt/fmt_test.go
index bf50675f54..444297d926 100644
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -220,6 +220,8 @@ var fmtTests = []struct {
 	{"%+.3e", 0.0, "+0.000e+00"},
 	{"%+.3e", 1.0, "+1.000e+00"},
 	{"%+.3f", -1.0, "-1.000"},
++	{"%+07.2f", 1.0, "+001.00"},
++	{"%+07.2f", -1.0, "-001.00"},
 	{"% .3E", -1.0, "-1.000E+00"},
 	{"% .3e", 1.0, " 1.000e+00"},
 	{"%+.3g", 0.0, "+0"},
diff --git a/src/pkg/fmt/format.go b/src/pkg/fmt/format.go
index 2e2b0716ed..a54f12ee9f 100644
--- a/src/pkg/fmt/format.go
+++ b/src/pkg/fmt/format.go
@@ -372,7 +372,10 @@ func (f *fmt) formatFloat(v float64, verb byte, prec, n int) {
 	default:
 		// There's no sign, but we might need one.
 		if f.plus {
-\t\t\tslice[0] = '+'
+\t\t\tf.buf.WriteByte('+')
+\t\t\tf.wid--
+\t\t\tf.pad(slice[1:])
+\t\t\treturn
 		} else if f.space {
 			// space is already there
 		} else {

コアとなるコードの解説

src/pkg/fmt/fmt_test.go

テストファイルfmt_test.goには、新しいテストケースが2つ追加されています。

{"%+07.2f", 1.0, "+001.00"},
{"%+07.2f", -1.0, "-001.00"},

これらのテストケースは、+フラグと0フラグ、そして幅指定を組み合わせた浮動小数点数の書式設定が正しく機能することを確認するためのものです。特に、1.0という正の数に対して+001.00という出力が期待されており、これが修正前のバグの具体的な症状を示しています。負の数-1.0に対するテストケースも追加されており、符号が正しく扱われることを確認しています。

src/pkg/fmt/format.go

format.goファイル内の(*fmt).formatFloatメソッドが修正されています。このメソッドは浮動小数点数の書式設定を担当しています。

元のコードでは、f.plus+フラグが指定されている)が真の場合に、slice[0] = '+'という行で、数値の文字列表現の先頭に直接+を挿入していました。このsliceは、数値の桁を格納するための内部バッファの一部であると考えられます。この方法では、符号が追加された後にパディングの計算が行われると、符号の分の幅が考慮されず、パディングがずれてしまう可能性がありました。

修正後のコードでは、以下の変更が加えられています。

		if f.plus {
			f.buf.WriteByte('+') // バッファに直接 '+' を書き込む
			f.wid--             // 幅を1減らす(符号の分)
			f.pad(slice[1:])    // 残りの幅とスライス(符号を除いた部分)でパディングを行う
			return              // ここで処理を終了
		}
  1. f.buf.WriteByte('+'): 符号+を、数値の文字列表現が格納されるバッファ(f.buf)に直接書き込みます。これにより、符号が数値の先頭に確実に配置されます。
  2. f.wid--: 書式指定で指定された全体の幅(f.wid)から1を減らします。これは、先ほど書き込んだ符号+が1文字分の幅を占めるため、残りのパディングに必要な幅を正しく計算するためです。
  3. f.pad(slice[1:]): f.padメソッドを呼び出してパディングを行います。ここで重要なのは、引数としてslice[1:]が渡されている点です。これは、元の数値の文字列表現から先頭の1文字(符号が挿入されるべき位置)を除いた部分をパディングの対象とすることで、符号がパディングのロジックに干渉しないようにしています。
  4. return: このifブロック内で符号の追加とパディングが完了するため、後続のパディング処理をスキップしてメソッドを終了します。

この修正により、符号がパディングの計算に先行して処理され、かつ符号自身の幅が全体の幅から正しく差し引かれるため、%+07.2fのような書式指定で期待通りのゼロパディングが実現されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のfmtパッケージに関するWeb検索結果
  • Go言語のPrintf書式指定に関するドキュメントやチュートリアル
  • コミットメッセージとコードの差分