数値をそのまま画面に出すと 1234567 のように読みにくくなります。3桁ごとにカンマを入れる処理を正規表現で書いた経験がある方も多いと思いますが、通貨記号の位置、小数点の記号、桁区切りの文字は国によって違うため、自前で書くと際限なく分岐が増えていきます。
これを標準機能で解決するのが Intl.NumberFormat です。桁区切り、通貨、パーセント、単位付きの表記、「1.2万」のような省略表記まで、オプションを指定するだけで正しい形式に整えてくれます。この記事では基本の使い方から主要なオプション、実務でつまずきやすい点までを解説します。
目次
基本の使い方
new Intl.NumberFormat() でフォーマッターを作り、format() に数値を渡します。第1引数はロケール(言語と地域)、第2引数はオプションのオブジェクトです。
const formatter = new Intl.NumberFormat('ja-JP');
console.log(formatter.format(1234567)); // "1,234,567"
console.log(formatter.format(1234.5678)); // "1,234.568"(小数は既定で3桁まで)
// ロケールを変えると区切り文字も変わる
console.log(new Intl.NumberFormat('de-DE').format(1234567.89)); // "1.234.567,89"
console.log(new Intl.NumberFormat('en-IN').format(1234567)); // "12,34,567"
ドイツ語圏では桁区切りがピリオド、小数点がカンマになります。インドの表記では3桁ずつではなく、下3桁のあとは2桁ずつで区切られます。こうした違いを自分で実装するのは現実的ではありません。
ロケールを省略して new Intl.NumberFormat() と書くと、実行環境の設定が使われます。サイトの表示言語が決まっているなら、'ja-JP' のように明示したほうが結果が安定します。
toLocaleString とどちらを使うか
同じことは Number.prototype.toLocaleString() でもできます。引数の形式もまったく同じです。
// 1回だけ整形するならこちらが手軽
console.log((1234567).toLocaleString('ja-JP')); // "1,234,567"
使い分けの基準は回数です。フォーマッターの生成にはロケールデータの読み込みなどのコストがかかるので、一覧の各行を整形するようなループでは、Intl.NumberFormat のインスタンスをループの外で1つ作って使い回すほうが明らかに速くなります。単発の表示なら toLocaleString() で十分です。
通貨として表示する
style: 'currency' と currency に通貨コードを指定します。currency は必須で、書き忘れると TypeError になります。
const yen = new Intl.NumberFormat('ja-JP', {
style: 'currency',
currency: 'JPY',
});
console.log(yen.format(1234567)); // "¥1,234,567"
const usd = new Intl.NumberFormat('ja-JP', {
style: 'currency',
currency: 'USD',
});
console.log(usd.format(1234.5)); // "$1,234.50"
// 同じ USD でも、英語ロケールなら記号の出方が変わる
console.log(new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'JPY',
}).format(1234567)); // "¥1,234,567"
注目したいのは、小数点以下の桁数が通貨ごとに自動で決まることです。日本円は小数を持たないので 1,234,567、米ドルは2桁なので 1,234.50 と、指定しなくても正しい桁数になります。
通貨の見せ方は currencyDisplay で変えられます。
currencyDisplay | 出力例(ja-JP / USD / 1234.5) |
|---|---|
symbol(既定) | $1,234.50 |
narrowSymbol | $1,234.50(地域を区別する記号を省いた短い形) |
code | USD 1,234.50 |
name | 1,234.50 米ドル |
複数通貨を扱うサイトでは code や name が便利です。symbol だと、米ドル・カナダドル・豪ドルがどれも $ になり、読者が取り違える可能性があります。
パーセントで表示する
style: 'percent' を指定すると、渡した数値が100倍されてパーセント表記になります。ここは間違えやすいところで、0.25 を渡すと 25%、25 を渡すと 2,500% です。
const percent = new Intl.NumberFormat('ja-JP', {
style: 'percent',
maximumFractionDigits: 1,
});
console.log(percent.format(0.256)); // "25.6%"
console.log(percent.format(1)); // "100%"
// 達成率の計算からそのまま渡せる
const rate = 4280 / 10000;
console.log(percent.format(rate)); // "42.8%"
割り算の結果をそのまま渡せる設計になっているので、* 100 を自分で書く必要はありません。むしろ書いてしまうと100倍ずれます。
小数点以下の桁数をそろえる
桁数の指定には次の4つのオプションがあります。
| オプション | 説明 |
|---|---|
minimumIntegerDigits | 整数部の最小桁数。足りない分は先頭が 0 で埋められる |
minimumFractionDigits | 小数部の最小桁数。足りない分は末尾が 0 で埋められる |
maximumFractionDigits | 小数部の最大桁数。超えた分は四捨五入される |
maximumSignificantDigits | 有効数字の最大桁数。整数部と小数部を通して数える |
// 表で数値を縦に並べるときは、小数桁をそろえると読みやすい
const fixed = new Intl.NumberFormat('ja-JP', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
console.log(fixed.format(3)); // "3.00"
console.log(fixed.format(3.14159)); // "3.14"
// 桁区切りを消したいとき
const plain = new Intl.NumberFormat('ja-JP', { useGrouping: false });
console.log(plain.format(1234567)); // "1234567"
toFixed() でも小数桁はそろえられますが、桁区切りは入りません。Intl.NumberFormat なら両方を1つのフォーマッターで扱えます。
「1.2万」のような省略表記にする
SNS の「いいね数」のように、大きな数を短く見せたいときは notation: 'compact' を使います。日本語ロケールでは万・億の単位になります。
const compact = new Intl.NumberFormat('ja-JP', {
notation: 'compact',
maximumFractionDigits: 1,
});
console.log(compact.format(1234)); // "1234"
console.log(compact.format(12345)); // "1.2万"
console.log(compact.format(123456789)); // "1.2億"
// 英語ロケールなら K / M / B になる
console.log(new Intl.NumberFormat('en-US', {
notation: 'compact',
}).format(12345)); // "12K"
compactDisplay: 'long' を足すと、英語では 12 thousand のような長い形になります。notation にはほかに scientific(指数表記)と engineering(3の倍数の指数)も指定できます。
単位を付けて表示する
style: 'unit' と unit を組み合わせると、距離・重さ・データ量などを単位付きで表示できます。
console.log(new Intl.NumberFormat('ja-JP', {
style: 'unit',
unit: 'kilometer',
}).format(12.5)); // "12.5 km"
console.log(new Intl.NumberFormat('ja-JP', {
style: 'unit',
unit: 'megabyte',
unitDisplay: 'narrow',
}).format(256)); // "256MB"
// 「毎時」のように 2 つの単位を組み合わせる
console.log(new Intl.NumberFormat('ja-JP', {
style: 'unit',
unit: 'kilometer-per-hour',
}).format(80)); // "時速 80 km"
指定できる単位は仕様で決められた一覧(kilometer・gram・liter・byte・percent・day など)に限られていて、自由な文字列は使えません。一覧にない単位は RangeError になるので、対応表を確認してから使ってください。unitDisplay は short(既定)・narrow・long の3種類です。
部品ごとに取り出す formatToParts
「通貨記号だけ小さく表示したい」というように、整形結果の一部だけスタイルを変えたいことがあります。そのときは formatToParts() を使うと、文字列ではなく部品の配列が返ってきます。
const formatter = new Intl.NumberFormat('ja-JP', {
style: 'currency',
currency: 'JPY',
});
console.log(formatter.formatToParts(1234));
// [
// { type: 'currency', value: '¥' },
// { type: 'integer', value: '1' },
// { type: 'group', value: ',' },
// { type: 'integer', value: '234' }
// ]
// 通貨記号だけ span で囲む
function renderPrice(value) {
return formatter
.formatToParts(value)
.map((part) =>
part.type === 'currency'
? `<span class="price__symbol">${part.value}</span>`
: part.value
)
.join('');
}
通貨記号が前に来るか後ろに来るかはロケール次第なので、自分で文字列を切り貼りするより formatToParts() に任せるほうが安全です。
思った表示にならないとき
カンマの位置に半角スペースではない文字が入っている
フランス語などのロケールでは、桁区切りにノーブレークスペースという特殊な空白文字が使われます。見た目は空白ですが、' ' とは別の文字です。整形後の文字列を replace(/ /g, '') のように加工しようとすると、消えずに残って混乱します。整形結果は表示専用と割り切り、再加工が必要なら元の数値から作り直してください。
大きな数の精度が落ちる
JavaScript の数値は約 9007兆(Number.MAX_SAFE_INTEGER)を超えると正確に表せません。それより大きい値を扱うなら BigInt を使います。format() は BigInt もそのまま受け取れます。また、新しい環境では数値を表す文字列を渡すこともでき、こちらも精度を落とさずに整形されます。
const nf = new Intl.NumberFormat('ja-JP');
console.log(nf.format(123456789012345678901234567890n));
// "123,456,789,012,345,678,901,234,567,890"
環境によって表記が少し違う
Intl の出力はロケールデータに基づくため、ブラウザーやそのバージョン、OS によって細部が変わることがあります。¥(全角)と ¥(半角)の違いなどが典型です。したがって、テストで整形結果の文字列を完全一致で比較するのは避け、数値と表示を切り離して検証するのが無難です。
まとめ
Intl.NumberFormat は、数値をロケールに合わせて読みやすい文字列に整える標準機能です。style を decimal・currency・percent・unit と切り替えるだけで、桁区切り、通貨記号、パーセント、単位付き表記に対応できます。通貨の小数桁のように、こちらが知らなくても正しく処理してくれる部分が多いのが大きな利点です。
気をつけたいのは、percent が100倍する仕様であること、単位名が決められた一覧に限られること、そして整形結果の文字列を加工しないことです。一覧表のように何度も整形する場面では、フォーマッターを1つ作って使い回すとパフォーマンスも良くなります。単発なら toLocaleString() で手軽に済ませましょう。