金額や割合を小数第2位に丸めたとき、(1.005).toFixed(2) が "1.00" になって、1.01 を期待していた人は多いと思います。この記事では、toFixed・Math.round・Intl.NumberFormat の3つで、同じ数を丸めたときの結果を Node.js で実測しました。どれが正しいという話ではなく、3つの結果が一致しない数があるので、使い分けの目印を書きます。
確かめた環境
- Node.js v24.14.1(2026-10-22)
- 下の出力は、すべて手元でそのまま実行した結果です
- ブラウザ(Chrome・Safari)では実行していません。特に
Intl.NumberFormatの結果は、環境によって違う可能性があります
Q1. (1.005).toFixed(2) はなぜ "1.00" なのか
console.log((1.005).toFixed(2)); // 1.00
console.log(Math.round(1.005 * 100) / 100); // 1
console.log(1.005 * 100); // 100.49999999999999
console.log(new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2, maximumFractionDigits: 2,
}).format(1.005)); // 1.01
1.005 は、コンピューターの中では 1.005 ちょうどではありません。桁を増やして表示すると分かります。
console.log((1.005).toFixed(20)); // 1.00499999999999989342
実際の値は 1.00499999999999989… で、1.005 よりほんの少し小さい数です。だから toFixed(2) は切り下げて 1.00 になります。Math.round(1.005 * 100) も、掛け算の結果が 100.49999999999999 になるので 100 に丸まります。
一方で Intl.NumberFormat は 1.01 を返しました。この3つは、同じ 1.005 を渡しても答えが2通りに分かれます。
Q2. .5 のとき、toFixed と Math.round は一致するか
正の数の .5 では、今回の範囲では一致しました。
console.log([0.5, 1.5, 2.5, 3.5].map(n => n.toFixed(0))); // [ '1', '2', '3', '4' ]
console.log([0.5, 1.5, 2.5, 3.5].map(Math.round)); // [ 1, 2, 3, 4 ]
偶数に寄せる丸め(2.5 → 2)ではなく、.5 は大きい方へ切り上がっていました。
Q3. マイナスの .5 では結果が分かれる
console.log([-0.5, -1.5, -2.5].map(Math.round)); // [ -0, -1, -2 ]
console.log((-2.5).toFixed(0), (-1.5).toFixed(0)); // -3 -2
Math.round(-2.5) は -2、(-2.5).toFixed(0) は -3 でした。Math.round は .5 をプラス方向(数直線の右)へ、toFixed は、ちょうど半分のとき0から遠い方へ丸めていました。Intl.NumberFormat の -2.5(小数なし)は -3、0.5 は 1 でした。
返金やマイナスの差額を丸めるコードでは、Math.round と toFixed を入れ替えると結果が変わります。
また、Math.round(-0.5) の結果は -0 です。console.log では 0 と表示されるので気づきにくいですが、Object.is(Math.round(-0.4), -0) は true でした。
Q4. toFixed の戻り値は文字列
console.log(typeof (1.5).toFixed(1)); // string
console.log((1.5).toFixed(1) + 1); // 1.51
足し算に使うと、数ではなく文字がつながって "1.51" になります(小数の足し算そのものがずれる話は、MENTAの別記事で書きました)。計算に戻すときは Number(...) で数に直します。
Q5. 小数第1位や第2位でも、見た目と結果がずれる
console.log((8.345).toFixed(2)); // 8.35
console.log((1.45).toFixed(1)); // 1.4
console.log((10.235).toFixed(2)); // 10.23
console.log((1.45).toFixed(20)); // 1.44999999999999995559
8.345 は 8.35 に上がり、1.45 と 10.235 は下がりました。どれも「5で切り上げ」ではなく、コンピューターが持っている実際の値がちょうどより上か下かで決まっています。toFixed(20) で見ると、8.345 は 8.34500000000000063949(上)、10.235 は 10.23499999999999943157(下)、1.45 は 1.44999999999999995559(下)でした。
Q6. 大きすぎる数は指数表記になる
console.log((1e21).toFixed(2)); // 1e+21
1e21 以上の数では、toFixed は小数点の表示にならず、指数表記の文字列を返します。
使い分けの目印
実測した範囲で、次のように考えると迷いにくいです。
- 画面に見せるだけ →
Intl.NumberFormat(桁区切りもできる。ただし環境差は未確認) - 「1.005 → 1.01」を期待する金額計算 → 一般的には、小数の
Numberではなく整数(円・銭の単位)で持つ方法があります。この記事では実測していません。銭未満の端数をどう丸めるかは別に決める必要があります - 一時しのぎ →
Number((x + Number.EPSILON).toFixed(2))は、1.005では1.01になりました。ただし2.675では2.67のままでした(toFixedだけでも2.67)。どの数でも通る方法ではありません - マイナスが入るなら、
Math.roundとtoFixedを混ぜない
AIにコードを書かせるときの確認
AIが書いた金額計算に toFixed や Math.round が入っていたら、1.005・2.5・-2.5 の3つを入力して、期待した結果になるか確かめてください。テストが「100円 → 110円」のようなきれいな数だけだと、丸めのずれは見つかりません。
未経験者向けの講座を運営しています
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。
- ▶ ライブデモ(インストール不要・無料で今すぐ見られます)→ https://trial-web-ayies128s-projects.vercel.app/?ref=qiita
- 教材完全版+月5,500円のメンタリング(全24セッション+チャット質問し放題)→ https://menta.work/plan/20251?ref=qiita
- 無料体験版(git clone して自分の手元で動かす・最初の数セッション分・⭐ Star もよろしくお願いします)→ https://github.com/ayies128/next-ai-camp-trial
- YouTube『AIエンジニア情報局』(AI×開発ニュースを1本5分でキャッチアップできる別運営チャンネル・無料)→ https://www.youtube.com/channel/UC1rXVD9WYsQPQEWZyd-A1KA/?ref=qiita
※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。