【JS】Math.min()はInfinityになるのでまじで気を付けて
こんにちは!ブログの続かない Mizutani(@sirycity)です。ブログも続かないの間違いでした。
今日はMath.min(とMath.max)の挙動がまじで危ないって話です。
Math.minとは
最小の数字を求める関数です。簡単ね。こんなかんじ。
const n = Math.min(1, 2, 3)
// 1
Math.maxもいっしょ。最大。
const n = Math.max(1, 2, 3)
// 3
Math.minとInfinity
Math.minは引数がないとInfinityになります。こう。
const n = Math.min()
// Infinity
ちょっと不思議だよね。この挙動がわりとまじで危険。後で詳しく話すね。
Math.minの引数について
Math.minの引数はちょっと変わっていて、個数がいくつでも良いんです。こんな感じに。
const n = Math.min(1, 2)
// 1
const n = Math.min(1, 2, 3, 4, 5, 6, 7)
// 1
ところで引数は配列じゃないの?
ところでこういう関数って、普通はこうしません?
Math.min([1, 2])
Math.min([1, 2, 3, 4, 5, 6, 7])
こんな感じに引数は配列にしますよね。引数部分を別にすると、こんな感じに。
const n = [1, 2, 3, 4, 5, 6, 7]
Math.min(n)
こうしたいですよね。こうすると綺麗だし。
…でも、こうはできないんです。関数がそういう仕様だから。Math.minできたの大昔だしな。
引数を綺麗に書くために
とはいえ、やっぱ引数は綺麗に書きたい。そんな時にはスプレッド構文を使ってこう書けます。↓
const n = [1, 2, 3, 4, 5, 6, 7]
Math.min(...n)
// 1
これがMath.min使う上で基本となる書き方じゃないでしょうか。…しかしこのスプレッドを使った書き方が危険。危険って何回言ってるんだ。
引数を条件付きにした場合
今までのMath.minの使い方は一番小さい数字を探す でしたね。でも例えば、3以上の中で一番小さい数字を探すとかだったらどうします?こうします?
const n = [1, 2, 3, 4, 5, 6, 7]
const over3 = n.filter(i => i >= 3)
Math.min(...over3)
// 3
こうします。3以上で一番小さい数字なので3。正解。今の所は問題なく…見えますよね。
ついに出現するInfinity
じゃあですよ。10以上の中で一番小さい数字を探す とかやったらどうだと思います?
どう思います?10以上ですよ。でも数字は全部10以下ですよね。エラーだと思います?nullになると思う?正解はこちら。
const n = [1, 2, 3, 4, 5, 6, 7]
const over10 = n.filter(i => i >= 10)
Math.min(...over10)
// Infinity
(゚Д゚)ハァ?
(゚Д゚)ハァ?
どうしてこうなった
今のInfinity野郎をもう1回見てみましょう。
const n = [1, 2, 3, 4, 5, 6, 7]
const over10 = n.filter(i => i >= 10)
Math.min(...over10)
// Infinity
どうしてInfinityになるんでしょうか。順番に見ていきましょう。
2行目のover10、こちらですが
const over10 = n.filter(i => i >= 10)
この結果は空配列[]
になります。filterが全部空振ってるからです。filterが全部空振ることってあんまりないからちょっと分かりにくいですね。
そして次、
Math.min(...over10)
Math.minの引数ですが、空配列をスプレッドしてるのでemptyになってます。つまり、引数無しになってるんですね。そう、引数あるっぽく見えるけどこれ引数ないんです。分かりやすく言うと↓の2つは全く同じ。
fn()
fn(...[])
このjs特有のemptyって概念は非常にややこしくこれだけで1本書けちゃうくらいなんですが、ともかく引数がないんです。
そして引数がないってことはMath.minはInfinityになります。以上。
どういう時に困るのか
こんなレアケースないじゃんって思いませんか?ところがあったんですよ。
営業時間内の最寄りのスーパーまでの距離
これを深夜にやると最寄りのスーパーまでの距離がInfinityになります。
なぜ困るのか
不具合を見つけにくいからです。
- filterは結果が0個になってもエラーにならない
のはまだ分かるとして、
- Math.minは引数がなくてもエラーにならない
- Infinityは数字なので型情報でエラーに気づきにくい
という、JavaScriptの適当さが完全に負の影響を及ぼす典型となりました。
あと、最小を調べてるのになんで最も大きい数が出てくる んですかね。まだ-Infinityなら分かる(分からない)
じゃあどうするのか
ヘルパー関数的なのを作っても良い…かもしれません。めんどいけど。
const mathMin = arg => {
const n = Math.min(arg)
if (n === Infinity) return null
return n
}
結論
Math.min()はエラーにしてくれ。せめてnull。以上。