【问题标题】:What is the best way to produce random double on POSIX?在 POSIX 上产生随机双精度的最佳方法是什么?
【发布时间】:2010-09-13 20:49:42
【问题描述】:

我想在 [0.0, 1.0) 范围内获得 均匀分布

如果可能,请让实现使用来自 /dev/urandom 的随机字节。

如果您的解决方案是线程安全的,那就太好了。如果不确定,请注明。

some solution我看了其他答案后想到的。

【问题讨论】:

    标签: c++ multithreading random posix


    【解决方案1】:

    这似乎是个不错的方法:

    unsigned short int r1, r2, r3;
    // let r1, r2 and r3 hold random values
    double result = ldexp(r1, -48) + ldexp(r2, -32) + ldexp(r3, -16);
    

    这是基于 NetBSD 的 drand48 实现。

    【讨论】:

    • 是否有人知道为什么这会在 0.0...1.0 范围内给出统一的结果,而我假设包含 0.0 和排除 1.0?如果是的话,如果添加它真的会改善这个答案。非常感谢对此进行扩展的编辑。
    • 只是为了澄清,统一我的意思是没有明显的偏见。对于大多数使用 /dev/urandom 而不是原始问题中提出的 PRNG 的应用程序来说,这通常非常重要。
    【解决方案2】:

    简单:假设 IEEE,double 具有 52 位精度。所以生成一个 52 位(或更大)的无符号随机整数(例如通过从 dev/urandom 读取字节),将其转换为双精度并除以 2^(它的位数)。

    这给出了数字上的均匀分布(因为一个值在给定范围内的概率与范围成正比)直到第 52 个二进制数字。

    复杂:但是,在 [0,1) 范围内有很多 double 值,上面无法生成。具体来说,不会出现 [0,0.5) 范围内的一半值(设置了最低有效位的值)。 [0,0.25) 范围内的四分之三的值(设置了至少 2 位中的任何一个的值)不会发生,等等,一直到只有一个小于 2^-51 的正值是可能的,尽管 double 能够代表这种值的 squillions。所以不能说在指定范围内真正统一到全精度。

    当然,我们不想以相等的概率选择其中一个双打,因为这样得到的数字平均会太小。我们仍然需要结果在给定范围内的概率与范围成正比,但在适用于哪些范围方面具有更高的精度。

    认为以下作品。我没有特别研究或测试过这个算法(你可能可以通过没有代码的方式看出),而且我个人不会在没有找到表明它有效的适当参考资料的情况下使用它。但这里是:

    • 指数从 52 开始,然后选择一个 52 位随机无符号整数(假设尾数为 52 位)。
    • 如果整数的最高有效位为 0,则指数加一,整数左移一位,并用新的随机位填充最低有效位。
    • 重复直到您在最重要的位置达到 1,否则指数对于您的双倍而言太大(1023。或者可能是 1022)。
    • 如果您找到 1,请将您的值除以 2^exponent。如果你全为零,则返回 0 (我知道,这实际上不是一个特殊情况,但它强调 0 返回的可能性很小 [编辑:实际上它可能是一个特殊情况 - 这取决于你是否想要生成denorms。如果没有,那么一旦你连续有足够的 0,你就丢弃剩下的任何东西并返回 0。但实际上这不太可能可以忽略不计,除非随机源不是随机的)。

    我不知道这样的随机替身是否有任何实际用途,请注意。您对随机数的定义应在一定程度上取决于它的用途。但是,如果您可以从其所有 52 个有效位的随机性中受益,那么这实际上可能会有所帮助。

    【讨论】:

    • 我本来不同意你的复杂方法,但一旦我通读了整件事,它实际上是有道理的。虽然我不是这方面的专家,所以我的意见不应该计算在内。 :-P
    • 我也不是——我认为最好的方法不是设计需要“随机浮点数”的算法,除非你确定你知道那应该是什么意思。我不确定我是否...
    【解决方案3】:

    从文件读取是线程安全的 AFAIK,因此使用 fopen() 从 /dev/urandom 读取将产生“真正随机”的字节。

    尽管可能存在潜在的陷阱,但我认为以整数形式访问的任何此类字节集除以该大小的最大整数后,将产生一个介于 0 和 1 之间且大致具有该分布的浮点值。

    例如:

    #include <limits.h>
    #include <stdint.h>
    #include <stdio.h>
    ...
    FILE* f = fopen("/dev/urandom", "r");
    uint32_t i;
    fread(&i, sizeof(i), 1, f);  // check return value in real world code!!
    fclose(f);
    double theRandomValue = i / (double) (UINT32_MAX);
    

    【讨论】:

    • 一些注意事项:(1)您不希望 2 ^ 32 之后的“- 1”,因为 phjr 要求排除输出 1.0。 (2) C++中没有**。只有
    • 而且你不能使用'int'作为变量,因为它是一个关键字。
    【解决方案4】:

    诀窍是您需要一个 54 位随机发生器来满足您的要求。几行带有联合的代码将这 54 位粘贴在尾数中,然后你就有了你的号码。诀窍不是双浮动,诀窍是您想要的随机化器。

    【讨论】:

    • 根据 java.util.Random 您需要 53 位,而不是 54 位。阅读 nextDouble() 方法上的 cmets 以了解原因。否则,你就在正确的轨道上。
    • 虽然 Java 方法没有为此使用联合,但它只是生成一个 53 位的大数字,然后将其除以 (1
    • 这也应该避免在 50% 的时间内生成非规范化的值,联合会这样做。您的浮点单元和库可能会或可能不会优雅地处理 denorms。
    【解决方案5】:
    #include <stdlib.h>
    printf("%f\n", drand48());
    

    /dev/random:

    double c;
    fd = open("/dev/random", O_RDONLY);
    unsigned int a, b;
    read(fd, &a, sizeof(a));
    read(fd, &b, sizeof(b));
    if (a > b)
       c = fabs((double)b / (double)a);
    else
        c = fabs((double)a / (double)b);
    

    c 是你的随机值

    【讨论】:

    • 这里有一个bug——如果a或b为负数,那么比较不一定会选择幅度较小的值作为分子。另外,我太昏暗了,看不出它会产生均匀分布的输出。
    • 另外,有一次在 2^64 中,您中了双倍大奖,并且除以 0 未定义行为。
    • 想想,这从来都不是统一的。如果任何一个值为 0,则您得到 0,这几乎是 2^31 中的 1,这远远超过了 52 位尾数精度的双精度数。
    【解决方案6】:

    /dev/urandom 不是 POSIX,一般不可用。

    在 [0,1) 中均匀生成双精度的标准方法是在 [0,2^N) 范围内生成一个整数并除以 2^N。所以选择你最喜欢的随机数生成器并使用它。对于模拟,我的是Mersenne Twister,因为它非常快,但仍然没有很好的相关性。实际上,它可以为您做到这一点,甚至有一个版本可以为较小的数字提供更高的精度。通常你给它一个种子,这有助于调试的可重复性或向其他人展示你的结果。当然,如果没有指定,你可以让你的代码从 /dev/urandom 中获取一个随机数作为种子。

    出于加密目的,您应该改用其中一个标准加密库,例如 openssl),它确实会在可用时使用 /dev/urandom。

    至于线程安全,大多数都不会,至少在标准接口中是这样,所以你需要在上面构建一个层,或者只在一个线程中使用它们。那些是线程安全的让你提供他们修改的状态,这样你就可以有效地运行多个非交互随机数生成器,这可能不是你想要的。

    【讨论】:

      猜你喜欢
      • 2012-03-17
      • 2010-10-12
      • 1970-01-01
      • 2012-05-16
      • 1970-01-01
      • 2020-08-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多