当前位置: 首页 > 知识库问答 >
问题:

这是一个“足够好”的随机算法;如果速度更快,为什么不用呢?

赫连实
2023-03-14

我创建了一个名为QuickRandom的类,它的任务是快速生成随机数。它非常简单:只需取旧值,乘以一个双精度,然后取小数部分。

以下是我的QuickRandom课程的全部内容:

public class QuickRandom {
    private double prevNum;
    private double magicNumber;

    public QuickRandom(double seed1, double seed2) {
        if (seed1 >= 1 || seed1 < 0) throw new IllegalArgumentException("Seed 1 must be >= 0 and < 1, not " + seed1);
        prevNum = seed1;
        if (seed2 <= 1 || seed2 > 10) throw new IllegalArgumentException("Seed 2 must be > 1 and <= 10, not " + seed2);
        magicNumber = seed2;
    }

    public QuickRandom() {
        this(Math.random(), Math.random() * 10);
    }

    public double random() {
        return prevNum = (prevNum*magicNumber)%1;
    }

}

下面是我为测试它而编写的代码:

public static void main(String[] args) {
        QuickRandom qr = new QuickRandom();

        /*for (int i = 0; i < 20; i ++) {
            System.out.println(qr.random());
        }*/

        //Warm up
        for (int i = 0; i < 10000000; i ++) {
            Math.random();
            qr.random();
            System.nanoTime();
        }

        long oldTime;

        oldTime = System.nanoTime();
        for (int i = 0; i < 100000000; i ++) {
            Math.random();
        }
        System.out.println(System.nanoTime() - oldTime);

        oldTime = System.nanoTime();
        for (int i = 0; i < 100000000; i ++) {
            qr.random();
        }
        System.out.println(System.nanoTime() - oldTime);
}

这是一个非常简单的算法,只需将前一个两倍乘以一个“神奇的数字”两倍。我很快就把它组合在一起,所以我可能会做得更好,但奇怪的是,它似乎运行良好。

这是main方法中注释掉的行的示例输出:

0.612201846732229
0.5823974655091941
0.31062451498865684
0.8324473610354004
0.5907187526770246
0.38650264675748947
0.5243464344127049
0.7812828761272188
0.12417247811074805
0.1322738256858378
0.20614642573072284
0.8797579436677381
0.022122999476108518
0.2017298328387873
0.8394849894162446
0.6548917685640614
0.971667953190428
0.8602096647696964
0.8438709031160894
0.694884972852229

嗯,很随意。事实上,这对游戏中的随机数生成器是有效的。

下面是未注释输出部分的示例输出:

5456313909
1427223941

哇!它的执行速度几乎是数学的4倍。随机

我记得在某个地方读到过关于数学的书。随机使用系统。nanoTime()以及大量疯狂的模和除法。真的有必要吗?我的算法执行得更快,而且看起来很随机。

  • 我的算法是否“足够好”(比如,对于一个真正随机数不太重要的游戏)
  • 为什么Math。random(随机)当看起来只是简单的乘法和去掉小数就足够了的时候,你会这么做吗

共有3个答案

莫欣悦
2023-03-14

您的随机数函数很差,因为它的内部状态太少——函数在任何给定步骤中输出的数字完全依赖于前一个数字。例如,如果我们假设magicNumber是2(作为示例),那么序列:

0.10 -> 0.20

类似的序列强烈反映了这一点:

0.09 -> 0.18
0.11 -> 0.22

在许多情况下,这将在游戏中产生明显的相关性——例如,如果连续调用函数来生成对象的X和Y坐标,对象将形成清晰的对角线模式。

除非您有充分的理由相信随机数生成器正在减慢您的应用程序(这是非常不可能的),否则没有充分的理由尝试编写自己的。

沈子昂
2023-03-14

你所描述的是一种叫做线性同余发生器的随机发生器。发电机的工作原理如下:

  • 从种子值和乘数开始

这个生成器有很多很好的特性,但是作为一个好的随机源,它有很多重要的问题。上面链接的维基百科文章描述了一些优势和劣势。简而言之,如果你需要好的随机值,这可能不是一个很好的方法。

希望这有帮助!

贺兴平
2023-03-14

您的QuickRandom实现并没有真正的统一分布。当Math时,较低值的频率通常较高。random()的分布更加均匀。以下是一个SSCCE,它显示:

package com.stackoverflow.q14491966;

import java.util.Arrays;

public class Test {

    public static void main(String[] args) throws Exception {
        QuickRandom qr = new QuickRandom();
        int[] frequencies = new int[10];
        for (int i = 0; i < 100000; i++) {
            frequencies[(int) (qr.random() * 10)]++;
        }
        printDistribution("QR", frequencies);

        frequencies = new int[10];
        for (int i = 0; i < 100000; i++) {
            frequencies[(int) (Math.random() * 10)]++;
        }
        printDistribution("MR", frequencies);
    }

    public static void printDistribution(String name, int[] frequencies) {
        System.out.printf("%n%s distribution |8000     |9000     |10000    |11000    |12000%n", name);
        for (int i = 0; i < 10; i++) {
            char[] bar = "                                                  ".toCharArray(); // 50 chars.
            Arrays.fill(bar, 0, Math.max(0, Math.min(50, frequencies[i] / 100 - 80)), '#');
            System.out.printf("0.%dxxx: %6d  :%s%n", i, frequencies[i], new String(bar));
        }
    }

}

平均结果如下所示:

QR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  11376  :#################################                 
0.1xxx:  11178  :###############################                   
0.2xxx:  11312  :#################################                 
0.3xxx:  10809  :############################                      
0.4xxx:  10242  :######################                            
0.5xxx:   8860  :########                                          
0.6xxx:   9004  :##########                                        
0.7xxx:   8987  :#########                                         
0.8xxx:   9075  :##########                                        
0.9xxx:   9157  :###########                                       

MR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  10097  :####################                              
0.1xxx:   9901  :###################                               
0.2xxx:  10018  :####################                              
0.3xxx:   9956  :###################                               
0.4xxx:   9974  :###################                               
0.5xxx:  10007  :####################                              
0.6xxx:  10136  :#####################                             
0.7xxx:   9937  :###################                               
0.8xxx:  10029  :####################                              
0.9xxx:   9945  :###################    

如果你重复测试,你会发现QR分布变化很大,这取决于初始种子,而MR分布是稳定的。有时,它会达到所需的均匀分布,但通常不会。下面是一个更极端的例子,它甚至超出了图的边界:

QR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  41788  :##################################################
0.1xxx:  17495  :##################################################
0.2xxx:  10285  :######################                            
0.3xxx:   7273  :                                                  
0.4xxx:   5643  :                                                  
0.5xxx:   4608  :                                                  
0.6xxx:   3907  :                                                  
0.7xxx:   3350  :                                                  
0.8xxx:   2999  :                                                  
0.9xxx:   2652  :                                                  
 类似资料:
  • 问题内容: 方法链接 是对象方法返回对象本身以使结果被另一个方法调用的实践。像这样: 这似乎被认为是一种好习惯,因为它会产生可读的代码或“流畅的界面”。但是,对我而言,它似乎打破了面向对象本身所隐含的对象调用表示法-生成的代码不代表对先前方法的结果执行的动作,通常这是面向对象的代码的工作方式: 这种差异设法为“调用结果对象”的点标记创建了两种不同的含义:在链接的上下文中,以上示例将被视为保存参与者

  • 问题内容: 使用cPickle读取一个1 GB的NetworkX图形数据结构花了我一个小时的时间(将它作为二进制pickle文件存储在磁盘上时为1 GB)。 请注意,文件会快速加载到内存中。换句话说,如果我运行: 如何加快上一个操作的速度? 请注意,我已经尝试使用两种二进制协议(1和2)对数据进行腌制,并且似乎对使用哪种协议并没有太大的影响。还要注意,尽管我在上面使用“ loads”(意思是“ l

  • 问题内容: 我可以依靠地图的随机迭代顺序在Web应用程序中实现客户端的随机“配对”吗?我试过环顾四周,但似乎找不到这种随机性有多大的细分。 该算法将类似于: 当连接的客户端> 1000个时,这是否足够?还是应该维护一个单独的客户端片段并从中随机选择? 问题答案: 尽管它说是随机的(随机的)(spec,blog,hashmap源,另一个blog,SO),但分布远非完美。 为什么?因为我们喜欢地图 快

  • 在求树的直径时,我们考虑以下最大值: 1:左子树的直径 2:右子树的直径 3:左子树高度右子树高度1. 为什么这三个是必要的?为什么是3。光靠自己是不够的。让我们看一个简单的3节点树和2节点树的例子。在前一个例子中,仅第3点就给出了1=3。而在后一种情况下,仅第3点就给出0 1=2。 在这种情况下,为什么我们需要找到三的最大值。请解释

  • 我读到种子是用来初始化随机数生成器的。但似乎种子的随机性对于从生成器获得良好的随机性并不重要。所以我想明白什么是种子?为什么这么叫?最后,为什么计算机系统中的时间被用来产生这样的种子?

  • 问题内容: 为了在工作中进行演示,我想比较NodeJS和C的性能。这是我写的: Node.js(for.js): 我使用GCC编译for.c并运行它: 结果: 然后我在NodeJS中尝试了它: 结果: 在运行了无数次之后,我发现无论如何它都是成立的。如果我将for.c切换double为long在循环中使用a而不是a ,则C花费的时间甚至更长! 不是试图发动火焰战争,但是为什么执行相同操作的Node