JavaScriptのparseFloat()parseInt()について、仕様を参照しつつ、適当な値を渡して何を返すか実験してみる。

parseFloat(string)の仕様

仕様では、parseFloat()は以下のような処理を実行するよう定められている。

  1. 引数に渡された値を文字列に変換する。
  2. 変換した文字列の前後の空白を削除する。
  3. 空白を削除した文字列をUnicodeコードポイントの配列に変換する。
  4. +あるいは-、あるいは数値で始まる10進数の文字列を、先頭から可能な限り長く取得する(取得できない文字列はNaNを返す)。
  5. パースした浮動小数点数を返す。

上記の通り、parseFloat()は、渡された文字列の先頭から数値(10進数リテラル)に解釈できるものを取り出し、解釈できなかった時点で処理を止める。10進数リテラルに解釈できなかった部分はただ無視され、そのことに対する情報は示されない。

parseInt(string, radix)の仕様

parseInt()仕様は、以下のような手順を定めている。

  1. 第1引数に渡された値を文字列に変換する。
  2. 変換した文字列の前後の空白を削除する。
  3. 符号を1にする。
  4. 文字列の先頭が0x002D (HYPHEN-MINUS)なら、符号を-1にする。
  5. 文字列の先頭が0x002B (PLUS SIGN)か0x002D (HYPHEN-MINUS)なら、文字列をインデックス1からの部分文字列と解釈する。
  6. 第2引数に渡された基数を、32ビット整数に変換する。
  7. stripPrefixフラグ(接頭辞を削除するかどうか)をtrueとする。
  8. 基数が0でなく、2より小さい、あるいは36より大きいとき、NaNを返す。
  9. 基数が0でなく、16でもないとき、stripPrefixフラグをfalseとする。
  10. 基数が0のとき、基数を10とする。
  11. stripPrefixフラグがtrueのときで、文字列の長さが2以上かつ文頭が0x0Xのとき、文字列をインデックス2からの部分文字列と判断し、基数を16とする。
  12. ここまでに定まった基数桁に含まれる数字を先頭から探していき、該当しない文字を見つけた時点で終端と判断する。
  13. もし文頭から終端までが空なら、NaNを返す。
  14. パースした整数を定まった基数で計算し直し、10進数を返す(負数の場合のみ-がつく。また、20桁以上桁は0としても良い)。

parseFloat()と同じく、文頭の+-を除き、指定された基数の桁に含まれない文字はただ無視される。

返す値のサンプル

html
<html>
  <head><style>table{border-collapse:collapse;}table,th,td{border:solid 1px #ddd;padding:8px;}</style></head>
  <body>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"></script>
    <script>
      const { input, table, tr, th, td } = van.tags;
      const values = [
        "0",
        "-1",
        "+1",
        "0.1",
        ".10",
        "1e1",
        "1e+1",
        "1e-1",
        "1e1",
        ,
        "aaa",
        "",
        "1.",
        " 1",
        " 1 ",
        "e-4",
        "1+1",
        "--1",
        "1 1 1",
        "ー1",
        "2a",
        "a+1",
        "1",
      ];

      const TableRows = values.map((v) =>
        tr(
          td(`"${v}"`),
          td(parseFloat(v)),
          td(parseInt(v)),
          td(parseInt(v, 2)),
          td(parseInt(v, 16))
        )
      );

      const Table = table(
        { style: "text-align:center" },
        tr(
          th("検証する値"),
          th("parseFlaot(v)"),
          th("parseInt(v)"),
          th("parseInt(v, 2)"),
          th("parseInt(v, 16)")
        ),
        TableRows
      );
      van.add(document.body, Table);
    </script>
  </body>
</html>

当然だが、仕様の通りである。parseInt()に渡す基数が変わると、NaNだった文字列が急に意味を持つのが地味におもしろく、直感的に理解しづらい。

例えばparseInt(102, 2)の返り値は2となる。処理の流れとしては、まず102の先頭から2進数が探され、10が取得される。次に2進数の10が10進数に直されて2となり、これが返される。

整理してみたらわりと簡単だった。わかりづらいと感じたのは私だけかもしれない。

おまけ:parseFloat()とparseInt()の挙動チェッカー

html

<html>
  <head><style>table{border-collapse:collapse;}table,th,td{border:solid 1px #ddd;padding:8px;}</style></head>
  <body>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"></script>
    <script>
      const { div, input, span } = van.tags;
      const floatResult = van.state("入力待ち...");
      const intResult = van.state("入力待ち...");
      van.add(
        document.body,
        div(
          span("parseFloat("),
          input({
            type: "text",
            oninput: (e) => (floatResult.val = parseFloat(e.target.value)),
          }),
          span(")"),
          div("結果:", span(floatResult))
        ),
        div(
          span("parseInt("),
          input({
            id: "int1st",
            type: "text",
            oninput: (e) =>
              (intResult.val = parseInt(
                e.target.value,
                document.querySelector("#int2nd").value
              )),
          }),
          span(", "),
          input({
            id: "int2nd",
            type: "text",
            oninput: (e) =>
              (intResult.val = parseInt(
                document.querySelector("#int1st").value,
                e.target.value
              )),
            value: 10,
          }),
          span(")"),
          div("結果:", span(intResult))
        )
      );
    </script>
  </body>
</html>

参考資料