l'essentiel est invisible pour les yeux

Wednesday, November 14, 2007

[Javascript] 64ビット演算のエミュレート

「最近記事書いてないね」って突っ込まれる。
プログラムは、たくさん書いているのですが。

ECMA-262で定義されている通り、ビット演算子は符号付き32ビット整数しか扱えない。Number型は倍精度フォーマット IEEE 754型なので64ビットまで扱えるので、結果が2の31乗以上の値になるビット演算子を扱う時には2の32乗を加算して、補正してやる必要がある。

※ Math.pow関数など、2の53乗以上の数は誤差が出るので注意


console.log((Math.pow(2, 53)-1).toString(2)); // => 11111111111111111111111111111111111111111111111111111
console.log((Math.pow(2, 54)-1).toString(2)); // => 1000000000000000000000000000000000000000000000000000000


64ビット演算のエミュレート。
ECMAScript4にならないと演算子のオーバーロードができないので、関数呼び出しでエミュレートする。関数の呼び出しコストについては後述。


var ASSERT = function() { // {{{
var arg = new Array(arguments.length);
for(var i=0,len=arg.length;i<len;++i)i>[i] = arguments[i];
if('console' in window && 'assert' in console) { // use Assert of Firebug
console.assert(arg.shift(), arg.pop());
} else {
if(!arg.shift()) throw(arg.pop());
}
}; // }}}


// 4294967296 is Math.pow(2, 32);
Number.prototype.bitwiseAnd = function(rhs) {
return (this & rhs > -1)? this & rhs : this & rhs + 4294967296;
};
Number.prototype.bitwiseOr = function(rhs) {
return ((this | rhs) > -1)? this | rhs : (this | rhs) + 4294967296;
};
Number.prototype.bitwiseNot = function() {
return ((~this) > -1)? ~this : (~this) + 4294967296;
};
Number.prototype.bitwiseXor = function(rhs) {
return ((this ^ rhs) > -1)? this : (this ^ rhs) + 4294967296;
};

ASSERT(0xffffffff.bitwiseAnd(0xffffffff) == 1, 'bitwiseAnd: 0xffffffff & 0xffffffff is equal 4294967295.');
ASSERT(-0x1.bitwiseAnd(0xffffffff) == -1, 'bitwiseAnd: -0x1 & 0xffffffff is equal -1.');
ASSERT(0x1.bitwiseOr(0xffffffff) == 4294967295, 'bitwiseOr: 0x1 | 0xffffffff is equal 4294967295.');
ASSERT(-0x1.bitwiseOr(0xffffffff) == -4294967295, 'bitwiseOr: -0x1 | 0xffffffff is equal 4294967295.');
ASSERT(0x1.bitwiseXor(0xffffffff) == 4294967294, 'bitwiseXor: 0x1 ^ 0xffffffff is equal 4294967294.');
ASSERT(-0x1.bitwiseXor(0xffffffff) == -4294967294, 'bitwiseXor: 0x1 ^ 0xffffffff is equal -4294967294.');
ASSERT((~1) == -2, 'signed 32 bitwiseNot: (~1) is equal -2');
ASSERT(0x1.bitwiseNot() == 4294967294, 'bitwiseNot: 0x1 ^ 0xffffffff is equal 4294967294.');
ASSERT(-0x1.bitwiseNot() == -4294967294, 'bitwiseNot: 0x1 ^ 0xffffffff is equal -4294967294.');

delete ASSERT;



命令数を減らして高速化したいというニーズから、ビット演算を使用する事が多いかと思うので、上記の場合、関数呼び出しのコストが余分にかかる事に気をつける必要があります。インライン展開したコードに比べて、関数が呼び出されるコードでは、約2.5倍程遅くなるので必要に応じてインライン展開してください。

AND演算を100000回実行した結果
インライン: 387ms
bitwiseAnd関数: 991ms