String.format("%d", i)で数字が出てくると思ってたら死んだ話

# あるSlackでの会話

![Screen Shot 2015-07-31 at 11.20.03 AM.png](https://qiita-image-store.s3.amazonaws.com/0/12052/47e322bf-d008-c9e1-cff8-c6b8d09c1235.png)

# 何がおきているのか

Android 端末でプラットフォームの API バージョンを出すのに、ちょっと色気を出して、

```java
String.format("android-%d", Build.VERSION.SDK_INT)
```

なんて書いたりします。で、だいたい `android-22` みたいな感じになるんですけど、でもやっぱり世界ってのは広くて、ふと見たらなんか `android-١٦` とか `android-၂၂` とか不思議なやつがいるんですよ。 **何それ読めない。**

もしかして SDK_INT が変な端末がいるのかな？？と一瞬考えたんですが、 [`Build.VERSION.SDK_INT`](http://developer.android.com/reference/android/os/Build.VERSION.html#SDK_INT) は名前通りプリミティブな `public static final int` なので疑いようがなかった。


# AndroidじゃなくてJavaの仕様

でよくよく調べると、 `java.util.Formatter` のドキュメントに [Number Localization Algorithm](http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#l10n algorithm) なんてものが書かれていて、

> 
1. Each digit character *d* in the string is replaced by a locale-specific digit computed relative to the current locale's zero digit *z*; that is *d* - `'0'` + *z*.

要するに数字は [`DecimalFormatSymbols#getZeroDigit()`](http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormatSymbols.html#getZeroDigit()) を基準に計算した文字に置き換えられますと。アラビア語(ar)だったら `٠` で、 ビルマ語(my)だったら `၀` ですよと。ああ `၀` ってゼロだったのか……

[Androidの `String.format` のドキュメント](http://developer.android.com/reference/java/lang/String.html#format(java.lang.String, java.lang.Object...))にはこう書いてあって、わずかな優しさが垣間見えます。

> If you're formatting a string other than for human consumption, you should use the `format(Locale, String, Object...)` overload and supply `Locale.US`. See "[Be wary of the default locale](http://developer.android.com/reference/java/util/Locale.html#default_locale)".

デフォルトのロケールは人間以外が扱うのには使うべきじゃないぞと。

> The default locale is not appropriate for machine-readable output. The best choice there is usually **`Locale.US`** – this locale is guaranteed to be available on all devices, and the fact that it has no surprising special cases and is frequently used (especially for computer-computer communication) means that it tends to be the most efficient choice too.

すべてのデバイスに存在が保証されていて、驚くような特殊な仕様もなくて、コンピュータ間のやりとりに特によく使われる、つまり一番効率的な選択であるところの、 `String.format(Locale.US, "%d", 0)` を使えよ、って話ですね。

# いろんなロケールで数字を出してみた

![device-2015-07-31-115124.png](https://qiita-image-store.s3.amazonaws.com/0/12052/ae2a66ac-8a6e-508e-ea0b-715b6c59c590.png)

**見てるだけでなぜか辛い気持ちになれる！不思議！**ちなみに [Android のバージョンごとに標準で存在するロケールが違います](http://developer.android.com/reference/java/util/Locale.html#locale_data)。

手元で見てみたい方はこちら:
[![Try it on your device via DeployGate](https://dply.me/eccqpi/button/large)](https://dply.me/eccqpi#install)

ソースコードはこちら:
https://github.com/tnj/localized-digits

# まとめ

 * Java の `String.format` で ASCII な数字を出したかったら `String.format(Locale.US, "%d", ...)` を使うこと。
 * こいつは `sprintf(3)` の単なる代替じゃなかった

## おまけ

というかこれを読めないのは俺だけじゃなくて君も `Integer.parseInt()` できないでしょ。と思ったんですが、

```java
Integer.parseInt("၁၂၃၄၅၆၇၈၉၀"); // => 1234567890
Integer.parseInt("༡༢༣༤༥༦༧༨༩༠"); // => 1234567890
```

できるのかよ！

## おまけ2: Japanese OK?

```java
Integer.parseInt("１２３４５６７８９０"); // => 1234567890
Integer.parseInt("①②③④⑤⑥⑦⑧⑨⓪"); // => java.lang.NumberFormatException: Invalid int: "①②③④⑤⑥⑦⑧⑨⓪"
Integer.parseInt("一二三四五六七八九〇"); // => java.lang.NumberFormatException: Invalid int: "一二三四五六七八九〇"
```

全角数字は見た目も同じということもあって読めるようですが(適当なことをいう)、丸数字や漢数字は読めないようです。まあ「十二億三千四百五十六万七千八百九十」とか「壱拾弐億参千四百五拾六万七千八百九拾」とか渡されても困るもんね……

ざっと見たところ `Character.digit` で一文字ずつ見て正の値が返ってくる ( `Character.getType` で `DECIMAL_DIGIT_NUMBER` が返ってくる ) 文字を数字と見なしているようなので、 Unicode でどう定義されているのか次第って感じですね。ローマ数字(Ⅶとか)もだめなので、 *数字だけど文化の中で何らかの数値を表すために使われていないものはdigitではない* って感じなんだと思う。たぶん。深追いはしない。今回formatの挙動が主題でparseはおまけなんだからね！