【Golang】数値をコンマ区切りで出力する【1000単位のカンマ区切りフォーマット出力】

> Go 言語（以下 Golang）で、数字を3桁ごとにカンマ区切りする処理をしたい。
>
> つまり 1000 の単位で区切りたいのです。例えば `int(1000)` ---> `"1,000"` と[コンマ](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%9E)区切りでフォーマット出力したい。しかし、`fmt` パッケージには数字のフォーマット処理をする専用の関数がなさげ。
>
> どうしよう。

**独自実装やサードパーティのパッケージを使わずに、<font color=darkorange>golang の純正パッケージでフォーマットできないものか</font>**、[「golang コンマ区切り カンマ区切り 出力 フォーマット」でググる](https://www.google.com/search?q=golang+%E3%82%B3%E3%83%B3%E3%83%9E%E5%8C%BA%E5%88%87%E3%82%8A+%E3%82%AB%E3%83%B3%E3%83%9E%E5%8C%BA%E5%88%87%E3%82%8A+%E5%87%BA%E5%8A%9B+%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88)も独自実装だったり、すでにコンマ区切りの値を数値に変換する処理といったものばかりだったので自分のググラビリティとして。

## TL; DR <sub><sup>（今北産業）</sup></sub>

1. Go の準標準モジュール `golang.org/x/text` の [message](https://pkg.go.dev/golang.org/x/text/message) パッケージで可能<br>・ `message` は、ロケールに合わせたテキスト整形ツール郡で `fmt` の代替パッケージ
1. 桁の区切りカンマやピリオドは国によって記法が異なる。`message` で調整が可能
1. マスター、動くものをくれ

    ```go:基本構文
    package main
    
    import (
    	"golang.org/x/text/language"
    	"golang.org/x/text/message"
    )
    
    func main() {
    	value := 1000
    
    	fmtX := message.NewPrinter(language.Japanese)
    
    	fmtX.Printf("%d\n", value)
    
    	// Output: 1,000
    }
    ```
    - [オンラインで動作をみる](https://go.dev/play/p/v8ayiGr79i1) @ Go playground

    ```go:言語による表記の違い
    package main
    
    import (
    	"golang.org/x/text/language"
    	"golang.org/x/text/message"
    	"golang.org/x/text/number"
    )
    
    func main() {
    	value := 1234567.89
    
    	for _, langTag := range []language.Tag{
    		language.English,
    		language.Japanese,
    		language.French,
    		language.Hindi,
    		language.German,
    	} {
    		p := message.NewPrinter(langTag)
    
    		p.Printf("%s: %v\n", langTag, number.Decimal(value))
    	}
    
    	// Output:
    	// en: 1,234,567.89
    	// ja: 1,234,567.89
    	// fr: 1 234 567,89
    	// hi: 12,34,567.89
    	// de: 1.234.567,89
    }
    ```
    - [オンラインで動作をみる](https://go.dev/play/p/UNPfT77CIdH) @ Go playground

- 併せて読みたい
    - 特定言語のフォーマッターを利用する例
    -  [【Golang】GoからPythonを呼び出してみよう【Python】](https://qiita.com/dhulripos/items/71e125b9f311b7dabe15#_reference-4152710b438d48f3e9fc) @ Qiita

## TS; DR <sub><sup>（フォーマット出力に関するコマケーこと）</sup></sub>

### 1000 単位の区切りにコンマは国によって異なる

<font color="darkorange">**小数点や桁の区切りは、国によって記法が異なる**</font>ことをご存じでしょうか。

例えば、日本では `1234567.89` という値は `1,234,567.89` とも「百二十三万四千五百六十七点八九」とも記載できます。

しかし、`1.234.567,89` だったり `12,34,567.89`、`1 234 567,89` や `1'234'567,89` という、[日本とは異なる記法の国も多い](https://en.wikipedia.org/wiki/Decimal_separator#Examples_of_use)のです。

他にも、日付の 2001 年 10 月 11 日を `01/10/11` という `YMD` 記法の国もあれは、`11/10/01`（`DMY`）だったり `10/11/01`（`MDY`）という国もあります。

つまり、国によって記法が異なるということです。

### 表記のローカライズ

そこで、「その国に合わせた記述方法」に変換することを「<ruby>ローカライズ<rt><strong>local</strong>ize</rt></ruby>」（[地域化](https://ja.wikipedia.org/wiki/%E5%9B%BD%E9%9A%9B%E5%8C%96%E3%81%A8%E5%9C%B0%E5%9F%9F%E5%8C%96)）と言います。

OS で「<ruby>ロケール<rt><strong>local</strong>e</rt></ruby>」を変更すると、数値や日付の表記が変わるのを経験したことはないでしょうか。アレです。

:::note info
ゆうて、筆者も「PHP なら <code>[number_format](https://www.php.net/manual/ja/function.number-format.php)</code>、Python なら [f 文字列](https://docs.python.org/ja/3/reference/lexical_analysis.html#f-strings)の `f"{num:,}"` があるのに、なぜ Go には標準で数値のフォーマッターがないんだ」と思い調べていたところ、[number](https://pkg.go.dev/golang.org/x/text/number) モジュールがフォーマッターであることはわかったものの、`message` の `Printer` オブジェクトを通して処理しています。

なぜそんな七面倒臭いことをするのだろうと思って [message](https://pkg.go.dev/golang.org/x/text/message) のドキュメントを読んでいたら「ロケールの問題か！」と[気付かされた](https://media.tenor.com/YctxttUmGMYAAAAC/forehead-slap-slapping-forehead.gif)のです。
:::

[message](https://pkg.go.dev/golang.org/x/text/message) から以下の抜粋を翻訳してみました。

> **Localized Formatting**
>
> A format string can be localized by replacing any of the print functions of fmt with an equivalent call to a Printer.
>
> ```go
> p := message.NewPrinter(message.MatchLanguage("en"))
> p.Println(123456.78) // Prints 123,456.78
>
> p.Printf("%d ducks in a row", 4331) // Prints 4,331 ducks in a row
>
> p := message.NewPrinter(message.MatchLanguage("nl"))
> p.Printf("Hoogte: %.1f meter", 1244.9) // Prints Hoogte: 1,244.9 meter
> 
> p := message.NewPrinter(message.MatchLanguage("bn"))
> p.Println(123456.78) // Prints ১,২৩,৪৫৬.৭৮
> ```
>
> Printer currently supports numbers and specialized types for which packages exist in x/text. Other builtin types such as time.Time and slices are planned.
>
> Format strings largely have the same meaning as with fmt with the following notable exceptions:
>
> - flag # always resorts to fmt for printing
> - verb 'f', 'e', 'g', 'd' use localized formatting unless the '#' flag is specified.
> - verb 'm' inserts a translation of a string argument.
>
> See package fmt for more options.
>
> （[Localized Formatting](https://pkg.go.dev/golang.org/x/text/message#hdr-Localized_Formatting) | message @ pkg.go.dev より）
>
> ▼ 以下筆者翻訳
>
> ### ローカライズされたフォーマットによる文字列整形
>
> 文字列のフォーマット（整形）は、`fmt` の `Print` 系の関数を `message.Printer` オブジェクトのメソッドに置き換えることでローカライズ（[地域化](https://ja.wikipedia.org/wiki/%E5%9B%BD%E9%9A%9B%E5%8C%96%E3%81%A8%E5%9C%B0%E5%9F%9F%E5%8C%96)）することができます。
>
> ```go
> p := message.NewPrinter(message.MatchLanguage("en"))
> p.Println(123456.78) // 出力: "123,456.78"
>
> p.Printf("%d ducks in a row", 4331) // 出力: "4,331 ducks in a row"
>
> p := message.NewPrinter(message.MatchLanguage("nl"))
> p.Printf("Hoogte: %.1f meter", 1244.9) // 出力: "Hoogte: 1,244.9 meter"
>
> p := message.NewPrinter(message.MatchLanguage("bn"))
> p.Println(123456.78) // 出力: "১,২৩,৪৫৬.৭৮"
> ```
>
> 現在、`message.Printer` 型は、数字と `x/text` パッケージに存在する特殊な型のみをサポートしています。その他の `time.Time` やスライスなどのビルトインの型は、今後実装が予定されています。
>
> 文字列のフォーマットは、以下の顕著な例外を除いて、 `fmt` とほぼ同じ意味を持っています
>
> - `#` フラグは常に `fmt` を使用します
> - "`%d`" などの `f`, `e`, `g`, `d` 動詞は、`#` フラグが指定されない限り、地域化された書式を使用します
> - `m` 動詞は、引数の文字列の翻訳を挿入します。（※）
>
> その他のオプションについては `fmt` パッケージのドキュメントをご覧ください。

:::note info
※ 【筆者注】 `message` には[国際化のための翻訳辞書機能がある](https://pkg.go.dev/golang.org/x/text/message#hdr-Translation)ようです。しかし、ドキュメントのサンプルが動かないため、使い方もよくわかりません。分かり次第追記か別記事にしたいと思います。
:::
## 参考文献

- [Localized Formatting](https://pkg.go.dev/golang.org/x/text/message#hdr-Localized_Formatting) | message | text | golang.org @ pkg.go.dev
- "[How to fmt.Printf an integer with thousands comma](https://stackoverflow.com/a/46811454/18152508)" @ Stackoverflow