JavaScriptで数値に変換するにはいくつもの方法がある。これはJavaScriptが強制型変換ができる「ゆるい」言語であるからであり、変換によっても利点と欠点がある。あらゆる期待を満たす変換方法はないので、取捨選択を迫られることになる。
主要な値とその変換結果
| undefined | null | true | 0 | Number.MAX_SAFE_INTEGER + 1 | Infinity | NaN | ’10' | '0x10’ | new Date() | [] | {} | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| parseInt(x) | NaN | NaN | NaN | 0 | 9007199254740992 | NaN | NaN | 10 | 16 | NaN | NaN | NaN |
| parseFloat(x) | NaN | NaN | NaN | 0 | 9007199254740992 | Infinity | NaN | 10 | 0 | NaN | NaN | NaN |
| +x | NaN | 0 | 1 | 0 | 9007199254740992 | Infinity | NaN | 10 | 16 | [^1] | 0 | NaN |
| -(-x) | NaN | 0 | 1 | 0 | 9007199254740992 | Infinity | NaN | 10 | 16 | [^1] | 0 | NaN |
| ~~x | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 10 | 16 | [^2] | 0 | 0 |
| x + 0 | NaN | 0 | 1 | 0 | 9007199254740992 | Infinity | NaN | 10 | 16 | [^1] | 0 | NaN |
| x - 0 | NaN | 0 | 1 | 0 | 9007199254740992 | Infinity | NaN | 10 | 16 | [^1] | 0 | NaN |
| x * 1 | NaN | 0 | 1 | 0 | 9007199254740992 | Infinity | NaN | 10 | 16 | [^1] | 0 | NaN |
| x | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 10 | 16 | [^2] | 0 | 0 |
| Number(x) | NaN | 0 | 1 | 0 | 9007199254740992 | Infinity | NaN | 10 | 16 | [^1] | 0 | NaN |
| new Number(x) | NaN | 0 | 1 | 0 | 9007199254740992 | Infinity | NaN | 10 | 16 | [^1] | 0 | NaN |
[^1] 現在のUNIXタイムスタンプをミリ秒で返す。
[^2] 現在のUNIXタイムスタンプをミリ秒で返すが、32bitに丸められる(オーバーフローする)。
変換のパターン
1. parseInt()
parseInt() は整数値とみなせる値を整数値に変換する。基数を指定しないとプレフィクスに従って変換するので、0x10 は16として認識される。型チェックがあるので不正な値はNaNになる。
2. parseFloat()
おおよそ parseInt() よりも緩めのバージョンとして変換する。無限大を認識するが、基数プレフィクスは認識せず、数値として読み取れる部分まで解釈する。
3. 数値への強制型変換
+x, -(-x), x + 0, x - 0, x * 1, Number(x) がこれにあたる。数値であればそれが返却されるし、そうでなければプリミティブとしての評価を期待されているので、暗黙的に valueOf() が呼び出されることになる。
配列の場合
空の配列に対して valueOf() を呼び出すと空文字列 "" になるので、これを数値に変換すると0になる。
オブジェクトの場合
toString() がオーバーライドされていない限り、valueOf() は [object Object] になり、これを数値に変換するとNaNになる。
4. 32bit整数値への強制型変換
~~x または x | 0 では32bit整数値に変換される。x | 0 における手順は以下のとおり。
- BitwiseORExpressionとして解釈される。
EvaluateStringOrNumericBinaryExpressionが実行される。ApplyStringOrNumericBinaryOperator()が実行される。Number::bitwiseOr()が実行される。NumberBitwiseOp()が実行される。ここでToInt32()が呼び出される。ToInt32()は数値として認識できない値を0として扱う。
Dateオブジェクトの場合
数値に変換はできるものの、ToInt32()変換ルールによりオーバーフローする。
オブジェクトの場合
オブジェクトを valueOf() した結果が数値扱いできないため、同じく ToInt32() 変換ルールにより0扱いされて 0 | 0 になる。
5. new Number()
変換結果としての値は3.と同じではあるものの、値をオブジェクトにする。型検査をすり抜ける可能性があるので推奨されない。
const x = new Number(42)
console.log(x) // Number {}
console.log(`${x}`) // '42'
console.log(typeof x) // 'object'
console.log(x instanceof Number) // true
console.log(Number.isInteger(x)) // false (!!)総評
ほとんどの状況では parseInt(x) または +x が好ましいように思う。簡単にまとめると相違点は:
parseInt(x)は整数値であることを期待する。parseFloat(x)は浮動小数点数であることを期待する。+xは強制型変換ができるなら数値にする。x | 0の結果は32bit整数値であり、ほかの変換方法と異なりNaNにはならない。
となる。ただし、前提となる型チェックや、正規表現による入力検査なしには期待どおりの結果にならないことが考えられるし、特にユーザーからの入力を変換する際には、変換結果がNaNになることによる計算の失敗を誘発されやすいので、ユースケースに即した変換関数を自前で用意すべきである。
Takuo KihiraさんもJavaScript で parseInt / parseFloat を使わない方が良い理由について述べているので、あわせてこちらも確認してみてほしい。