我想在Java,Python和JavaScript中实现XorShift PRNG。给定相同的种子,不同的实现必须生成完全相同的序列。到目前为止,我还没能做到这一点。
我在Java中的实现
在Java中具有以下XorShift PRNG实现(其中x
是long
字段):
public long randomLong() {
x ^= (x << 21);
x ^= (x >>> 35);
x ^= (x << 4);
return x;
}
如果我将x
定为1,则前往randomLong()
的前四次调用将生成:
35651601
1130297953386881
-9204155794254196429
144132848981442561
我在Python中的实现
无论有没有numpy,我都试过了。下面是使用numpy的版本。
def randomLong(self):
self.x ^= np.left_shift(self.x, 21)
self.x ^= np.right_shift(self.x, 35)
self.x ^= np.left_shift(self.x, 4)
return self.x
使用相同的种子,Python函数将生成:
35651601
1130297953386881
-9204155787274874573 # different
143006948545953793 # different
我的JavaScript实现
我还没有尝试过,因为JavaScript的唯一数字类型似乎是基于IEEE 754的双打,这会打开不同的蠕虫病毒。
我认为原因是
Java和Python有不同的数字类型。 Java有32位和64位整数,而Python有很时髦的大型int。
似乎移位运算符具有不同的语义。例如,在Java中有逻辑和算术移位,而在Python中只有一种移位(逻辑?)。
问题
我很高兴答案能让我用这三种语言写一个PRNG,而且速度很快。它不一定非常好。我已经考虑将C libs实现移植到其他语言,虽然它不是很好。
我已经阅读了有人建议使用Python的java.util.Random类的SO。我不想要这个,因为我也需要JavaScript中的功能,而且我不知道这些包存在那里。
答案 0 :(得分:5)
我很高兴答案能让我用这三种语言写一个PRNG,而且速度很快。它不一定非常好。
您可以用3种语言实现32位linear congruential generator。
的Python:
seed = 0
for i in range(10):
seed = (seed * 1664525 + 1013904223) & 0xFFFFFFFF
print(seed)
爪哇:
int seed = 0;
for (int i = 0; i < 10; i++) {
seed = seed * 1664525 + 1013904223;
System.out.println(seed & 0xFFFFFFFFL);
}
JavaScript的:
var seed = 0;
for (var i = 0; i < 10; i++) {
// The intermediate result fits in 52 bits, so no overflow
seed = (seed * 1664525 + 1013904223) | 0;
console.log(seed >>> 0);
}
输出:
1013904223
1196435762
3519870697
2868466484
1649599747
2670642822
1476291629
2748932008
2180890343
2498801434
请注意,在所有3种语言中,每次迭代都会打印一个无符号的32位整数。
答案 1 :(得分:3)
棘手的部分是逻辑右移。如果您可以访问NumPy,那么在Python中最容易做的就是将x
存储为uint64
值,以便算术和逻辑右移是完全相同的操作,并将输出值转换为返回前int64
,例如:
import numpy as np
class XorShiftRng(object):
def __init__(self, x):
self.x = np.uint64(x)
def random_long(self):
self.x ^= self.x << np.uint64(21)
self.x ^= self.x >> np.uint64(35)
self.x ^= self.x << np.uint64(4)
return np.int64(self.x)
需要那些丑陋的转换值转换来阻止NumPy发出奇怪的转换错误。无论如何,这会产生与Java版本完全相同的结果:
>>> rng = XorShiftRng(1)
>>> for _ in range(4):
... print(rng.random_long())
...
35651601
1130297953386881
-9204155794254196429
144132848981442561
答案 2 :(得分:1)
Java和python之间的结果差异是由于语言实现整数的方式不同。 java long是64位有符号整数,在最左边的位中有符号。 Python是......好吧,不同。
据推测,python根据数字的大小编码具有不同位长的整数
>>> n = 10
>>> n.bit_length()
4
>>> n = 1000
>>> n.bit_length()
10
>>> n = -4
>>> n.bit_length()
3
负整数(可能)被编码为符号和幅度,尽管符号似乎没有在任何位中设置。标志通常位于最左边的位,但不在此处。我想这与蟒蛇的数字位长不同有关。
>>> bin(-4)
'-0b100'
其中64位2的补码中的-4将是:
0b1111111111111111111111111111111111111111111111111111111111111100
这在算法上产生了巨大的差异,因为向左或向右移动0b100会产生与移位0b1111111111111111111111111111111111111111111111111111111111111100不同的结果。
幸运的是,这是一种欺骗python的方法,但这涉及自己在两种表示之间切换。
首先需要一些位掩码:
word_size = 64
sign_mask = 1<<(word_size-1)
word_mask = sign_mask | (sign_mask - 1)
现在强迫python成为2的补充,所有的需求都是合乎逻辑的&#39;和&#39;用掩码这个词
>>> bin(4 & word_mask)
'0b100'
>>> bin(-4 & word_mask)
'0b1111111111111111111111111111111111111111111111111111111111111100'
这是算法工作所需的。除非您需要在返回值时将数字转换回来,因为
>>> -4 & word_mask
18446744073709551612L
因此,需要将数字从2的补码转换为带符号的数字:
>>> number = -4 & word_mask
>>> bin(~(number^word_mask))
'-0b100'
但这仅适用于负整数:
>>> number = 4 & word_mask
>>> bin(~(number^word_mask))
'-0b1111111111111111111111111111111111111111111111111111111111111100'
由于正整数应该按原样返回,这样会更好:
>>> number = -4 & word_mask
>>> bin(~(number^word_mask) if (number&sign_mask) else number)
'-0b100'
>>> number = 4 & word_mask
>>> bin(~(number^word_mask) if (number&sign_mask) else number)
'0b100'
所以我已经实现了这样的算法:
class XORShift:
def __init__(self, seed=1, word_length=64):
self.sign_mask = (1 << (word_length-1))
self.word_mask = self.sign_mask | (self.sign_mask -1)
self.next = self._to2scomplement(seed)
def _to2scomplement(self, number):
return number & self.word_mask
def _from2scomplement(self, number):
return ~(number^self.word_mask) if (number & self.sign_mask) else number
def seed(self, seed):
self.next = self._to2scomplement(seed)
def random(self):
self.next ^= (self.next << 21) & self.word_mask
self.next ^= (self.next >> 35) & self.word_mask
self.next ^= (self.next << 4) & self.word_mask
return self._from2scomplement(self.next)
用1播种,算法返回4个第一个数字:
>>> prng = XORShift(1)
>>> for _ in range(4):
>>> print prng.random()
35651601
1130297953386881
-9204155794254196429
144132848981442561
当然,你可以通过使用numpy.int64免费获得这个,但这并没有那么有趣,因为它隐藏了原因差异。
我无法在JavaScript中实现相同的算法。似乎JavaScript使用32位无符号整数并且向右移动35位,数字包围。我没有进一步调查过。