[インデックス 19563] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおける、浮動小数点数のゼロパディング時の符号の扱いに関するバグ修正と、関連するテストの追加を目的としています。具体的には、src/pkg/fmt/fmt_test.go
とsrc/pkg/fmt/format.go
の2つのファイルが変更されています。
src/pkg/fmt/fmt_test.go
では、浮動小数点数と複素数のフォーマットに関するテストケースが大幅に追加されています。特に、C言語のprintf
関数との比較を通じて、Goのfmt
パッケージの挙動が期待通りであることを検証するための詳細なテストが導入されました。これにより、ゼロパディング、符号の表示、スペースフラグの挙動、無限大の扱いなど、様々なフォーマットオプションの組み合わせが網羅的にテストされています。
src/pkg/fmt/format.go
では、fmt
パッケージの内部で実際にフォーマット処理を行うformatFloat
関数と、複素数をフォーマットするfmt_c64
、fmt_c128
、そして新しく導入されたfmt_complex
関数が修正されています。これらの変更は、ゼロパディング時に符号が正しく配置されるようにすること、不要な+
符号が表示されないようにすること、そして無限大に対してゼロパディングが適用されないようにすることを目的としています。
コミット
commit b559392e1b84f521a8b9c2d3c1164960c62799c8
Author: Rob Pike <r@golang.org>
Date: Tue Jun 17 14:56:54 2014 -0700
fmt: fix signs when zero padding.
Bug was introduced recently. Add more tests, fix the bugs.
Suppress + sign when not required in zero padding.
Do not zero pad infinities.
All old tests still pass.
This time for sure!
Fixes #8217.
LGTM=rsc
R=golang-codereviews, dan.kortschak, rsc
CC=golang-codereviews
https://golang.org/cl/103480043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b559392e1b84f521a8b9c2d3c1164960c62799c8
元コミット内容
fmt: fix signs when zero padding.
Bug was introduced recently. Add more tests, fix the bugs.
Suppress + sign when not required in zero padding.
Do not zero pad infinities.
All old tests still pass.
This time for sure!
Fixes #8217.
LGTM=rsc
R=golang-codereviews, dan.kortschak, rsc
CC=golang-codereviews
https://golang.org/cl/103480043
変更の背景
このコミットの主な背景は、fmt
パッケージにおける浮動小数点数のゼロパディング処理に最近導入されたバグの修正です。具体的には、以下の問題が報告されていました。
- ゼロパディング時の符号の誤った配置: 以前のバージョンでは、負の浮動小数点数をゼロパディングする際に、符号(
-
)がゼロパディングされた数値の途中に挿入されてしまうという問題がありました。例えば、-1.0
を%020f
でフォーマットすると、-000000000001.000000
のように、符号がゼロの後に来てしまうことがありました。これは、C言語のprintf
の挙動とは異なり、符号は常に数値の先頭に位置すべきです。 - 不要な
+
符号の表示: ゼロパディング時に、正の数に対して不要な+
符号が表示されるケースがありました。fmt
パッケージのフォーマットルールでは、+
フラグが指定されていない限り、正の数には符号を付けないのが一般的です。 - 無限大へのゼロパディングの適用: 無限大(
+Inf
,-Inf
)は数値ではないため、ゼロパディングを適用すべきではありませんでした。しかし、以前の実装では無限大に対してもゼロパディングが適用されてしまう可能性がありました。
これらの問題は、fmt
パッケージの出力がC言語のprintf
の挙動と異なることや、期待されるフォーマットと異なる結果を生み出すことから、ユーザーエクスペリエンスに影響を与えていました。このコミットは、これらのバグを修正し、fmt
パッケージのフォーマット挙動をより堅牢で予測可能なものにすることを目的としています。特に、fmt_test.go
に追加されたC言語のprintf
との比較テストは、Goのfmt
パッケージが標準的なフォーマットルールに準拠していることを保証するための重要なステップです。
前提知識の解説
このコミットを理解するためには、以下のGo言語のfmt
パッケージに関する基本的な知識と、一般的な数値フォーマットの概念が必要です。
Go言語の fmt
パッケージ
fmt
パッケージは、Go言語においてフォーマットされたI/O(入出力)を実装するためのパッケージです。C言語のprintf
やscanf
に似た機能を提供し、様々なデータ型を文字列に変換したり、文字列からデータを解析したりすることができます。
主な関数には以下のようなものがあります。
fmt.Printf
: フォーマットされた文字列を標準出力に出力します。fmt.Sprintf
: フォーマットされた文字列を返します。fmt.Fprintf
: フォーマットされた文字列を指定されたio.Writer
に出力します。
フォーマット動詞 (Verbs)
fmt
パッケージでは、値をどのようにフォーマットするかを制御するために「フォーマット動詞」と呼ばれる特殊な文字を使用します。例えば、%d
は整数、%s
は文字列、%f
は浮動小数点数を表します。
フラグ (Flags)
フォーマット動詞に加えて、フォーマットの挙動をさらに細かく制御するために「フラグ」を使用できます。このコミットに関連する重要なフラグは以下の通りです。
0
(ゼロパディング): フィールド幅を指定した場合に、数値の左側をスペースではなくゼロで埋めます。-
符号が付きます。+
(プラス符号): 数値が正の場合でも、常に+
符号を付けます。負の数には-
符号が付きます。
フィールド幅と精度
- フィールド幅: フォーマットされた出力が占める最小の文字数を指定します。例えば、
%10f
は浮動小数点数を少なくとも10文字幅で表示します。 - 精度: 浮動小数点数の場合、小数点以下の桁数を指定します。例えば、
%.2f
は小数点以下2桁まで表示します。
浮動小数点数と無限大
浮動小数点数は、小数点を持つ数値を表すデータ型です。Goではfloat32
とfloat64
があります。
無限大(Infinity)は、数値が非常に大きいか小さい場合に表現される特殊な浮動小数点数値です。Goではmath.Inf(1)
で正の無限大、math.Inf(-1)
で負の無限大を表します。これらの特殊な値は、通常の数値とは異なるフォーマットルールが適用されることがあります。
複素数
複素数は、実部と虚部を持つ数値です。Goではcomplex64
とcomplex128
があります。fmt
パッケージでは、複素数を(実部+虚部i)
の形式でフォーマットします。
技術的詳細
このコミットにおける技術的な変更は、主にfmt
パッケージの浮動小数点数と複素数のフォーマットロジックに集中しています。
src/pkg/fmt/fmt_test.go
の変更点
- C言語
printf
との比較テストの追加: 最も顕著な変更は、C言語のprintf
の出力と比較するための広範なテストケースが追加されたことです。これにより、Goのfmt
パッケージが標準的なフォーマット挙動に準拠していることが保証されます。特に、ゼロパディング、符号の表示(+
、スペース)、フィールド幅、精度など、様々な組み合わせでの浮動小数点数のフォーマットが検証されています。- 例えば、
%07.2f
で1.0
をフォーマットした場合、C言語のprintf
では0001.00
となりますが、以前のGoの実装では異なる結果になる可能性がありました。このテストの追加により、Goのfmt
がC言語のprintf
と同じ結果を返すように修正されました。 - 負の数に対するゼロパディングのテストも強化され、符号が常に数値の先頭に位置するように修正されたことが確認できます。
- 例えば、
- 無限大のゼロパディングに関するテストの追加: 無限大(
math.Inf
)に対してゼロパディングが適用されないことを確認するためのテストが追加されました。これは、無限大が数値ではないため、ゼロで埋めるという概念が適用されないという仕様に合致させるためのものです。 - 複素数フォーマットの網羅的なテスト (
TestComplexFormatting
): 複素数のフォーマットが、実部と虚部を個別にフォーマットした場合と同じ結果になることを検証するための新しいテスト関数TestComplexFormatting
が追加されました。このテストは、+
、0
、スペースフラグ、様々なフォーマット動詞(f
,e
,g
など)の組み合わせを網羅的に試行し、複素数のフォーマットが期待通りに行われることを保証します。特に、虚部には常に符号が付くべきであるというルールが考慮されています。
src/pkg/fmt/format.go
の変更点
formatFloat
関数の修正:- 無限大のゼロパディング抑制:
math.IsInf(v, 0)
で無限大を検出し、f.zero = false
とすることで、無限大に対してゼロパディングが適用されないように修正されました。これにより、無限大が不適切にゼロで埋められることがなくなります。 - ゼロパディング時の符号の配置ロジックの改善:
f.zero && f.widPresent && f.wid > len(num)
の条件分岐内で、符号の出力ロジックが改善されました。- 以前は単純に
f.buf.WriteByte(num[0])
で符号を出力していましたが、新しいロジックではf.space
フラグと数値の符号を考慮しています。 f.space
フラグが設定されており、かつ数値が正の場合(v >= 0
)は、符号の代わりにスペースを出力します(f.buf.WriteByte(' ')
)。これはC言語のprintf
の挙動に合わせたものです。f.plus
フラグが設定されている場合、または数値が負の場合(v < 0
)は、num[0]
(符号)を出力します。- これにより、ゼロパディング時に符号が常に数値の先頭に正しく配置され、不要な
+
符号が表示されないようになります。
- 以前は単純に
- 無限大のゼロパディング抑制:
- 複素数フォーマットの共通化 (
fmt_complex
の導入):fmt_c64
とfmt_c128
(それぞれcomplex64
とcomplex128
をフォーマットする関数)の内部ロジックが、新しく導入された共通のヘルパー関数fmt_complex
に集約されました。fmt_complex
関数は、実部と虚部をそれぞれformatFloat
関数を使ってフォーマットします。- 重要な変更点として、虚部をフォーマットする際には、常に
f.plus = true
とf.space = false
を設定しています。これは、複素数の虚部には常に符号(+
または-
)が付くという数学的な慣習と、C言語のprintf
の挙動に合わせたものです。これにより、虚部が正の場合でも+
符号が明示的に表示されるようになります。 - また、実部と虚部のフォーマット後に、元の
f.plus
、f.space
、f.wid
フラグの状態を復元するように修正されています。これにより、複素数フォーマットが他のフォーマット処理に影響を与えないようになります。
これらの変更により、fmt
パッケージの浮動小数点数および複素数のフォーマット挙動は、より正確で、C言語のprintf
との互換性が高まり、予測可能になりました。
コアとなるコードの変更箇所
src/pkg/fmt/format.go
formatFloat
関数内のゼロパディングと符号の処理
--- a/src/pkg/fmt/format.go
+++ b/src/pkg/fmt/format.go
@@ -368,14 +368,25 @@ func (f *fmt) formatFloat(v float64, verb byte, prec, n int) {
} else {
num[0] = '+'
}
+ // Special handling for infinity, which doesn't look like a number so shouldn't be padded with zeros.
+ if math.IsInf(v, 0) {
+ if f.zero {
+ defer func() { f.zero = true }()
+ f.zero = false
+ }
+ }
// num is now a signed version of the number.
// If we're zero padding, want the sign before the leading zeros.
// Achieve this by writing the sign out and then padding the unsigned number.
if f.zero && f.widPresent && f.wid > len(num) {
- f.buf.WriteByte(num[0])
- f.wid--
+ if f.space && v >= 0 {
+ f.buf.WriteByte(' ') // This is what C does: even with zero, f.space means space.
+ f.wid--
+ } else if f.plus || v < 0 {
+ f.buf.WriteByte(num[0])
+ f.wid--
+ }
f.pad(num[1:])
- f.wid++ // Restore width; complex numbers will reuse this value for imaginary part.
return
}
// f.space says to replace a leading + with a space.
複素数フォーマットの共通化と虚部の符号処理
--- a/src/pkg/fmt/format.go
+++ b/src/pkg/fmt/format.go
@@ -436,60 +447,46 @@ func (f *fmt) fmt_fb32(v float32) { f.formatFloat(float64(v), 'b', 0, 32) }
// fmt_c64 formats a complex64 according to the verb.
func (f *fmt) fmt_c64(v complex64, verb rune) {
- f.buf.WriteByte('(')
- r := real(v)
- oldPlus := f.plus
- for i := 0; ; i++ {
- switch verb {
- case 'b':
- f.fmt_fb32(r)
- case 'e':
- f.fmt_e32(r)
- case 'E':
- f.fmt_E32(r)
- case 'f', 'F':
- f.fmt_f32(r)
- case 'g':
- f.fmt_g32(r)
- case 'G':
- f.fmt_G32(r)
- }
- if i != 0 {
- break
- }
- f.plus = true
- r = imag(v)
- }
- f.plus = oldPlus
- f.buf.Write(irparenBytes)
+ f.fmt_complex(float64(real(v)), float64(imag(v)), 32, verb)
}
// fmt_c128 formats a complex128 according to the verb.
func (f *fmt) fmt_c128(v complex128, verb rune) {
+ f.fmt_complex(real(v), imag(v), 64, verb)
+}
+
+// fmt_complex formats a complex number as (r+ji).
+func (f *fmt) fmt_complex(r, j float64, size int, verb rune) {
f.buf.WriteByte('(')
- r := real(v)
oldPlus := f.plus
+ oldSpace := f.space
+ oldWid := f.wid
for i := 0; ; i++ {
switch verb {
case 'b':
- f.fmt_fb64(r)
+ f.formatFloat(r, 'b', 0, size)
case 'e':
- f.fmt_e64(r)
+ f.formatFloat(r, 'e', doPrec(f, 6), size)
case 'E':
- f.fmt_E64(r)
+ f.formatFloat(r, 'E', doPrec(f, 6), size)
case 'f', 'F':
- f.fmt_f64(r)
+ f.formatFloat(r, 'f', doPrec(f, 6), size)
case 'g':
- f.fmt_g64(r)
+ f.formatFloat(r, 'g', doPrec(f, -1), size)
case 'G':
- f.fmt_G64(r)
+ f.formatFloat(r, 'G', doPrec(f, -1), size)
}
if i != 0 {
break
}
+ // Imaginary part always has a sign.
f.plus = true
- r = imag(v)
+ f.space = false
+ f.wid = oldWid
+ r = j
}
+ f.space = oldSpace
f.plus = oldPlus
+ f.wid = oldWid
f.buf.Write(irparenBytes)
}
コアとなるコードの解説
formatFloat
関数内のゼロパディングと符号の処理
この変更は、浮動小数点数のゼロパディング時に符号が正しく扱われるようにするためのものです。
-
無限大のゼロパディング抑制:
if math.IsInf(v, 0) { if f.zero { defer func() { f.zero = true }() f.zero = false } }
math.IsInf(v, 0)
は、v
が正または負の無限大であるかをチェックします。もし無限大であれば、f.zero
フラグ(ゼロパディングが有効かどうかを示す)を一時的にfalse
に設定します。defer
文を使うことで、関数が終了する際にf.zero
を元の値に戻すことを保証しています。これにより、無限大に対してゼロパディングが適用されるのを防ぎます。 -
ゼロパディング時の符号の配置ロジックの改善:
if f.zero && f.widPresent && f.wid > len(num) { if f.space && v >= 0 { f.buf.WriteByte(' ') // This is what C does: even with zero, f.space means space. f.wid-- } else if f.plus || v < 0 { f.buf.WriteByte(num[0]) f.wid-- } f.pad(num[1:]) return }
このブロックは、ゼロパディングが有効で、かつ指定されたフィールド幅が数値の長さよりも大きい場合に実行されます。
if f.space && v >= 0
: もしspace
フラグが設定されており、かつ数値が正の場合(例:% 07.2f
で1.0
をフォーマット)、C言語のprintf
の挙動に合わせて、符号の代わりにスペースを出力します。その後、フィールド幅を1減らします。else if f.plus || v < 0
: 上記の条件に当てはまらず、かつplus
フラグが設定されている場合、または数値が負の場合(例:%+07.2f
で1.0
、または%07.2f
で-1.0
をフォーマット)、数値の符号(num[0]
)を出力します。その後、フィールド幅を1減らします。f.pad(num[1:])
: 符号が出力された後、残りの数値(num[1:]
は符号を除いた部分)をゼロでパディングします。 このロジックにより、ゼロパディング時に符号が常に数値の先頭に正しく配置され、C言語のprintf
との互換性が向上します。
複素数フォーマットの共通化と虚部の符号処理
この変更は、複素数のフォーマット処理を改善し、特に虚部の符号の表示を標準的な慣習に合わせるためのものです。
-
fmt_complex
ヘルパー関数の導入:fmt_c64
とfmt_c128
の重複するフォーマットロジックをfmt_complex
という新しい関数にまとめました。これにより、コードの重複が減り、保守性が向上します。 -
虚部の符号の強制表示:
// Imaginary part always has a sign. f.plus = true f.space = false f.wid = oldWid r = j
複素数の虚部をフォーマットする直前に、
f.plus = true
とf.space = false
を設定しています。これは、複素数の虚部には常に+
または-
の符号が付くという数学的な慣習(例:1+2i
ではなく1+2.00i
、1-2i
ではなく1-2.00i
)に合わせるためのものです。f.space = false
は、スペースフラグが設定されていても虚部には適用されないことを保証します。f.wid = oldWid
は、実部で使われたフィールド幅を虚部でも再利用できるようにしています。 -
フラグの状態の復元:
f.space = oldSpace f.plus = oldPlus f.wid = oldWid
複素数の実部と虚部のフォーマットが完了した後、
f.space
、f.plus
、f.wid
の各フラグを、複素数フォーマット開始前の元の状態に復元しています。これにより、複素数フォーマットが他のフォーマット処理に意図しない影響を与えることを防ぎます。
これらの変更により、Goのfmt
パッケージは、浮動小数点数と複素数のフォーマットにおいて、より正確で、標準的な挙動に準拠するようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/b559392e1b84f521a8b9c2d3c1164960c62799c8
- Go CL (Code Review): https://golang.org/cl/103480043
参考にした情報源リンク
- Go言語
fmt
パッケージ公式ドキュメント: https://pkg.go.dev/fmt - C言語
printf
フォーマット指定子に関する情報 (一般的なリファレンス): (例: https://www.cplusplus.com/reference/cstdio/printf/) - IEEE 754 浮動小数点数標準 (無限大の表現に関する一般的な情報): (例: https://en.wikipedia.org/wiki/IEEE_754)# [インデックス 19563] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおける、浮動小数点数のゼロパディング時の符号の扱いに関するバグ修正と、関連するテストの追加を目的としています。具体的には、src/pkg/fmt/fmt_test.go
とsrc/pkg/fmt/format.go
の2つのファイルが変更されています。
src/pkg/fmt/fmt_test.go
では、浮動小数点数と複素数のフォーマットに関するテストケースが大幅に追加されています。特に、C言語のprintf
関数との比較を通じて、Goのfmt
パッケージの挙動が期待通りであることを検証するための詳細なテストが導入されました。これにより、ゼロパディング、符号の表示、スペースフラグの挙動、無限大の扱いなど、様々なフォーマットオプションの組み合わせが網羅的にテストされています。
src/pkg/fmt/format.go
では、fmt
パッケージの内部で実際にフォーマット処理を行うformatFloat
関数と、複素数をフォーマットするfmt_c64
、fmt_c128
、そして新しく導入されたfmt_complex
関数が修正されています。これらの変更は、ゼロパディング時に符号が正しく配置されるようにすること、不要な+
符号が表示されないようにすること、そして無限大に対してゼロパディングが適用されないようにすることを目的としています。
コミット
commit b559392e1b84f521a8b9c2d3c1164960c62799c8
Author: Rob Pike <r@golang.org>
Date: Tue Jun 17 14:56:54 2014 -0700
fmt: fix signs when zero padding.
Bug was introduced recently. Add more tests, fix the bugs.
Suppress + sign when not required in zero padding.
Do not zero pad infinities.
All old tests still pass.
This time for sure!
Fixes #8217.
LGTM=rsc
R=golang-codereviews, dan.kortschak, rsc
CC=golang-codereviews
https://golang.org/cl/103480043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b559392e1b84f521a8b9c2d3c1164960c62799c8
元コミット内容
fmt: fix signs when zero padding.
Bug was introduced recently. Add more tests, fix the bugs.
Suppress + sign when not required in zero padding.
Do not zero pad infinities.
All old tests still pass.
This time for sure!
Fixes #8217.
LGTM=rsc
R=golang-codereviews, dan.kortschak, rsc
CC=golang-codereviews
https://golang.org/cl/103480043
変更の背景
このコミットの主な背景は、fmt
パッケージにおける浮動小数点数のゼロパディング処理に最近導入されたバグの修正です。具体的には、以下の問題が報告されていました。
- ゼロパディング時の符号の誤った配置: 以前のバージョンでは、負の浮動小数点数をゼロパディングする際に、符号(
-
)がゼロパディングされた数値の途中に挿入されてしまうという問題がありました。例えば、-1.0
を%020f
でフォーマットすると、-000000000001.000000
のように、符号がゼロの後に来てしまうことがありました。これは、C言語のprintf
の挙動とは異なり、符号は常に数値の先頭に位置すべきです。 - 不要な
+
符号の表示: ゼロパディング時に、正の数に対して不要な+
符号が表示されるケースがありました。fmt
パッケージのフォーマットルールでは、+
フラグが指定されていない限り、正の数には符号を付けないのが一般的です。 - 無限大へのゼロパディングの適用: 無限大(
+Inf
,-Inf
)は数値ではないため、ゼロパディングを適用すべきではありませんでした。しかし、以前の実装では無限大に対してもゼロパディングが適用されてしまう可能性がありました。
これらの問題は、fmt
パッケージの出力がC言語のprintf
の挙動と異なることや、期待されるフォーマットと異なる結果を生み出すことから、ユーザーエクスペリエンスに影響を与えていました。このコミットは、これらのバグを修正し、fmt
パッケージのフォーマット挙動をより堅牢で予測可能なものにすることを目的としています。特に、fmt_test.go
に追加されたC言語のprintf
との比較テストは、Goのfmt
パッケージが標準的なフォーマットルールに準拠していることを保証するための重要なステップです。
前提知識の解説
このコミットを理解するためには、以下のGo言語のfmt
パッケージに関する基本的な知識と、一般的な数値フォーマットの概念が必要です。
Go言語の fmt
パッケージ
fmt
パッケージは、Go言語においてフォーマットされたI/O(入出力)を実装するためのパッケージです。C言語のprintf
やscanf
に似た機能を提供し、様々なデータ型を文字列に変換したり、文字列からデータを解析したりすることができます。
主な関数には以下のようなものがあります。
fmt.Printf
: フォーマットされた文字列を標準出力に出力します。fmt.Sprintf
: フォーマットされた文字列を返します。fmt.Fprintf
: フォーマットされた文字列を指定されたio.Writer
に出力します。
フォーマット動詞 (Verbs)
fmt
パッケージでは、値をどのようにフォーマットするかを制御するために「フォーマット動詞」と呼ばれる特殊な文字を使用します。例えば、%d
は整数、%s
は文字列、%f
は浮動小数点数を表します。
フラグ (Flags)
フォーマット動詞に加えて、フォーマットの挙動をさらに細かく制御するために「フラグ」を使用できます。このコミットに関連する重要なフラグは以下の通りです。
0
(ゼロパディング): フィールド幅を指定した場合に、数値の左側をスペースではなくゼロで埋めます。-
符号が付きます。+
(プラス符号): 数値が正の場合でも、常に+
符号を付けます。負の数には-
符号が付きます。
フィールド幅と精度
- フィールド幅: フォーマットされた出力が占める最小の文字数を指定します。例えば、
%10f
は浮動小数点数を少なくとも10文字幅で表示します。 - 精度: 浮動小数点数の場合、小数点以下の桁数を指定します。例えば、
%.2f
は小数点以下2桁まで表示します。
浮動小数点数と無限大
浮動小数点数は、小数点を持つ数値を表すデータ型です。Goではfloat32
とfloat64
があります。
無限大(Infinity)は、数値が非常に大きいか小さい場合に表現される特殊な浮動小数点数値です。Goではmath.Inf(1)
で正の無限大、math.Inf(-1)
で負の無限大を表します。これらの特殊な値は、通常の数値とは異なるフォーマットルールが適用されることがあります。
複素数
複素数は、実部と虚部を持つ数値です。Goではcomplex64
とcomplex128
があります。fmt
パッケージでは、複素数を(実部+虚部i)
の形式でフォーマットします。
技術的詳細
このコミットにおける技術的な変更は、主にfmt
パッケージの浮動小数点数と複素数のフォーマットロジックに集中しています。
src/pkg/fmt/fmt_test.go
の変更点
- C言語
printf
との比較テストの追加: 最も顕著な変更は、C言語のprintf
の出力と比較するための広範なテストケースが追加されたことです。これにより、Goのfmt
パッケージが標準的なフォーマット挙動に準拠していることが保証されます。特に、ゼロパディング、符号の表示(+
、スペース)、フィールド幅、精度など、様々な組み合わせでの浮動小数点数のフォーマットが検証されています。- 例えば、
%07.2f
で1.0
をフォーマットした場合、C言語のprintf
では0001.00
となりますが、以前のGoの実装では異なる結果になる可能性がありました。このテストの追加により、Goのfmt
がC言語のprintf
と同じ結果を返すように修正されました。 - 負の数に対するゼロパディングのテストも強化され、符号が常に数値の先頭に位置するように修正されたことが確認できます。
- 例えば、
- 無限大のゼロパディングに関するテストの追加: 無限大(
math.Inf
)に対してゼロパディングが適用されないことを確認するためのテストが追加されました。これは、無限大が数値ではないため、ゼロで埋めるという概念が適用されないという仕様に合致させるためのものです。 - 複素数フォーマットの網羅的なテスト (
TestComplexFormatting
): 複素数のフォーマットが、実部と虚部を個別にフォーマットした場合と同じ結果になることを検証するための新しいテスト関数TestComplexFormatting
が追加されました。このテストは、+
、0
、スペースフラグ、様々なフォーマット動詞(f
,e
,g
など)の組み合わせを網羅的に試行し、複素数のフォーマットが期待通りに行われることを保証します。特に、虚部には常に符号が付くべきであるというルールが考慮されています。
src/pkg/fmt/format.go
の変更点
formatFloat
関数の修正:- 無限大のゼロパディング抑制:
math.IsInf(v, 0)
で無限大を検出し、f.zero = false
とすることで、無限大に対してゼロパディングが適用されないように修正されました。これにより、無限大が不適切にゼロで埋められることがなくなります。 - ゼロパディング時の符号の配置ロジックの改善:
f.zero && f.widPresent && f.wid > len(num)
の条件分岐内で、符号の出力ロジックが改善されました。- 以前は単純に
f.buf.WriteByte(num[0])
で符号を出力していましたが、新しいロジックではf.space
フラグと数値の符号を考慮しています。 f.space
フラグが設定されており、かつ数値が正の場合(v >= 0
)は、符号の代わりにスペースを出力します(f.buf.WriteByte(' ')
)。これはC言語のprintf
の挙動に合わせたものです。f.plus
フラグが設定されている場合、または数値が負の場合(v < 0
)は、num[0]
(符号)を出力します。- これにより、ゼロパディング時に符号が常に数値の先頭に正しく配置され、不要な
+
符号が表示されないようになります。
- 以前は単純に
- 無限大のゼロパディング抑制:
- 複素数フォーマットの共通化 (
fmt_complex
の導入):fmt_c64
とfmt_c128
(それぞれcomplex64
とcomplex128
をフォーマットする関数)の内部ロジックが、新しく導入された共通のヘルパー関数fmt_complex
に集約されました。fmt_complex
関数は、実部と虚部をそれぞれformatFloat
関数を使ってフォーマットします。- 重要な変更点として、虚部をフォーマットする際には、常に
f.plus = true
とf.space = false
を設定しています。これは、複素数の虚部には常に符号(+
または-
)が付くという数学的な慣習と、C言語のprintf
の挙動に合わせたものです。これにより、虚部が正の場合でも+
符号が明示的に表示されるようになります。 - また、実部と虚部のフォーマット後に、元の
f.plus
、f.space
、f.wid
フラグの状態を復元するように修正されています。これにより、複素数フォーマットが他のフォーマット処理に影響を与えないようになります。
これらの変更により、fmt
パッケージの浮動小数点数および複素数のフォーマット挙動は、より正確で、C言語のprintf
との互換性が高まり、予測可能になりました。
コアとなるコードの変更箇所
src/pkg/fmt/format.go
formatFloat
関数内のゼロパディングと符号の処理
--- a/src/pkg/fmt/format.go
+++ b/src/pkg/fmt/format.go
@@ -368,14 +368,25 @@ func (f *fmt) formatFloat(v float64, verb byte, prec, n int) {
} else {
num[0] = '+'
}
+ // Special handling for infinity, which doesn't look like a number so shouldn't be padded with zeros.
+ if math.IsInf(v, 0) {
+ if f.zero {
+ defer func() { f.zero = true }()
+ f.zero = false
+ }
+ }
// num is now a signed version of the number.
// If we're zero padding, want the sign before the leading zeros.
// Achieve this by writing the sign out and then padding the unsigned number.
if f.zero && f.widPresent && f.wid > len(num) {
- f.buf.WriteByte(num[0])
- f.wid--
+ if f.space && v >= 0 {
+ f.buf.WriteByte(' ') // This is what C does: even with zero, f.space means space.
+ f.wid--
+ } else if f.plus || v < 0 {
+ f.buf.WriteByte(num[0])
+ f.wid--
+ }
f.pad(num[1:])
- f.wid++ // Restore width; complex numbers will reuse this value for imaginary part.
return
}
// f.space says to replace a leading + with a space.
複素数フォーマットの共通化と虚部の符号処理
--- a/src/pkg/fmt/format.go
+++ b/src/pkg/fmt/format.go
@@ -436,60 +447,46 @@ func (f *fmt) fmt_fb32(v float32) { f.formatFloat(float64(v), 'b', 0, 32) }
// fmt_c64 formats a complex64 according to the verb.
func (f *fmt) fmt_c64(v complex64, verb rune) {
- f.buf.WriteByte('(')
- r := real(v)
- oldPlus := f.plus
- for i := 0; ; i++ {
- switch verb {
- case 'b':
- f.fmt_fb32(r)
- case 'e':
- f.fmt_e32(r)
- case 'E':
- f.fmt_E32(r)
- case 'f', 'F':
- f.fmt_f32(r)
- case 'g':
- f.fmt_g32(r)
- case 'G':
- f.fmt_G32(r)
- }
- if i != 0 {
- break
- }
- f.plus = true
- r = imag(v)
- }
- f.plus = oldPlus
- f.buf.Write(irparenBytes)
+ f.fmt_complex(float64(real(v)), float64(imag(v)), 32, verb)
}
// fmt_c128 formats a complex128 according to the verb.
func (f *fmt) fmt_c128(v complex128, verb rune) {
+ f.fmt_complex(real(v), imag(v), 64, verb)
+}
+
+// fmt_complex formats a complex number as (r+ji).
+func (f *fmt) fmt_complex(r, j float64, size int, verb rune) {
f.buf.WriteByte('(')
- r := real(v)
oldPlus := f.plus
+ oldSpace := f.space
+ oldWid := f.wid
for i := 0; ; i++ {
switch verb {
case 'b':
- f.fmt_fb64(r)
+ f.formatFloat(r, 'b', 0, size)
case 'e':
- f.fmt_e64(r)
+ f.formatFloat(r, 'e', doPrec(f, 6), size)
case 'E':
- f.fmt_E64(r)
+ f.formatFloat(r, 'E', doPrec(f, 6), size)
case 'f', 'F':
- f.fmt_f64(r)
+ f.formatFloat(r, 'f', doPrec(f, 6), size)
case 'g':
- f.fmt_g64(r)
+ f.formatFloat(r, 'g', doPrec(f, -1), size)
case 'G':
- f.fmt_G64(r)
+ f.formatFloat(r, 'G', doPrec(f, -1), size)
}
if i != 0 {
break
}
+ // Imaginary part always has a sign.
f.plus = true
- r = imag(v)
+ f.space = false
+ f.wid = oldWid
+ r = j
}
+ f.space = oldSpace
f.plus = oldPlus
+ f.wid = oldWid
f.buf.Write(irparenBytes)
}
コアとなるコードの解説
formatFloat
関数内のゼロパディングと符号の処理
この変更は、浮動小数点数のゼロパディング時に符号が正しく扱われるようにするためのものです。
-
無限大のゼロパディング抑制:
if math.IsInf(v, 0) { if f.zero { defer func() { f.zero = true }() f.zero = false } }
math.IsInf(v, 0)
は、v
が正または負の無限大であるかをチェックします。もし無限大であれば、f.zero
フラグ(ゼロパディングが有効かどうかを示す)を一時的にfalse
に設定します。defer
文を使うことで、関数が終了する際にf.zero
を元の値に戻すことを保証しています。これにより、無限大に対してゼロパディングが適用されるのを防ぎます。 -
ゼロパディング時の符号の配置ロジックの改善:
if f.zero && f.widPresent && f.wid > len(num) { if f.space && v >= 0 { f.buf.WriteByte(' ') // This is what C does: even with zero, f.space means space. f.wid-- } else if f.plus || v < 0 { f.buf.WriteByte(num[0]) f.wid-- } f.pad(num[1:]) return }
このブロックは、ゼロパディングが有効で、かつ指定されたフィールド幅が数値の長さよりも大きい場合に実行されます。
if f.space && v >= 0
: もしspace
フラグが設定されており、かつ数値が正の場合(例:% 07.2f
で1.0
をフォーマット)、C言語のprintf
の挙動に合わせて、符号の代わりにスペースを出力します。その後、フィールド幅を1減らします。else if f.plus || v < 0
: 上記の条件に当てはまらず、かつplus
フラグが設定されている場合、または数値が負の場合(例:%+07.2f
で1.0
、または%07.2f
で-1.0
をフォーマット)、数値の符号(num[0]
)を出力します。その後、フィールド幅を1減らします。f.pad(num[1:])
: 符号が出力された後、残りの数値(num[1:]
は符号を除いた部分)をゼロでパディングします。 このロジックにより、ゼロパディング時に符号が常に数値の先頭に正しく配置され、C言語のprintf
との互換性が向上します。
複素数フォーマットの共通化と虚部の符号処理
この変更は、複素数のフォーマット処理を改善し、特に虚部の符号の表示を標準的な慣習に合わせるためのものです。
-
fmt_complex
ヘルパー関数の導入:fmt_c64
とfmt_c128
の重複するフォーマットロジックをfmt_complex
という新しい関数にまとめました。これにより、コードの重複が減り、保守性が向上します。 -
虚部の符号の強制表示:
// Imaginary part always has a sign. f.plus = true f.space = false f.wid = oldWid r = j
複素数の虚部をフォーマットする直前に、
f.plus = true
とf.space = false
を設定しています。これは、複素数の虚部には常に+
または-
の符号が付くという数学的な慣習(例:1+2i
ではなく1+2.00i
、1-2i
ではなく1-2.00i
)に合わせるためのものです。f.space = false
は、スペースフラグが設定されていても虚部には適用されないことを保証します。f.wid = oldWid
は、実部で使われたフィールド幅を虚部でも再利用できるようにしています。 -
フラグの状態の復元:
f.space = oldSpace f.plus = oldPlus f.wid = oldWid
複素数の実部と虚部のフォーマットが完了した後、
f.space
、f.plus
、f.wid
の各フラグを、複素数フォーマット開始前の元の状態に復元しています。これにより、複素数フォーマットが他のフォーマット処理に意図しない影響を与えることを防ぎます。
これらの変更により、Goのfmt
パッケージは、浮動小数点数と複素数のフォーマットにおいて、より正確で、標準的な挙動に準拠するようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/b559392e1b84f521a8b9c2d3c1164960c62799c8
- Go CL (Code Review): https://golang.org/cl/103480043
参考にした情報源リンク
- Go言語
fmt
パッケージ公式ドキュメント: https://pkg.go.dev/fmt - C言語
printf
フォーマット指定子に関する情報 (一般的なリファレンス): (例: https://www.cplusplus.com/reference/cstdio/printf/) - IEEE 754 浮動小数点数標準 (無限大の表現に関する一般的な情報): (例: https://en.wikipedia.org/wiki/IEEE_754)