在Java和Python

时间:2016-01-07 15:09:06

标签: javascript java python numpy random

我想在Java,Python和JavaScript中实现XorShift PRNG。给定相同的种子,不同的实现必须生成完全相同的序列。到目前为止,我还没能做到这一点。

我在Java中的实现

在Java中具有以下XorShift PRNG实现(其中xlong字段):

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实现移植到其他语言,虽然它不是很好。

  • 我可以修复上面的实现,以便它们起作用吗?
  • 我应该切换到另一个更容易在prog.langs上实现的PRNG函数吗?

我已经阅读了有人建议使用Python的java.util.Random类的SO。我不想要这个,因为我也需要JavaScript中的功能,而且我不知道这些包存在那里。

3 个答案:

答案 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位,数字包围。我没有进一步调查过。