几种随机数生成方式

谈到随机性,这大概是一个令人困惑哲学问题吧。随机行为精确地说究竟指的是什么,最好是有定量的定义。Kolmogorov曾提出一种判定随机性的方法:对于无穷的随机数序列,无法用其子序列描述。J.N.Franklin则认为:如果一个序列具有从一个一致同分布的随机变量中独立抽样获得的每个无限序列都有的性质,则是随机的。这些定义都不是很精确,有时甚至会导致矛盾。可见数学家在谈到这个问题时是多么的审慎。随机数生成器只是一种产生符合特定分布的随机数的算法。这些所谓的随机数序列实际上是周期性的。从实用的角度出发,随机数生成器如果能够在尽可能多的场合中产生正确的结果,那么它就是好的。但是这个愿望无法完全实现。因为每一个生成器都会在特定的场合失效,比如说可能无法达到随机数的均匀性或者随机数之间隐藏着关联。已经有大量的随机数生成器,但是找到好的、易移植的、达到工业水准的随机数生成器是一个难以实现的目标。生成非均匀分布的标准方法是先产生均匀分布随机数,然后将其转化为特定分布的随机数。

一个比较简单的方法,用随机数填充一个位图

几种随机数生成方式填充的位图

这是每种方法生成 500x500的位图的所用时间

MersenneTwister: 5449毫秒
Math.random: 111毫秒
Random: 82毫秒
ThreadLocalRandom: 74毫秒

哪个比较差一目了然。

生成部分代码

    public static boolean Math_random_nextBoolean() {
        return getNextBoolean(Math.random(), Math.random());
    }

    public static boolean MersenneTwister_nextBoolean() {
        return getNextBoolean(new MersenneTwister(System.nanoTime()).nextDouble(), new MersenneTwister(System.nanoTime()).nextDouble());
    }

    public static boolean Random_nextBoolean() {
        return getNextBoolean(new Random(System.nanoTime()).nextDouble(), new Random(System.nanoTime()).nextDouble());
    }

    public static boolean ThreadLocalRandom_nextBoolean() {
        return getNextBoolean(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble());
    }

    private static boolean getNextBoolean(double probability, double probabilitynew) {
        if (probability < 0.0 || probability > 1.0)
            throw new IllegalArgumentException("probability must be between 0.0 and 1.0 inclusive.");
        if (probability == 0.0) return false;             // fix half-open issues
        else if (probability == 1.0) return true; // fix half-open issues
        return probabilitynew < probability;
    }
/**
     * Returns the next pseudorandom, uniformly distributed
     * {@code double} value between {@code 0.0} and
     * {@code 1.0} from this random number generator's sequence.
     *
     * <p>The general contract of {@code nextDouble} is that one
     * {@code double} value, chosen (approximately) uniformly from the
     * range {@code 0.0d} (inclusive) to {@code 1.0d} (exclusive), is
     * pseudorandomly generated and returned.
     *
     * <p>The method {@code nextDouble} is implemented by class {@code Random}
     * as if by:
     *  <pre> {@code
     * public double nextDouble() {
     *   return (((long)next(26) << 27) + next(27))
     *     / (double)(1L << 53);
     * }}</pre>
     *
     * <p>The hedge "approximately" is used in the foregoing description only
     * because the {@code next} method is only approximately an unbiased
     * source of independently chosen bits. If it were a perfect source of
     * randomly chosen bits, then the algorithm shown would choose
     * {@code double} values from the stated range with perfect uniformity.
     * <p>[In early versions of Java, the result was incorrectly calculated as:
     *  <pre> {@code
     *   return (((long)next(27) << 27) + next(27))
     *     / (double)(1L << 54);}</pre>
     * This might seem to be equivalent, if not better, but in fact it
     * introduced a large nonuniformity because of the bias in the rounding
     * of floating-point numbers: it was three times as likely that the
     * low-order bit of the significand would be 0 than that it would be 1!
     * This nonuniformity probably doesn't matter much in practice, but we
     * strive for perfection.]
     *
     * @return the next pseudorandom, uniformly distributed {@code double}
     *         value between {@code 0.0} and {@code 1.0} from this
     *         random number generator's sequence
     * @see Math#random
     */
    public double nextDouble() {
        return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;
    }

Random类中实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。

相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。这点在生成多个随机数字时需要特别注意。

/**
     * Returns a {@code double} value with a positive sign, greater
     * than or equal to {@code 0.0} and less than {@code 1.0}.
     * Returned values are chosen pseudorandomly with (approximately)
     * uniform distribution from that range.
     *
     * <p>When this method is first called, it creates a single new
     * pseudorandom-number generator, exactly as if by the expression
     *
     * <blockquote>{@code new java.util.Random()}</blockquote>
     *
     * This new pseudorandom-number generator is used thereafter for
     * all calls to this method and is used nowhere else.
     *
     * <p>This method is properly synchronized to allow correct use by
     * more than one thread. However, if many threads need to generate
     * pseudorandom numbers at a great rate, it may reduce contention
     * for each thread to have its own pseudorandom-number generator.
     *
     * @return  a pseudorandom {@code double} greater than or equal
     * to {@code 0.0} and less than {@code 1.0}.
     * @see Random#nextDouble()
     */
    public static double random() {
        return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
    }
  • MersenneTwister.java

    梅森旋转算法(Mersenne twister) 是一个伪随机数发生算法。由。Makoto Matsumoto(松本真) 和Takuji Nishimura(西村拓士)在1997年开发的,基于有限二进制字段上的矩阵线性递归field F_{2}。 可以快速产生高质量的伪随机数, 修正了古典随机数发生算法的很多缺陷。

梅森旋转算法这个名字来自周期长度取自梅森素数的这样一个事实。这个算法通常使用两个相近的变体,不同之处在于使用了不同的梅森素数。一个更新的和更常用的是MT19937, 32位字长。 还有一个变种是64位版的MT19937-64。 对于一个k位的长度,Mersenne Twister会在[0,2^k-1]的区间之间生成离散型均匀分布的随机数。

Java实现有2个版本

一个是快速版本,非线程安全。MersenneTwisterFast.java,java.util.Random比它慢1/3。

一个是普通版本MersenneTwister.java
,线程安全,但比java.util.Random慢1/3。

经验

Chris Marasti-Georg 指出:

Math.round(Math.random() * 10)
使分布不平衡,例如:0.0 - 0.499999将四舍五入为0,而0.5至1.499999将四舍五入为1。那么如何使用旧式语法来实现正确的均衡分布,如下:
Math.floor(Math.random() * 11)
幸运的是,如果我们使用java.util.Random或java.util.concurrent.ThreadLocalRandom就不用担心上述问题了。
Java实战项目里面介绍了一些不正确使用java.util.Random API的危害。这个教训告诉我们不要使用:
Math.abs(new Random().nextInt())%n
而使用:
new Random().nextInt(n)
其他请参考:http://blog.csdn.net/jianhua0902/article/details/8487005

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容

  • 方法1 (数据类型)(最小值+Math.random()*(最大值-最小值+1)) 例: (int)(1+Math...
    GB_speak阅读 40,932评论 2 6
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,716评论 0 33
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,531评论 18 399
  • 内容主要参考http://www.cnblogs.com/Fskjb/archive/2009/08/29/155...
    锅与盆阅读 7,989评论 1 4
  • 唐鹤升 不清楚从哪天起 也不知道为什么 心中开始萦绕某个声音 温暖如冬日的围炉 深情如蔚蓝的海水 富有磁性的声音 ...
    唐鹤升阅读 169评论 0 1