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

What is the best way to produce random double on POSIX?

本文关键字:最佳 方法 是什么 双精度 随机 POSIX      更新时间:2023-10-16

我想在范围 [0.0, 1.0] 中获得均匀分布

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

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

查看我在阅读其他答案后想到的一些解决方案。

似乎是很好的方法:

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

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

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

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

当然,我们不想选择概率相等的双精度之一,因为那样结果的平均数字会太小。我们仍然需要结果在给定范围内的概率与该范围成正比,但对于适用于哪些范围具有更高的精度。

我认为以下有效。我没有特别研究或测试过这个算法(正如你可能从没有代码的方式看出来),就个人而言,如果没有找到表明它是有效的适当参考资料,我不会使用它。但这里是:

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

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

从文件中读取是线程安全的 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);

诀窍是您需要一个满足您要求的 54 位随机发生器。 几行代码,加上一个联合,把这54位贴在尾数上,你就有了你的号码。 诀窍不是双重浮动,诀窍是您想要的随机发生器。

#include <stdlib.h>
printf("%fn", 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 是您的随机值

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

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

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

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