随机浮点数生成


271

如何在C ++中生成随机浮点数?

我以为我可以将整数兰特除以某物,这是否足够?


2
这取决于您想要的数字以及随机性。通常rand()会给出15位的随机性,但浮点数具有23位的精度,因此它将丢失一些值。
Pete Kirkham 2009年

1
我已经更新了答案,以包括所有可用的主要选项,而专注random于C ++ 11中添加的标头的选择又得到了标准文档N3924:C ++ 14中不鼓励rand()的支持。我rand()在回答中主要考虑了历史因素,但也意识到确实存在遗留应用程序。
Shafik Yaghmour 2014年

我的答案包括如何避免每次使用<random>标头都得到相同的数字
Andreas DM

Answers:


381

rand()可用于在C ++中生成伪随机数。结合RAND_MAX一些数学知识,您可以选择任意间隔生成随机数。这足以用于学习目的和玩具程序。如果需要具有正态分布的真正随机数,则需要采用更高级的方法。


这将生成一个从0.0到1.0(含)的数字。

float r = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);

这将产生从0.0号到一些任意的floatX

float r2 = static_cast <float> (rand()) / (static_cast <float> (RAND_MAX/X));

这将生成一个从任意LO到任意的数字HI

float r3 = LO + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(HI-LO)));

请注意,rand()如果您需要真正的随机数,该功能通常将不足。


调用之前rand(),您必须先通过调用“种子”随机数生成器srand()。这应该在程序运行期间执行一次,而不是每次调用一次rand()。通常这样做是这样的:

srand (static_cast <unsigned> (time(0)));

为了打电话rand还是srand必须的#include <cstdlib>

为了打电话time,你必须#include <ctime>


22
别忘了先播!
克拉姆

14
最好注意,这两个限制都包括在内。
dmckee ---前主持人小猫,

14
这个答案是误导的。上周的《 Going Native 2013》对此进行了报道。rand()被认为有害,请 参阅channel9.msdn.com/Events/GoingNative/2013/…,以获取非常详细的说明。
阿德·米勒

14
我不明白为什么这么多人赞成这个答案。这在数学上是不正确的。RAND_MAX是一个很小的数字(通常为2 ^ 16)。这意味着从浮点数的23位中,您只能随机获得15位。其他可能为零。实际上,您将获得均匀分布但精度较低的随机数。例如,您的随机生成器可以生成0.00001和0.00002,但不能生成0.000017。因此,您的分布均匀,但精度较低(精度比实际浮点低256倍)。
DanielHsH 2014年

10
@DanielHsH:OP特别询问了可以使用什么机制来生成随机浮点数rand()。这个问题和我的回答特别关注于基础知识的学习,而不关注高精度。您必须学会走路,然后才能跑步。
John Dibling

137

C ++ 11为您提供了许多新选项random。关于该主题的规范性论文为N3551,C ++ 11中的随机数生成

要了解使用为什么rand()会出现问题,请参阅在GoingNative 2013活动期间由Stephan T. Lavavej提供的rand()被认为有害的演示材料。幻灯片在评论中,但这是直接链接

由于遗留代码可能仍需要其支持,因此我还将介绍boost和使用rand

下面的示例是从cppreference站点中提取的,并使用std :: mersenne_twister_engine引擎和std :: uniform_real_distribution来生成[0,10)时间间隔中的数字,并注释掉了其他引擎和分布(实时查看):

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

int main()
{
    std::random_device rd;

    //
    // Engines 
    //
    std::mt19937 e2(rd());
    //std::knuth_b e2(rd());
    //std::default_random_engine e2(rd()) ;

    //
    // Distribtuions
    //
    std::uniform_real_distribution<> dist(0, 10);
    //std::normal_distribution<> dist(2, 2);
    //std::student_t_distribution<> dist(5);
    //std::poisson_distribution<> dist(2);
    //std::extreme_value_distribution<> dist(0,2);

    std::map<int, int> hist;
    for (int n = 0; n < 10000; ++n) {
        ++hist[std::floor(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 ****
2 ****
3 ****
4 *****
5 ****
6 *****
7 ****
8 *****
9 ****

输出会根据您选择的分布而有所不同,因此,如果我们决定使用均值stddev均为std :: normal_distribution的值,例如,输出将与此类似(请参见live):2dist(2, 2)

-6 
-5 
-4 
-3 
-2 **
-1 ****
 0 *******
 1 *********
 2 *********
 3 *******
 4 ****
 5 **
 6 
 7 
 8 
 9 

以下是其中的一些代码的修改版本N3551实时查看):

#include <algorithm>
#include <array>
#include <iostream>
#include <random>

std::default_random_engine & global_urng( )
{
    static std::default_random_engine u{};
    return u ;
}

void randomize( )
{
    static std::random_device rd{};
    global_urng().seed( rd() );
}

int main( )
{
  // Manufacture a deck of cards:
  using card = int;
  std::array<card,52> deck{};
  std::iota(deck.begin(), deck.end(), 0);

  randomize( ) ;  

  std::shuffle(deck.begin(), deck.end(), global_urng());
  // Display each card in the shuffled deck:
  auto suit = []( card c ) { return "SHDC"[c / 13]; };
  auto rank = []( card c ) { return "AKQJT98765432"[c % 13]; };

  for( card c : deck )
      std::cout << ' ' << rank(c) << suit(c);

   std::cout << std::endl;
}

结果将类似于:

5H 5S AS 9S 4D 6H TH 6D KH 2S QS 9H 8H 3D KC TD 7H 2D KS 3C TC 7D 4C QH QC QD JD AH JC AC KD 9D 5C 2H 4H 9C 8C JH 5D 4S 7C AD 3S 8S TS 2C 8D 3H 6C JS 7S 6S

促进

当然,Boost.Random也总是一个选项,在这里我使用boost :: random :: uniform_real_distribution

#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>

int main()
{
    boost::random::mt19937 gen;
    boost::random::uniform_real_distribution<> dist(0, 10);

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

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

rand()

如果必须使用,rand()则可以转到C FAQ中有关如何生成浮点随机数的指南。,基本上给出了与此类似的示例来生成on间隔[0,1)

#include <stdlib.h>

double randZeroToOne()
{
    return rand() / (RAND_MAX + 1.);
}

并生成范围为的随机数[M,N)

double randMToN(double M, double N)
{
    return M + (rand() / ( RAND_MAX / (N-M) ) ) ;  
}

1
你能解决你的问题randMToN吗?请注意,[M,N]或者是+ 1.从上添加randZeroToOne。->想像这样称呼它:randMToN(0.0, 1.0);
BeyelerStudios '16

1
还应注意将处的零除(N-M)。对付这种误差的最好方法是在这里找到:stackoverflow.com/questions/33058848/...
贝乔博士

61

看一下Boost.Random。您可以执行以下操作:

float gen_random_float(float min, float max)
{
    boost::mt19937 rng;
    boost::uniform_real<float> u(min, max);
    boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > gen(rng, u);
    return gen();
}

试一试,您最好传递相同的mt19937对象,而不是每次都构造一个新的mt19937对象,但希望您能理解。


1
uniform_real使用半开间隔[min,max),这意味着您将获得最小值,但永远不会达到最大值。这是要考虑的事情,尽管如果您以某种方式四舍五入,则可以解决此问题。
德黑湾2011年

20
现在,这是C ++ 11的一部分。
Tomas Andrle 2011年

在实际应用中,@ Wolf碰到任何特定浮点值的几率非常低,以至于端点是包含还是排除都没有关系。如果需要,max但可以使用开放式min,则可以轻松地反转间隔:return min + max - gen();
Mark Ransom

26

在现代情况下,c++您可以使用<random>附带的标头c++11
要获得随机数float,您可以使用std::uniform_real_distribution<>

您可以使用函数来生成数字,如果您不想一直保持数字相同,请将引擎和分配设置为 static
例:

float get_random()
{
    static std::default_random_engine e;
    static std::uniform_real_distribution<> dis(0, 1); // rage 0 - 1
    return dis(e);
}

最好将放在float容器中,例如std::vector

int main()
{
    std::vector<float> nums;
    for (int i{}; i != 5; ++i) // Generate 5 random floats
        nums.emplace_back(get_random());

    for (const auto& i : nums) std::cout << i << " ";
}

输出示例:

0.0518757 0.969106 0.0985112 0.0895674 0.895542

std::uniform_real_distribution<> dis(0, 1); // rage 0 - 1在技术上是不正确,1.0永远不会被产生,见en.cppreference.com/w/cpp/numeric/random/... To create a distribution over the closed interval [a,b], std::nextafter(b, std::numeric_limits<RealType>::max()) may be used as the second parameter.
Troyseph

1
这应该是公认的答案,它吓坏2020年
亚历

25

用两个float值调用代码,该代码可在任何范围内使用。

float rand_FloatRange(float a, float b)
{
    return ((b - a) * ((float)rand() / RAND_MAX)) + a;
}

可能值得一提的是,这是C99或C ++ 11中fmaf()(或fma()C ++中的float 重载)的潜在用例,它可能会保留更高的精度。如中fmaf((float)rand() / RAND_MAX, b - a, a)
蒂姆·恰斯(TimČas)'17

22

如果您使用的是C ++,而不是C,那么请记住,在技术报告1(TR1)和C ++ 0x草案中,它们在头文件中添加了用于随机数生成器的功能,我认为它与Boost相同。随机库绝对比C库函数rand更灵活和“现代”。

此语法提供了选择生成器(如mersenne twister mt19937)然后选择分布(正态,贝努利,二项式等)的能力。

语法如下(从本网站借来的无耻):

  #include <iostream>
  #include <random>

  ...

  std::tr1::mt19937 eng;  // a core engine class 
  std::tr1::normal_distribution<float> dist;     

  for (int i = 0; i < 10; ++i)        
      std::cout << dist(eng) << std::endl;

2
现在在C ++ 11中,也可以使用min和max值初始化dist。
圣艾蒂安

将最小值和最大值放在初始化器中并在获取值时提供生成器对我来说似乎很奇怪-我希望如果不是这样,那就好了。
yoyo

6

在某些系统上(目前可以想到带有VC的Windows)RAND_MAX小得离谱。e。只有15位。除以时,RAND_MAX仅生成15位尾数,而不是23个可能的尾数。这可能对您来说不是问题,但在这种情况下您会丢失一些值。

哦,只是注意到已经有关于该问题的评论。无论如何,这里有一些代码可以为您解决这个问题:

float r = (float)((rand() << 15 + rand()) & ((1 << 24) - 1)) / (1 << 24);

未经测试,但可能有效:-)


浮点数r =(float)((rand()<< 9)| rand())/ RAND_MAX呢?(也未经测试)
陷阱

Argh,对不起,除以RAND_MAX不会带您去任何地方……这个窍门的全部目的是要拥有比RAND_MAX大的东西……也为我解决了这一问题。
乔伊(Joey)

2
在没有理论的情况下小心组成随机数...连续调用rand()可能并不完全独立。提示:如果它是线性同余生成器,请注意连续调用的低位:它在0和1之间交替
。– RBerteig

我知道。对于某些应用程序,这可能就足够了。但是,是的,在这种情况下,您可能应该使用两个以上的调用。在这种情况下,没有万灵药,您甚至不能依靠它作为LCG。其他PRNG具有弱的高位。Boost解决方案应该是最好的。
乔伊(Joey)

(nb:MSVC中rand返回的低位不是RNG状态的最低位。对于100 rand()调用,我得到以下信息:1100100000111111101010010010011010110101110110111010011111100100000000010100011011000000100101100011. Java使用48位LCG,仅使用32位,因此VC似乎做类似的事情)
乔伊(Joey


2

到目前为止,我对任何答案都不满意,因此我编写了一个新的随机浮点函数。它对float数据类型进行按位假设。它仍然需要带有至少15个随机位的rand()函数。

//Returns a random number in the range [0.0f, 1.0f).  Every
//bit of the mantissa is randomized.
float rnd(void){
  //Generate a random number in the range [0.5f, 1.0f).
  unsigned int ret = 0x3F000000 | (0x7FFFFF & ((rand() << 8) ^ rand()));
  unsigned short coinFlips;

  //If the coin is tails, return the number, otherwise
  //divide the random number by two by decrementing the
  //exponent and keep going. The exponent starts at 63.
  //Each loop represents 15 random bits, a.k.a. 'coin flips'.
  #define RND_INNER_LOOP() \
    if( coinFlips & 1 ) break; \
    coinFlips >>= 1; \
    ret -= 0x800000
  for(;;){
    coinFlips = rand();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    //At this point, the exponent is 60, 45, 30, 15, or 0.
    //If the exponent is 0, then the number equals 0.0f.
    if( ! (ret & 0x3F800000) ) return 0.0f;
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
  }
  return *((float *)(&ret));
}

7
有趣的方法,我想给予好评,但我真的不明白这是怎么回事
哈森

2

在我看来,以上答案的确给出了一些“随机”浮点数,但它们都不是真正的随机浮点数(即,它们错过了一部分浮点数表示形式)。在我着手实现之前,首先让我们看一下浮点数的ANSI / IEEE标准格式:

|符号(1位)| e(8位)| f(23位)|

这个单词代表的数字是(-1 *符号)* 2 ^ e * 1.f

请注意,“ e”数是一个有偏差的数字(偏差为127),范围为-127至126。最简单(实际上是最随机的)函数就是将随机int的数据写入浮点型,从而

int tmp = rand();
float f = (float)*((float*)&tmp);

请注意,如果这样做float f = (float)rand();,它将整数转换为浮点数(因此10将变为10.0)。

因此,现在,如果您想限制最大值,可以执行类似操作(不确定是否可行)

int tmp = rand();
float f = *((float*)&tmp);
tmp = (unsigned int)f       // note float to int conversion!
tmp %= max_number;
f -= tmp;

但是如果您看一下float的结构,您会发现float的最大值约为2 ^ 127,这比int的最大值(2 ^ 32)大得多,因此排除了float的重要部分浮点数可以表示的数字。这是我的最终实现:

/**
 * Function generates a random float using the upper_bound float to determine 
 * the upper bound for the exponent and for the fractional part.
 * @param min_exp sets the minimum number (closest to 0) to 1 * e^min_exp (min -127)
 * @param max_exp sets the maximum number to 2 * e^max_exp (max 126)
 * @param sign_flag if sign_flag = 0 the random number is always positive, if 
 *              sign_flag = 1 then the sign bit is random as well
 * @return a random float
 */
float randf(int min_exp, int max_exp, char sign_flag) {
    assert(min_exp <= max_exp);

    int min_exp_mod = min_exp + 126;

    int sign_mod = sign_flag + 1;
    int frac_mod = (1 << 23);

    int s = rand() % sign_mod;  // note x % 1 = 0
    int e = (rand() % max_exp) + min_exp_mod;
    int f = rand() % frac_mod;

    int tmp = (s << 31) | (e << 23) | f;

    float r = (float)*((float*)(&tmp));

    /** uncomment if you want to see the structure of the float. */
//    printf("%x, %x, %x, %x, %f\n", (s << 31), (e << 23), f, tmp, r);

    return r;
}

使用此函数randf(0, 8, 0)将返回0.0到255.0之间的随机数


1
你有一个错误。rand()%frac_mod将不起作用,因为MAX_RAND通常低于(1 << 23)。
DanielHsH

我必须承认我不知道MAX_RAND的确切大小。它仍然会继续工作,尽管它可能是无用的声明,但是它仍然会工作。8%10 = 8这样就可以了,但是如果MAX_RAND总是小于(1 << 23),则可以删除它。
user2546926

2
不,您有点不对。RandMax通常约为65,000。这意味着从23位中您只能随机获得15位。其他可能为零。您确实会获得随机数,但精度较低。例如,您的随机生成器可以生成0.001和0.002,但不能生成0.0017。因此,您的分布均匀,但精度较低(精度比float精度低256倍)。
DanielHsH 2013年

这个答案有两个错误。固定指数范围:int e = (rand() % (max_exp - min_exp)) + min_exp_mod;和尾数:int f = (int)(frac_mod * (float)rand() / RAND_MAX);替换上面的相应行。请注意,尾数错误很严重:对于RAND_MAX较小的尾数,1 << 23您只会将较低的有效位随机化,并始终将最高有效位设为0!
BeyelerStudios '16

2

如果知道浮点格式是IEEE 754(几乎包括Intel和ARM在内的几乎所有现代CPU),则可以使用按位方法从随机整数构建随机浮点数。仅当您无法访问C ++ 11 randomBoost.Random两者都更好时,才应考虑这一点。

float rand_float()
{
    // returns a random value in the range [0.0-1.0)

    // start with a bit pattern equating to 1.0
    uint32_t pattern = 0x3f800000;

    // get 23 bits of random integer
    uint32_t random23 = 0x7fffff & (rand() << 8 ^ rand());

    // replace the mantissa, resulting in a number [1.0-2.0)
    pattern |= random23;

    // convert from int to float without undefined behavior
    assert(sizeof(float) == sizeof(uint32_t));
    char buffer[sizeof(float)];
    memcpy(buffer, &pattern, sizeof(float));
    float f;
    memcpy(&f, buffer, sizeof(float));

    return f - 1.0;
}

这将比使用除法的分布更好。


8
我不确定您为什么要说这会带来“更好的分配”。实际上,这将产生与just 完全相同的分布return (float)random23 / (1 << 23)。(是的,我刚刚对此进行了测试,修改了您的函数以使其random32作为参数并针对从零到最大的所有值运行它(1 << 23)-1。是的,您的方法的确提供与除以相同的结果1 << 23。)
Ilmari Karonen

1

对于C ++,它可以在dist变量指定的范围内生成实数浮点数

#include <random>  //If it doesnt work then use   #include <tr1/random>
#include <iostream>

using namespace std;

typedef std::tr1::ranlux64_base_01 Myeng; 
typedef std::tr1::normal_distribution<double> Mydist;

int main() { 
       Myeng eng; 
       eng.seed((unsigned int) time(NULL)); //initializing generator to January 1, 1970);
       Mydist dist(1,10); 

       dist.reset(); // discard any cached values 
       for (int i = 0; i < 10; i++)
       {
           std::cout << "a random value == " << (int)dist(eng) << std::endl; 
       }

       return (0);
}

1
您只是复制并粘贴了此答案中的代码吗? stackoverflow.com/a/1118739/1538531
德里克(Derek)

其实不。看到它们看起来有多相似,我感到很惊讶!但是我确实在1970年1月1日初始化了发动机发电机。
Marco167 2013年

很公平。我确实注意到您已将生成器初始化到了时代,但该代码是相似的!
德里克(Derek)2013年

我给举一个TR1示例有点奇怪,您能解释一下在什么情况下有人不得不使用TR1而不是C ++ 11吗?
Shafik Yaghmour 2014年

0

rand()返回一个介于0和RAND_MAX之间的整数。要获得介于0.0和1.0之间的随机数,请先将rand()的int return强制转换为浮点数,然后除以RAND_MAX。


0
#include <cstdint>
#include <cstdlib>
#include <ctime>

using namespace std;

/* single precision float offers 24bit worth of linear distance from 1.0f to 0.0f */
float getval() {
    /* rand() has min 16bit, but we need a 24bit random number. */
    uint_least32_t r = (rand() & 0xffff) + ((rand() & 0x00ff) << 16);
    /* 5.9604645E-8 is (1f - 0.99999994f), 0.99999994f is the first value less than 1f. */
    return (double)r * 5.9604645E-8;
}

int main()
{
    srand(time(NULL));
...

我无法发布两个答案,所以这是第二个解决方案。log2随机数,向0.0f的偏倚很大,但实际上是从1.0f到0.0f的随机浮动。

#include <cstdint>
#include <cstdlib>
#include <ctime>

using namespace std;

float getval () {
    union UNION {
        uint32_t i;
        float f;
    } r;
    /* 3 because it's 0011, the first bit is the float's sign.
     * Clearing the second bit eliminates values > 1.0f.
     */
    r.i = (rand () & 0xffff) + ((rand () & 0x3fff) << 16);
    return r.f;
}

int main ()
{
    srand (time (NULL));
...
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.