【问题标题】:Algorithm for generating a three dimensional random number space生成三维随机数空间的算法
【发布时间】:2011-07-05 16:45:53
【问题描述】:

我正在寻找一种算法来在大范围的三(或更好的 n)维空间中生成伪随机数。当使用种子初始化时,生成器应该能够为同一个种子重复生成相同的数字。

但与编程语言中可用的大多数生成器不同,它不应该只返回序列中的下一个随机数,而是为特定坐标生成数字,无论请求值的顺序是什么。

空间的大小应该被认为太大了,无法在初始化时生成所有数字。在 Java 中,它可能看起来像这样:

Random3D gen = new Random3D(seed);
int n1 = gen.getInt(3,0,6);
int n2 = gen.getInt(2,-3,1);
...

我该如何做这样的事情?

我用 java.util.Random 编写了一些代码,在 Java 中进行了尝试,但结果的质量不是很好。

【问题讨论】:

  • 向我们展示您到目前为止所写的内容。 “结果的质量不是很好”是什么意思?熵?分布?他们都是3分?这个问题需要更多信息。
  • 您真的希望 3 维空间中的每个点都被均匀随机选择吗?这听起来像是你可能真的想要像 perlin 噪声这样的情况。
  • 它最终只是一个hash generator,它应该接收作为参数的 n 坐标,每个坐标长度可以是 32 位或 64 位(如下面的评论中所述),以及一个种子,并输出一些值
  • 有人问我需要什么。 @Nick Johnson:正是为了这个目的。我实现了一个 3D perlin 噪声生成器,由于生成的噪声云应该可以在任何方向上扩展,我需要三维随机性。
  • @David Titarenco:我说质量差的意思是生成的数字很好,但是当我比较不同种子的数字时,会出现模式。我只使用位运算,在许多情况下,相同坐标处的数字差异是相同的。这是我到目前为止的代码:gist.github.com/1067065

标签: java algorithm math random


【解决方案1】:

如果您希望对于相同的坐标始终收到相同的结果,那么当您指定种子时,您并不是在寻找真正的随机生成器。

您想要一种快速、可靠的算法吗?对于一个快速的,看看Mersenne twister。更强大的可以看Blum Blum Shub

您可以使用您的 n 维坐标加上您的种子来生成伪随机数生成器。例如,您可以计算 sha1 或 md5 或坐标 + 种子的任何其他哈希值并在 PRNG 中使用它。

编辑:对于一个简单的解决方案,math.random 可以接收 48 位的种子(小于 md5 输出),这对于您的问题可能有点小(您提到具有高维度,对吧?坐标?)

【讨论】:

  • +1 表示使用坐标的一些散列作为 BBS 等算法的索引输入。 Mersenne Twister 好像没有办法直接计算x_n,是吗?
  • @Paŭlo Ebermann:不,梅森没有。我将编辑上述算法的答案,这些算法只能接收一个数字作为种子。
  • 哈希是个好主意。但随之而来的问题是如何以不出现模式的方式准备坐标作为散列函数的输入。如果我只是简单地添加所有坐标,那么我会得到 (3,2,1) 和 (1,2,3) 相同的数字。我之前可以进行一些位移或异或运算,但结果中仍会保留一些模式。
  • @oyophant:如果加上 1 + 2 +3,那么 3 +2 +1 将是相同的。如果将它们连接起来,“123seed”不同于“321seed”,“321seed”不同于“213seed”。而且他们的 MD5(例如)也会有很大的不同:6c8d99... 与 5431aa 不同... 与 20546c 不同...
【解决方案2】:

我相信您可以通过使用输入数字作为用于计算传递给标准 RNG 的种子的值来解决您的问题;如果我没看错你的问题,你希望从相同的输入产生相同的“随机”数字,这个解决方案将提供。

【讨论】:

    【解决方案3】:

    我实现了 answer from woliveirajr 给出的想法:使用显式(非迭代)形式的 Blum Blum Shub 伪随机数生成器以及消息摘要从参数中生成正确的索引。

    (您也可以从my github repository获取此来源。)

    package de.fencing_game.paul.examples;
    
    import java.math.BigInteger;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Random;
    
    
    /**
     * A pseudo random number generator, which does not
     * produce a series of numbers, but each number determined by
     * some input (and independent of earlier numbers).
     *<p>
     * This is based on the
     * <a href="http://en.wikipedia.org/wiki/Blum_Blum_Shub">Blum Blum Shub
     *  algorithm</a>, combined with the SHA-1 message digest to get the
     *  right index.
     *</p>
     *<p>
     * Inspired by the question
     *  <a href="https://stackoverflow.com/q/6586042/600500">Algorithm
     *   for generating a three dimensional random number space</a> on
     * Stack Overflow, and the answer from woliveirajr.
     */
    public class PseudoRandom {
    
        /**
         * An instance of this class represents a range of
         * integer numbers, both endpoints inclusive.
         */
        public static final class Range {
    
            public int min;
            public int max;
    
            public Range(int min, int max) {
                this.min = min;
                this.max = max;
            }
    
            /**
             * clips a (positive) BigInteger to the range represented
             * by this object.
             * @returns an integer between min and max, inclusive.
             */
            final int clip(BigInteger bigVal) {
                BigInteger modulus =
                    BigInteger.valueOf(max + 1L - min);
                return (int)(min + bigVal.mod(modulus).longValue());
            }
        }
    
    
        /* M = p * q =
           510458987753305598818664158496165644577818051165198667838943583049282929852810917684801057127 *
           1776854827630587786961501611493551956300146782768206322414884019587349631246969724030273647
         */
        /**
         * A big number, composed of two large primes.
         */
        private static final BigInteger M =
            new BigInteger("90701151669688414188903413878244126959941449657"+
                           "82009133495922185615411523457607691918744187485"+
                           "10492533485214517262505932675573506751182663319"+
                           "285975046876611245165890299147416689632169");
    
        /* λ(M) = lcm(p-1, q-1) */
        /**
         * The value of λ(M), where λ is the Carmichael function.
         * This is the lowest common multiple of the predecessors of
         * the two factors of M.
         */
        private static final BigInteger lambdaM =
            new BigInteger("53505758348442070944517069391220634799707248289"+
                           "10045667479610928077057617288038459593720911813"+
                           "73249762745139558184229125081884863164923576762"+
                           "05906844204771187443203120630003929150698");
    
        /**
         * The number 2 as a BigInteger, for use in the calculations.
         */
        private static final BigInteger TWO = BigInteger.valueOf(2);
    
    
    
        /**
         * the modular square of the seed value.
         */
        private BigInteger s_0;
    
        /**
         * The MessageDigest used to convert input data
         * to an index for our PRNG.
         */
        private MessageDigest md;
    
    
    
        /**
         * Creates a new PseudoRandom instance, using the given seed.
         */
        public PseudoRandom(BigInteger seed) {
            try {
                this.md = MessageDigest.getInstance("SHA-1");
            }
            catch(NoSuchAlgorithmException ex) {
                throw new RuntimeException(ex);
            }
            initializeSeed(seed);
        }
    
        /**
         * Creates a new PseudoRandom instance, seeded by the given seed.
         */
        public PseudoRandom(byte[] seed) {
            this(new BigInteger(1, seed));
        }
    
        /**
         * Creates a new PseudoRandom instance,
         * seeded by the current system time.
         */
        public PseudoRandom() {
            this(BigInteger.valueOf(System.currentTimeMillis()));
        }
    
        /**
         * Transforms the initial seed into some value that is
         * usable by the generator. (This is completely deterministic.)
         */
        private void initializeSeed(BigInteger proposal) {
    
            // we want our seed be big enough so s^2 > M.
            BigInteger s = proposal;
            while(s.bitLength() <= M.bitLength()/2) {
                s = s.shiftLeft(10);
            }
            // we want gcd(s, M) = 1
            while(!M.gcd(s).equals(BigInteger.ONE)) {
                s = s.add(BigInteger.ONE);
            }
            // we save s_0 = s^2 mod M
            this.s_0 = s.multiply(s).mod(M);
        }
    
        /**
         * calculates {@code x_k = r.clip( s_k )}.
         */
        private int calculate(Range r, BigInteger k) {
            BigInteger exp = TWO.modPow(k, lambdaM);
            BigInteger s_k = s_0.modPow(exp, M);
            return r.clip(s_k);
        }
    
    
        /**
         * returns a number given by a range, determined by the given input.
         */
        public int getNumber(Range r, byte[] input) {
            byte[] dig;
            synchronized(md) {
                md.reset();
                md.update(input);
                dig =  md.digest();
            }
            return calculate(r, new BigInteger(1, dig));
        }
    
    
        /**
         * returns a number given by a range, determined by the given input.
         */
        public int getNumber(Range r, int... input) {
            byte[] dig;
            synchronized(md) {
                md.reset();
                for(int i : input) {
                    md.update(new byte[]{ (byte)(i >> 24), (byte)(i >> 16),
                                          (byte)(i >> 8), (byte)(i >> 0)} );
                }
                dig = md.digest();
            }
            return calculate(r, new BigInteger(1, dig));
        }
    
        /**
         * Test method.
         */
        public static void main(String[] test) {
            PseudoRandom pr = new PseudoRandom("Hallo Welt".getBytes());
    
            Range r = new Range(10, 30);
            for(int i = 0; i < 10; i++) {
                System.out.println("x("+i+") = " + pr.getNumber(r, i));
            }
            for(int i = 0; i < 5; i++) {
                for(int j = 0; j < 5; j++) {
                    System.out.println("x("+i+", "+j+") = " +
                                       pr.getNumber(r, i, j));
                }
            }
            // to show that it really is deterministic:
            for(int i = 0; i < 10; i++) {
                System.out.println("x("+i+") = " + pr.getNumber(r, i));
            }
        }
    }
    

    我任意选择了这些大素数 - 我不知道它们是否真的在密码学上是安全的(例如 p-1q-1 是否具有必要的分解属性)。如果你真的需要安全,你应该对这些数字保密(例如自己生成)。

    另外,我使用输入种子来生成 s(和 s_0)——而不是使用固定的 s(具有已知的良好属性,比如大句号) ,并使用种子作为消息摘要的输入(连同我在这里使用的输入)。

    当然,也可以直接使用消息摘要的输出,而不是仅将其用作 BBS 的索引。

    【讨论】:

    • 我会保存你的实现,以备不时之需......我很好奇@oyophant 是否会告诉我们他将在哪里使用它:)
    • @woliveirajr:查看问题评论。 @Paŭlo Ebermann:谢谢。我也在尝试你的解决方案。
    • BBS 可能对您的应用程序来说太过分了——它相当慢,因为它在密码学上是安全的。直接使用散列数据,或者如果这还不够,重复散列可能会更快,并且使用散列输出播种非加密 PRNG 几乎肯定就足够了。
    • @Nick:我想你就在这里。我主要是为了好玩才实现这个的。
    • @nick @Paŭlo :如果@oyophant 说他不需要强随机,只需要一些恒定的随机值来产生噪音,我想他会得到不同的答案......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 2012-01-05
    • 1970-01-01
    • 1970-01-01
    • 2020-03-22
    • 1970-01-01
    相关资源
    最近更新 更多