JavaScriptのToNumberとparseInt/parseFloatの仕様と実装の違い
ECMAScriptの規格書は非常に細部に至るまで細かく仕様が規定されていますが、100%全て正しく実装している処理系は僕の知る限りありません。過去に広まってしまったソフトとの後方互換性を維持するため、あえて仕様にあわせていないのだろうというものもあります。ECMAScriptの規格と各ブラウザのScriptエンジンとの違いは、いずれ機会があればまとめたいと思っていますが、コーナーケース(いわゆる重箱の隅系)になると、膨大な量になります。
今回は、文字列を数値に変換する際に登場するToNumberとparseInt/parseFloatについて簡単に紹介します。ToNumberは規格書に登場する内部の実装関数であり、parseInt/parseFloatはご存知JavaScriptのグローバル関数です。
ToNumberの引数が文字列のとき、規格書ではその文字列の文法が厳密に規定されていて、文法に合わない文字列のときはNaNを返すという仕様になっています。その文法で注意すべき点が2つ。
- 0xで始まる16進文字列には符号がつかない
- "Infinity"という文字列だけがInfinityになる
実際のブラウザでどうなるか見てみましょう。
ToNumber | Firefox3.5.4 | Chrome4.0 | Safari4.0.2 | Opera10.00 | IE8.0 |
"0123" | 123 | 123 | 123 | 123 | 123 |
"0x123" | 291 | 291 | 291 | 291 | 291 |
"+0x123" | 291 | NaN | NaN | 291 | NaN |
"-0x123" | -291 | -291 | NaN | -291 | NaN |
"Infinity" | Infinity | Infinity | Infinity | Infinity | Infinity |
"infinity" | Infinity | NaN | NaN | Infinity | NaN |
"inf" | Infinity | NaN | NaN | Infinity | NaN |
"info" | NaN | NaN | NaN | NaN | NaN |
SafariとIEが仕様を満足しているようです。Chromeは網羅テストにうっかりミスでもあるのでしょうか。さすがに、"info"がInfinityになるブラウザはありませんが、Operaは変です。ちなみに、ToNumberはJavaScriptレベルではNumber(x), +x, x-0, x*1, x/1 などxを数値に変換する際に使われます。
次にparseInt/parseFloatですが、parseIntの方はToNumberとは無関係です。parseIntの第2引数にradixを指定できるので、第1引数の文字列には[0-9][a-z][A-Z]が有効な数値文字列となるからです。一方、parseFloatの有効な数値文字列はToNumberに与える文字列のうち、0xで始まる16進数表記を除いたものになります。もちろん両者とも有効数値文字列の後に任意の文字列がついてもよいです。
parseIntについて、実際のブラウザの結果をみてみましょう。
parseInt | Firefox3.5.4 | Chrome4.0 | Safari4.0.2 | Opera10.00 | IE8.0 |
"0123" | 83 | 83 | 83 | 123 | 83 |
"0x123" | 291 | 291 | 291 | 291 | 291 |
"+0x123" | 291 | 291 | 291 | 291 | 291 |
"-0x123" | -291 | -291 | -291 | -291 | -291 |
"Infinity" | NaN | NaN | NaN | NaN | NaN |
"0123"のとき、Operaだけ違う結果になりました。parseInt関数を使うときには、たとえ10進数であっても必ずradixを指定した方が安全です。ECMAScriptの規格の3rd Editionではradixが指定されていない場合に0で始まる文字列を8進数として解釈するか、あるいは10進数として解釈するかは実装依存(ただし10進数を推奨)でしたが、5th Edtionのドラフトではその記述が消えて、完全に10進数として解釈する記述になっています。Operaだけが現在の仕様の推奨に従っています。でも、後方互換性の問題が心配です。
次に、parseFloatを見てみましょう。面倒なので違いがある部分だけ。
parseFloat | Firefox3.5.4 | Chrome4.0 | Safari4.0.2 | Opera10.00 | IE8.0 |
"information" | NaN | NaN | NaN | Infinity | NaN |
あれあれ?Operaさんだけ変です。"information"をparseFloatしたらInfinityになってしまうなんて!parseFloatはToNumberの文字列と同じ文字列を解釈するので、推測するに、Operaは先頭の3文字が大文字小文字関係なく"inf"だったらInfinityとしてしまうような実装をToNumberでしているのでしょう。
このように、ToNumberとparseInt/parseFloatだけを取り上げても各ブラウザでの実装の違いがでてきます。言語実装者にとっては非常に興味深い面白い話ですが、Webプログラマにとっては厄介な話です。ECMAScriptの規格全体で見たとき、このようなコーナーケースでの実装の違い、というか規格に従っていない実装が多量にあって、HTML5も重要だけど、JavaScriptの言語だけでもしっかりして欲しいな、なんて思います。