端的に言ってしまうと…
javascriptでUInt32な数値を扱いたかったんです
ん?
いや、とっ掛かりはやはりXorShift
な訳で、ある乱数列にデータを重畳してプログラム間で受け渡ししたいな、と
まぁその様に思った次第です
でも今回のプログラム間、ってのがC# ⇔ javascript
なんです
ここで問題になるのがjavascript
の乱数はseed
を必要としません、ってか「受け付けません」
それでは乱数列の共有など出来るハズもありませんから、何かseed
を指定できる簡便な乱数発生器はないかな?
と思って行き当たったのがXorShift
なんです
XorShift
これはググって頂ければすぐ分かりますが、すんごい軽量ながらseed
を元に結構ガチな乱数列を捻り出してくれる(らしい)疑似乱数発生器です
おーっ!
こいつぁ御誂え向き…
なんて思ったのも束の間
そこそこの周期を期待するなら、unsigned int
な数値を扱える必要があるぅ?
いやいやいやー
御冗談を
javascript
の扱える数値に型なんて概念ありませんぜ
なんなら、整数と小数どころか数値列と文字列の境だって曖昧だっちゅーの
実装(C#)
ま、それはさておき、やってみましょに
まずはC#からですね
public class XorShift {
private uint t = 0u;
private uint x = 123456789u;
private uint y = 362436069u;
private uint z = 521288629u;
private uint w = 88675123u;
public XorShift() { }
public XorShift(int seed) {
w = (uint)seed;
}
public uint Next() {
t = x ^ (x << 11);
x = y;
y = z;
z = w;
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
return w - 1;
}
public uint Next(uint maxValue) {
return Next() % maxValue;
}
public uint Next(uint minValue, uint maxValue) {
return minValue + (Next() % (maxValue - minValue));
}
public void NextBytes(byte[] buffer) {
for(int i = 0; i < buffer.Length; i++)
buffer[i] = (byte)Next(256);
}
public double NextDouble() {
return (double)Next() / uint.MaxValue;
}
}
こんな感じですかね?
uint
を返してしまう処が本家Random
と違う処ではありますが、メソッドの構成などは合わせてみました
ここでポイントとなるのは、Next()
の最後でw - 1
を返している処ですね
実は問題として指摘されているのが、XorShift
はzero
を返さないよね? と…
うーん、そうなんだー
でも、zeroを返さない(かも知れない)けど、1は返す可能性があるよね?
じゃぁ、1引いちゃえばいいじゃん…
なんてね
実はNextDouble()
の仕様から考えると、その方が健全かなと云う結論になります
つまり、最後に1を減ずる事でNext()
の定義域が0 <= Next() < uint.MaxValue - 1
となって、必然的にNextDouble()
の定義域は0.0 <= NextDouble() < 1.0
になる、と云う訳です
逆に云うと、Next()
はuint.MaxValue
を返さないよね?
なんですけど、そこは目を瞑る事で7方位は丸く収まるので良いのではないでしょうか
で、実行!
XorShift rand = new XorShift();
for(int i = 0; i < 10; i++)
Console.WriteLine(rand.Next().ToString("x08"));
みたいな感じで、こう
dca345e9
1b5116e5
951049a9
d88d00af
1ec7825d
8db24145
9af81442
2ac00f2b
0837ad57
17906568
これをベースにしてjavascript
の実装が同じ乱数列を返せば目的は達成!
と云う事で…
実装(javascript)
あ!
ここに来てなんですが、私の云っている「javascript
」はwindows shell
で使われる方です
IE
とかChrome
とかEdge
で実行できる方のjavascript
ではなくて…
つまり、ちょっとばかり古いjavascript
と云う事
聞く処に因ると、新しい奴は、class
定義ができたり、部分的に型の定義ができるらしい…
とか、そんな便利なんじゃない方です
勿論、スクリプトエンジンを指定すれば新しい方で動いたりする訳なんですが、起動時に1枚噛ませないといけなくなるので面倒臭いでしょ?
さて、そんな訳で取り敢えずC#
をjavascript
に書き直してみる事にしましょうか
var XorShift = function(seed) {
this.t = 0;
this.x = 123456789;
this.y = 362436069;
this.z = 521288629;
this.w = seed === undefined ? 88675123 : seed;
XorShift.prototype.next = function() {
this.t = this.x ^ (this.x << 11);
this.x = this.y;
this.y = this.z;
this.z = this.w;
this.w = (this.w ^ (this.w >>> 19)) ^ (this.t ^ (this.t >>> 8));
return this.w - 1;
}
XorShift.prototype.nextDouble = function() {
return this.next() / 0xffffffff;
}
}
こんな感じ?
(一部のオーバロードは諸事情で割愛しました)
で、実行!
var rand = new XorShift();
for(var i=0; i<10; i++)
WScript.Echo(rand.next().toString(16));
-235cba17
1b5116e5
-6aefb657
-2772ff51
1ec7825d
-724dbebb
-6507ebbe
2ac00f2b
837ad57
17906568
う~~~ん…
全然違う、どころかマイナスになっちゃうよ(uint
であって欲しいのだが…)
考察
えーっと、何でうまくいかないか、って云うのを-
だって、
javascript
はuint
表現できないんだもん
の一言で片づけるのは安直です
何故ならjavascript
の整数の有効桁数って53bit
くらいあるんですよ?
高々32bit
で表わされるビット列を11bit
程度左シフトしたところで、msb
まで侵食して符号反転しちゃうとは考えられません
符号は別の論理が働いて反転させられてしまっていると考えるのが合理的です
一つ実験してみましょう
> 0x7fffffff
2147483647
> 0x7fffffff + 1
2147483648
> (2147483648).toString(16)
80000000
>
ほら、所謂int.MaxValue
に1
を足してもint.MinValue
にならずにuint
領域に踏み込んでしまっているでしょ?
工夫すればuint
の計算だってできる可能性があるって事じゃないでしょうか?
ところで、さっきのjavascript
の実行結果、よ~く見てみるとマイナス符号が付かない奴はC#
と結果が一致してますね…
でもマイナスの方は全然違う感じ
お!
そうだマイナス符号付いちゃってるんで、ビット反転してみるってのはどうでしょう?
> (0x235cba17 ^ 0xffffffff).toString(16)
-235cba18
>
あれ?
ほぼ同じだ…
あー、0xffffffff
が符号反転の原因になってるのかなぁ
じゃぁ、取敢えずこう
> (0x235cba17 ^ 0x7fffffff).toString(16)
5ca345e8
>
大分近くなった(C#ではdca345e9)
乱数列の正符号側がずっーっと一致しているのと考え合わせると、ビット列は保存されている様です
今回の仮実装での表示の仕方(され方?)が問題なんでしょう
となると、マイナス表示されない様にするにはどうしたら良いか?
を探れば良いと云う事ですね
ゴールに向けて
んー
肝はやっぱり「符号(サインビット)」かと思います
そもそもjavascript
って+0
と-0
が表現できてしまう(らしい)ので、サインビットは数値とは別に存在すると云う事ですよね
で、ここでもう一回テストです
> 123456789 << 1
246913578
> 123456789 << 2
493827156
> 123456789 << 3
987654312
> 123456789 << 4
1975308624
> 123456789 << 5
-344350048
> 123456789 << 6
-688700096
> 123456789 << 7
-1377400192
> 123456789 << 8
1540166912
> 123456789 << 9
-1214633472
> 123456789 << 10
1865700352
> 123456789 << 11
-563566592
マイナスになったりプラスになったりしますね
10進
だと良く分からないので16進
で表現してみましょうか
> (123456789).toString(16)
75bcd15
> (123456789 << 1).toString(16)
eb79a2a
> (123456789 << 2).toString(16)
1d6f3454
> (123456789 << 3).toString(16)
3ade68a8
> (123456789 << 4).toString(16)
75bcd150
> (123456789 << 5).toString(16)
-14865d60
4ビット
左シフトまではプラスの数値ですが、5ビット
シフトすると負数になってしまいます
つまり、4ビット
シフトまでは32ビット
数値として考えた時のmsb
はゼロなので正数を表しているけど、5ビット
シフトした時にmsb
に1
が立ってしまうので負数と認識してしまうのではないでしょうか?
その結果、どこか違う所に管理されている「サインビット」が立ってしまうんじゃないのかなぁ…
と云う事で75bcd150
の方を左シフトした結果から5ビット
シフトの数値を考えてみましょう
ここでjavascript
の力を借りてしまうと話がややこしくなるので、手作業です
7 5 b c d 1 5 0
0111 0101 1011 1100 1101 0001 0101 0000
<<
1110 1011 0111 1001 1010 0010 1010 0000
e b 7 9 a 2 a 0
~
0001 0100 1000 0110 0101 1101 0101 1111
1 4 8 6 5 d 5 f
+1
0001 0100 1000 0110 0101 1101 0110 0000
1 4 8 6 5 d 6 0
シフトしたうえで2の補数
をとると5ビット
左シフトの結果と一致しました!
つまり、数値としてはeb79a2a0
として表現されているんですが、サインビットが立っているので表示させると2の補数
形式で解釈されている、と考える事が出来ます
?? あれ? 普通じゃん
………
じゃぁ、ビット列としては期待通りになっているけど、何かのタイミングで立ってしまっているどこか違うところにある「サインビット」をクリアすることができればuint
の数値として表現してくれるのでは?
!!
ここで閃きました
サインなし右シフトって演算があるじゃないですか!
いや、何の論理的な根拠もないですけど、実験実験
> (123456789 << 5).toString(16)
-14865d60
> ((123456789 << 5) >>> 0).toString(16)
eb79a2a0
>
ほら、綺麗に5ビット
左シフトの結果が得られました
何でこうなるのかはjavascript
の中の人に詳しい説明を聞きたい気分ですが、取敢えずサインなし0ビット右シフト
を行う事でどっかにあるサインビットはクリアされるようです
ビット列演算の結果は常に保存されていた様なので、return
の値だけuint
補正を掛ければ良い筈
と云う事で最終結果
var XorShift = function(seed) {
this.t = 0;
this.x = 123456789;
this.y = 362436069;
this.z = 521288629;
this.w = seed === undefined ? 88675123 : seed;
XorShift.prototype.next = function(min, max) {
this.t = this.x ^ (this.x << 11);
this.x = this.y;
this.y = this.z;
this.z = this.w;
this.w = (this.w ^ (this.w >>> 19)) ^ (this.t ^ (this.t >>> 8));
return (this.w >>> 0) - 1;
}
XorShift.prototype.nextDouble = function() {
return this.next() / 0xffffffff;
}
}
結果は、こうなりました
dca345e9
1b5116e5
951049a9
d88d00af
1ec7825d
8db24145
9af81442
2ac00f2b
837ad57
17906568
C#
の結果と合いましたね
参考情報
REPLは役に立ちました