在POSIX上产生随机双精度的最佳方法是什么?

时间:2020-03-06 14:50:59  来源:igfitidea点击:

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

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

如果解决方案是线程安全的,那也很好。如果不确定,请指出。

阅读其他答案后,请参阅我考虑过的一些解决方案。

解决方案

#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是随机值

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

尽管可能存在陷阱,但是将这样一组字节访问为整数(除以该大小的最大整数)的方法将产生一个介于0到1之间且近似于该分布的浮点值。

例如:

FILE* f = fopen("/dev/urandom", "r");
int32_t int;
fread(&int, sizeof(int32_t), 1, f);
fclose(f);
double theRandomValue = int / (double) (2 ** 32 - 1);

诀窍是我们需要一个符合我们要求的54位随机数发生器。几行代码加上一个联合,将这54位粘贴在尾数上,我们便有了自己的号码。诀窍不是"双浮点数",而是我们所需的随机数。

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

这给出了数值均匀的分布(因为值在给定范围内的概率与该范围成正比)低至第52个二进制数位。

复杂:但是,在上述[0,1)范围内,有很多双精度值无法生成。具体来说,范围[0,0.5)(设置了最低有效位的值)的一半值不会出现。 [0,0.25)范围内的值的四分之三(设置了至少2位的值)不会出现,依此类推,一直到只有一个小于2 ^ -51的正值是可能的,尽管有两倍能够代表这样的价值。因此,不能说它在指定范围内达到完全精确的统一。

当然,我们不希望以相等的概率选择其中一个双精度数,因为这样得出的平均数将太小。我们仍然需要结果在给定范围内的概率与该范围成正比,但是在适用范围上具有更高的精度。

我认为以下作品。我还没有特别研究或者测试过该算法(就像我们可能通过没有代码的方式可以看到的那样),而且就我个人而言,如果没有找到表明其有效的正确引用,我就不会使用它。但是这里:

  • 从52开始计算指数,然后选择一个52位随机无符号整数(假设尾数为52位)。
  • 如果整数的最高有效位为0,则将指数增加1,将整数左移1,然后用新的随机位填充最低有效位。
  • 重复该操作,直到我们在最高有效位达到1,否则该指数对于双倍来说太大(1023. 或者可能是1022)。
  • 如果找到1,则将值除以2 ^指数。如果全为零,则返回0(我知道,这实际上不是特例,但要强调的是,0返回的可能性很小[编辑:实际上,这可能是特例-这取决于我们是否要生成如果不是这样,那么一旦连续有足够的0,就丢弃所有剩余的内容并返回0。但是实际上,除非随机源不是随机的,否则这几乎不可能忽略不计。

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

这似乎是一个很好的方法:

`

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实现。

/ dev / urandom不是POSIX,并且通常不可用。

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

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

至于线程安全性,至少在标准接口中不会,因此我们需要在其上构建一层,或者仅在一个线程中使用它们。线程安全的线程提供了它们修改的状态,因此,我们可以有效地运行多个非交互的随机数生成器,而这些生成器可能不是我们想要的。