JavaScript: parseInt(n, 10)の振る舞いを40の具体例から確認してみた

JavaScriptで、文字列を整数としてパースする方法として`parseInt(n, 10)`があります。この投稿では、`n`にどんな値を与えたらどんな戻り値になるのかを具体例をもとに検証していきます。

## `parseInt`の基本的なこと

`parseInt`は第1引数に文字列の値を取ることができ、それを整数値の`number`型に変換する関数です。第2引数は、基数を指定します。基数は省略可能で、省略した場合は第1引数がどんな文字列で始まるかで基数を仮定します。例えば、`"0x"`始まりなら`16`、`"0"`始まりなら`8`といった具合にです。第2引数は極力指定するほうが良いとされています。`parseInt`で整数と解釈できなかった場合は`NaN`が戻り値になります。

`parseInt`の詳細については次のMDNのドキュメントをご覧ください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/parseInt

`parseInt`の仕様書を確認したい方は、次のECMAScript 2022の仕様書をご覧ください。

https://tc39.es/ecma262/#sec-parseint-string-radix

## `parseInt(n, 10)`に様々な文字列を与えてみる

ここからは`parseInt(n, 10)`に様々な文字列を与えてみて、どのような戻り値になるか見ていきます。

まず、テストを手軽に行いたいので、次のようなテスト実行関数を作っておきます。この関数`test`は、引数`cases`に文字列配列を与えると、入力とパース結果の表が出力されるようになっています。

```js
function test(cases) {
  console.table(cases.map((value) => [value, parseInt(value, 10)]));
}
```

<!--
console.table = function (table) {
  console.log(`入力 | 結果\n----|----:`);
  table.forEach(([input, result]) => {
    console.log(`\`%o\` | \`%o\``, input, result);
  });
  console.log("");
};
-->

### 符号

まず、符号がどう扱われるか見てみます。

```js
test(["-1", "-0", "0", "1", "+0", "+1"]);
```

入力 | 結果
----|----:
`'-1'` | `-1`
`'-0'` | `-0`
`'0'` | `0`
`'1'` | `1`
`'+0'` | `0`
`'+1'` | `1`

1文字目が負の符号の場合は、負の整数としてパースされます。JavaScriptは0にも符号がつけられるので、`"-0"`は`-0`になります。

### 小数

次に小数です。

```js
test(["-1.2", "-0.1", "-0.0", "0.0", "0.1", "1.2", "+0.0", "+0.1", "+1.2"]);
```

入力 | 結果
----|----:
`'-1.2'` | `-1`
`'-0.1'` | `-0`
`'-0.0'` | `-0`
`'0.0'` | `0`
`'0.1'` | `0`
`'1.2'` | `1`
`'+0.0'` | `0`
`'+0.1'` | `0`
`'+1.2'` | `1`

小数風の文字列は`.`以降が無視されるような振る舞いです。

### 数字に続く数字以外の文字

```js
test(["0px", "-0px", "1px", "-1px"]);
```

入力 | 結果
----|----:
`'0px'` | `0`
`'-0px'` | `-0`
`'1px'` | `1`
`'-1px'` | `-1`

文字列が数字で始まっている場合、数字じゃない文字に出くわすまで整数としてパースされます。

### 桁区切り文字

ES2021からはnumeric separatorという文法が追加されて、numberリテラルに桁の区切りを書けるようになりました。桁の区切り文字は`_`です。例えば、`1000`と`1_000`は同じ値になります。`parseInt`ではnumeric separator風の文字列を扱えるのか試します。

```js
test(["123_456", "123,456"]);
```

入力 | 結果
----|----:
`'123_456'` | `123`
`'123,456'` | `123`

これもやはり、数字以外の文字に出会うとそこでパースをやめます。

### bigint風の文字列

ES2020からはbigint型が追加されました。bigintはリテラル表記で数字の末尾に`n`を書く形式です。例えば、`100n`はbigint型の`100`になります。`parseInt`ではbigintリテラル風の文字列がどう扱われるのでしょうか。

```js
test(["1n", "1000n"]);
```

入力 | 結果
----|----:
`'1n'` | `1`
`'1000n'` | `1000`

末尾のnは単純に無視されます。

### 0始まりの数字

0で始まる数字を見ていきます。

```js
test(["05", "005", "050", "00", "00500px"]);
```

入力 | 結果
----|----:
`'05'` | `5`
`'005'` | `5`
`'050'` | `50`
`'00'` | `0`
`'00500px'` | `500`

文字列のはじめにある0は無視されます。

### 前後に空白文字がある数字

前後に空白文字が入っている数字はどうでしょうか。

```js
test([" 1 ", "\n1\n", "\t1\t"]);
```

入力 | 結果
----|----:
`' 1 '` | `1`
`'\n1\n'` | `1`
`'\t1\t'` | `1`

数字の前後の空白は取り除かれた上でパースされます。

### NaNになる文字列

最後に結果が`NaN`、つまりパース失敗になるケースを見ていきます。

```js
test(["", "a", "-", "+", "a0", "a1", "a1b"]);
```

入力 | 結果
----|----:
`''` | `NaN`
`'a'` | `NaN`
`'-'` | `NaN`
`'+'` | `NaN`
`'a0'` | `NaN`
`'a1'` | `NaN`
`'a1b'` | `NaN`

空白文字やそもそも数字を含んでいないのは`NaN`になります。数字を含んでいても、`-`と`+`を以外の文字で始まる文字列は`NaN`になります。

## 確認した振る舞いのまとめ

`parseInt(n, 10)`は

- 正負の符号は解釈される
- 前後の空白文字は取り除かれた上で、パースされる
- 1文字目が数字じゃないと`NaN`になる
- 1文字目が数字なら、途中に数字以外が入っていても`NaN`にはならず、数字が続くところまでパースされる

という振る舞い確認されました。

## 所感

ユースケースによりますが、ユーザーの入力に対して`parseInt(n, 10)`をかけるときは、その前に`n`に対して正規表現で`/^\d+$/`などのチェックをしたほうが、成り行き感がなくなるのでより安心だと思いました。