如何在C ++中生成0到1之间均匀分布的随机双精度?

时间:2009-08-27 12:24:43

标签: c++ random

如何从C ++生成0到1之间均匀分布的随机双精度?

当然我可以想到一些答案,但我想知道标准做法是什么,有:

  • 良好的标准合规性
  • 良好的随机性
  • 速度快

(速度比我的应用程序的随机性更重要)。

非常感谢!

PS:如果重要,我的目标平台是Linux和Windows。

13 个答案:

答案 0 :(得分:55)

旧学校解决方案如:

double X=((double)rand()/(double)RAND_MAX);

应符合您的所有标准(便携式,标准型和快速型)。 很明显,生成的随机数必须是标准程序,如下所示:

srand((unsigned)time(NULL));

答案 1 :(得分:31)

在C ++ 11和C ++ 14中,random header有更好的选择。 Stephan T. Lavavej 的演示文稿rand() Considered Harmful解释了为什么我们应该避免在C ++中使用rand()而支持random标题和N3924: Discouraging rand() in C++14进一步强调了这一点。

以下示例是cppreference网站上示例代码的修改版本,并使用std::mersenne_twister_engine引擎和std::uniform_real_distribution生成[0,1)范围内的数字( { {3}} 的):

#include <iostream>
#include <iomanip>
#include <map>
#include <random>

int main()
{
    std::random_device rd;


    std::mt19937 e2(rd());

    std::uniform_real_distribution<> dist(0, 1);

    std::map<int, int> hist;
    for (int n = 0; n < 10000; ++n) {
        ++hist[std::round(dist(e2))];
    }

    for (auto p : hist) {
        std::cout << std::fixed << std::setprecision(1) << std::setw(2)
                  << p.first << ' ' << std::string(p.second/200, '*') << '\n';
    }
}

输出类似于以下内容:

0 ************************
1 *************************

由于帖子提到速度很重要,所以我们应该考虑描述不同随机数引擎的cppreference部分(强调我的):

  

选择使用哪种引擎涉及许多权衡*:   **线性同余引擎适度快并且非常小   国家的储存要求。 滞后的斐波纳契生成器是   甚至在没有高级算术指令的处理器上也非常快   设置,以更大的状态存储为代价,有时更少   理想的光谱特性。 Mersenne twister慢了   具有更大的状态存储要求但具有正确的参数   具有最长的非重复序列,具有最理想的   光谱特征(对于给定的理想定义)。

因此,如果需要更快的生成器,see it liveranlux24_base可能比ranlux48_base更好的选择。

兰特()

如果您被迫rand()使用 C常见问题解答作为mt19937上的指南,请为我们提供一个与此类似的示例,用于生成间隔{{1 }}:

[0,1)

并生成#include <stdlib.h> double randZeroToOne() { return rand() / (RAND_MAX + 1.); }

范围内的随机数
[M,N)

答案 2 :(得分:6)

random_real class中的Boost random library就是您所需要的。

答案 3 :(得分:5)

如果您使用C++ TR1,请按以下步骤操作。

答案 4 :(得分:3)

如果速度是您最关心的问题,那么我只需要使用

double r = (double)rand() / (double)RAND_MAX;

答案 5 :(得分:3)

C ++ 11标准库包含一个不错的框架和几个可维护的生成器,这对于家庭作业和袖口使用来说是完全足够的。

但是,对于生产级代码,在使用它们之前,您应该确切知道各种生成器的具体属性,因为所有这些都有它们的警告。此外,他们都没有通过像TestU01这样的PRNG的标准测试,除了使用具有慷慨奢侈因素的ranlux发电机。

如果你想要坚实,可重复的结果,那么你必须携带自己的发电机。

如果您想要便携性,那么您必须携带自己的发电机。

如果您可以使用受限制的可移植性,那么您可以将boost或C ++ 11框架与您自己的生成器结合使用。

更多详细信息 - 包括一个简单但快速生成优质和丰富链接的代码 - 可以在我对类似主题的回答中找到:

对于专业统一的浮点偏差,还有两个问题需要考虑:

  • 开放与半开放与封闭范围,即(0,1),[0,1)或[0,1]
  • 从积分转换为浮点的方法(精度,速度)

两者实际上都是同一枚硬币的两面,因为转换方法会考虑包含/排除0和1.以下是半开区间的三种不同方法:

// exact values computed with bc

#define POW2_M32   2.3283064365386962890625e-010
#define POW2_M64   5.421010862427522170037264004349e-020

double random_double_a ()
{
   double lo = random_uint32() * POW2_M64;
   return lo + random_uint32() * POW2_M32;
}

double random_double_b ()
{
   return random_uint64() * POW2_M64;
}

double random_double_c ()
{
   return int64_t(random_uint64()) * POW2_M64 + 0.5;
}

random_uint32()random_uint64()是您实际功能的占位符,通常会作为模板参数传递)

方法 a 演示如何创建一个均匀偏差,不会因较低值的过度精度而产生偏差; 64位代码没有显示,因为它更简单,只需要屏蔽掉11位。所有函数的分布均匀,但没有这个技巧,在接近0的区域中将有比其他地方更多的不同值(由于变化的ulp,网格间距更细)。

方法 c 显示了如何在某些流行平台上更快地获得统一偏差,其中FPU只知道带符号的64位整数类型。您最常见的是方法 b 但是编译器必须在引擎盖下生成大量额外代码以保留无符号语义。

混合并匹配这些原则,以创建您自己的定制解决方案。

JürgenDoornik的优秀论文Conversion of High-Period Random Numbers to Floating Point解释了所有这些。

答案 6 :(得分:2)

首先包括stdlib.h

#include<stdlib.h>

然后,下面的函数可以在C编程语言中生成一个范围之间的随机双数。

double randomDouble() {
    double lowerRange = 1.0;
    double upperRange = 10.0;
    return ((double)rand() * (upperRange - lowerRange)) / (double)RAND_MAX + lowerRange;
}

这里RAND_MAX在stdlib.h中定义

答案 7 :(得分:1)

正如我所看到的,有三种方法可以解决这个问题,

1)简单的方法。

double rand_easy(void)
{       return (double) rand() / (RAND_MAX + 1.0);
}

2)安全的方式(符合标准)。

double rand_safe(void)
{
        double limit = pow(2.0, DBL_MANT_DIG);
        double denom = RAND_MAX + 1.0;
        double denom_to_k = 1.0;
        double numer = 0.0;

        for ( ; denom_to_k < limit; denom_to_k *= denom )
           numer += rand() * denom_to_k;

        double result = numer / denom_to_k;
        if (result == 1.0)
           result -= DBL_EPSILON/2;
        assert(result != 1.0);
        return result;
}

3)自定义方式。

通过消除rand(),我们不再需要担心任何特定版本的特性,这为我们自己的实现提供了更多的余地。

注意:此处使用的发电机周期为≅1.8e+ 19.

#define RANDMAX (-1ULL)
uint64_t custom_lcg(uint_fast64_t* next)
{       return *next = *next * 2862933555777941757ULL + 3037000493ULL;
}

uint_fast64_t internal_next;
void seed_fast(uint64_t seed)
{       internal_next = seed;
}

double rand_fast(void)
{
#define SHR_BIT (64 - (DBL_MANT_DIG-1))
        union {
            double f; uint64_t i;
        } u;
        u.f = 1.0;
        u.i = u.i | (custom_lcg(&internal_next) >> SHR_BIT);
        return u.f - 1.0;
}

无论选择何种,功能都可以扩展如下,

double rand_dist(double min, double max)
{       return rand_fast() * (max - min) + min;
}

double rand_open(void)
{       return rand_dist(DBL_EPSILON, 1.0);
}

double rand_closed(void)
{       return rand_dist(0.0, 1.0 + DBL_EPSILON);
}

最后的注释:快速版本 - 用C语言编写 - 可以在C ++中使用,以用作std::generate_canonical的替代,并且可以用于任何发射值的发生器有足够的重要位。

大多数64位发生器利用它们的全宽,因此可以不加修改地使用(移位调整)。例如这与std::mt19937_64引擎一样正常。

答案 8 :(得分:0)

考虑到简单性和速度是您的主要标准,您可以添加如下的小型通用助手: -

  // C++ rand generates random numbers between 0 and RAND_MAX. This is quite a big range
  // Normally one would want the generated random number within a range to be really
  // useful. So the arguments have default values which can be overridden by the caller
  int nextRandomNum(int low = 0, int high = 100) const {
    int range = (high - low) + 1;
    // this modulo operation does not generate a truly uniformly distributed random number
    // in the span (since in most cases lower numbers are slightly more likely), 
    // but it is generally a good approximation for short spans. Use it if essential
    //int res = ( std::rand() % high + low );
    int res = low + static_cast<int>( ( range * std::rand() / ( RAND_MAX + 1.0) ) );
    return res;
  }

随机数生成是一个经过充分研究,复杂和高级的主题。除了其他答案中提到的算法之外,你可以找到一些简单但有用的算法: -

Eternally Confuzzled

答案 9 :(得分:-1)

您可以尝试使用Mersenne Twister算法。

http://en.wikipedia.org/wiki/Mersenne_twister

它具有速度和随机性的良好融合,以及GPL实现。

答案 10 :(得分:-1)

这是我最终用于满足我的需求:

int range_upper_bound = 12345;
int random_number =((double)rand()/(double)range_upper_bound);

答案 11 :(得分:-2)

double randDouble()
{
  double out;
  out = (double)rand()/(RAND_MAX + 1); //each iteration produces a number in [0, 1)
  out = (rand() + out)/RAND_MAX;
  out = (rand() + out)/RAND_MAX;
  out = (rand() + out)/RAND_MAX;
  out = (rand() + out)/RAND_MAX;
  out = (rand() + out)/RAND_MAX;

  return out;
}

不如double X=((double)rand()/(double)RAND_MAX);快,但分发更好。该算法仅给出RAND_MAX均匀间隔的返回值选择;这个给出了RANDMAX ^ 6,所以它的分布只受到double的精度的限制。

如果你想要一个long double,只需添加几个迭代。如果你想要[0,1]而不是[0,1]中的数字,只需要将第4行读为out = (double)rand()/(RAND_MAX);

答案 12 :(得分:-2)

//Returns a random number in the range (0.0f, 1.0f).
// 0111 1111 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
// seee eeee eeee vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv
// sign     = 's'
// exponent = 'e'
// value    = 'v'
double DoubleRand() {
  typedef unsigned long long uint64;
  uint64 ret = 0;
  for (int i = 0; i < 13; i++) {
     ret |= ((uint64) (rand() % 16) << i * 4);
  }
  if (ret == 0) {
    return rand() % 2 ? 1.0f : 0.0f;
  }
  uint64 retb = ret;
  unsigned int exp = 0x3ff;
  retb = ret | ((uint64) exp << 52);
  double *tmp = (double*) &retb;
  double retval = *tmp;
  while (retval > 1.0f || retval < 0.0f) {
    retval = *(tmp = (double*) &(retb = ret | ((uint64) (exp--) << 52)));
  }
  if (rand() % 2) {
    retval -= 0.5f;
  }
  return retval;
}

这应该可以解决问题,我使用this维基百科的文章来帮助创建这个。我认为它与drand48();

一样好