JavaScriptの整数化処理の性能比較

JavaScriptの数値の整数化処理の実装方法やアルゴリズムによる、実行速度の違いをテストしてみました。 個人的に整数化が必要になるシーンがしばしばあるのですが、整数化の方法は複数存在し、 何を使うのが最適か知っておきたいと思ったことがきっかけです。

環境・条件は以下のとおりです。

環境・条件


小数を整数にする

処理種類

※「こちらのページ」で各ソースの確認と、 あなたのブラウザを使って実際にテストを実行できます。


「test01:parseInt()」は、本来は文字列で表現された数値を整数に変換する関数です。 その名称から数値の整数化でもつい使いたくなりますし、数値に対して使用してもでも 暗黙的に文字列に変換してくれるためエラーにはなりませんが、本来は目的が異なります。

「test02:num.toFixed(0)」は、数値を任意の小数点位置の固定小数点表記の文字列に変換する関数です。 「小数点第2位で表示を統一」などとしたい場合に便利です。 引数に「0」を指定することにより、整数表現の文字列を返します。

「test03:num.toLocaleString()」は、現在のロケールまたは指定されたロケールでの既定の書式を使用して、 数値を文字列に変換します。引数の指定により、カンマ区切り, 最小桁数, 最大桁数, 小数の最小桁数, 小数の最大桁数 などを調節した表現の文字列を生成します。特にカンマ区切りを自動的に行ってくれるのはとても便利です。

test01~test03は、本来整数化を行うことが目的ではない関数・メソッドを利用した処理です。 どれも「文字列→整数値」もしくは「数値→整数の文字列表現」と型変換を伴います。

「test04:Math.floor()」は、数値の整数化に最も適した組み込み関数です。小数値を受け取り整数値を返します。 今回用意した処理では唯一、一切の型変換を伴わない処理です。

test05~test10はすべてビット演算を使用しています。JavaScriptではビット演算時は 32ビット整数に変換して計算します(JavaScriptの基本です!)。 その過程で小数部は切り捨てられることを利用しての整数化です。


ただし、ビット演算を使用した処理には大きな欠点があります。 元の数値型は倍精度浮動小数点数を使用しており、整数なら53ビット (9007199254740992・9007兆・16桁・(2 ^ 53))までを正確に表現できます。 しかしそれが32ビット整数に変換されてしまうため、 大きな数は上位ビットが切り捨てられて桁あふれしてしまいます。

具体的には2147483647(21億・10桁・(2 ^ 31 - 1))が正しく計算できる上限です。 例えば「12345678901.2345」をtest01~test04で処理すると、期待通りに「12345678901」になります。 しかしtest05~test10で処理すると、「-539222987」になってしまいます。

その他、test02, test03は実は四捨五入をするので、「123.56」を処理すると124になり、それ以外では123になります。 また、負数の場合に切捨ての仕方が異なり、test01とtest05~test10では、単純に小数部をカットした値になるのに対し、 「test04:Math.floor()」では、「引数として与えた数以下の最大の整数を返す」ので、 「-123.45」を処理するとtest04では「-124」になりそれ以外だと「-123」になるという違いもあります。

…と、実はいろいろ小さな差異が出てしまうのですが、その辺はあまり重要ではないと考え、 前提条件として「仕様の細かい差は無視する」ということにしました。


結果

左から順に、Chrome43, IE11, Firefox38(1回目, 2回目)での結果です。


※「こちら」のページで各ソースの確認と、 あなたのブラウザを使って実際にテストを実行できます。


まず、IEではInfinityになってしまうことが多くグラフが描写できないため、 解消するために「Normalize results」のチェックをはずして実行しました。 条件を合わせるために他のブラウザでも「Normalize results」のチェックをはずして実行しました。

また実行のたびに結果が変わりやすかったため、何度も実行し信頼が高そうな数字を採用しています。 さらにFirefoxでは、本当に実行のたびに結果が大きく変わってしまうため、 数字をそろえようとするのをやめ、2回分の結果を載せることにしました。 そのため、閲覧者のPCで実行して確認することを強くお勧めします。


繰り返しになりますが、test01~test03は、本来整数化を行うことが目的ではない関数・メソッドを利用した処理です。 どれも「文字列→整数値」もしくは「数値→整数の文字列表現」と型変換を伴います。 そのため、test04~test10より遅くなっているのでしょう。機能が豊富なメソッド・関数ほど遅くなっています。

得られる結果が同じであるからなんとなく使ってしまいがちですが、 用途をきちんと考え適切な選択をすることが重要だと気づかさる結果でした。 特に数値の整数化にparseIntを使っている例を非常に多く見かけます。 うっかり犯してしまいがちな選択ミスです。それぞれの関数・メソッドの役割をきちんと理解し、 実装の差異に何が必要なのかよく考えて適切な関数・メソッドを選択するようにしましょう。


Chromeではtest04~test10はほぼ同じ処理速度でした。ビット演算時は 「倍精度浮動小数点数→32ビット整数→倍精度浮動小数点数」と、 内部で2回型変換が行われるため、「Math.floor()」より遅くなると予想していました。 しかし、私の想像よりはるかに「倍精度浮動小数点数・32ビット整数」間の型変換のコストは小さいようです。

IEでは、test05~test10の中でtest10だけがやや遅くなりました。 これは、test10はビット計算を2度行っているためだと思われます。 むしろChromeでは差が出なかったことの方が意外でした。


Firefoxでは実行のたびに結果が激しく変わりました。そのため評価は避けます。 ただFirefoxに限らず、ChromeでもIEでも実行のたびに結果が変わりやすい傾向はありました。 これは、処理内容が極めて単純でプリミティブな処理であるため、 わずかな環境変化に影響されやすいためだと思われます。

現実には、画面への表示もコンソール出力も一切せず、また計算結果を一切使用せず ただ小数を整数にして値を捨てるだけを何十万回も繰り返すだけの処理というのはありえないでしょう。 ですので、ここで大きく出た処理速度差は、グラフの見た目ほど気にする必要はないと思います。

例えば、上記の処理に整数化した値をコンソール出力をする処理を加えるだけで、 全体的な処理速度は大幅に低下します。以下のグラフがその結果です。 これを見る限りtest03以外はほぼ同じ処理速度と評価せざるを得ません。 その理由は、コンソール出力をする処理のコストが整数化処理に比べると非常に大きいことが主因でしょう。


まとめ