JavaScriptにおける高精度数値処理と金額フォーマットの実践ガイド

JavaScriptで金額計算や超長整数(例:ID)を扱う際、Number型の制限による精度喪失は頻出の課題です。特に金融系アプリケーションでは、誤差が法的・信頼性上のリスクにつながるため、慎重な設計が不可欠です。

精度問題の実例

JSON経由で受信した大きな整数が、パース時点で既に破損しているケースがあります:

const json = '{"order_id": 9007199254740995}';
const data = JSON.parse(json);
console.log(data.order_id); // → 9007199254740996(意図しない丸め!)

これはNumber型の安全整数範囲(Number.MAX_SAFE_INTEGER === 9007199254740991)を超えたためです。

安全整数の検証

実行時チェックには標準APIが利用可能です:

Number.isSafeInteger(9007199254740991); // true
Number.isSafeInteger(9007199254740992); // false

大整数の処理戦略

BigInt:ネイティブな任意精度整数

ES2020以降で利用可能なBigIntは、符号付き任意長整数をサポートします:

const id = 9007199254740995n;
const nextId = id + 1n; // 正しく 9007199254740996n

// 注意:Numberとの混在演算は禁止
// 1n + 1; // TypeError!
1n + BigInt(1); // OK

ただし、BigIntは小数点を含まない整数専用であり、金利計算などには不向きです。

金融計算向け:decimal.js

小数点を含む高精度演算には、decimal.jsが推奨されます。以下は典型的な使用例です:

import { Decimal } from 'decimal.js';

// 小数計算(0.1 + 0.2 = 0.3)
const a = new Decimal('0.1');
const b = new Decimal('0.2');
console.log(a.plus(b).toString()); // "0.3"

// 通貨表示(2桁固定)
const amount = new Decimal('1234567.8');
console.log(amount.toFixed(2)); // "1234567.80"

// 大数演算(例:10億円 × 1.05%)
const principal = new Decimal('1000000000.00');
const rate = new Decimal('0.0105');
const interest = principal.times(rate);
console.log(interest.toFixed(2)); // "10500000.00"

※ 文字列で初期化することを強く推奨します(new Decimal(0.1)は浮動小数点誤差を引き込む可能性あり)。

金額のフォーマット

Intl.NumberFormat:国際化対応のベストプラクティス

通貨単位・小数桁数・ロケールを一括管理できます:

// 中国元(¥)表示
const cnFormatter = new Intl.NumberFormat('zh-CN', {
  style: 'currency',
  currency: 'CNY',
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});
console.log(cnFormatter.format(1234567.8)); // "¥1,234,567.80"

// 米ドル($)表示(USロケール)
const usFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});
console.log(usFormatter.format(1234567.8)); // "$1,234,567.80"

中文大写金額変換

中国語圏の財務文書では、数字を「壹貳参」形式で出力する必要があります。オープンソースライブラリnzhを活用しましょう:

import nzh from 'nzh';

// 人民元表記(小数点含む)
console.log(nzh.cn.toMoney('123456.78')); 
// "人民币壹拾贰万叁仟肆佰伍拾陆元柒角捌分"

設計原則のまとめ

  • 計算時は常に小数点を排除:金額は「円」ではなく「銭(1/100円)」単位の整数で保持
  • 入力・出力は文字列経由:JSONやAPI通信では、数値フィールドを文字列として扱い、DecimalBigIntへ明示的に変換
  • 表示はIntl APIで統一:ロケール・通貨・桁区切りを分離し、UI層で一貫して適用

タグ: javascript decimaljs bigint intl-numberformat nzh

6月14日 21:50 投稿