🍣

Go で浮動小数点数をいい感じに文字列変換する

2020/09/23に公開約2,500字2件のコメント

先日 Go で float64 な値を文字列変換しようとした時に、なかなかいい感じに変換できなかったけど案外簡単な方法で解決できたよ、というお話です。
ここで言う いい感じに変換 とは、float64型の 3.14 5 9.99999 をstring型の "3.14" "5" "9.99999" に変換することです。

上記の3つの数値を Go の標準パッケージの1つであるfmtパッケージを使って変換し標準出力する例を考えます。
まずは fmtパッケージのドキュメント を読んでみましょう。すると、浮動小数点数のフォーマットについて下記のような記述があります。

Floating-point and complex constituents:

%b	decimalless scientific notation with exponent a power of two,
	in the manner of strconv.FormatFloat with the 'b' format,
	e.g. -123456p-78
%e	scientific notation, e.g. -1.234456e+78
%E	scientific notation, e.g. -1.234456E+78
%f	decimal point but no exponent, e.g. 123.456
%F	synonym for %f
%g	%e for large exponents, %f otherwise. Precision is discussed below.
%G	%E for large exponents, %F otherwise
%x	hexadecimal notation (with decimal power of two exponent), e.g. -0x1.23abcp+20
%X	upper-case hexadecimal notation, e.g. -0X1.23ABCP+20

なんかいっぱいありますが %f っぽいですね。コードにしてみると次のようになります。

package main

import "fmt"

func main() {
	var p float64 = 3.14
	var f float64 = 5
	var n float64 = 9.999999
	fmt.Printf("3.14->%f 5->%f 9.99999->%f\n", p, f, n)
}

このコードを go run で実行すると…

3.14->3.140000 5->5.000000 9.999999->9.999999

これは いい感じ ではありません。末尾の 0 は表示したくない。
そこで、もう一度fmtパッケージのドキュメントをよく読んでみます。先程引用した %g のところに書いてある Precision is discussed below. というのが気になります。ざっくり訳すと「精度については下で議論されてます」ということのようです。そこでもう少し下の方まで読み進めると次のような記述があります。

For floating-point values, width sets the minimum width of the field and precision sets the number of places after the decimal, if appropriate, except that for %g/%G precision sets the maximum number of significant digits (trailing zeros are removed). For example, given 12.345 the format %6.3f prints 12.345 while %.3g prints 12.3. The default precision for %e, %f and %#g is 6; for %g it is the smallest number of digits necessary to identify the value uniquely.

ズバリの内容が書かれてありました! %g を使うと末尾の 0 なしでその数だと識別できる最小限の精度で表示してくれるようです。これはまさに いい感じ です。
以上を踏まえて先程のコードを修正します。

package main

import "fmt"

func main() {
	var p float64 = 3.14
	var f float64 = 5
	var n float64 = 9.999999
	fmt.Printf("3.14->%g 5->%g 9.99999->%g\n", p, f, n)  // %f を %g に変更しました
}

このコードで改めて go run を実行すると…

3.14->3.14 5->5 9.999999->9.999999

期待通り いい感じ に出力されました!

まとめ

Go で浮動小数点数を文字列変換する際は %g でフォーマットするといい感じに変換してくれます。

ちなみに今回の例では fmt.Printf を使って変換した文字列を標準出力しましたが、文字列として取得したいときには fmt.Sprintf を使うと同様にフォーマットされた文字列が取得できます。

Discussion

%v を使うと型が変わっても書式を直す手間がない(ケースによります)ので便利だと思います。

コメントありがとうございます!
ここには○○型の値が入る、と自分に言い聞かせる意味で %v は極力使わない、という自分ルールを課しているということにご指摘いただいて気が付きました🙄
最初から %v のデフォルトフォーマットを参考にすればよかったですね😅

ログインするとコメントできます