function bset( canon ) {
var Work = new Array();
var size = Math.ceil( canon.length / 5 );
// 最大要素の桁数
var len0 = canon.length % 5;
if(len0==0) len0=5;
// とりあえず降順に格納。先頭には0がないので必ず十進法で評価される
Work[0] = eval( canon.substr( 0, len0 ) );
var pos = len0;
var tmp;
for( var i=1; i<size; i++ ) {
tmp = canon.substr( pos , 5 );
pos += 5;
// 数値に変換。必ず十進法で評価されるよう0を消す
Work[i] = eval( "1" + tmp ) - BIG_UNIT;
}
// 昇順にする
return my_reverse( Work );
}
逆にこの形式の配列を文字列としての自然な整数表現に変換する bshow() を作っておく。
function badd( B1, B2 ) { var sign1 = bsign(B1), sign2 = bsign(B2); var ret_sign; // 結果の正負の判定 if( sign1 * sign2 <0 ) { var abscmp = babscmp( B1, B2 ); if( abscmp > 0 ) ret_sign = sign1; else if( abscmp < 0 ) ret_sign = sign2; else ret_sign = 1; // 符号だけ異なって絶対値が同じ、和はゼロ } else if( sign1 >= 0 ) { ret_sign = 1; // 正の数同士の足し算 } else { ret_sign = -1; // 負の数同士の足し算 } var sum = new Array(); var max = Math.max( B1.length, B2.length ); for( var i=0; i < max; i++ ) { sum[i] = 0; if( B1[i] ) sum[i] += B1[i]; if( B2[i] ) sum[i] += B2[i]; } // くりあがり等 for( var i=0; i<sum.length; i++ ) { if( ret_sign >= 0 ) { // 結果は非負 while( sum[i] >= BIG_UNIT ) { sum[i]-=BIG_UNIT; if(typeof sum[i+1] == "number" ) sum[i+1]++; else sum[i+1] = 1; } while( sum[i] < 0 ) { sum[i]+=BIG_UNIT; sum[i+1]--; } } else { // 結果は負 while( sum[i] > 0 ) { sum[i]-=BIG_UNIT; sum[i+1]++; } while( sum[i] <= - BIG_UNIT ) { sum[i]+=BIG_UNIT; if(typeof sum[i+1] == "number" ) sum[i+1]--; else sum[i+1] = -1; } } } return bset( bshow( sum ) ); }いったん bshow してから bset し直すことで格納形式を正規化する。数字の頭の0を消したり、近い数の引き算の結果、配列の長さが縮んだときに縮んだ部分を︵0の入った配列でなく︶未定義に戻して大きさと配列の長さが対応するようにするため。
function bsub( B1, B2 ) { return ( badd( B1, bneg(B2) ) ); }
function bmul( B1, B2 ) {
var zeros, _mul;
var Work = bset("0");
for( var idx1=0; idx1 < B1.length; idx1++ ) {
for( var idx2=0; idx2 <B2.length; idx2++ ) {
zeros = getZeros( ( idx1 + idx2 )*5 );
_mul = ( B1[idx1] * B2[idx2] ).toString(10) + zeros;
Work = badd( Work, bset(_mul) );
}
}
return Work;
}
function getZeros( how_many ) {
var _z = how_many % 10;
var _z10 = (how_many - _z) / 10;
var ret = "";
for( var i = 0; i < _z10; i++ ) ret += "0000000000";
for( var i = 0; i < _z; i++ ) ret += "0";
return ret;
}
A = 12345 67890 22345 67890 32345 67890 42345 67890 52345 67890 62345 67890 72345 67890 82345 67890 92345 67890
B = 333 33333 33335 55555 55555 00000 00000 00000 00777 77777 77777 77777 77777 77788 88888 88888 44444 44444 44222
X = A+B =333 45679 01225 77901 23445 32345 67890 42345 68668 30123 45668 40123 45668 50134 56779 71234 12335 36790 12112
A = X-B =12345 67890 22345 67890 32345 67890 42345 67890 52345 67890 62345 67890 72345 67890 82345 67890 92345 67890
OK!
B = X-A =333 33333 33335 55555 55555 00000 00000 00000 00777 77777 77777 77777 77777 77788 88888 88888 44444 44444 44222
OK!
X = (A+B)+C =11 11444 56779 01225 77901 26778 65678 00214 00330 58069 40007 55516 55110 64586 91876 41197 20775 97220 65281 18218
Y = A+(B+C) =11 11444 56779 01225 77901 26778 65678 00214 00330 58069 40007 55516 55110 64586 91876 41197 20775 97220 65281 18218
OK!
X = (A*B)*C =457 24736 67040 08535 28521 59731 75098 57851 98732 72216 07382 63656 75462 12291 46674 46059 98307 52619 87597 43724 32377 94439 41806 17489 01610 69323 68417 76537 81637 16605 73585 89499 96060 73430 88742 68657 66261 31020 27937 30357 75150 21930 72184 91569 07868 59150 40102 41205 24915 15855 15963 72042 85290 50770 39540 27480
Y = A*(B*C) =457 24736 67040 08535 28521 59731 75098 57851 98732 72216 07382 63656 75462 12291 46674 46059 98307 52619 87597 43724 32377 94439 41806 17489 01610 69323 68417 76537 81637 16605 73585 89499 96060 73430 88742 68657 66261 31020 27937 30357 75150 21930 72184 91569 07868 59150 40102 41205 24915 15855 15963 72042 85290 50770 39540 27480
OK!
X = (A+B)*C =3705 07544 54359 13580 24672 72016 45760 70208 62626 73290 32358 67811 66471 18914 27745 80607 63768 77462 02779 69691 66999 61942 58478 34043 20661 44378 45529 00907 68299 65553 11252 97441 26123 94130 72547 34217 23471 55872
Y = A*B+A*C =3705 07544 54359 13580 24672 72016 45760 70208 62626 73290 32358 67811 66471 18914 27745 80607 63768 77462 02779 69691 66999 61942 58478 34043 20661 44378 45529 00907 68299 65553 11252 97441 26123 94130 72547 34217 23471 55872
OK!
Cost: 53.316 sec.
// PはBigInt形式でも通常の自然数でもかまわない。 function bpow( B, P ) { var power; if( typeof P == "number" ) power = P; else if( typeof P == "object") power = b2float(P); else return null; if( power != Math.floor(power) || power > 2048 || power < 0 ) { _debug("[ERROR] inadequate power at function bpow(): power=" + power ); return null } // 3乗までは直接計算する if( power == 0 ) { if( babscmp( B, [0] ) == 0 ) return [ 0 ]; else return [1]; } else if( power == 1 ) return B; else if( power == 2 ) return bmul( B, B ); else if( power == 3 ) return bmul( bmul( B, B ), B ); // powerを2進展開する var bin_str = power.toString(2); var max = bin_str.length - 1; var bin_digit = new Array(); for( var i=0; i<=max ; i++ ) { bin_digit[i] = bin_str.charAt( max - i ); } // Bの2^n乗を計算する var _B = new Array(); // 配列の配列 _B[0] = B; // 最初の要素はB自身 for( var n = 1; n <= max; n++ ) { _B[n] = bmul( _B[ n-1 ] , _B[ n-1 ] ); } var Ret = [1]; for( var i = 0; i < bin_digit.length; i++ ) { if( bin_digit[i] == 1 ) Ret = bmul( Ret, _B[i] ); } return Ret; }例題: 2, 4, 8, 16, 32, ... という2倍2倍で増えてく数列には誰しも興味を持つものだが、1000項めまでいくどどのくらい大きくなるだろうか? 21000を計算するには、bpow( [2], [1000] ) を実行する。結果は次の通り。 10 71508 60718 62673 20948 42504 90600 01810 56140 48117 05533 60744 37503 88370 35105 11249 36122 49319 83788 15695 85812 75946 72917 55314 68251 87145 28569 23140 43598 45775 74698 57480 39345 67774 82423 09854 21074 60506 23711 41877 95418 21530 46474 98358 19412 67398 76755 91655 43946 07706 29145 71196 47768 65421 67660 42983 16526 24386 83720 56680 69376.︵302桁︶ Netscape4.79上での計算時間 43.542 秒であった。 ちなみに JavaScriptの組み込み関数 Math.pow( 2 , 1000 ) を用いると結果は 1.0715086071862673e+301 であって最初の十数桁しか分からない。したがって正確な値が必要なときは、独自の﹁BigInt﹂を使う必要がある。
function bdivmod( B1, B2 ) {
var sign2 = bsign( B2 );
// 第0近似
var _Q = _b2fdiv2b( B1, B2 );
var _R = new Array();
var step;
for( step=1; step<100; step++ ) {
// 剰余を求める
_R = bsub( B1, bmul( B2, _Q ) );
// 剰余が非負で割る数B2より絶対値が小さければ計算終了
if( bsign( _R ) >=0 && babscmp( _R, B2 ) < 0 ) return [ _Q, _R ];
// しからざれば ...
if( sign2 >= 0 || step%2==0 ) {
// 残差をB2で割った商を足して_Qを補正
_Q = badd( _Q, _b2fdiv2b( _R, B2 ) );
} else {
// 負数で割る場合、商+1も試さねばならない
_Q = badd( _Q , [1] );
}
}
_debug("[Error] Too many steps at function bdivmod: _R=" + bshow(_R));
return null;
}
B2が負の場合の上の実装はアドホックで効率が悪い。けれど負の数で割ることは、あまりないので、とりあえず次へ進む。
下請け関数 _b2fdiv2b は、BigInt形式の巨大整数2つを引数にとって、floatで割り算した整数商をふたたびBigInt形式で返す。
function _b2fdiv2b( B1, B2 ) { return bset( Math.floor( b2float(B1)/b2float(B2) ).toString() ); }ところでJavaScriptで浮動小数点演算を行うと、2.2220000222222167e+53のような形式で返ることがあるから、bset() は、この形式の文字列を解釈できるように拡張されなければならない。
var part = str.match(/^(\-?)(\d+)\.(\d+)e([\+|\-]\d+)/i); if(part) { var power = eval( part[4] ); if( power < 0 ) return "0"; // part[3] の右端に必要なだけ文字0をつなげる // もしpart[3]がe10をかけても小数部分があるなら、小数部分は捨てる var c; var ret = ""; for( var i=0; i < power; i++ ) { c = part[3].charAt(i) || "0" ; ret += c; } ret = part[2].toString() + ret; // マイナス符号 if( part[1] ) ret = part[1] + ret; return ret; }上記はさしあたって必要なロジックであり、すべての科学記法形式の数値をパースできるわけではないので注意。この拡張によって下請け関数は正常に仕事をすることができるから、結局、巨大整数の割り算が実装された。
function bdiv( B1, B2 ) {
var result = bdivmod( B1, B2 );
return result[0];
}
function bmod( B1, B2 ) {
var result = bdivmod( B1, B2 );
return result[1];
}
bdiv と bmod は同じ関数 bdivmod のラッパーだから、商と余りの両方が必要なときには、個別に呼ばず、直接 bdivmod を呼べば同じ計算を2回しないですむ。bdivmod は配列を返す。第0要素が整数商、第1要素が剰余。負の数の除法でも剰余は0以上になるようにしてある。戻り値もBigInt形式だから、通常の形式で表示するには、bshow を使う。
例題259-1 は素数か?
本来のJavaScriptでは253までの数しか扱えないので、259-1を扱うにはBigIntが必要である。ただし、
259-1 = 576 46075 23034 23487
は10進18桁もあるので、かたっぱしから割ってみる方法では、運が良くないと途方もなく時間がかかる。10万以下には因数がない︵この検証には約4分かかった︶。あきらめずに10万~20万の範囲を検証してみる:
var A=bset("2"), B=bset("59"), C=bset("1"); var N = bsub( bpow( A, B ), C ); var I; document.write("<p>Checking whether it is divisible by:<br>"); for(var i=100001; i<200000; i+=2 ) { if(i%1000==1) document.write( "" + (i-1) + " ...<br>"); if(i%3==0||i%5==0||i%7==0||i%11==0||i%13==0||i%17==0||i%19==0|| i%23==0||i%29==0||i%31==0||i%37==0||i%41==0||i%43==0||i%47==0|| i%53==0||i%59==0||i%61==0||i%67==0||i%71==0||i%73==0||i%79==0|| i%83==0||i%89==0||i%93==0||i%97==0||i%101==0||i%103==0||i%107==0|| i%109==0||i%113==0||i%127==0||i%131==0||i%137==0||i%139==0||i%149==0)continue; var I = bset( i.toString() ); if( bmod( N, I ) == 0 ) { document.write( " <strong>" + i + "</strong> found!</p>" ); break; } }このスクリプトは試行範囲を刻々とフィードバックしながら計算をつづけ、やがて因数 179951 を発見する。259-1 は合成数であり、 259-1 = 179951×320 34317 80337 と分解される︵これは bdiv, bmod を使って一瞬で確認できる︶。
// BのP乗をmodで割ったときの余りを求める // P, mod はBigInt形式でも数値でもかまわない。 function bpowmod( B, P, mod ) { if( !isBigInt(B) ) return null; var power; if( typeof P == "number" ) power = P; else if( typeof P == "object") power = b2float(P); else return null; if( power != Math.floor(power) || power < 0 ) { _debug("[ERROR] inadequate power at function bpowmod(): power=" + power ); return null } else if( power > Math.pow(2, 52) ) { _debug("[NOT IMPLEMENTED YET] a big power at function bpowmod(): power=" + power ); return null } var M = new Array(); if( typeof mod == "number" ) M = bset( mod.toString(10) ); else if( isBigInt( mod ) ) M = bcopy( mod ); else return null; var fmod = b2float(M); // modは2以上でなければならない if( fmod <= 1 ) { _debug("[ERROR] inadequate mod at function bpowmod(): mod=" + bshow(M) ); return null } // 0乗と1乗は直接計算 if( power == 0 ) { if( babscmp( B, [0] ) == 0 ) return [ 0 ]; else return [1]; } else if( power == 1 ) return bmod( B, M ); // powerを2進展開する var bin_str = power.toString(2); var max = bin_str.length - 1; var bin_digit = new Array(); for( var i=0; i<=max ; i++ ) { bin_digit[i] = bin_str.charAt( max - i ); } // Bの2^n乗をMで割った剰余計算する var _B = new Array(); // 配列の配列 _B[0] = bmod( B, M ); for( var n = 1; n <= max; n++ ) { _B[n] = bmod( bmul( _B[ n-1 ] , _B[ n-1 ] ) , M ); } var Ret = [1]; for( var i = 0; i < bin_digit.length; i++ ) { if( bin_digit[i] == 1 ) Ret = bmod( bmul( Ret, _B[i] ), M ); } return Ret; }例題: 694314719を17441で割った余りを求めよ。 ﹁高校数学で遊ぶ公開鍵暗号RSA﹂では手動で計算したが、ここまでの準備があれば、たった一行のスクリプト var answer = bpowmod( [6943], [14719], [17441] ); を実行するだけでいい。答は4980。
bset(str) |
文字列strの形で与えられた巨大整数を一定の形式の配列(以下BigInt形式と仮称)に格納、その配列を返す。メモリが許す限り無制限の桁数の数を格納できる。 |
---|---|
bshow(B) |
BigIntオブジェクトBを通常の十進数としての自然な文字列で返す。 |
badd(A,B) |
2つの巨大整数AとBの和を返す。 |
bsub(A,B) |
AからBを引いた差を返す。 |
bmul(A,B) |
AとBの積を返す。 |
bdiv(A,B) |
AをBで割った整数商を返す。 |
bmod(A,B) |
AをBで割った余りを返す。 |
bdivmod(A,B) |
商と余りを同時に(長さ2の配列に入れて)返す。 |
bpow(A,P) |
AのP乗を返す。 |
bpowmod(A,P,M) |
AのP乗をMで割った余りを返す。 |
bsign(A) |
Aが負のとき負の数を、そうでなければ負でない数を返す。 |
bneg(A) |
Aと絶対値が等しく符号が反対のオブジェクトを返す。 |
babscmp(A,B) |
AとBの絶対値を比較して、Aのほうが大きければ正の数、Aのほうが小さければ負の数、等しければ0を返す。 |
b2float(A) |
Aを浮動小数点数にキャストして返す。戻り値はJavaScriptで数としてそのまま使えるが、Aの絶対値が253を越えている場合、精度が失われる。 |
bcopy(B) |
Bと等しいBigInt配列を作って返す。コピーをいじってもオリジナルのBには影響しない。 |
isBigInt(B) |
Bが形の良いBigIntオブジェクトならtrueを返す。そうでなければfalseを返す。 |
bdigit(B) |
Bの桁数(十進法)を返す。 |
document.write( Math.pow( 7, 20 ) ); // 79792266297612000 document.write( Math.bpow( 7, 20 ) ); // 79792266297612001上の例では720 を計算している。JavaScript の組み込み関数
Math.pow
では精度が失われる。BigIntライブラリをインクルードすることによって使用可能になる Math.bpo
w
メソッドは、正確な答を返す。
以下は古い記事︵bigint.js v0.4︶です。
はるかに高速化された bigint.js v0.5 をごらんください。
objBigint = new Bigint( arg )
巨大整数オブジェクトを生成するコンストラクタ。arg は文字列でも数値でも良い。文字列では、数字以外の文字は無視されるので、適当にスペースを入れて書いてもかまわない。小数点以下も無視される。頭のマイナス符号は認識される。数値の場合、JavaScriptの数値として評価される。20桁の整数を表す objBigを生成する例:
objBig = new Bigint("1234567890
55555 00000")
Bigint.n( arg )
new演算子を使わないで書きたい場合に使う。例えば、Bigin
t.n(1)
は、整数値1と等価な無名オブジェクトを返す。objBig に1を足したいとき、1を表す名前のあるオブジェクトをわざわざ作らずに、
Bigint.add( objBig, Bigint.n(1)
)
とするとができる。実際には、Bigintメソッドの強力な型変換機能により、
Bigint.add( objBig, 1 )
と書いてかまわない。
objBigint.toString(radix)
オブジェクトを文字列に変換する。radix を省略した場合、適当に桁ごとに区切った10進数が返る。これは、そのまま最終的な出力をするのに適した形である。戻り値を eval
で10進数の数値に変換できることが保証されるためには、radix = 10 を明示することが必要であり、かつ戻り値が数値としてJavaScriptで扱える範囲であることで必要充分となる。radix = 2 で2進展開される。JavaScript自身の toStrin
g(2)
と違って、大きな数でも最後の桁まで正確に展開する。.toString() メソッドは JavaScript の同名メソッドを上書きしない。Bigintオブジェクトに対してこのメソッドが呼ばれたときに限って、ここに書いたようになる。BigIntライブラリをインクルードしただけで、すべての toString が巨大整数対応版になるわけではない。2または10以外の radix を指定した場合の動作は未定義。用例は下記デモページにあります。
objBigint.negative()
符号が反対の整数に対応したオブジェクトを返す。objBigintは、Bigintオブジェクトでなければならない。
objBigint.sign()
オブジェクトが表す整数の符号を調べる。正なら1、負なら-1、ゼロなら0を返す。objBigintは、Bigintオブジェクトでなければならない。
objBigint.abs()
オブジェクトが表す整数と絶対値が等しく符号が正の整数に対応するオブジェクトを返す。objBigintは、Bigintオブジェクトでなければならない。
objBigint.getDigits()
オブジェクトが表す整数が10進表記で何桁か?の整数を返す。objBigintは、Bigintオブジェクトでなければならない。
Bigint.cmp(o1, o2)
o1, o2が表す整数を符号も含めて大小比較。前者が大きければ1、後者が大きければ-1、等しければ0を返す。o1, o2は、Bigintオブジェクトであってもなくてもかまわない。数値でも文字列でも勝手に解釈してBigintに変換される。そして戻り値は必ずBigintオブジェクトだ。以下の関数で、Bigintオブジェクトを入れるべきところは、全部、数値や数値を表す文字列を入れても正しく動作することが保証される。
Bigint.equal(o1, o2)
2つのオブジェクトが表す整数が等しければ true、さもなければ false を返す。用例は下記デモページに。
Bigint.add(o1, o2)
, Bigint.sub(o
1, o2)
, Bigint.mul(o1, o2)
2オブジェクトが表す整数の和、差、積に対応する新しいオブジェクトを返す。
Bigint.div(o1, o2)
, Bigint.mod(o
1, o2)
2オブジェクトの整数商と剰余のオブジェクト。ここで剰余は0以上o2になるように整数商ともども選ばれる。負の数の割り算のときには注意。
Bigint.divmod(o1, o2)
2オブジェクトの整数商と剰余のオブジェクトを長さ2の配列でいっぺんに返す。商と剰余が両方必要なときは、この関数を呼んだ方が効率がよい。
Bigint.pow(o1, o2)
o1のo2乗を返す。
Bigint.powmod(o1, o2, o3)
o1のo2乗をo3で割った余りを返す。指数が巨大のとき、Big
int.pow(o1, o2)
を実際に計算させてから、modをとるのは、非現実的に効率が悪い。必ず専用関数 powmod を使う。
Math.badd
, Math.bsub
, Math.bmul
, Math.bdiv
, Math.bmod
, Math.bpow
, etc.
JavaScript組み込みのMathオブジェクトに、これらのメソッドが追加される。これらのメソッドの引数は、数値、数値を表す文字列、Bigintオブジェクトのいずれでもかまわない。Mathオブジェクトに追加されるこれらのメソッドは数値を表記した文字列を返す。その文字列がJavaScriptの数値として評価できる範囲内であれば、そのままJavaScriptの数値として用いることもできるが、とりあえず文字列なので、例えば +1
と書くと、末尾に文字としての1がくっつく。一見ふべんなようだが、そもそも JavaScript では表現できない整数値を文字列で書くという発想なのだから、文字列なのは当然。どうしても数値でほしければ、b2float
を使えるが、精度は保証されない。Mathオブジェクトへのメソッド追加は、あくまで余興というか、ちょっと試す用。Bigintオブジェクトのメソッド︵戻り値もBigintオブジェクト︶のほうが、連続して使い倒せる。
<!-- ライブラリを呼び出す -->
<script type="text/javascript"
src="http://m17n.cool.ne.jp/demo/bigint/bigint_0_4.js"></script>
<script type="text/javascript">
// 試したいコードを書く
var a = Math.pow( 2, 100 );
var b = Math.bpow( 2, 100 );
_debug( "2の100乗は、JavaScript自身によると、" + a );
_debug( "でも、正確な値は、" + b );
</script>
.toString(2)
の壁が253 でなく232 あたりにある︵メモ1︶。いずれにせよ、10進20桁の数を2進数表示に変換するのは .toString(2)
ではまにあわないので、自力でやる必要がある。
function radix2( arg ) { var N = new Array(); if( isBigInt( arg ) ) N = bcopy( arg ); else N = bset( arg.toString() ); var f = b2float(N); var digit = Math.ceil( Math.log(f) / Math.log(2) ); while(1) { var P = bset(digit-1); var test = b2float( bdiv( N, bpow([2],P) ) ); if( test >= 2 ) digit++; else if( test <1) digit--; else break; } var Power = new Array(digit); for( var i=0; i<=digit-1; i++ ) { if( i==0 ) Power[i] = [1]; else Power[i] = bmul( Power[i-1] , [2] ); } var R = bcopy(N); var ret = ""; for( var power = digit-1; power >= 0; power-- ) { var X = Power[ power ]; var Q = bdiv( R , X ); if(b2float(Q)==1) { ret += "1"; R = bsub( R, X ); } else { ret += "0"; } } return ret; }これ以外については、24ビットの場合のコードの+-×÷%等の演算を、badd, bsub, bmul, bdiv, bmod といった巨大整数対応版で置き換えるだけで良い。 このスクリプトは、IE、Mozilla、Netscapeのどれでも動作する。
Math.random()
を使っている。
乱数をタネにして、その周辺の奇数のなかに素数がないか、あさる。このロジックは24ビット版と同じだ。64ビットの場合、鍵を構成する素数は32ビット、つまり10進10桁であるが、このくらいの大きさなら、総当たりで素数判定するのが簡単だし、いちばん高速でもある。しかし、もっと桁数が大きくなると、実際に割ってみる方法で素数性を確認するのでは、破綻する。﹁確率的﹂な素数検定が必要になる。また、53ビットの壁を越えた割り算の場合、現バージョンの bdiv
はとても遅いことに注意する。
ともかく、今のコンピュータ︵2002年︶だと、10桁程度の数は単純な総当たりでさくっと素因数分解できる。
そういうわけだから、64ビット鍵を構成するための32ビット程度の素数について、それが本当に素数かどうか確認するのは、総当たり割り算で良い。
しかし、いったん2素数をかけて64ビット鍵︵10進20桁︶を得ると、今度は、総当たりでもとに戻すのはコスト的に非現実になる。これはRSAの核心をミニチュア版ながら、かいまみせてくれる。例えば、
第一の素数 P = 52682 42677
第二の素数 Q = 60009 18397
が素数であることは1秒程度で確認できるが、それらをかけた合成数
PQ = 31614 29440 02698 28769
をもとに戻すのは、桁違いなコストを要する。実際、i=3 から始めて i=50億以上までループしなければ、分解できない。やってみれば分かるが、1億どころか10万程度でもけっこう大変だ。50億といったら一晩かかっても、たぶん一週間かかってもJavaScriptでは、分解できないかもしれない。しかし、bmul( P, Q ) は1秒未満のコストで計算できる。素因数分解が計算量的に一方通行である、という観念は、こういうことを表している ―― もっとも﹁どんな方法を使っても素因数分解は難しい﹂ということが証明されているわけでは、ない。
n.toString(2)
は数値 nを2進数表記した文字列を返す。しかし、Netscape 4 では232 以上、言い換えれば2進32桁以上の文字列が生ずべき場合に、正しく変換できないばかりか、
/0/0/0/0//000000/00000//000/0/0
のような奇妙な文字列を返すことがある。'1' (0x31) と '0' (0x30) からなる列を返すべきときに '/' (0x2F) が返るのは、﹁繰り上がりすぎて﹂オーバーランしているものと思われる。オーバーフロー時のエラー処理ルーティンがちゃんとなってないようだ。IEおよびMozillaでは、この問題は生じない。ただし253 を越えると変換で精度が失われるが、これは JavaScript の一般的な制限である。