【问题标题】:Generating random floating-point values based on random bit stream基于随机比特流生成随机浮点值
【发布时间】:2011-06-28 06:13:06
【问题描述】:

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

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

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

我想实现

double GetRandomVal(double fMin, double fMax);

注意事项:

  • 我不希望结果精度受到限制(例如只有 5 位)。
  • 必须严格均匀分布
  • 我不是要求对现有库的引用。我想知道如何从头开始实施它。
  • 对于伪代码/代码,C++ 将不胜感激

【问题讨论】:

  • 为什么要重新发明轮子?所有平台都有很好的(伪)随机来源。
  • 听起来很像我在谷歌工作时遇到的一个面试问题!
  • 您是否考虑过在提供的范围内不完全有 2^n 个可能值的可能性?统一到底有多统一? :)
  • @the JinX:伪随机和实际随机是完全不同的野兽......考虑制作一次性加密垫 - 你不会用 rand() 这样做;-P
  • @the JinX:问题是关于如何将现有的“良好随机源”变成均匀分布的随机源doubles.

标签: c++ algorithm random


【解决方案1】:

这个问题是不恰当的。浮点数上的均匀分布是什么意思?

根据discrepancy 的提示,解决您的问题的一种方法是定义您想要最小化以下值的分布:

其中x 是您使用GetRandomVal(double fMin, double fMax) 函数采样的random variable, 表示随机x 小于或等于t 的概率。

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

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

【讨论】:

    【解决方案2】:

    令我惊讶的是,对于这个古老的问题,没有人提供最佳答案的实际代码。 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 位,请改用它。

    【讨论】:

    • 我相信你的方法不会导致严格的均匀分布。从范围 [1..2] 到范围 [0..1] 的 2^52 值之间不存在可以统一的一对一映射。一些随机值会比其他值更常见。
    • 在 IEEE-754 double 中尽可能统一。此处的 2^52 双精度值实际上是等距的,范围从 1.0 到可表示为双精度的最大值
    • 我还应该指出,我已经对该代码进行了广泛的一致性测试,发现它非常健壮。我的库包括欢迎您检查的测试。
    • >> “尽可能统一” - 我不能同意这一点。挑战是生成范围 [0..1) 内的所有可表示值,并使用确保一致性的 PDF。您的实现可能适用于任何实际目的,但远非完美(理论上)。
    • 你是对的,在区间 [0,1) 中有超过 2^52 个可表示的值,但它们并不统一,因此可以生成所有这些的代码同时仍然是统一的将是相当复杂的。我想这是可行的,并且可能是一个有趣的练习,但是你只会比给出的代码获得一点额外的精度,所以我怀疑它是否值得。
    【解决方案3】:

    我想我永远不会相信你真的需要这个,但写起来很有趣。

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

    【讨论】:

    • 人们可以通过用一段读取实际指数的代码替换 frexp 来使其适用于非正规或零。之后,负 a 和 b 不会太难(尽管您必须考虑双精度数按符号大小顺序排列的事实,并相应地调整 x_bits 的采样)。
    • 难以置信!这太棒了!
    【解决方案4】:

    这是一种方法。

    IEEE Std 754 双精度格式如下:

    [s][     e     ][                          f                         ]
    

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

    注意内存中的布局在 little-endian 机器上会有所不同。

    对于0

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

    通过将 s 设置为 0,将 e 设置为 1023,将 f 设置为比特流中的 52 个随机位,您将在区间 [1.0, 2.0) 中获得一个随机双精度。这个区间的独特之处在于它包含 2 ** 52 个双精度数,并且这些双精度数是等距的。如果然后从构造的双精度数中减去 1.0,则会在区间 [0.0, 1.0) 中得到一个随机双精度数。此外,关于等距的性质是保留的。 从那里您应该能够根据需要进行缩放和翻译。

    【讨论】:

    • 这真的很酷。虽然我承认我不明白:D
    【解决方案5】:

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

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

    在 [rand.util.canonical] 和 [rand.dist.uni.real] 部分中,包含足够的信息来实现您想要的,尽管语法略有不同。这并不容易,但这是可能的。我从个人经历说。一年前,我对随机数一无所知,但我能够做到。虽然花了我一段时间... :-)

    【讨论】:

      【解决方案6】:

      要在 [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】:

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

        【讨论】:

        • ... 这将是一个 0 到 2^n-1 的整数范围。我想要一个范围为 Min 到 Max 的浮点数。
        • @Lior Kogan 为什么假设 Ben 在谈论整数。数字流也可以是浮点数。整数部分采样 n 位,小数部分采样 n 位。
        • 或者采样两个整数,然后用较小的除以较大的。
        【解决方案8】:

        这很容易,只要您有一个精度与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 ;
        }
        

        【讨论】:

        • 实际上,您正在从 2^64 个可能值映射到 2^53 个可能值这样的映射不会提供均匀分布(是的,这样的准确性对我来说很重要)。
        • 浮点数不是均匀分布的。如果您需要比这更好的,您将不得不自己构建神话般的 Real RAM。
        • @a dabbler:谢谢,确实如此。但是,在给定的狭窄范围内(例如从 34 到 35)的浮点数不会均匀分布吗?
        • @Lior Kogan:为什么不统一?通过压缩这些值,您不会改变一致性,就像 IEEE 浮点数的基本颗粒度一样。
        • 扮演魔鬼的拥护者:这个函数不使用接近零的值的完整有效位,也许提问者希望在随机数的罕见事件中执行额外的、准确性敏感的处理与界限相比非常小。 正确 的做法可能是提取一个额外的随机值,但谁知道呢?像往常一样,我们不知道真正的问题是什么。
        猜你喜欢
        • 2010-10-15
        • 2023-01-03
        • 1970-01-01
        • 1970-01-01
        • 2019-08-19
        • 2021-11-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多