Java Apache Math3 梅森旋转算法 VS Python 随机数

6 投票
3 回答
741 浏览
提问于 2025-04-18 15:59

我现在有个任务,要把一些Python代码转换成Scala代码,主要是为了研究。
我在使用Apache Math3这个公共库时,遇到了关于MersenneTwister的困难。

在Python中:

SEED = 1234567890

PRIMARY_RNG = random.Random()
PRIMARY_RNG.seed(SEED)
n = PRIMARY_RNG.randrange((2**31) - 1) #1977150888

在Scala中:

val Seed = 1234567890
val PrimaryRNG = new MersenneTwister(Seed)
val n = PrimaryRNG.nextInt(Int.MaxValue) //1328851649

我这里缺少了什么呢?这两个都是MersenneTwister,
而且 Int.MaxValue = 2147483647 = (2**31) - 1

3 个回答

0

如果有人需要这样做,我根据CPython的实现,想出了一个可用的版本。

注意:如果你用字符串作为种子,random.seed() 在Python 2和3之间有变化。这里的pythonStringHash函数与Python 2的版本兼容,或者在Python 3中,你可以使用random.seed(s, version=1)

private static long pythonStringHash(String s) {
  char[] chars = s.toCharArray();
  long x;
  if (s.isEmpty()) {
    x = 0;
  } else {
    x = chars[0] << 7;
  }

  for (char c : chars) {
    x = ((1000003 * x) ^ c);
  }

  x ^= chars.length;
  if (x == -1) {
    return -2;
  }
  return x;
}

private static void pythonSeed(MersenneTwister random, long seed) {
  int[] intArray;
  if (Long.numberOfLeadingZeros(seed) >= 32) {
    intArray = new int[] { (int) seed };
  } else {
    intArray = new int[] { (int) seed, (int) (seed >> 32) };
  }
  random.setSeed(intArray);
}

public static RandomGenerator pythonSeededRandom(String seed) {
  MersenneTwister random = new MersenneTwister();
  pythonSeed(random, pythonStringHash(seed));
  return random;
}

从这里开始,pythonSeededRandom("foo").nextDouble() 的结果应该和random.seed("foo"); random.random() 一样。

3

Apache Commons Math 似乎是用一个整数作为随机数的基础来源,虽然我不太确定它是怎么提取这个整数的。而 Python 则是用 C 版本的算法生成的双精度浮点数。

在种子值的处理上可能也会有差别,但因为它们读取位的方式都不一样,所以即使底层的伪随机生成器是相同的,也不太可能有可比性。

2

正如我在评论中提到的,获取下一个整数的主要算法在Python和Apache Math之间是相同的(源代码可以在这里这里这里找到)。通过查看代码,我们发现主要的区别在于这两个版本如何初始化随机数生成器。Python版本会把给定的种子转换成一个数组,然后从这个数组中进行初始化,而Apache Math版本则有一个单独的算法来从一个数字进行初始化。因此,如果你想让Apache Math的nextInt(...)方法的行为和Python的randrange(...)方法一样,你应该用一个数组来初始化Apache Math的版本。

(我不懂Scala,所以下面的代码是用Java写的)

MersenneTwister rng = new MersenneTwister();
rng.setSeed(new int[] {1234567890});
System.out.println(rng.nextInt(Integer.MAX_VALUE)); // 1977150888

另外要注意的是,像random()nextDouble()这些其他方法是完全不同的,所以这个初始化机制可能只适用于让nextInt(...)randrange(...)返回相同的结果。

撰写回答