JavaScript の number はどのくらいまで正確に表示できるか

金額を表示する際、日本円だと整数表示が基本ですが、ドルだと小数点以下2桁分まで表示したりします。なんとなく小数が出てくると精度的に不安になってくるので、どれくらいまでなら正確に表示できるか試してみました。

number で頑張りたい背景

某コンビニで小数点以下の金額も表示するというニュースを最近耳にしました。日本円で表示するのは珍しいなと思いつつ、そういえば私が見てきたシステムも金額が入る DB 列は小数点以下2桁まで入るなというのを思い出しました()。

バックエンド側は Java であれば BigDecimal, PHP であれば BC Math とかがあるので大丈夫ですが、フロントエンド側で JavaScript を使って金額表示するのは不安で仕方がありません。現状 JavaScript は BigInt がありますが、 BigDecimal は存在しないためです。また、金額表示で使える Intl.NumberFormat は number か BigInt しか受け付けてくれない状態です。ライブラリを導入すればよいかもしれませんが、 JavaScript 標準でどこまでできるか試したくなりました。

表示がおかしくなる境界値

調べてみたところ、 70368744177664.01 が限界のようです。

70368744177664.01 の結果が 70368744177664.02 になる

今回調べたのはあくまで表示の限界です。計算だと桁数はあまり関係なく 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 でも表示はできそうです。兆を超える数字はなかなか扱わないので十分だと思います。

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください