toFixed の「なぜ期待通りに丸まらないことがあるのか」を理解するには 丸めルール(tie-breaking) と 浮動小数点(binary floating-point)の表現誤差 の両方を知る必要があります。初心者向けに噛み砕いて、実例と回避策(実務的な手法)までまとめます。
1. 先に短く結論
toFixed(n)は「小数第 n 位に丸めて文字列で返す」メソッド。丸めルールは **「最も近い値へ丸める(round to nearest)」で、ちょうど真ん中(.5)だった場合は 偶数へ丸める(round-half-to-even、いわゆる bankers’ rounding)」です。- ただし JavaScript の数値は IEEE-754 の倍精度二進浮動小数点 なので、10進で表した「1.005」のような数が内部的に 正確には 1.004999999999…(少し小さい) などになってしまい、その結果
toFixedが期待どおりに「四捨五入」しないケースが発生します。
2. 丸め方式(もう少し詳しく)
- ECMAScript(言語仕様)の
Number.prototype.toFixedの定義では、内部で「最も近い値」を選び、候補がちょうど等距離(=.5 のケース)が 2つある場合は『偶数にする』 と明記されています(= round half to even)。つまり1.235→ 1.23 か 1.24 を決める際、どちらも等距離の“ちょうど真ん中”なら小数部の末尾の偶奇で決まります。
(補足)この「ties-to-even」は会計や統計でよく使われる「banker’s rounding」で、連続した丸めでバイアス(常に切り上げ/切り捨てに偏る)を減らすための方式です。
3. 浮動小数点誤差で起きる典型例(実例)
よく出るハマりどころを実際に見てみましょう。
console.log((1.005).toFixed(2)); // 実際にブラウザで試すと "1.00" になることがある
JavaScript期待は "1.01" ですが、実際には "1.00" が出ることがあります。理由は:
- 10 進で「1.005」は内部では二進で正確に表現できず、実際は
1.0049999999999999...のように ほんの少し小さい値 になっていることがある。 - その結果「最も近い値へ丸める」ときに 切り捨て側 に判定されて
"1.00"になる、ということです。
(別例)
console.log((0.615).toFixed(2)); // ブラウザによっては "0.61" になることがある
JavaScript同じ仕組みです。内部二進表現のズレが丸めの判定に影響します。
4. なぜ二進数だとズレるのか(初心者向けの直感)
- コンピュータは多くの場合 2進(0/1)で分数を表現します。
0.5(1/2)は2進で正確に表現できるが、0.1(1/10)や0.005(5/1000)は 有限桁の2進分数では表現できない。- 結果として内部表現は「元の10進の値に非常に近いが完全には一致しない」値になり、丸め判定(.5 に近いかどうか)で想定と逆の結果になることがある、というわけです。
5. 実務的な回避策(おすすめ順に) — コード例つき
A. 表示だけなら Intl.NumberFormat を使う(ロケール対応で安全)
表示目的(ユーザーに見せる文字列)なら Intl.NumberFormat が堅牢で推奨です。丸めも内部で適切に処理します。
const n = 1.005;
const s = new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(n);
console.log(s); // "1.01"(環境によるが通常期待どおり)
JavaScriptIntl.NumberFormat はロケールや通貨表示も扱えるので、金額などを表示するなら最優先で検討してください。
B. ある程度の精度で数値として丸めたいなら Number.EPSILON を使うテクニック
浮動小数点の丸め誤差を吸収するために Number.EPSILON を足してから丸める方法があります(万能ではないが多くのケースで有効)。
function roundTo(num, decimals = 2) {
const factor = 10 ** decimals;
return Math.round((num + Number.EPSILON) * factor) / factor;
}
console.log(roundTo(1.005, 2)); // 1.01
// 必要なら文字列で固定小数点にする:
console.log(roundTo(1.005, 2).toFixed(2)); // "1.01"
JavaScript注意点:Number.EPSILON を足す手法は「典型的な誤差」を補正する簡単なワークアラウンドで、全ケースを完璧にカバーするわけではありません(極端に大きな値や極小の桁では注意)。St
C. 整数化して丸める(乗算→丸め→除算)+EPSILON
Math.round を利用する伝統的な方法:
function roundTo2(num) {
const factor = 100;
return Math.round((num + Number.EPSILON) * factor) / factor;
}
console.log(roundTo2(1.005)); // 1.01
JavaScripttoFixed は文字列を返すのに対し、こちらは数値を返すので後段で計算に使うときに便利です。
D. 正確な十進演算が必要なら十進ライブラリを使う(金融アプリ等)
会計や通貨計算など 絶対に丸め誤差を許せない 場面では、decimal.js / Big.js / bignumber.js のような十進(decimal)ライブラリを使うのが最も確実です。これらは十進を正確に扱うため、1.005 のようなケースでも期待どおりの丸めを行えます。
6. その他の実用メモ
toFixedの戻り値は 文字列。数値に戻したければNumber(s)等で変換する。toFixedのfractionDigitsは仕様で範囲制約があり(通常 0〜100 のような上限が仕様に明記されています)極端な値はエラーになるので注意。- 表示だけの目的なら
toFixedで手早く済ませても良いが、丸め誤差の可能性は頭に入れておく。より安全なのはIntl.NumberFormatまたは decimal ライブラリの利用。
7. まとめ(要点)
toFixedの丸めは round to nearest、ties to even(仕様に基づく)。- JavaScript の 二進浮動小数点 表現の「少しずれる」性質が原因で、期待通りの丸めにならないことがある。代表的なハマりは
1.005→toFixed(2)。 - 回避策:表示なら
Intl.NumberFormat、数値丸めならMath.round+Number.EPSILONのテクニック、厳密に扱うなら decimal ライブラリを使う。
