フォームに入力された値や、URL・データ属性から取り出した値は、見た目が数字でも中身は「文字列」であることがほとんどです。そのまま計算しようとすると意図しない結果になるため、数値へ変換する必要があります。JavaScriptには parseInt・parseFloat・Number という代表的な3つの方法があり、それぞれ変換のルールが異なります。この記事では初心者〜中級者の方に向けて、3つの違いと使い分け、NaN の正しい判定方法まで、動くコードで確認しながら解説します。
目次
変換方法によって結果が変わる
まず押さえておきたいのは、同じ文字列でも「どの変換方法を使うか」で結果が変わるということです。大きく分けると、parseInt と parseFloat は文字列の先頭から数値として読めるところまでを解釈し、途中に数字でない文字が現れたらそこで読み取りを止めます。一方 Number(および単項プラス +)は文字列全体を1つの数値として解釈し、途中に不正な文字が混じっていれば NaN(Not-a-Number、数値でない値)を返します。
この性格の違いから、"12px" のような単位付きの値は parseInt がうまく扱え、逆に「数字だけかどうか」を厳密に判定したいなら Number が向いています。まずは代表的な入力に対する結果を一覧で見比べてみましょう。細かい理由はこのあと順番に説明します。
| 入力(文字列) | parseInt | parseFloat | Number / + |
|---|---|---|---|
"12" | 12 | 12 | 12 |
"12px" | 12 | 12 | NaN |
"3.14" | 3 | 3.14 | 3.14 |
"3.14em" | 3 | 3.14 | NaN |
" 10 " | 10 | 10 | 10 |
""(空文字) | NaN | NaN | 0 |
"abc" | NaN | NaN | NaN |
"1.5e3" | 1 | 1500 | 1500 |
空文字だけ結果が分かれている点に注目してください。Number("") は 0 になりますが、parseInt("") は NaN です。この違いは実務でつまずきやすいので、あとで詳しく触れます。
parseInt で整数に変換する
parseInt は文字列を先頭から読み、整数として解釈できる部分だけを取り出します。先頭の空白は無視され、数字でない文字が現れた時点で読み取りを終了します。小数点も「数字でない文字」として扱われるため、"3.14" を渡すと小数点以下は切り捨てられ 3 になります(四捨五入ではありません)。
console.log(parseInt("12px")); // 12 先頭の数字だけ取り出す
console.log(parseInt(" 42abc")); // 42 先頭の空白は無視される
console.log(parseInt("3.14")); // 3 小数点で止まる(切り捨て)
console.log(parseInt("abc")); // NaN 先頭が数字でないと NaN
console.log(parseInt("")); // NaN 空文字も NaN
第2引数の基数(radix)は必ず指定する
parseInt は parseInt(文字列, 基数) の形で、第2引数に「何進数として解釈するか」を表す基数(radix)を渡せます。基数を 10 にすれば10進数、2 にすれば2進数、16 にすれば16進数として読み取ります。
console.log(parseInt("101", 2)); // 5 2進数として解釈
console.log(parseInt("ff", 16)); // 255 16進数として解釈
console.log(parseInt("10", 10)); // 10 10進数として解釈
この基数は省略できますが、省略せずに常に指定することが強く推奨されています。省略した場合、文字列が "0x" または "0X" で始まると16進数として解釈されるという特別ルールがあり、意図しない結果を招くことがあるためです。
// "0x" で始まると、基数を省略すると16進数扱いになる
console.log(parseInt("0x1f")); // 31 16進数の 1f として解釈された
console.log(parseInt("0x1f", 16)); // 31 基数を明示しても同じ結果
console.log(parseInt("0x1f", 10)); // 0 10進数指定なら "0" までしか読まない
// 基数を必ず 10 で明示しておけば、この揺れを避けられる
console.log(parseInt("08", 10)); // 8
ユーザー入力や外部データを扱うときは、どんな文字列が来るか分かりません。parseInt(value, 10) のように基数を明示しておくと、こうした特別ルールに振り回されずに済みます。
parseFloat で小数を含めて変換する
parseFloat は parseInt と同じく先頭から読んで途中で止まりますが、こちらは小数点や指数表記(1.5e3 のような e を使った表記)も数値の一部として解釈します。基数の指定はなく、常に10進数として読み取ります。CSSの "3.14em" や "1.5rem" のように、小数を含む単位付きの値から数値を取り出したいときに便利です。
console.log(parseFloat("3.14em")); // 3.14 小数点まで読み取り、単位で止まる
console.log(parseFloat("1.5e3")); // 1500 指数表記も解釈できる
console.log(parseFloat(" 0.5")); // 0.5 先頭の空白は無視される
console.log(parseFloat("abc")); // NaN 数値として読めないと NaN
Number で文字列全体を数値にする
Number は前の2つとは考え方が違い、文字列全体を1つの数値として解釈します。途中に数値として不正な文字が1つでも混じっていれば、部分的に読み取ることはせず NaN を返します。この「全体が数値でなければ NaN」という厳密さが Number の特徴です。
console.log(Number("3.14")); // 3.14 全体が数値なら変換できる
console.log(Number("12px")); // NaN 余計な文字があると NaN
console.log(Number(" 10 ")); // 10 前後の空白は無視される
注意したいのは空文字と空白だけの文字列の扱いです。Number("") は 0 になり、スペースだけの Number(" ") も 0 になります。数字が1つも入っていないのに 0 が返るため、未入力のフォーム値をそのまま Number にかけると、意図せず 0 として計算が進んでしまうことがあります。
console.log(Number("")); // 0 空文字は 0 になる(parseInt は NaN)
console.log(Number(" ")); // 0 空白だけでも 0
// 真偽値や null なども数値に変換される
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
このように Number は文字列以外の値(真偽値・null・undefined など)も数値へ変換します。null が 0、undefined が NaN になる点はセットで覚えておくと、思わぬバグを防げます。
単項プラス +str との違い
値の前に + を付ける「単項プラス」でも数値へ変換できます。変換ルールは Number と同じで、空文字は 0、余計な文字が混じれば NaN になります。実質的な結果は Number と変わらないため、どちらを使うかは好みや可読性の問題です。関数呼び出しの形で明示的に書きたいなら Number()、短く書きたいなら + と使い分けるとよいでしょう。
console.log(+"3.14"); // 3.14 Number("3.14") と同じ
console.log(+""); // 0 空文字は 0
console.log(+null); // 0
console.log(+"12px"); // NaN 余計な文字があると NaN
NaN を正しく判定する
これまで見てきたとおり、変換に失敗すると結果は NaN になります。ところが NaN には「自分自身とも等しくない」という特殊な性質があり、value === NaN のような比較では判定できません。必ず専用の関数を使います。
判定用の関数には Number.isNaN とグローバルの isNaN の2つがあり、振る舞いが異なります。グローバルの isNaN は、引数がまず Number によって数値へ変換されてから判定されます。そのため isNaN("abc") のように「文字列だが変換すると NaN になる値」でも true を返してしまい、「値が本当に NaN かどうか」を厳密には確かめられません。一方 Number.isNaN は変換を行わず、値がそのまま NaN のときだけ true を返します。
console.log(NaN === NaN); // false NaN は自分自身とも等しくない
// グローバル isNaN は引数を数値変換してから判定する
console.log(isNaN("abc")); // true "abc" は変換すると NaN
// Number.isNaN は変換せず、値そのものが NaN のときだけ true
console.log(Number.isNaN("abc")); // false "abc" は文字列であって NaN ではない
console.log(Number.isNaN(NaN)); // true
// 変換結果が NaN かどうかを安全に確かめる書き方
const n = Number("12px"); // NaN
if (Number.isNaN(n)) {
console.log("数値に変換できませんでした");
}
基本的には、変換結果が正しく数値になったかを確かめるときは Number.isNaN を使うと安全です。あわせて、値が整数かどうかを調べたい場合は Number.isInteger が使えます。小数を含む値には false を返すので、整数を期待する処理の入力チェックに便利です。
console.log(Number.isInteger(3)); // true
console.log(Number.isInteger(3.5)); // false 小数は整数ではない
console.log(Number.isInteger("3")); // false 文字列も false(変換しない)
3つの使い分けの指針
ここまでの内容をふまえると、使い分けはシンプルに整理できます。"12px" や "3.14em" のように数値のあとに単位や余計な文字が付いている値から数値だけを取り出したいなら、途中で止まってくれる parseInt(整数でよい場合)や parseFloat(小数を含む場合)が適しています。逆に、入力が「純粋な数字」かどうかを厳密に判定したい、あるいは真偽値なども含めて変換したいなら Number や単項プラスが向いています。
特にフォームの数値入力を検証するときは、途中まで読めてしまう parseInt だと "12abc" が 12 として通ってしまいます。「全体が数値でなければエラーにしたい」ケースでは Number と Number.isNaN の組み合わせが確実です。
変換後の整形にも触れておく
数値へ変換したあと、表示のために桁を整えたい場面もあります。toFixed は小数点以下の桁数を指定して文字列として返し、toString に基数を渡すと2進数や16進数の文字列表現が得られます。いずれも戻り値は文字列になる点に注意してください。
console.log((3.14159).toFixed(2)); // "3.14" 小数第2位までの文字列
console.log((255).toString(16)); // "ff" 16進数の文字列
console.log((5).toString(2)); // "101" 2進数の文字列
まとめ
文字列を数値に変換する3つの方法は、次のように整理できます。parseInt と parseFloat は先頭から読めるところまでを解釈し、単位付きの値から数値を取り出すのに向いています。parseInt では基数(radix)を 10 のように明示するのが安全です。Number と単項プラス + は文字列全体を数値にし、不正な文字があれば NaN、空文字は 0 になります。厳密に数値かどうかを判定したいときはこちらを使い、変換結果の確認には Number.isNaN を用いましょう。入力の性質と「途中で止めたいか/全体を厳密に見たいか」で選べば、迷わず使い分けられます。