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通信では、数値フィールドを文字列として扱い、
DecimalやBigIntへ明示的に変換 - 表示はIntl APIで統一:ロケール・通貨・桁区切りを分離し、UI層で一貫して適用