Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2009-02-03

Re:浮動小数点数を整数にする高速(?)な方法 - JavaScript比較

01:36

浮動小数点数を整数にするには、僕は普段、

n = Math.abs(Math.floor(Number(n)));

とやっている。とりあえずどんなObjectだろうと整数かNaNに出来る。

(中略)

ということで、parseInt()は、この目的には使わない方がよさそうだ。

Math.abs(Math.floor())にするか、Math.floor(Math.abs())にするかは、好みで良いと思う。

浮動小数点数を整数にする高速(?)な方法 - JavaScript比較 - c4se記

3つの理由から、この記事はちょっとアレゲかなと思ってしまう。

  1. Math オブジェクトの関数を呼ぶのは気持ち悪い。
  2. floor と abs の順番が違うと結果が変わる。
  3. Math.floor は意図しない結果を返すかもしれない。

Math オブジェクトの関数を呼ぶのは気持ち悪い

1つ目は、何度も書いているように、Math.floor が関数呼び出しであることと、Math がグローバルオブジェクトであること。

javascript:(function(){var n=1000000;var t=new Date().getTime();
for(;--n>=0;)Math.floor(1.234567890);
alert(new Date().getTime()-t)})()
BrowserVersionms
Firefox 3.0.5 278
Safari 3.2.1 282
Opera 9.63 481
javascript:(function(){var n=1000000;var t=new Date().getTime();
var m=Math;
for(;--n>=0;)m.floor(1.234567890);
alert(new Date().getTime()-t)})()
BrowserVersionms
Firefox 3.0.5 117
Safari 3.2.1 191
Opera 9.63 424
javascript:(function(){var n=1000000;var t=new Date().getTime();
var f=Math.floor;
for(;--n>=0;)f(1.234567890);
alert(new Date().getTime()-t)})()
BrowserVersionms
Firefox 3.0.5 90
Safari 3.2.1 139
Opera 9.63 342

このように、何度も呼び出すなら Math.floor を別の変数に入れておいたほうが結果は良くなる。(何度も呼び出さないならこの程度の演算で速度を気にする必要は無いはず)

しかし、関数呼び出しである Math.floor よりも、プリミティブ演算 (含ビット演算) を使うほうが格段に速い。

例えば、0以上2147483648未満の少数に限られるが、「その数を越えない最大の整数 (の正部分)」を求めるなら、amachang さんの書いている ~~ 演算が使え、速度は以下のようになる。

javascript:(function(){var n=1000000;var t=new Date().getTime();
for(;--n>=0;)~~1.234567890;
alert(new Date().getTime()-t)})()
BrowserVersionms
Firefox 3.0.5 30
Safari 3.2.1 50
Opera 9.63 70

floor と abs の順番が違うと結果が変わる

「0以上2147483648未満の少数」限定なんてルール違反と言われればそうなのだが、原文のほうも定義が曖昧なので、厳密な方法は挙げようにも挙げられない。

理由はこういうこと。

javascript:alert(Math.floor(Math.abs(-3.14)));
//=> 3
javascript:alert(Math.abs(Math.floor(-3.14)));
//=> 4

つまり、求めたいものが「その数を越えない最大の整数の正部分」なのか「その数の正部分を越えない最大の整数」なのか、元記事からは知りかねる。

(「とりあえずどんな Object だろうと (値は気にしないが) 整数か NaN に出来る」というだけなら ~~ でも良いのでは?)

ちなみに

javascript:alert(parseInt(-3.14));
//=> -3

Math.floor は意図しない結果を返すかもしれない

最後に、Math.floor を使うときには結果に十分注意したほうがいい。

javascript:alert(Math.floor( ( 0.1 + 0.7 ) * 10 ))
//=> 7

これは意図する結果か?

例は以下から拝借した。


結論

  • ベンチマークを取るときは定義を曖昧にしない。
  • 「浮動小数点数を整数にする」のは、プリミティブ演算を使えばまだ高速にできそう。(その場合は使える値の最大値が出てきてしまうかもしれない)

追記

どうやら返事が来たようで。この日記もちゃんとした突っ込みが来たのは始めてなので、けっこう嬉しい。

自然数しか許されないところに。ユーザーは配列をつっこむかもしれない。負数? 実数? 文字列を突っ込みやがるかもしれない。Nodeオブジェクトがやってくる可能性もある。

そういう想定での、整形。

つまり、

var f = function(n){
  n = toNatural(n);
  
  // something usefull
  
};
/* toNatural()は想像上の処理 */

のような。この場合、

var fun = function(n){
  n = Math.floor(n);
};

の繰り返しより

var fun = function(n){
  var Mf = Math.floor;
  n = Mf(n);
  // 以後Mfは使われない。
};

の繰り返しが効率がよいというのは、ないだろう。

まぁライブラリ全体をクロージャで囲って、そこでローカル変数宣言しておく、というのも無いじゃないが。好みにあわないだけだ。

とくに反省は無い…… - c4se記

クロージャを使って効率を出すのは「好みの問題」で却下というわけだそうです。

これも実験してみた。

javascript:
(function(){
var t=new Date().getTime();
var fun = function(){
  Math.floor(Math.abs(1.23456789));
  /*何か別の処理*/
};
for(var i=0;i<1000000;i++)fun();
alert(new Date().getTime()-t)
})()
BrowserVersionms
Firefox 3.0.5 740
Safari 3.2.1 764
Opera 9.63 1158
javascript:
(function(){
var t=new Date().getTime();
var floor = Math.floor;
var abs = Math.abs;
var fun = function(){
  floor(abs(1.23456789));
  /*何か別の処理*/
};
for(var i=0;i<1000000;i++)fun();
alert(new Date().getTime()-t)
})()
BrowserVersionms
Firefox 3.0.5 408
Safari 3.2.1 492
Opera 9.63 835
javascript:
(function(){
var t=new Date().getTime();
var toNatural = function(n){
  var floor = Math.floor;
  var abs = Math.abs;
  return floor(abs(n))
};
var fun = function(){
  toNatural(1.23456789);
  /*何か別の処理*/
};
for(var i=0;i<1000000;i++)fun();
alert(new Date().getTime()-t)
})()
BrowserVersionms
Firefox 3.0.5 911
Safari 3.2.1 1073
Opera 9.63 1450

最後の方法が遅いのは、関数呼び出しが前2つの2回から3回に増えてるからかな?

2番目は1番目に比べてスピード6割増しだが、数万回の繰り返しなら数 ms 程度の差なので気にしなくてもよさそう。

トラックバック - http://javascript.g.hatena.ne.jp/edvakf/20090203
 |