2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Math.max(...[]) は -Infinity、Math.max([1, 2, 3]) は NaN — 6つのケースを3つの環境で実測

2
Posted at

PHP の max() / min() に NAN が混ざると、引数の順番や PHP のバージョンで答えが変わる、という記事が話題になっていました。では JavaScript の Math.max() はどうなのか。空の配列、NaN、文字列、配列そのものなどを渡して、macOS 27.0.1 上の Node.js v24.14.1・Google Chrome 155・WebKit(Safari と同じ系統のブラウザエンジン。Mac の cmux というアプリに入っているもの)の3つで確かめました。

結果は、6つの疑問のうち5つは3つの環境で同じで、違ったのは「何個まで渡せるか」だけでした。その1つ以外はエラーにならず、そのまま値が返ってくるので、おかしな値に気づきにくいのが厄介なところです。

Q1. 空の配列を ... でばらして渡すと、エラーになる?

なりません。-Infinity(マイナスの無限大)が返ります(... を付けずに空の配列をそのまま渡したときの話は Q4 です)。

console.log(Math.max(...[])); // -Infinity
console.log(Math.min(...[])); // Infinity
console.log(Math.max());      // -Infinity

... は配列の中身をばらして引数として渡す書き方(スプレッド構文)です。中身が空だと引数が0個になり、Math.max() は比べる相手がいないときの出発点(どんな数よりも小さい値)として -Infinity を返します。MDN の Math.max() のページにも、引数がないときは -Infinity を返すと書かれています。

困るのは、一覧の ID を「いちばん大きい番号 + 1」で作るときです。

const todos = [];
const nextId = Math.max(...todos.map((t) => t.id)) + 1;
console.log(nextId); // -Infinity

一覧が空のときだけ、次の ID が -Infinity になります。エラーは出ません。

const safeNextId = Math.max(0, ...todos.map((t) => t.id)) + 1;
console.log(safeNextId); // 1

最初に 0 を1つ入れておくと、空のときは 0 + 1 で 1 から始まります(ID が 0 以下にならない前提の書き方です)。

Q2. NaN が混ざると、順番で結果が変わる?

JavaScript では変わりませんでした。NaN(数ではない、という意味の値)がどこにあっても、結果は NaN です。MDN にも、引数のどれかが NaN、または変換すると NaN になるなら NaN を返す、と書かれています。

console.log(Math.max(NaN, 1, 3)); // NaN
console.log(Math.max(1, NaN, 3)); // NaN
console.log(Math.max(1, 3, NaN)); // NaN
console.log(Math.min(1, NaN, 3)); // NaN
console.log(Math.max(1, undefined)); // NaN

undefined も数にすると NaN になるので、同じく結果が NaN になります。よく起きるのは、一部のデータに項目がないときです。

const items = [{ price: 300 }, { price: 1200 }, { name: "送料" }];
console.log(Math.max(...items.map((i) => i.price))); // NaN

3つ目に price がないので undefined が混ざり、最大値が NaN になりました。数だけを残してから渡します。

const prices = items.map((i) => i.price).filter((p) => Number.isFinite(p));
console.log(Math.max(...prices)); // 1200

Number.isFinite() は、ふつうの数(NaN や無限大ではない数)のときだけ true を返します。?? 0 で 0 に置きかえる方法もありますが、それは「ない」を「0円」として扱う、という決めごとになるので、どちらにするかは場面で決めます。

この書き方には2つ気をつける点があります。全部のデータに price がないと prices が空になり、Q1 の -Infinity に戻ります。また、Number.isFinite("1200") は false なので、API やフォームから "1200" のような文字で届く値は、黙って外されます。文字で届くなら、先に数に変換してから絞り込みます(文字から数への変換のずれは「文字列を数値に変換する4つの方法、挙動の違い5選」にまとめています)。

Q3. 文字列や null を渡すと?

数に変換してから比べます。返ってくるのは数です。

console.log(Math.max("9", "10")); // 10
console.log(Math.max(1, "10px")); // NaN
console.log(Math.max(-1, null));  // 0
console.log(Math.max(-1, ""));    // 0

"9" と "10" は、文字として並べると "9" のほうが後ですが、Math.max は数にしてから比べるので 10 が返ります(返り値は文字ではなく数の 10)。"10px" は数にできないので NaN です。

気をつけたいのは null と空の文字 "" で、どちらも 0 として扱われます。全部マイナスの値の中に null が1つ混ざると、存在しないはずの 0 が最大値になります。

Q4. 配列をそのまま渡すと?

console.log(Math.max([1, 2, 3])); // NaN
console.log(Math.max([5]));       // 5
console.log(Math.max([]));        // 0

... を付け忘れて配列をそのまま渡すと、配列が1つの引数として数に変換されます。[1, 2, 3] は文字にすると "1,2,3" なので数にできず NaN、[5] は "5" なので 5、[] は "" なので 0 です。

厄介なのは、要素が1つの配列だと正しそうな値が返ることです。テストのデータが1件だけだと気づけず、2件以上になってから NaN になります。

Q5. 要素が多いと、何個まで渡せる?

スプレッド構文で渡せる個数には上限があり、ここだけ環境で違いました。同じ値を並べた配列を Math.max(...配列) に渡して、エラーにならない最大の個数を探しました(3回ずつ測って同じ値)。

環境 エラーにならなかった最大の個数 1つ増やしたとき
Node.js v24.14.1 124,159 RangeError: Maximum call stack size exceeded
Google Chrome 155 123,587 RangeError: Maximum call stack size exceeded
WebKit(cmux 内蔵) 638,616 RangeError: Maximum call stack size exceeded.

10万個まではどの環境でも通り、100万個はどの環境でもエラーでした。この上限は、関数を呼び出すときに引数を積んでおく場所(コールスタック)の大きさに左右されるので、呼び出し方や環境で変わります。上の数字は「この測り方ではこうだった」という目安です。

実際、関数の呼び出しを深く重ねた中(関数の中で関数を呼ぶのを何千回も重ねた、いちばん奥)で同じように測ると、上限は下がりました。2000回重ねた中では Node.js 100,137・Chrome 99,567・WebKit 610,586、5000回重ねた中では Node.js 64,137・Chrome 83,571・WebKit 568,586 個でした。アプリの中のどこで呼ぶかによっては、10万個より少なくても止まることがあります。

件数が多くなりうるなら、スプレッドを使わずに、reduce で1つずつ Math.max に通します(MDN の Math.max() のページにも同じ書き方が載っています)。

const big = Array.from({ length: 1_000_000 }, (_, i) => i);
const max = big.reduce((a, b) => Math.max(a, b), -Infinity);
console.log(max); // 999999

100万個でも、3つの環境とも 999999 が返りました。1つずつ Math.max に通すので、NaN が混ざれば NaN、"9" と "10" なら 10、空の配列なら -Infinity と、Math.max(...配列) と同じ結果になり、個数の上限だけがなくなります。

同じ reduce でも、Math.max の代わりに > で比べる書き方には注意が要ります。

const bigger = (arr) => arr.reduce((a, b) => (a > b ? a : b), -Infinity);
console.log(bigger([NaN, 1, 3]));           // 3
console.log(bigger([1, 3, NaN]));           // NaN
console.log(bigger([undefined, 300, 1200])); // 1200
console.log(bigger([300, 1200, undefined])); // undefined
console.log(bigger(["9", "10"]));          // 9

> は相手が NaN や undefined だと必ず false になるので、混ざっている位置で結果が変わりました。冒頭の PHP と同じく「順番で答えが変わる」状態です。また、文字の "9" と "10" は数にせず文字として比べるので、"9" が返ります(console.log の表示では引用符が付かず 9 と出ます)。この書き方を使うなら、Q2 のように数だけに絞った配列に使います。

Q6. 日付を渡すと、日付が返ってくる?

返ってくるのは日付ではなく、数(1970年1月1日 0時(世界標準時)からのミリ秒)です。

const a = new Date(2026, 9, 1);
const b = new Date(2026, 9, 16);
const latest = Math.max(a, b);
console.log(latest);           // 1792076400000
console.log(new Date(latest)); // 2026-10-15T15:00:00.000Z

日付を数に変換して比べるので、いちばん新しい日付を求めること自体はできます。日付として使うなら new Date() で戻します。最後の表示は世界標準時なので、日本時間の 10月16日 0:00 が 15T15:00 と出ています(Node.js の表示。日本時間の設定で実行)。

AI が書いたコードを見るときは

  • Math.max(...配列) の配列が空になることはないか(空なら -Infinity)
  • 配列の中に undefined・null・空の文字が混ざる可能性はないか(NaN や 0 になる)
  • ... を付け忘れて Math.max(配列) になっていないか(要素が1つのときだけ正しく見える)
  • 件数が数万〜10万を超えることはないか(深い呼び出しの中では上限が下がるので、多くなりうるなら reduce で1つずつ Math.max に通す)
  • reduce を (a, b) => (a > b ? a : b) で書いていないか(NaN や undefined の位置で結果が変わる)

未経験者向けの講座を運営しています

未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。

※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?