iTranslated by AI
Pitfalls in JavaScript Math.min and Math.max Behaviors
Date of Article: 2019/05
I found some behaviors of Math.min / Math.max that seem more likely to trip someone up than expected, so I'd like to summarize them.
What I'm writing here is also documented on MDN.
Math.min / Math.max return NaN if even one element cannot be converted to a number
First, these functions return NaN if the arguments include NaN.
Math.min(1, 2, 3, NaN)
// => NaN
And all arguments received are cast to Number.
Math.min(10, undefined)
// => NaN
Math.min(10, "foo")
// => NaN
Things that can be cast to Number and become numeric values are treated as such.
Math.min(10, null)
// => 0 (Number(null) is evaluated as 0)
Math.min(10, true)
// => 1 (Number(true) is evaluated as 1)
Since how values behave when converted to numbers can feel a bit like a guessing game, it's preferable to ensure such values don't enter the function in the first place.
Math.min / Math.max return Infinity when arguments are empty
Roughly speaking, it looks like this:
Math.min()
// => Infinity
Math.max()
// => -Infinity
The behavior when passing nothing into functions like min or max varies by language, but this is how JavaScript handles it.
As for why it behaves this way, I'll leave that to the following helpful articles.
- https://qiita.com/KtheS/items/9811b88d6e7edbf32f36
- https://charlieharvey.org.uk/page/why_math_max_is_less_than_math_min
(Personally, it made sense to me when considering them as the initial values for a reduce function.)
When should you be careful?
You might think, "Wait, normally you wouldn't write it that way," so let me explain the background of how I came to understand this behavior.
While it's true it doesn't happen often, you should be a bit careful when you want to write something in combination with a filter, for example:
const items = [item1, item2]
.map(Number) // NaN could be mixed in here
Math.min(...items)
// =0 could result in NaN
const items = [item1, item2]
.filter(item => item > 0) // items could become an empty array here
Math.min(...items)
// =0 could result in Infinity
By the way, spreading an array like Math.min(...items) in the code above is quite convenient.
It allows us to write very concisely what used to be done in the form of Math.min.apply(null, items).
Discussion
間違った値が入ってもそのままエラー発生せずに動いてしまって、結果、あとあとから何か変な値が別のところで発見されて困って、その別の所で分岐コードを書かなければいけない、
ということが起きやすいのが、JavaScriptを難しくしている問題のひとつと思います。
自作ライブラリのParts.js を作っているのですが、そこのmaxでは、数値以外の値を評価しようとするとエラーになるようにしていました。配列以外を渡したらエラーとか、配列の要素なしを渡したらnullを返すとか、maxみたいな基本的なものでもいろいろ考えることがいくつもあるので、JSって面白いような難しいような感じです。