さっそくつっこまれてしまった。
Re:浮動小数点数を整数にする高速(?)な方法 - JavaScript比較
http://javascript.g.hatena.ne.jp/edvakf/20090203/1233678994
ちょっと、いや、かなりひやりとしたが、今は突っ込まれてうれしいレベルなのである。つっこみ自体始めてなのだから。
まぁ自分は、素人の目の前でコードを動かしてびっくりさせることが専門だとまだ自認しているし(自慢してるわけじゃないぞ)、プログラムよりはiTunesでの楽曲管理のほうが幾分も得意だという状態だ。
根本的に、専門は哲学、社会・文芸評論なのは、忘れてはいない。そちらの鍛錬を欠かしたことはない。
反省会
さて、内容に関して、題名に反して反省してみる。修正といった方が正しいかもしれない。
3つの理由から、この記事はちょっとアレゲかなと思ってしまう。
- Math 関数を呼ぶのは気持ち悪い。
- floor と abs の順番が違うと結果が変わる。
- Math.floor は意図しない結果を返すかもしれない。
致命傷になるのは2と3。
〈効率より精度〉、は原則だから。
しかし、関数呼び出しである Math.floor よりも、プリミティブ演算を使うほうが格段に早い。
例えば、0以上2147483648未満の少数に限られるが、「その数を越えない最大の整数 (の正部分)」を求めるなら、amachang さんの書いている ~~ 演算が使え、速度は以下のようになる。
そう。「~」はnot、否定演算子、ビット演算子の一種。正しい。
オペランドは32bit整数に強制変換される。めでたしめでたし。――ではダメなのだ。〈効率より精度〉。(私の説明が不足していたのだが。)
「floor と abs の順番が違うと結果が変わる」はしまった、と思った。
(parseInt()は符号を揃えないのでダメ。気付くのが遅い。)
Math.floor(3.1); // -> 3 Math.floor(3.0); // -> 3 Math.floor(-3.1); // -> -4 Math.floor(-3.0); // -> -3
(ミスった……。)失態の原因は、Math.floor()の定義は「切り落とし」だと思っていたから。
ではなく、「超えない最大の整数」だったのか。信じきってて試したことはなかった。
Math.ceil(3.1); // -> 4 Math.ceil(3.0); // -> 3 Math.ceil(-3.1); // -> -3 Math.ceil(-3.0); // -> -3
ということで、好みと目的次第で、調節するしかなさそうだ。
「Math.floor は意図しない結果を返すかもしれない」
はて?
おぉっとぅ。二進誤差か。失念していた。
なるほど。Number#toFixed()か。
n = Math.abs(parseInt(Number(n).toFixed(5)));
(長い。効率はすごく悪そうだ。10進Objectをつくるよりはましな気もする。)
おぉ、こわいこわい。
でも精度で言えば、十進数で処理すれば絶対(いちおう)。
1番について。
1つ目は、何度も書いているように、Math.floor が関数呼び出しであることと、Math がグローバルオブジェクトであること。
いくら素人だからって、これは知ってる。(喜ばしいことだ。)
確かに、Math.floor()だけを繰り返し使うのなら、
(function(){ for(var i=0; i<100000; ++i) Math.floor(n); })();
より
(function(){ var Mf = Math.floor; for(var i=0; i<100000; ++i) Mf(n); })();
が効率がいい。突っ込み記事に書いてあるように、スコープチェーンの辿り方が原因だ。
だがこのような使い方は、想定できない。少なくとも、僕が想定したのはこれではない。
関数の入り口で、引数を整形するためのコードだ。(また明らかに僕の説明不足だが。)
つまり。
自然数しか許されないところに。ユーザーは配列をつっこむかもしれない。負数? 実数? 文字列を突っ込みやがるかもしれない。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は使われない。 };
の繰り返しが効率がよいというのは、ないだろう。
まぁライブラリ全体をクロージャで囲って、そこでローカル変数宣言しておく、というのも無いじゃないが。好みにあわないだけだ。
前言を翻すようだけど、まぁここまで書きながら、専用の関数つくった方がいいんじゃないか、と思えてきたが……。
許せ
断定的な口調やタイトルなのは、ほら、あれだ、釣りだ。
文章は先ず、タイトル命だと思っている。手にとってもらえなければ、読まれることはないからだ。そして、古き良き読者だけをかためてゆくつもりはない。
3つの理由から、この記事はちょっとアレゲかなと思ってしまう。
いや、アレゲLunaticです。まぁアレゲなことするためにこのブログを始めたんだ。許せ。
アレゲでないものは
http://c4se.sakura.ne.jp/ne_index.html
本サイトへ。更新遅いけど。
一部、本サイト掲載前に、こっちに文章を載せている。このブログも無意味ではない。
(と云うか、始めは更新情報のブログにするつもりだったんだ……。)