基于随机比特流生成随机浮点值

时间:2011-02-16 10:16:51

标签: c++ algorithm random

给定随机源(随机比特流的生成器),如何在给定范围内生成均匀分布的随机浮点值?

假设我的随机源看起来像:

unsigned int GetRandomBits(char* pBuf, int nLen);

我想实施

double GetRandomVal(double fMin, double fMax);

备注:

  • 我不希望限制结果精度(例如只有5位数)。
  • 严格的统一分配是必须的
  • 我不是要求提供对现有库的引用。我想知道如何从头开始实施它。
  • 对于伪代码/代码,C ++非常感谢

8 个答案:

答案 0 :(得分:8)

我认为我不相信你真的需要这个,但写作很有趣。

#include <stdint.h>

#include <cmath>
#include <cstdio>

FILE* devurandom;

bool geometric(int x) {
  // returns true with probability min(2^-x, 1)
  if (x <= 0) return true;
  while (1) {
    uint8_t r;
    fread(&r, sizeof r, 1, devurandom);
    if (x < 8) {
      return (r & ((1 << x) - 1)) == 0;
    } else if (r != 0) {
      return false;
    }
    x -= 8;
  }
}

double uniform(double a, double b) {
  // requires IEEE doubles and 0.0 < a < b < inf and a normal
  // implicitly computes a uniform random real y in [a, b)
  // and returns the greatest double x such that x <= y
  union {
    double f;
    uint64_t u;
  } convert;
  convert.f = a;
  uint64_t a_bits = convert.u;
  convert.f = b;
  uint64_t b_bits = convert.u;
  uint64_t mask = b_bits - a_bits;
  mask |= mask >> 1;
  mask |= mask >> 2;
  mask |= mask >> 4;
  mask |= mask >> 8;
  mask |= mask >> 16;
  mask |= mask >> 32;
  int b_exp;
  frexp(b, &b_exp);
  while (1) {
    // sample uniform x_bits in [a_bits, b_bits)
    uint64_t x_bits;
    fread(&x_bits, sizeof x_bits, 1, devurandom);
    x_bits &= mask;
    x_bits += a_bits;
    if (x_bits >= b_bits) continue;
    double x;
    convert.u = x_bits;
    x = convert.f;
    // accept x with probability proportional to 2^x_exp
    int x_exp;
    frexp(x, &x_exp);
    if (geometric(b_exp - x_exp)) return x;
  }
}

int main() {
  devurandom = fopen("/dev/urandom", "r");
  for (int i = 0; i < 100000; ++i) {
    printf("%.17g\n", uniform(1.0 - 1e-15, 1.0 + 1e-15));
  }
}

答案 1 :(得分:5)

这是一种方法。

IEEE Std 754双格式如下:

[s][     e     ][                          f                         ]

其中s是符号位(1位),e是偏置指数(11位),f是小数(52位)。

请注意,在小端机器上,内存中的布局会有所不同。

对于0&lt; e&lt; 2047年,代表的数字是

(-1)**(s)   *  2**(e – 1023)  *  (1.f)

通过将s设置为0,e到1023以及来自比特流的f到52个随机位,在区间[1.0,2.0]中得到一个随机加倍。这个区间是独特的,因为它包含2 ** 52个双打,并且这些双精度是等距的。如果然后从构造的double中减去1.0,则在区间[0.0,1.0]中得到一个随机双精度数。而且,保持等距的属性。 从那里你应该能够根据需要进行缩放和翻译。

答案 2 :(得分:3)

这很简单,只要您的整数类型具有与double一样多的精度位。例如,IEEE双精度数具有53位精度,因此64位整数类型就足够了:

#include <limits.h>
double GetRandomVal(double fMin, double fMax) {
  unsigned long long n ;
  GetRandomBits ((char*)&n, sizeof(n)) ;
  return fMin + (n * (fMax - fMin))/ULLONG_MAX ;
}

答案 3 :(得分:3)

我很惊讶,对于这个问题,没有人有真正的代码来获得最佳答案。 User515430's answer做对了 - 你可以利用IEEE-754双格式直接将52位放入一个双数而根本没有数学运算。但他没有给出代码。这就是我的公共领域ojrandlib

double ojr_next_double(ojr_generator *g) {
    uint64_t r = (OJR_NEXT64(g) & 0xFFFFFFFFFFFFFull) | 0x3FF0000000000000ull;
    return *(double *)(&r) - 1.0;
}

NEXT64()获得一个64位随机数。如果你有一个更有效的方法只获得52位,那就改用它。

答案 4 :(得分:2)

这可能不是你想要的答案,而是这里的规范:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3225.pdf

部分[rand.util.canonical]和[rand.dist.uni.real]中的

包含足够的信息来实现您想要的内容,但语法略有不同。这并不容易,但有可能。我是根据个人经验说的。一年前,我对随机数字一无所知,我能够做到。虽然我花了一段时间......: - )

答案 5 :(得分:0)

我可能误解了这个问题,但是什么阻止你只是从随机比特流中采样下一个n比特并将其转换为基数为10的数字,范围为0到2 ^ n - 1.

答案 6 :(得分:0)

在[0..1中获取随机值[你可以做类似的事情:

double value = 0;
for (int i=0;i<53;i++)
   value = 0.5 * (value + random_bit());  // Insert 1 random bit
   // or value = ldexp(value+random_bit(),-1);
   // or group several bits into one single ldexp
return value;

答案 7 :(得分:0)

这个问题是不合适的。浮标上的均匀分布甚至意味着什么?

discrepancy中获取提示,实现问题的一种方法是定义您希望最小化以下值的分布:

\int_{t=fmin}^{fmax} \left(p\left(x \leq \text{t} \right ) - \frac{t-fmin}{fmax-fmin} \right )^2dt

x是您使用GetRandomVal(double fMin, double fMax)函数抽样的random variablep(x <= t表示随机x小于或等于{{0}}的概率t

现在你可以继续尝试评估a dabbler's answer。 (提示所有未能使用整个精度并且坚持使用例如52位的答案将使此最小化标准失败。)

但是,如果您只是希望能够以相同的可能性生成落入指定范围的所有浮点模式,即使这意味着例如要求GetRandomVal(0,1000)将创建0到1.5之间的更多值。在1.5到1000之间,这很容易:当解释为位模式时,任何IEEE浮点数的间隔都很容易映射到unsigned int64的非常少的间隔。参见例如question。在任何给定的时间间隔内生成unsigned int64的均等分布随机值很容易。