JavaScript Tips | 文字列ユーティリティ:業務用 - 通貨変換表示

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「通貨変換表示」

ここで目指すのは、単に「金額にカンマを付ける」だけではなく、
「通貨単位や通貨コードを付けて、人間が見て意味が分かる形で表示する」ユーティリティです。

たとえば、次のような表示を安定して出したいイメージです。

formatCurrencyDisplay(1234567, "JPY");   // "¥1,234,567"
formatCurrencyDisplay(1234.5, "USD");    // "$1,234.50"
formatCurrencyDisplay(99.9, "EUR");      // "€99.90"
JavaScript

さらに、為替レートを使って「円 → ドル」のような通貨変換を行い、その結果を表示用に整えるところまでを、ひとまとまりのユーティリティとして考えていきます。


「通貨変換表示」を分解して考える

通貨変換表示は、実は三つのステップに分解できます。

一つ目は、元の金額を別の通貨に変換することです。
二つ目は、変換後の数値を「金額として」フォーマットすることです。
三つ目は、通貨記号や通貨コードを付けて、最終的な文字列にすることです。

この三つを毎回バラバラに書くのではなく、「変換」と「表示」を小さな関数に分けておくと、業務コードがかなり読みやすくなります。


まずは「通貨記号付きの表示」だけを作る

最初に、為替変換は置いておいて、「ある通貨の金額をきれいに表示する」関数から作ります。

ここでは、通貨コードごとに簡単な記号マップを用意し、整数部分はカンマ区切り、小数は任意の桁数で揃える、というルールにします。

const CURRENCY_SYMBOLS = {
  JPY: "¥",
  USD: "$",
  EUR: "€",
};

function formatCurrencyDisplay(amount, currencyCode = "JPY", options = {}) {
  const { fractionDigits = null } = options;

  if (amount == null || amount === "") {
    return "";
  }

  const num = Number(amount);
  if (!Number.isFinite(num)) {
    return "";
  }

  const sign = num < 0 ? "-" : "";
  const absNum = Math.abs(num);

  let intPartStr;
  let fracPartStr = "";

  if (fractionDigits == null) {
    const [intStr, fracStr = ""] = absNum.toString().split(".");
    intPartStr = intStr;
    fracPartStr = fracStr;
  } else {
    const fixed = absNum.toFixed(fractionDigits);
    const [intStr, fracStr = ""] = fixed.split(".");
    intPartStr = intStr;
    fracPartStr = fracStr;
  }

  const intWithComma = intPartStr.replace(/\B(?=(\d{3})+(?!\d))/g, ",");

  const numberPart =
    fracPartStr && fractionDigits !== 0
      ? intWithComma + "." + fracPartStr
      : intWithComma;

  const symbol = CURRENCY_SYMBOLS[currencyCode] ?? "";
  const codeSuffix = symbol ? "" : ` ${currencyCode}`;

  return sign + symbol + numberPart + codeSuffix;
}
JavaScript

この関数のポイントを、初心者向けにかみ砕いて説明します。


通貨表示関数の重要ポイントを深掘りする

最初に、値のチェックと数値変換をしています。

if (amount == null || amount === "") {
  return "";
}

const num = Number(amount);
if (!Number.isFinite(num)) {
  return "";
}
JavaScript

ここでは「値がない」「数値としておかしい」ものは、表示しない(空文字)という方針にしています。
業務画面では、null や空文字が混ざることが多いので、早めに弾いておくと後の処理がシンプルになります。

次に、マイナスの扱いです。

const sign = num < 0 ? "-" : "";
const absNum = Math.abs(num);
JavaScript

符号と絶対値を分けておくことで、「カンマ付与や小数処理は絶対値に対してだけ行う」というシンプルな構造にできます。最後に符号をくっつけるだけなので、マイナスでもロジックが崩れません。

小数の扱いは、オプションの fractionDigits で切り替えています。

if (fractionDigits == null) {
  const [intStr, fracStr = ""] = absNum.toString().split(".");
  intPartStr = intStr;
  fracPartStr = fracStr;
} else {
  const fixed = absNum.toFixed(fractionDigits);
  const [intStr, fracStr = ""] = fixed.split(".");
  intPartStr = intStr;
  fracPartStr = fracStr;
}
JavaScript

fractionDigits が null のときは、元の数値の小数をそのまま使います。
fractionDigits に 0 以上の整数を渡したときは、toFixed で丸めて、小数桁数を揃えます。

たとえば、次のような動きになります。

formatCurrencyDisplay(1234.5, "USD");                     // "$1,234.5"
formatCurrencyDisplay(1234.5, "USD", { fractionDigits: 2 }); // "$1,234.50"
formatCurrencyDisplay(1234, "USD", { fractionDigits: 2 });   // "$1,234.00"
JavaScript

整数部分へのカンマ付与は、以前やったものと同じ正規表現です。

const intWithComma = intPartStr.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
JavaScript

これで「右から 3 桁ごとにカンマ」が入ります。

最後に、通貨記号と通貨コードを組み立てます。

const symbol = CURRENCY_SYMBOLS[currencyCode] ?? "";
const codeSuffix = symbol ? "" : ` ${currencyCode}`;

return sign + symbol + numberPart + codeSuffix;
JavaScript

ここでは、通貨コードに対応する記号がマップにあればそれを使い、なければ記号なしで " USD" のようにコードを後ろに付ける、という挙動にしています。

たとえば、次のような結果になります。

formatCurrencyDisplay(1234567, "JPY");   // "¥1,234,567"
formatCurrencyDisplay(1234.5, "USD");    // "$1,234.5"
formatCurrencyDisplay(99.9, "EUR", { fractionDigits: 2 }); // "€99.90"
formatCurrencyDisplay(500, "AUD");       // "500 AUD"(記号がないのでコードを後ろに)
JavaScript

為替レートを使った「通貨変換+表示」

次に、「円をドルに変換して、その結果を表示する」ようなユーティリティを考えます。

ここでは、為替レートは外から渡す前提にして、「変換」と「表示」を分けて実装します。

function convertCurrency(amount, rate) {
  if (amount == null || amount === "") {
    return null;
  }

  const num = Number(amount);
  if (!Number.isFinite(num)) {
    return null;
  }

  const r = Number(rate);
  if (!Number.isFinite(r)) {
    return null;
  }

  return num * r;
}
JavaScript

この関数は、「元の金額」と「レート」を掛け算するだけの、とても素朴なものです。
大事なのは、「変換の責務」と「表示の責務」を分けていることです。

これを使って、「円 → ドル」の変換表示を行う関数を書いてみます。

function convertAndFormat(amount, fromCode, toCode, rate, options = {}) {
  const converted = convertCurrency(amount, rate);
  if (converted == null) {
    return "";
  }

  return formatCurrencyDisplay(converted, toCode, options);
}
JavaScript

使い方の例は次の通りです。

const rateJpyToUsd = 0.0075; // 例: 1 JPY = 0.0075 USD とする

convertAndFormat(100000, "JPY", "USD", rateJpyToUsd, { fractionDigits: 2 });
// "$750.00"

convertAndFormat(1234567, "JPY", "USD", rateJpyToUsd, { fractionDigits: 2 });
// "$9,259.25"(例のレートの場合)
JavaScript

ここでのポイントは、「どこでレートを決めるか」「どこで表示ルールを決めるか」を分けていることです。
レートの取得や更新は別の層(API や設定)に任せて、ユーティリティは「掛け算」と「フォーマット」だけに集中させると、テストしやすくなります。


Intl.NumberFormat を使う選択肢も知っておく

ブラウザや Node.js には、Intl.NumberFormat という国際化対応のフォーマッタが用意されています。
これを使うと、通貨表示をかなり簡単に書けます。

function formatCurrencyIntl(amount, currencyCode = "JPY", locale = "ja-JP") {
  if (amount == null || amount === "") {
    return "";
  }

  const num = Number(amount);
  if (!Number.isFinite(num)) {
    return "";
  }

  return new Intl.NumberFormat(locale, {
    style: "currency",
    currency: currencyCode,
  }).format(num);
}
JavaScript

たとえば、次のように動きます。

formatCurrencyIntl(1234567, "JPY", "ja-JP"); // "¥1,234,567"
formatCurrencyIntl(1234.5, "USD", "en-US");  // "$1,234.50"
formatCurrencyIntl(99.9, "EUR", "de-DE");    // "99,90 €"
JavaScript

ただし、Intl.NumberFormat はロケールによって記号の位置や小数点の記号が変わるため、「システム全体で完全に同じ見た目にしたい」という要件が強い場合は、先ほどのように自前でルールを決めたユーティリティを使う方がコントロールしやすいです。


実務で意識してほしい設計のポイント

一つ目は、「計算用の値」と「表示用の文字列」を絶対に混ぜないことです。
通貨変換は数値で行い、画面や帳票に出す直前でフォーマット関数を通して文字列にする、という流れを徹底してください。

二つ目は、「通貨表示のルール」を一箇所に閉じ込めることです。
画面ごとにバラバラに "¥" + value.toLocaleString() のようなコードを書き始めると、必ずどこかで表記揺れが起きます。
formatCurrencyDisplayconvertAndFormat のような関数をモジュールにまとめ、「通貨を見せるときは必ずここを通す」と決めてしまうと、システム全体の見た目が一気に整います。

三つ目は、「変換」と「表示」を分けることです。
レートの取得や更新は別の層に任せ、ユーティリティは「掛け算」と「フォーマット」だけに集中させると、テストしやすく、バグの原因も追いやすくなります。


少し手を動かして感覚をつかむ

コンソールで、次のようなコードを実際に打ってみてください。

formatCurrencyDisplay(1234567, "JPY");
formatCurrencyDisplay(1234.5, "USD", { fractionDigits: 2 });
formatCurrencyDisplay(-98765.4321, "EUR", { fractionDigits: 2 });

const rateJpyToUsd = 0.0075;
convertAndFormat(100000, "JPY", "USD", rateJpyToUsd, { fractionDigits: 2 });
convertAndFormat(1234567, "JPY", "USD", rateJpyToUsd, { fractionDigits: 2 });

formatCurrencyIntl(1234567, "JPY", "ja-JP");
formatCurrencyIntl(1234.5, "USD", "en-US");
JavaScript

どのような文字列が返ってくるかを眺めながら、「変換」と「表示」が頭の中で分かれている感覚をつかんでみてください。

そのうえで、自分のプロジェクトに

export function formatCurrencyDisplay(...) { ... }
export function convertCurrency(...) { ... }
export function convertAndFormat(...) { ... }
JavaScript

のような関数を置き、「通貨を扱うときは必ずここを通す」というルールを作ってみてください。
それだけで、あなたのシステムの通貨表示は、場当たり的な文字列連結から、意図と一貫性を備えた「業務レベルの通貨変換表示ユーティリティ」に変わっていきます。

タイトルとURLをコピーしました