Intl.NumberFormat API

JavaScriptの組み込み国際化APIで、どのロケールにも対応した数値フォーマットを実現

はじめに

アプリケーションで数値 1234567.89 を表示するとします。toString() を使うと、"1234567.89" のようなフォーマットになりますが、これは読みづらく、すべての人が小数点にピリオドを使い、数字を左から右へ読むことを前提としています。たとえば、アメリカのユーザーは "1,234,567.89"、ドイツのユーザーは "1.234.567,89"、インドのユーザーはグループ分けルールが異なるため "12,34,567.89" を期待します。

Intl.NumberFormat API を使えば、ロケールごとのルールに従って数値をフォーマットできます。千の桁区切り、小数点、桁グループ、通貨記号、パーセント記号、計量単位、数字体系などにすべて対応し、手作業での文字列操作やサードパーティ製ライブラリの利用も不要になります。

このガイドでは、世界中のユーザーに正しく数値を表示するために Intl.NumberFormat の基本的な使い方から、通貨フォーマット、省略表記、カスタム丸めなどの高度なオプションまで、段階的に説明します。

デフォルト設定で数値をフォーマットする

ロケール文字列を指定して new Intl.NumberFormat() を呼び出すとフォーマッターが生成されます。その後、作成したフォーマッターの format() メソッドに数値を渡します。

const formatter = new Intl.NumberFormat('en-US');
formatter.format(1234567.89);
// "1,234,567.89"

フォーマッターは千の位区切りや小数点などをロケールに応じて自動でフォーマットします。ロケールを指定しなければ、基本的にユーザーのシステム設定に基づく実行環境のデフォルトロケールが使用されます。

const formatter = new Intl.NumberFormat('de-DE');
formatter.format(1234567.89);
// "1.234.567,89"

ドイツでは千の位区切りにピリオド、小数点にカンマを使い、アメリカとは逆です。フォーマッターはロケールごとの違いを自動で処理してくれます。

ロケールコードの理解

ロケールコードは、言語とオプションで地域を識別し、language-REGION という形で記述します。言語には enes のような2文字のISO 639-1コード、地域には USMX のような2文字のISO 3166-1コードを使います。

new Intl.NumberFormat('en-US').format(1234.56);
// "1,234.56" (American English)

new Intl.NumberFormat('en-GB').format(1234.56);
// "1,234.56" (British English)

new Intl.NumberFormat('es-ES').format(1234.56);
// "1234,56" (European Spanish)

new Intl.NumberFormat('es-MX').format(1234.56);
// "1,234.56" (Mexican Spanish)

英語のバリアントは同じ記法を使いますが、スペイン語のバリアントでは異なります。ヨーロッパのスペイン語では4桁の数字に桁区切りを使わず、小数点にはカンマを使います。一方、メキシコのスペイン語はアメリカのルールに従います。

ユーザーの居住地域や言語の好みに応じてロケールを選択してください。通常、アプリケーションはユーザー設定やブラウザの言語設定、IPジオロケーションからロケールを判断します。

書式スタイルを選択する

style オプションで書式カテゴリを指定できます。コンストラクターの第2引数として options オブジェクトを渡してください。

new Intl.NumberFormat('en-US', {
  style: 'decimal'
}).format(1234.56);
// "1,234.56"

decimal スタイルがデフォルトです。他には currencypercentunit の各スタイルがあります。

通貨を記号やコードで書式化する

currency スタイルには、ISO 4217 通貨コードを含む currency オプションが必要です。

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).format(1234.56);
// "$1,234.56"

フォーマッターはデフォルトでドル記号を付け、小数点以下2桁に整形します。これは多くの通貨の標準です。ロケールによって記号の位置が異なる場合があります。

new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: 'EUR'
}).format(1234.56);
// "1.234,56 €"

ドイツの書式では、ユーロ記号は金額の後ろにスペースを挟んで配置します。どの通貨を表示するかは currency オプションで決まり、どのロケールの書式を使うかとは別です。ロケールが記法(フォーマット)を、通貨が記号を決定します。

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'EUR'
}).format(1234.56);
// "€1,234.56"

アメリカ方式でユーロ記号を使うと、金額の前にユーロ記号が付き、ヨーロッパ式ではなくアメリカ式の配置になります。

通貨表示フォーマットを制御する

currencyDisplay オプションで、書式化した文字列内での通貨の表示方法を変えられます。

const amount = 1234.56;

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  currencyDisplay: 'symbol'
}).format(amount);
// "$1,234.56"

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  currencyDisplay: 'code'
}).format(amount);
// "USD 1,234.56"

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  currencyDisplay: 'name'
}).format(amount);
// "1,234.56 US dollars"

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  currencyDisplay: 'narrowSymbol'
}).format(amount);
// "$1,234.56"

symbol オプションがデフォルトで、$ など通貨記号を表示します。code オプションは3文字の通貨コードを表示します。name オプションは通貨名を文字で表記します。narrowSymbol オプションはロケールごとの短い通貨記号を使いますが、省スペースな反面、曖昧なことがあります。

symbolnarrowSymbol の違いは、同じ記号を持つ通貨で特にはっきりとわかります。

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'CAD',
  currencyDisplay: 'symbol'
}).format(100);
// "CA$100.00"

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'CAD',
  currencyDisplay: 'narrowSymbol'
}).format(100);
// "$100.00"

カナダドルの場合、米ドルと区別するために symbol オプションを使うと CA$ のように表示されますが、narrowSymbol の場合は単に $ となります。

マイナスの通貨額を会計表記で表示する

currencySign オプションで、マイナス額の表示方法を制御できます。

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  currencySign: 'standard'
}).format(-1234.56);
// "-$1,234.56"

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  currencySign: 'accounting'
}).format(-1234.56);
// "($1,234.56)"

standard オプションがデフォルトで、マイナス記号を使用します。accounting オプションでは、会計の慣例に従ってマイナス額を括弧で囲みます。これにより、財務レポート内でマイナスの数字がより識別しやすくなります。

パーセンテージの書式設定

percent スタイルは数値を100倍してパーセント記号を付けます。

new Intl.NumberFormat('en-US', {
  style: 'percent'
}).format(0.1234);
// "12%"

new Intl.NumberFormat('en-US', {
  style: 'percent'
}).format(0.1256);
// "13%"

フォーマッタはデフォルトで最も近い整数に四捨五入します。小数点以下の桁数は digit オプションで制御できます。

new Intl.NumberFormat('en-US', {
  style: 'percent',
  minimumFractionDigits: 2
}).format(0.1234);
// "12.34%"

かけ算後の値ではなく、小数のパーセンテージを渡してください。フォーマッタ側で自動的に100倍されます。

単位付きの値を表示する

unit スタイルでは、単位識別子を持つ unit オプションが必要です。

new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer-per-hour'
}).format(100);
// "100 km/h"

new Intl.NumberFormat('en-GB', {
  style: 'unit',
  unit: 'mile-per-hour'
}).format(100);
// "100 mph"

利用可能な単位には長さ(meterkilometermile)、時間(secondminutehour)、デジタルストレージ(bytekilobytemegabyte)、温度(celsiusfahrenheit)など多くが含まれます。kilometer-per-hour のような複合単位は、単位同士をハイフンで組み合わせて表します。

unitDisplay オプションで単位の表記形式を選べます。

const distance = 1234.5;

new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'long'
}).format(distance);
// "1,234.5 kilometers"

new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'short'
}).format(distance);
// "1,234.5 km"

new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'narrow'
}).format(distance);
// "1,234.5km"

long オプションは単位名をすべて書き出します。short オプションは省略形を使います。narrow オプションは最もコンパクトな形になり、意味が曖昧になる場合もあります。

大きな数値をコンパクト表記で表示する

notation オプションで数値の表示形式を変更できます。compact を指定すると、ロケール固有の省略形で大きな数値が表現されます。

new Intl.NumberFormat('en-US', {
  notation: 'compact'
}).format(1234567);
// "1.2M"

new Intl.NumberFormat('en-US', {
  notation: 'compact'
}).format(987654321);
// "988M"

コンパクト表記はデフォルトで1桁小数に丸め、K(千)、M(百万)、B(十億)のような接尾辞が付きます。この形式はSNSのフォロワー数や動画の再生回数、分析ダッシュボードなどによく使われます。

compactDisplay オプションは接尾辞の長さを制御します。

new Intl.NumberFormat('en-US', {
  notation: 'compact',
  compactDisplay: 'short'
}).format(1234567);
// "1.2M"

new Intl.NumberFormat('en-US', {
  notation: 'compact',
  compactDisplay: 'long'
}).format(1234567);
// "1.2 million"

short オプションはデフォルトで記号を使用します。long オプションは大きさの単語をそのまま表記します。ロケールごとに異なる接尾辞が使われます。

new Intl.NumberFormat('zh-CN', {
  notation: 'compact'
}).format(123456789);
// "1.2亿"

中国語では、百億を表す際に 亿 を使います。これは数の区切り方が言語ごとに異なることを反映しています。

非常に大きいまたは小さい数値を科学的記数法で表す

scientific 記法は、数値を係数と10のべき乗の積として表現します。

new Intl.NumberFormat('en-US', {
  notation: 'scientific'
}).format(123456789);
// "1.235E8"

new Intl.NumberFormat('en-US', {
  notation: 'scientific'
}).format(0.00000123);
// "1.23E-6"

この書式は非常に大きな数(天文学的距離や分子数)、非常に小さな数(粒子質量やナノスケール測定)にも適しています。指数は常に1の倍数となります。

技術用途にはエンジニアリング記数法を使う

engineering 記法は科学的記数法に似ていますが、指数を3の倍数に限定します。

new Intl.NumberFormat('en-US', {
  notation: 'engineering'
}).format(123456789);
// "123.457E6"

new Intl.NumberFormat('en-US', {
  notation: 'engineering'
}).format(1234);
// "1.234E3"

エンジニアリング記数法はSI単位接頭辞(kilo、mega、gigaなど)と合致し、工学や物理で標準的に使われています。係数は1から999の範囲になります。

小数点以下の桁数は分数桁で制御

minimumFractionDigits および maximumFractionDigits オプションで小数点以下の表示桁数を指定できます。

new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
}).format(1234.5);
// "1,234.50"

new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
}).format(1234.567);
// "1,234.57"

最小値を設定すると必要に応じて末尾にゼロが表示されます。最大値を設定すると長い小数は四捨五入されます。通貨のフォーマットは通常小数点以下2桁、通常の小数フォーマットは最小0桁・最大3桁がデフォルトです。

new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 0,
  maximumFractionDigits: 0
}).format(1234.567);
// "1,235"

両方を0に設定すると最も近い整数に四捨五入されます。

有効数字で全体の精度をコントロール

minimumSignificantDigits および maximumSignificantDigits オプションで小数点の位置に関わらず合計の桁数を制御できます。

new Intl.NumberFormat('en-US', {
  minimumSignificantDigits: 3,
  maximumSignificantDigits: 3
}).format(1234.567);
// "1,230"

new Intl.NumberFormat('en-US', {
  minimumSignificantDigits: 3,
  maximumSignificantDigits: 3
}).format(0.001234);
// "0.00123"

有効数字は先頭のゼロ以外のすべての桁をカウントします。最初の例では3桁に四捨五入し、結果は 1230 です。2つ目の例では先頭のゼロ以降で3桁を残し、結果は 0.00123 となります。

有効桁数オプションは、小数点以下の桁数オプションを同時に指定した場合に優先されます。

丸め処理と丸めモードの選択

roundingMode オプションは、省略が必要な場合に数値がどのように丸められるかを決めます。

const value = 1.5;

new Intl.NumberFormat('en-US', {
  maximumFractionDigits: 0,
  roundingMode: 'halfExpand'
}).format(value);
// "2"

new Intl.NumberFormat('en-US', {
  maximumFractionDigits: 0,
  roundingMode: 'halfTrunc'
}).format(value);
// "1"

halfExpand モードはデフォルトで、0.5 をゼロから離れる方向に丸めます。これは学校でもよく教えられる一般的な丸め方法です。halfTrunc モードは、0.5 をゼロに近づく方向に丸めます。

他のモードには、以下があります:

  • ceil:常に正の無限大方向へ丸める
  • floor:常に負の無限大方向へ丸める
  • expand:常にゼロから離れる方向へ丸める
  • trunc:常にゼロに近づく方向へ丸める
  • halfCeil:0.5 を正の無限大方向へ丸める
  • halfFloor:0.5 を負の無限大方向へ丸める
  • halfEven:0.5 を一番近い偶数に丸める
const prices = [1.5, 2.5, 3.5];

prices.map(price =>
  new Intl.NumberFormat('en-US', {
    maximumFractionDigits: 0,
    roundingMode: 'halfEven'
  }).format(price)
);
// ["2", "2", "4"]

halfEven モードはバンカーズ・ラウンディングとも呼ばれ、繰り返し計算時の丸め誤差の偏りを減らします。0.5の場合、最も近い偶数に丸められます。その結果、1.52.5 の場合はどちらも 2 となり、3.5 の場合は 4 になります。

金融アプリケーションでは、料金を切り上げるために ceil、返金額を切り下げるために floor が使われます。統計用途では、丸め誤差の蓄積を最小限にするために halfEven が利用されます。

グルーピングオプションで千の位区切りを制御

useGrouping オプションで千の位区切り記号の表示有無を設定できます。

new Intl.NumberFormat('en-US', {
  useGrouping: true
}).format(123456);
// "123,456"

new Intl.NumberFormat('en-US', {
  useGrouping: false
}).format(123456);
// "123456"

true の値がデフォルトです。false の値は、すべての区切り記号を削除します。文字列の値を使えばより細かな制御も可能です。

new Intl.NumberFormat('en-US', {
  useGrouping: 'always'
}).format(1234);
// "1,234"

new Intl.NumberFormat('en-US', {
  useGrouping: 'min2'
}).format(1234);
// "1234"

always は、どの場合でも区切り記号を使います。min2 は4桁の数字に区切り記号を付けません。auto はロケールの設定に従い、多くの場合、min2 の動作と同じになります。

コンパクト表記では、数値の中に区切り記号が必要になるケースが稀なため、デフォルトで min2 になります。

sign display オプションで符号の表示を制御

signDisplay オプションで、プラス・マイナス記号の表示タイミングを指定できます。

new Intl.NumberFormat('en-US', {
  signDisplay: 'auto'
}).format(100);
// "100"

new Intl.NumberFormat('en-US', {
  signDisplay: 'always'
}).format(100);
// "+100"

auto の値がデフォルトで、負の数にはマイナス記号を表示し、正の数にはプラス記号は表示されません。always の値では、両方の記号が表示されます。

new Intl.NumberFormat('en-US', {
  signDisplay: 'exceptZero'
}).format(0);
// "0"

new Intl.NumberFormat('en-US', {
  signDisplay: 'always'
}).format(0);
// "+0"

exceptZero の値は、always の動作であっても、ゼロ値には符号を省略します。これにより +0-0 の表示が混乱しないように防ぎます。

new Intl.NumberFormat('en-US', {
  signDisplay: 'never'
}).format(-100);
// "100"

never の値は符号をすべてなくし、絶対値のみを表示します。

金融アプリケーションは利益をプラス記号で強調表示するために always を使います。温度表示では、+0°-0° を避けるために exceptZero が使われます。

書式付き出力をパーツに分割する

formatToParts() メソッドは、書式化された文字列の各構成要素を表すオブジェクトの配列を返します。

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).formatToParts(1234.56);

このメソッドの返却値:

[
  { type: 'currency', value: '$' },
  { type: 'integer', value: '1' },
  { type: 'group', value: ',' },
  { type: 'integer', value: '234' },
  { type: 'decimal', value: '.' },
  { type: 'fraction', value: '56' }
]

各オブジェクトには、構成要素を識別する type と、文字列が入った value があります。type には、currencyintegergroupdecimalfractionliteralminusSignplusSignpercentSign などが入ります。

これにより、各構成要素ごとにカスタムスタイルを適用できます。

const parts = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).formatToParts(1234.56);

const formatted = parts.map(part => {
  if (part.type === 'currency') {
    return `<span class="currency">${part.value}</span>`;
  }
  if (part.type === 'integer') {
    return `<span class="integer">${part.value}</span>`;
  }
  return part.value;
}).join('');

// <span class="currency">$</span><span class="integer">1</span>,<span class="integer">234</span>.56

これによってスタイル付きの構成要素を持つHTMLが生成されます。同じ手法で、負の金額だけ異なる色をつけたり、通貨記号を大きくしたり、各桁ごとにアニメーションをつけることも可能です。

数値の範囲をフォーマットする

formatRange() メソッドは、2つの数値を範囲としてフォーマットします。

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).formatRange(100, 200);
// "$100.00 – $200.00"

フォーマッターはロケールに特有の範囲区切り記号(英語の場合はエヌダッシュ)を使用し、両方の通貨記号を含みます。2つの値が同じ書式の場合、チルダ付きの単一値を返します。

new Intl.NumberFormat('en-US', {
  notation: 'compact'
}).formatRange(1200, 1800);
// "~1K"

12001800 の両方が、短縮表記の 1K となるため、フォーマッターは「約 1K」と表示します。

formatRangeToParts() メソッドは、範囲の各パーツを返します。

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).formatRangeToParts(100, 200);

このメソッドの返却値:

[
  { type: 'currency', value: '$', source: 'startRange' },
  { type: 'integer', value: '100', source: 'startRange' },
  { type: 'decimal', value: '.', source: 'startRange' },
  { type: 'fraction', value: '00', source: 'startRange' },
  { type: 'literal', value: ' – ', source: 'shared' },
  { type: 'currency', value: '$', source: 'endRange' },
  { type: 'integer', value: '200', source: 'endRange' },
  { type: 'decimal', value: '.', source: 'endRange' },
  { type: 'fraction', value: '00', source: 'endRange' }
]

source プロパティは、そのパートが開始値、終了値、または範囲セパレーターのいずれに由来するかを識別します。

価格、日付、測定値などの範囲は、ロケールごとに適切な書式で表示するためにこのメソッドを利用します。

解決済みオプションの確認

resolvedOptions() メソッドは、フォーマッターが実際に使用したオプションを表すオブジェクトを返します。

const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

formatter.resolvedOptions();

このメソッドの戻り値:

{
  locale: 'en-US',
  numberingSystem: 'latn',
  style: 'currency',
  currency: 'USD',
  currencyDisplay: 'symbol',
  currencySign: 'standard',
  minimumIntegerDigits: 1,
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
  useGrouping: 'auto',
  notation: 'standard',
  signDisplay: 'auto',
  roundingMode: 'halfExpand'
}

オブジェクトには明示的に設定されたオプションとデフォルト値が含まれます。locale は、リクエストしたロケールと異なるものの互換性のあるロケールに解決された場合、異なる値になることがあります。numberingSystem では、フォーマッターがどの数字文字を使用しているかを示します。

このメソッドを使うことで、すべての有効な設定を確認でき、想定外の書式となる場合のデバッグに役立ちます。

フォーマッターを再利用してパフォーマンス向上

NumberFormat インスタンスの作成にはロケールデータの読み込みやオプションの処理が含まれます。同じ設定で複数の値をフォーマットする場合はインスタンスを再利用してください。

// Inefficient: creates new formatter for each value
prices.map(price =>
  new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  }).format(price)
);

// Efficient: creates formatter once
const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

prices.map(price => formatter.format(price));

多くの値をフォーマットする際には、2つ目の方法の方が圧倒的に高速です。フォーマッターはループ内やコンポーネントの描画関数外で作成しましょう。

最近のJavaScriptエンジンは内部で NumberFormat インスタンスをキャッシュしますが、明示的な再利用を行うことでパフォーマンスやコードの可読性がより優れます。

ブラウザー対応状況の確認

Intl.NumberFormat APIはすべての最新ブラウザーでサポートされています。Chrome、Firefox、Safari、Edgeでは2016年から基本APIが使えます。formatRange()formatRangeToParts()、さらに拡張された roundingMode オプションのような高度な機能も、最近は現在のブラウザーで利用可能になっています。

サポート状況の確認方法:

if (typeof Intl !== 'undefined' && Intl.NumberFormat) {
  // NumberFormat is supported
  const formatter = new Intl.NumberFormat('en-US');
}

特定機能のサポートを調べるには:

const formatter = new Intl.NumberFormat('en-US');

if (typeof formatter.formatRange === 'function') {
  // formatRange is supported
}

古いブラウザーへの対応が必要な場合は、@formatjs/intl-numberformat のようなポリフィルを使うこともできますが、ほとんどの最新アプリケーションではネイティブAPIをそのまま利用できます。

NumberFormat を使うタイミング

Intl.NumberFormat を活用するシーン:

  • あらゆるUIコンテキストでユーザーに数値を表示する場合
  • ECサイトにおける金額表示
  • 分析ダッシュボードでパーセントを見せる場合
  • フォロワー数・閲覧数・その他のソーシャル系指標の表示
  • 測定値や科学計算値のフォーマット
  • 複数ロケール対応のグローバル対応アプリを構築する場合

次の場合にはIntl.NumberFormatを使用しないでください:

  • 内部計算やデータ処理
  • データベースへの値の保存
  • API用のデータシリアライズ
  • ユーザー入力を数値に戻して解析する場合

NumberFormatは表示用のツールです。計算や保存の際には生の数値を使用し、ユーザーに表示する際のみフォーマットを適用してください。