4つの丸め方の違い、負の数で結果が分かれる理由、toFixed が文字列を返すこと、そして 1.005 が 1.00 になる理由と、その回避策も効かない場面までを実行して確かめます。
丸め方は4つあります。負の数で結果が分かれます。
example.js
for (const n of [2.5, -2.5, 2.4, -2.4]) {
console.log(String(n).padStart(5), Math.round(n), Math.floor(n), Math.ceil(n), Math.trunc(n));
}
出力書き換えて実行できます
2.5 3 2 3 2
-2.5 -2 -3 -2 -2
2.4 2 2 3 2
-2.4 -2 -3 -2 -2
example.js
function roundHalfAwayFromZero(n) {
return Math.sign(n) * Math.round(Math.abs(n));
}
console.log(Math.round(-2.5), roundHalfAwayFromZero(-2.5));
console.log(Math.round(2.5), roundHalfAwayFromZero(2.5));
出力書き換えて実行できます
-2 -3
3 3
| 関数 |
やること |
-2.5 |
Math.round() |
より大きいほうへ(+0.5 して切り捨て) |
-2 |
Math.floor() |
小さいほうへ(下へ) |
-3 |
Math.ceil() |
大きいほうへ(上へ) |
-2 |
Math.trunc() |
0へ(小数を捨てる) |
-2 |
Math.round() は四捨五入ではない
-2.5 が -2 になります。 四捨五入なら -3 のはずです。
Math.round() は「0.5 を足して切り捨てる」という決まりです。
正の数では四捨五入と同じ結果になりますが、負の数ではずれます。
「絶対値で四捨五入」したいなら、自分で書きます。
example.js
console.log((1234.5678).toFixed(2));
console.log(typeof (1234.5678).toFixed(2));
出力書き換えて実行できます
1234.57
string
javascript
console.log((1.5).toFixed(1) + 1);
1.51
「四捨五入」と言われたら、負の数をどうするか先に確かめてください。
toFixed() は文字列を返す
example.js
console.log((1.005).toFixed(2));
console.log((1.015).toFixed(2));
console.log((1.025).toFixed(2));
出力書き換えて実行できます
1.00
1.01
1.02
example.js
function roundTo(n, digits) {
const p = 10 ** digits;
return Math.round(n * p) / p;
}
console.log(roundTo(1.005, 2));
console.log(1.005 * 100);
出力書き換えて実行できます
1
100.49999999999999
数値ではありません。 そのまま足すと文字列の連結になります。
example.js
const yen = 1005;
console.log(yen / 100);
console.log((yen / 100).toFixed(2));
console.log(Math.round(yen / 10) * 10);
出力書き換えて実行できます
10.05
10.05
1010
example.js
console.log((1234567.891).toLocaleString('ja-JP'));
console.log((1234567.891).toLocaleString('ja-JP', { maximumFractionDigits: 0 }));
console.log((1234.5).toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' }));
出力書き換えて実行できます
1,234,567.891
1,234,568
¥1,235
計算に戻すなら Number() を通してください。
→ 文字列を数値に変換する
1.005 が 1.00 になる
いちばん有名な落とし穴です。
example.js
console.log(Number.isInteger(3), Number.isInteger(3.0), Number.isInteger(3.5));
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.isSafeInteger(2 ** 53 - 1), Number.isSafeInteger(2 ** 53));
出力書き換えて実行できます
true true false
9007199254740991
true false
example.js
console.log(2 ** 53 === 2 ** 53 + 1);
出力書き換えて実行できます
true
3つとも切り上がっていません。
理由は toFixed() の不具合ではありません。1.005 という数がそもそも存在しないからです。
二進法で書けないので、実際には 1.005 よりわずかに小さい数が入っています。
→ なぜ 0.1 + 0.2 は 0.30000000000000004 になるのか
よくある回避策も効かない
「100倍して丸めて戻す」という書き方が知られています。これも同じ理由で外れます。
1.005 * 100 が 100.49999999999999 なので、Math.round() は 100 にします。
掛けた時点でずれています。
roundTo(1.234567, 3) のような普通の値では正しく働きます。効かないのは、二進法で表せない値がちょうど境界に来たときです。
どうすればよいか
お金や数量のように「ずれてはいけない数」は、小数で持たないでください。
最小単位(円・グラム・ミリ秒)の整数で持ち、表示するときだけ割る。
これがいちばん確実です。
表示だけが目的なら toFixed() で十分です。1円のずれが問題になる計算に使わない、
というだけの話です。
桁区切りや通貨は Intl に任せる
自分で3桁ごとにカンマを入れないでください。
言語ごとの区切り方は Intl が知っています。
ロケールを省くと環境の設定で結果が変わります。必ず 'ja-JP' のように書いてください。
整数かどうかを確かめる
3.0 は 3 と同じ数です。JavaScript に整数型と小数型の区別はありません。
安全に扱える整数は 2^53 - 1 までです。それを超えると、隣の数と区別できなくなります。
大きな識別子は BigInt か文字列で持ってください。
まとめ
Math.round() は四捨五入ではない。 -2.5 は -2
- 0へ寄せるなら
Math.trunc()、下へなら floor()、上へなら ceil()
toFixed() は文字列を返す。足すと連結になる
(1.005).toFixed(2) は 1.00。 その数がそもそも無いから
- 100倍して丸める回避策も、同じ理由で外れることがある
- ずれてはいけない数は、最小単位の整数で持つ
- 桁区切りと通貨は
toLocaleString()。ロケールを必ず書く