Math#random不太随机?

6

我发现我的程序出现了一些奇怪的问题。

这个程序基本上是用来进行组件点击的,因为它是测试随机性的一个概念。

enter image description here

如你所见,它正确地输出了,因为它应该有向中间点击的趋势,这做得很好。

问题是,它似乎存在偏差。

import java.applet.Applet;
import java.awt.Point;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.awt.*;

public class Testing extends Applet {

/**
 * 
 */
private static final long serialVersionUID = -2441995094105327849L;

public void init() {
    setSize(WIDTH, HEIGHT);
    img = createImage(WIDTH, HEIGHT);
    g = img.getGraphics();
    paint(g);
    setVisible(true);
    g.setColor(new Color(0, 0, 0));
    g.fillRect(0, 0, WIDTH, HEIGHT);
    g.setColor(new Color(55, 55, 55, 55));
    main();
}

public final static int WIDTH = 400, HEIGHT = 400;
public static int[] widths = new int[WIDTH], heights = new int[HEIGHT];
public static ArrayList<String> set = new ArrayList<String>();
public Image img;
public Graphics g;

public void paint(Graphics g) {
    g.drawImage(img, 0, 0, null);
}

public void update(Graphics g) {
    paint(g);
}

public void main() {
    int count101 = 0;
    int count100 = 0;
    int count99 = 0;
    try {
        PrintWriter pw = new PrintWriter(new FileWriter(
                new File("Data.dat")));
        Point center = new Point(WIDTH / 2, HEIGHT / 2);

        int runs = 10000000;

        for (int i = 0; i < runs; i++) {
            int x = center.x
                    - (int) ((Math.random() - Math.random())
                            * Math.random() * center.x);
            int y = center.y
                    - (int) ((Math.random() - Math.random())
                            * Math.random() * center.y);
            widths[x]++;
            heights[y]++;
            repaint();
            g.fillRect(x, y, 1, 1);
            if((x & y) == 101){
                count101++;
            }
            if((x & y) == 100){
                count100++;
            }
            if((x & y) == 99){
                count99++;
            }
        }
        System.out.println(count101);
        System.out.println(count100);
        System.out.println(count99);
        repaint();
        pw.flush();
        pw.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
}

这会不断输出有偏见的结果。

它会输出以下内容:

3640
10918
3741

这个结果具有相当的偏见,因为它大部分都遵循一个趋势,随着所有其他值的线性增长,但一旦达到100分,它决定让它比其他数字高6%。
有人知道这背后的原因吗?
顺便说一下,我有一个包含每个结果的txt文件,并重复了10,000,000次,附带百分比等信息,文件相当长,所以我不会贴出来,但我有这些信息。

2
你需要为随机函数提供种子。 - Austin Brunkhorst
1
输出结果不是你期望的吗?这就是随机性... - Luchian Grigore
6
你为什么要对 xy 进行按位与运算?你希望这样做有什么意义?或者你是期望 if((x & y) == 101)if ((x == 101) && (y == 101)) 的含义相同吗? - Jon Skeet
你是指 if((x&y)==101) 还是 if(x==101 && y==101) - assylias
你在问什么?为什么某些位模式的“计数”存在偏差? 简单来说,你的输入数据存在偏差,因此输出数据中也很可能出现这种偏差。此外,对随机生成的数字执行操作并不能保证结果是随机分布的。实际上,有一种理论认为,进行的转换越多,结果就越有可能呈正态分布(这可能就是你看到的情况)。 - Jochen
3个回答

18
这不是Java的Math.rand()或伪随机生成问题。这是导致奇怪(但预期的)行为的原因:
Math.random() - Math.random()

两个均匀分布随机变量之和(或差)不会得到均匀分布的变量。据我记得,它们会得到三角形分布

triangular distribution

请参见:两个标准均匀变量的均值分布

这就是你所看到的 - 一个完美的二维随机变量三角形分布的例子。此外,如果你不断添加均匀分布的随机变量,最终会得到正态分布

要实现均匀分布,你只需替换笨拙的:

int x = center.x
                - (int) ((Math.random() - Math.random())
                        * Math.random() * center.x);

使用简单:

int x = (int) (Math.random() * center.x * 2);

您的代码(不包括乘法)生成随机变量,其可能值范围为0center.x * 2,并且期望值center.x处。到目前为止还不错。但是该分布是三角形的,这意味着概率密度在该范围内不均等。
两个随机变量(其中一个不再均匀分布)的乘积具有更复杂的分布,但肯定不是均匀的。
后面的代码片段生成一个简单的、均匀分布的变量,其概率密度函数在整个空间中相等。
附注
在您的程序中这是明显的数学错误,但伪随机生成器实际上可以在空间中生成“非随机”模式。请查看奇怪的吸引子和TCP/IP序列号分析,该文章中的图片:

3d-space
(来源:coredump.cx)


感谢您建议使用(int)(Math.random() * center.x * 2);但这不是我想要的。基本上,我希望它更加专注于点击中心(这是一个游戏的宏工具,这是为了确保账户看起来更“人性化”)。例如,当您单击按钮时,您将更经常地朝向中心。您不会单击边缘上的最后一个像素。我所拥有的图片是期望结果,但没有偏向100分的偏见。正如您在这里看到的:http://pastebin.com/8wQuZFhU 我希望它更加线性。 - user1181445
1
@Legend:哦,我明白了!但这意味着我的答案非常全面,但离题了!看一下这些文章:n-球Box-Muller变换 - Tomasz Nurkiewicz

2

不要使用Math.random(),而是使用java.util.Random。首先,用当前时间对随机生成器进行种子初始化:

Random randGenerator = new java.util.Random(System.currentTimeMillis());

然后一个接一个地生成随机数。
randGenerator.nextDouble();

如果您不提供种子,那么使用Math.random()很好 - 它会利用当前时间自动给自己种子。 - BlueRaja - Danny Pflughoeft

0
在这种情况下,播种随机数并没有什么作用,因为它已经使用System.nanoTime()来进行自我播种了:
public Random() { this(++seedUniquifier + System.nanoTime()); }
private static volatile long seedUniquifier = 8682522807148012L;

Math.random() 方法重复使用同一个随机数生成器并使用默认构造函数来构建它。

问题在于你正在对两个随机数进行计算,这会改变分布。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接