金額を表示する際、日本円だと整数表示が基本ですが、ドルだと小数点以下2桁分まで表示したりします。なんとなく小数が出てくると精度的に不安になってくるので、どれくらいまでなら正確に表示できるか試してみました。
number で頑張りたい背景
某コンビニで小数点以下の金額も表示するというニュースを最近耳にしました。日本円で表示するのは珍しいなと思いつつ、そういえば私が見てきたシステムも金額が入る DB 列は小数点以下2桁まで入るなというのを思い出しました(例)。
バックエンド側は Java であれば BigDecimal, PHP であれば BC Math とかがあるので大丈夫ですが、フロントエンド側で JavaScript を使って金額表示するのは不安で仕方がありません。現状 JavaScript は BigInt がありますが、 BigDecimal は存在しないためです。また、金額表示で使える Intl.NumberFormat
は number か BigInt しか受け付けてくれない状態です。ライブラリを導入すればよいかもしれませんが、 JavaScript 標準でどこまでできるか試したくなりました。
表示がおかしくなる境界値
調べてみたところ、 70368744177664.01
が限界のようです。

今回調べたのはあくまで表示の限界です。計算だと桁数はあまり関係なく 0.1 + 0.2
の結果でも誤差が発生します。
また、小数点以下は2桁までしか見ていません。
調べ方は文字列で小数を組み立てて、 parseFloat してから toFixed(2) で戻したら一致するかを調べました。ざっくりと上記数字を探し当てて、それ未満の数値はサンプリングで調べました。なぜここで誤差が出るのか、理論を組み立てられていないので正確さは保証できません・・・。
符号違い(マイナス)も同じような結果になりました。JavaScript の number は符号専用のビットが存在するので、符号によって結果は変わらないと思っています。
以下ブラウザで動くサンプルコードです。
(function () {
const from = 70368744100000n
const to = 70368744200000n
for (let i = from; i <= to; i += 1n) {
for (let j = 0; j < 100; j++) {
const strNum = i + '.' + (j < 10 ? '0' : '') + j
const floatNum = parseFloat(strNum)
const result = floatNum.toFixed(2)
if (strNum !== result) {
// 一致しない場合
console.error(strNum + ' : ' + floatNum.toFixed(2))
throw Error()
}
}
if ((i % 10000n) === 0n) {
console.log(i)
}
}
})()
結構重たいので途中から node.js に切り替えて並列処理を書きました。
まとめ
decimal(15, 2) までなら JavaScript でも表示はできそうです。兆を超える数字はなかなか扱わないので十分だと思います。