JavaScript | 数値を固定小数点数形式で文字列に変換(toFixed() メソッド)

javascrpit JavaScript
スポンサーリンク

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"(環境によるが通常期待どおり)
JavaScript

Intl.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
JavaScript

toFixed は文字列を返すのに対し、こちらは数値を返すので後段で計算に使うときに便利です。

D. 正確な十進演算が必要なら十進ライブラリを使う(金融アプリ等)

会計や通貨計算など 絶対に丸め誤差を許せない 場面では、decimal.js / Big.js / bignumber.js のような十進(decimal)ライブラリを使うのが最も確実です。これらは十進を正確に扱うため、1.005 のようなケースでも期待どおりの丸めを行えます。

6. その他の実用メモ

  • toFixed の戻り値は 文字列。数値に戻したければ Number(s) 等で変換する。
  • toFixedfractionDigits は仕様で範囲制約があり(通常 0〜100 のような上限が仕様に明記されています)極端な値はエラーになるので注意。
  • 表示だけの目的なら toFixed で手早く済ませても良いが、丸め誤差の可能性は頭に入れておく。より安全なのは Intl.NumberFormat または decimal ライブラリの利用。

7. まとめ(要点)

  1. toFixed の丸めは round to nearest、ties to even(仕様に基づく)。
  2. JavaScript の 二進浮動小数点 表現の「少しずれる」性質が原因で、期待通りの丸めにならないことがある。代表的なハマりは 1.005toFixed(2)
  3. 回避策:表示なら Intl.NumberFormat、数値丸めなら Math.roundNumber.EPSILON のテクニック、厳密に扱うなら decimal ライブラリを使う。
タイトルとURLをコピーしました