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

时间:2022-08-25 18:23:55

I'd like to get uniform distribution in range [0.0, 1.0)

我希望在范围内得到均匀分布[0.0,1.0)

If possible, please let the implementation make use of random bytes from /dev/urandom.

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

It would also be nice if your solution was thread-safe. If you're not sure, please indicate that.

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

See some solution I thought about after reading other answers.

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

6 个解决方案

#1


3  

Simple: A double has 52 bits of precision assuming IEEE. So generate a 52 bit (or larger) unsigned random integer (for example by reading bytes from dev/urandom), convert it into a double and divide it by 2^(number of bits it was).

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

This gives a numerically uniform distribution (in that the probability of a value being in a given range is proportional to the range) down to the 52nd binary digit.

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

Complicated: However, there are a lot of double values in the range [0,1) which the above cannot generate. To be specific, half the values in the range [0,0.5) (the ones that have their least significant bit set) can't occur. Three quarters of the values in the range [0,0.25) (the ones that have either of their least 2 bits set) can't occur, etc, all the way to only one positive value less than 2^-51 being possible, despite a double being capable of representing squillions of such values. So it can't be said to be truly uniform across the specified range to full precision.

复杂:然而,在[0,1]范围内存在许多上述不能产生的双重值。具体而言,不能发生[0,0.5]范围内的值的一半(设置了最低有效位的值)。 [0,0.25]范围内的四分之三的值(设置了最少2位的值)不会发生等等,一直到只有一个小于2 ^ -51的正值是可能的,尽管双重能够代表这些价值的数量。因此,不能说在指定范围内达到完全精确的真正均匀。

Of course we don't want to choose one of those doubles with equal probability, because then the resulting number will on average be too small. We still need the probability of the result being in a given range to be proportional to the range, but with a higher precision on what ranges that works for.

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

I think the following works. I haven't particularly studied or tested this algorithm (as you can probably tell by the way there's no code), and personally I wouldn't use it without finding proper references indicating it's valid. But here goes:

我认为以下工作。我没有特别研究或测试过这种算法(你可以通过没有代码的方式来判断),而且如果没有找到适当的引用表明它是有效的,我个人不会使用它。但是这里:

  • Start the exponent off at 52 and choose a 52-bit random unsigned integer (assuming 52 bits of mantissa).
  • 在52处启动指数并选择52位随机无符号整数(假设52位尾数)。

  • If the most significant bit of the integer is 0, increase the exponent by one, shift the integer left by one, and fill the least significant bit in with a new random bit.
  • 如果整数的最高有效位为0,则将指数增加1,将整数左移1,并使用新的随机位填充最低有效位。

  • Repeat until either you hit a 1 in the most significant place, or else the exponent gets too big for your double (1023. Or possibly 1022).
  • 重复,直到你在最重要的地方击中1,否则指数对于你的双倍而言太大(1023或者可能是1022)。

  • If you found a 1, divide your value by 2^exponent. If you got all zeroes, return 0 (I know, that's not actually a special case, but it bears emphasis how very unlikely a 0 return is [Edit: actually it might be a special case - it depends whether or not you want to generate denorms. If not then once you have enough 0s in a row you discard anything left and return 0. But in practice this is so unlikely as to be negligible, unless the random source isn't random).
  • 如果您找到1,则将您的值除以2 ^指数。如果你得到全零,则返回0(我知道,这实际上并不是一个特殊情况,但强调0返回的可能性非常小[编辑:实际上它可能是一个特例 - 它取决于你是否想要生成如果没有,那么一旦你有足够的0连续你丢弃剩下的东西并返回0.但实际上这是不可能的,可以忽略不计,除非随机源不是随机的)。

I don't know whether there's actually any practical use for such a random double, mind you. Your definition of random should depend to an extent what it's for. But if you can benefit from all 52 of its significant bits being random, this might actually be helpful.

我不知道对于这样一个随机的双重是否真的有任何实际用途。你对随机的定义应该取决于它的用途。但是如果你可以从其所有52个有效位中获益,那么这实际上可能会有所帮助。

#2


3  

This seems to be pretty good way:

这似乎是相当不错的方式:


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);

This is based on NetBSD's drand48 implementation.

这是基于NetBSD的drand48实现。

#3


0  

Reading from files is thread-safe AFAIK, so using fopen() to read from /dev/urandom will yield "truly random" bytes.

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

Although there might be potential gotchas, methinks any set of such bytes accessed as an integer, divided by the maximum integer of that size, will yield a floating point value between 0 and 1 with approximately that distribution.

尽管可能存在潜在的问题,但是将任何一组这样的字节作为整数访问,除以该大小的最大整数,将产生一个介于0和1之间的浮点值,大约具有该分布。

Eg:

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);

#4


0  

The trick is you need a 54 bit randomizer that meets your requirements. A few lines of code with a union to stick those 54 bits in the mantissa and you have your number. The trick is not double float the trick is your desired randomizer.

诀窍是你需要一个符合你要求的54位随机数发生器。几行代码用一个联合来粘贴尾数中的54位,你有你的号码。诀窍不是双重浮动技巧是你想要的随机发生器。

#5


0  

#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 is your random value

c是你的随机值

#6


0  

/dev/urandom is not POSIX, and is not generally available.

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

The standard way of generating a double uniformly in [0,1) is to generate an integer in the range [0,2^N) and divide by 2^N. So pick your favorite random number generator and use it. For simulations, mine is the Mersenne Twister, as it is extremely quick, yet still not well correlated. Actually, it can do this for you, and even has a version that will give more precision for the smaller numbers. Typically you give it a seed to start with, which helps for repeatability for debugging or showing others your results. Of course, you can have your code grab a random number from /dev/urandom as the seed if one isn't specified.

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

For cryptographic purposes you should use one of the standard cryptographic libraries out there instead, such as openssl) which will indeed use /dev/urandom when available.

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

As to thread safety, most won't be, at least with the standard interfaces, so you'll need to build a layer on top, or only use them in one thread. The ones that are thread safe have you supply a state that they modify, so that instead you are effectively running multiple non-interacting random number generators, which may not be what you are looking for.

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

#1


3  

Simple: A double has 52 bits of precision assuming IEEE. So generate a 52 bit (or larger) unsigned random integer (for example by reading bytes from dev/urandom), convert it into a double and divide it by 2^(number of bits it was).

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

This gives a numerically uniform distribution (in that the probability of a value being in a given range is proportional to the range) down to the 52nd binary digit.

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

Complicated: However, there are a lot of double values in the range [0,1) which the above cannot generate. To be specific, half the values in the range [0,0.5) (the ones that have their least significant bit set) can't occur. Three quarters of the values in the range [0,0.25) (the ones that have either of their least 2 bits set) can't occur, etc, all the way to only one positive value less than 2^-51 being possible, despite a double being capable of representing squillions of such values. So it can't be said to be truly uniform across the specified range to full precision.

复杂:然而,在[0,1]范围内存在许多上述不能产生的双重值。具体而言,不能发生[0,0.5]范围内的值的一半(设置了最低有效位的值)。 [0,0.25]范围内的四分之三的值(设置了最少2位的值)不会发生等等,一直到只有一个小于2 ^ -51的正值是可能的,尽管双重能够代表这些价值的数量。因此,不能说在指定范围内达到完全精确的真正均匀。

Of course we don't want to choose one of those doubles with equal probability, because then the resulting number will on average be too small. We still need the probability of the result being in a given range to be proportional to the range, but with a higher precision on what ranges that works for.

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

I think the following works. I haven't particularly studied or tested this algorithm (as you can probably tell by the way there's no code), and personally I wouldn't use it without finding proper references indicating it's valid. But here goes:

我认为以下工作。我没有特别研究或测试过这种算法(你可以通过没有代码的方式来判断),而且如果没有找到适当的引用表明它是有效的,我个人不会使用它。但是这里:

  • Start the exponent off at 52 and choose a 52-bit random unsigned integer (assuming 52 bits of mantissa).
  • 在52处启动指数并选择52位随机无符号整数(假设52位尾数)。

  • If the most significant bit of the integer is 0, increase the exponent by one, shift the integer left by one, and fill the least significant bit in with a new random bit.
  • 如果整数的最高有效位为0,则将指数增加1,将整数左移1,并使用新的随机位填充最低有效位。

  • Repeat until either you hit a 1 in the most significant place, or else the exponent gets too big for your double (1023. Or possibly 1022).
  • 重复,直到你在最重要的地方击中1,否则指数对于你的双倍而言太大(1023或者可能是1022)。

  • If you found a 1, divide your value by 2^exponent. If you got all zeroes, return 0 (I know, that's not actually a special case, but it bears emphasis how very unlikely a 0 return is [Edit: actually it might be a special case - it depends whether or not you want to generate denorms. If not then once you have enough 0s in a row you discard anything left and return 0. But in practice this is so unlikely as to be negligible, unless the random source isn't random).
  • 如果您找到1,则将您的值除以2 ^指数。如果你得到全零,则返回0(我知道,这实际上并不是一个特殊情况,但强调0返回的可能性非常小[编辑:实际上它可能是一个特例 - 它取决于你是否想要生成如果没有,那么一旦你有足够的0连续你丢弃剩下的东西并返回0.但实际上这是不可能的,可以忽略不计,除非随机源不是随机的)。

I don't know whether there's actually any practical use for such a random double, mind you. Your definition of random should depend to an extent what it's for. But if you can benefit from all 52 of its significant bits being random, this might actually be helpful.

我不知道对于这样一个随机的双重是否真的有任何实际用途。你对随机的定义应该取决于它的用途。但是如果你可以从其所有52个有效位中获益,那么这实际上可能会有所帮助。

#2


3  

This seems to be pretty good way:

这似乎是相当不错的方式:


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);

This is based on NetBSD's drand48 implementation.

这是基于NetBSD的drand48实现。

#3


0  

Reading from files is thread-safe AFAIK, so using fopen() to read from /dev/urandom will yield "truly random" bytes.

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

Although there might be potential gotchas, methinks any set of such bytes accessed as an integer, divided by the maximum integer of that size, will yield a floating point value between 0 and 1 with approximately that distribution.

尽管可能存在潜在的问题,但是将任何一组这样的字节作为整数访问,除以该大小的最大整数,将产生一个介于0和1之间的浮点值,大约具有该分布。

Eg:

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);

#4


0  

The trick is you need a 54 bit randomizer that meets your requirements. A few lines of code with a union to stick those 54 bits in the mantissa and you have your number. The trick is not double float the trick is your desired randomizer.

诀窍是你需要一个符合你要求的54位随机数发生器。几行代码用一个联合来粘贴尾数中的54位,你有你的号码。诀窍不是双重浮动技巧是你想要的随机发生器。

#5


0  

#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 is your random value

c是你的随机值

#6


0  

/dev/urandom is not POSIX, and is not generally available.

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

The standard way of generating a double uniformly in [0,1) is to generate an integer in the range [0,2^N) and divide by 2^N. So pick your favorite random number generator and use it. For simulations, mine is the Mersenne Twister, as it is extremely quick, yet still not well correlated. Actually, it can do this for you, and even has a version that will give more precision for the smaller numbers. Typically you give it a seed to start with, which helps for repeatability for debugging or showing others your results. Of course, you can have your code grab a random number from /dev/urandom as the seed if one isn't specified.

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

For cryptographic purposes you should use one of the standard cryptographic libraries out there instead, such as openssl) which will indeed use /dev/urandom when available.

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

As to thread safety, most won't be, at least with the standard interfaces, so you'll need to build a layer on top, or only use them in one thread. The ones that are thread safe have you supply a state that they modify, so that instead you are effectively running multiple non-interacting random number generators, which may not be what you are looking for.

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