随机浮点数生成

Posted

技术标签:

【中文标题】随机浮点数生成【英文标题】:Random float number generation 【发布时间】:2010-10-15 17:46:48 【问题描述】:

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

我认为我可以将整数 rand 除以某个值,这样就足够了吗?

【问题讨论】:

这取决于你想要的数字,以及随机性。通常 rand() 会给出 15 位的随机性,但浮点数有 23 位的精度,所以它会遗漏一些值。 我已经更新了我的答案以包含所有可用的主要选项,并且我选择专注于 C++11 中添加的 random 标头,这进一步得到了标准文档 N3924: Discouraging rand() in C++14 的支持。我在回答中加入了rand(),主要是出于历史考虑,但也意识到遗留应用程序确实存在。 我的回答包括如何避免每次使用 <random> 标头得到相同的数字 【参考方案1】:

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


这将生成一个从 0.0 到 1.0 的数字,包括 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)));

要拨打randsrand,您必须#include &lt;cstdlib&gt;

要拨打time,您必须#include &lt;ctime&gt;

【讨论】:

别忘了先播种! 最好注意这两个限制都包含在内。 这个答案具有误导性。上周在 Going Native 2013 上对此进行了介绍; rand() 被认为是有害的,channel9.msdn.com/Events/GoingNative/2013/… 以获得非常详细的解释。 我不明白为什么这么多人赞成这个答案。它在数学上是不正确的。 RAND_MAX 是一个非常小的数字(通常为 2^16)。这意味着从 23 位浮点数中您只能随机生成 15 位。其他的可能为零。您确实会得到均匀分布但精度低的随机数。例如,您的随机生成器可以生成 0.00001 和 0.00002,但不能生成 0.000017。所以你有一个均匀的分布,但精度低(比实际浮点精度低 256 倍)。 @DanielHsH:OP 专门询问了哪些机制可以使用rand() 生成随机浮点数。这个问题和我的回答都特别关注基础知识,并不关心高精度。你必须先学会走路才能学会跑步。【参考方案2】:

C++11 通过random 为您提供了许多新选项。关于这个主题的规范论文是N3551, Random Number Generation in C++11

要了解为什么使用 rand() 会出现问题,请参阅 Stephan T. LavavejGoingNative 2013 活动期间提供的 rand() Considered Harmful 演示材料。幻灯片在 cmets 中,但这里是 direct link。

我还介绍了boost 以及使用rand,因为遗留代码可能仍需要它的支持。

下面的例子是从 cppreference 网站提炼出来的,它使用了std::mersenne_twister_engine 引擎和std::uniform_real_distribution,它在[0,10) 区间内生成数字,其他引擎和分布被注释掉了 (see it live em>):

#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 ****

输出将根据您选择的分布而有所不同,因此,如果我们决定使用 std::normal_distribution,meanstddev 的值都为 2例如dist(2, 2) 而不是输出类似于此 (see it live):

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

以下是N3551 (see it live) 中提出的部分代码的修改版本:

#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 常见问题解答 以获取有关How can I generate floating-point random numbers? 的指南,它基本上给出了一个与此类似的示例,用于在间隔[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) ) ) ;  

【讨论】:

你能修复你的randMToN吗?请注意它是[M,N],或者从上面的randZeroToOne 中添加回+ 1.。 -> 想想这样称呼它:randMToN(0.0, 1.0); 还要小心在(N-M) 处除以零。在这里可以找到处理此错误的好方法:***.com/questions/33058848/… 如果有人想看看它是如何在标准库中实现的,以及为什么它比简单的除法提供更好的值,请参见此处:github.com/microsoft/STL/blob/…【参考方案3】:

看看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 对象,而不是每次都构造一个新对象,但希望你能明白这一点。

【讨论】:

uniform_real 使用半开区间 [min, max),这意味着您将获得最小值但永远不会达到最大值。这是需要考虑的事情,但如果你以某种方式四舍五入,你可以克服这个问题。 现在是 C++11 的一部分。 @Wolf 在实际应用中,达到任何特定浮点值的几率是如此之低,以至于端点被包含或排除并不重要。如果您需要max,但可以使用开放式min,您可以轻松反转区间:return min + max - gen();【参考方案4】:

在现代c++ 中,您可以使用c++11 附带的&lt;random&gt; 标头。 要获得随机的float,您可以使用std::uniform_real_distribution&lt;&gt;

您可以使用函数来生成数字,如果您不希望数字始终相同,请将引擎和分布设置为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&lt;&gt; 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&lt;RealType&gt;::max()) may be used as the second parameter. 这应该是公认的答案,2020 年太可怕了。【参考方案5】:

使用两个float 值调用代码,代码在任何范围内都有效。

float rand_FloatRange(float a, float b)

    return ((b - a) * ((float)rand() / RAND_MAX)) + a;

【讨论】:

可能值得一提的是,这是 C99 或 C++11 中 fmaf()(或 C++ 中的浮点 fma() 重载)的潜在用例,它可能会保持更高的精度。如fmaf((float)rand() / RAND_MAX, b - a, a) 这可行,但请注意,它会在结果中引入(非常)小的偏差。有关更多信息,请参见此处(您的方法是“FP 乘法(偏差)”之一):pcg-random.org/posts/bounded-rands.html 添加到@Venryx,显然有一篇关于这个主题的完整论文:hal.archives-ouvertes.fr/hal-03282794/document。虽然很平凡,但这并不是一个真正解决的问题!【参考方案6】:

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

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

语法如下(无耻借用this site):

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

【讨论】:

现在在 C++11 中,dist 也可以用最小值和最大值初始化。 将最小值和最大值放在初始化程序中并在获取值时提供生成器对我来说似乎很奇怪 - 我更喜欢反过来,哦,好吧。【参考方案7】:

在某些系统上(目前想到的是带有 VC 的 Windows),RAND_MAX 非常小,即。 e.只有 15 位。当除以RAND_MAX 时,您只会生成 15 位的尾数,而不是 23 位可能的位。这对您来说可能是也可能不是问题,但在这种情况下您会遗漏一些值。

哦,刚刚注意到已经有针对该问题的评论。无论如何,这里有一些代码可以为你解决这个问题:

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

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

【讨论】:

float r = (float)((rand() 啊,抱歉,除以 RAND_MAX 不会带你去任何地方......这个技巧的重点是拥有大于 RAND_MAX 的东西......也为我解决了这个问题。跨度> 在没有理论的情况下组合随机数时要小心...对 rand() 的连续调用可能不是完全独立的。提示:如果它是一个线性同余生成器,请注意连续调用的低位:它在 0 和 1 之间交替。 我知道。不过,对于某些应用程序,这可能就足够了。但是,是的,在这种情况下,您可能不应该只使用两个调用。在这种情况下没有灵丹妙药,你甚至不能指望它是一个 LCG。其他 PRNG 的高位较弱。 Boost 解决方案应该是这里最好的。 (注:低比特通过兰特MSVC返回未在RNG状态的最低位为100兰特()调用我得到以下:1100100000111111101010010010011010101110110110111010011111100100000000010100011011000000100101100011. Java使用48位LCG并且只使用 32 位,VC 似乎也是这样做的)【参考方案8】:

drand48(3) 是 POSIX 标准方式。 GLibC 还提供了可重入版本,drand48_r(3)

该函数在 SVID 3 中被声明为过时,但没有提供足够的替代方案,因此 IEEE Std 1003.1-2013 仍然包含它,并且没有任何说明它很快就会消失。

在 Windows 中,标准方式是CryptGenRandom()。

【讨论】:

【参考方案9】:

到目前为止,我对任何答案都不满意,所以我编写了一个新的随机浮点函数。它对浮点数据类型进行按位假设。它仍然需要一个至少有 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));

【讨论】:

有趣的方法,我想投票,但我真的不明白发生了什么【参考方案10】:

在我看来,上述答案确实给出了一些“随机”浮点数,但它们都不是真正的随机浮点数(即它们错过了浮点数表示的一部分)。在我急于实现我的实现之前,让我们先看一下浮点数的 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;

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

/**
 * 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 之间的随机数

【讨论】:

你搞错了。 rand() % frac_mod 不起作用,因为 MAX_RAND 通常低于 (1 我不得不承认我不知道 MAX_RAND 的确切大小。无论如何,它仍然会起作用,它可能是一个无用的声明,但它仍然会起作用。 8 % 10 = 8 很好,但如果 MAX_RAND 总是小于 (1 不,你有点错。 RandMax 通常约为 65,000。这意味着从 23 位中您只能随机生成 15 位。其他的可能为零。您确实会得到随机数,但精度低。例如,您的随机生成器可以生成 0.001 和 0.002,但不能生成 0.0017。所以你有一个均匀的分布,但精度低(精度比浮点数低 256 倍)。 这个答案有两个错误。修复指数范围:int e = (rand() % (max_exp - min_exp)) + min_exp_mod; 和尾数:int f = (int)(frac_mod * (float)rand() / RAND_MAX); 替换上面各自的行。请注意,尾数错误很严重:对于较小的 RAND_MAX 1 &lt;&lt; 23,您只会随机化较低的有效位并始终为最高有效位获取 0!【参考方案11】:

如果您知道您的浮点格式是IEEE 754(几乎所有现代 CPU,包括 Intel 和 ARM),那么您可以使用按位方法从随机整数构建一个随机浮点数。仅当您无法访问 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;

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

【讨论】:

我不知道你为什么说这会产生“更好的分布”。事实上,这将与return (float)random23 / (1 &lt;&lt; 23) 提供完全相同的 分布。 (是的,我只是tested this,修改您的函数以将random32 作为参数并针对从零到(1 &lt;&lt; 23)-1 的所有值运行它。是的,您的方法确实给出了与除以@ 完全相同的结果987654331@.)【参考方案12】:

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

【讨论】:

您是否只是复制并粘贴了此答案中的代码? ***.com/a/1118739/1538531 实际上没有。看到它们长得如此相似,我有点惊讶!但我确实在 1970 年 1 月 1 日初始化了引擎发电机。 很公平。我确实注意到您将生成器初始化为纪元,但该死的代码是相似的! 我觉得举一个 TR1 的例子有点奇怪,你能解释一下在什么情况下有人必须使用 TR1 而不是 C++11?【参考方案13】:

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

【讨论】:

【参考方案14】:
#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));
...

【讨论】:

以上是关于随机浮点数生成的主要内容,如果未能解决你的问题,请参考以下文章

JMeter 如何生成指定取值范围指定小数位数的随机浮点数

JMeter 如何生成指定取值范围指定小数位数的随机浮点数

生成 0 到 1 之间的随机浮点数

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

如何用C语言生成(0,1)之间的随机浮点数

具有单个目标和命中目标机会的加权随机浮点数