C++生成随机数

22

我的输出是20个随机的1,而不是在10到1之间,请问这是为什么?

#include <iostream> 
#include <ctime> 
#include <cstdlib>

using namespace std;

int main() 
{ 
    srand((unsigned)time(0)); 
    int random_integer; 
    int lowest=1, highest=10; 
    int range=(highest-lowest)+1; 
    for(int index=0; index<20; index++){ 
        random_integer = lowest+int(range*rand()/(RAND_MAX + 1.0)); 
        cout << random_integer << endl; 
    } 
}

输出: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1


我只能生成1,没有随机数生成我的代码,我不知道实际的问题在哪里,请任何人清理我的代码... - visanio_learner
5
我可以建议使用类似于 (rand() % range) + lowest 的方法吗? - Some programmer dude
@JoachimPileborg,您的建议非常好,确实有所帮助,但是存在一个小问题,我需要的答案在1和10之间,但有时会产生0,而且没有数字10.... - visanio_learner
3
很不幸,@Joachim的建议实际上非常糟糕,因为它可能会使随机数更加偏倚。 - Konrad Rudolph
1
当然不是,任何严肃的应用程序都应该使用良好的随机数源。但对于像音频轨道洗牌这样的简单任务,没有人会注意到差异。 - hochl
显示剩余3条评论
11个回答

38
因为在你的平台上,RAND_MAX == INT_MAX

表达式range * rand()永远不可能取到大于INT_MAX的值。如果这个数学表达式大于INT_MAX,那么整数溢出会将它减少到一个介于INT_MININT_MAX之间的数字。将其除以RAND_MAX将始终产生零。

尝试这个表达式:

random_integer = lowest+int(range*(rand()/(RAND_MAX + 1.0)))

7
#define catch_of_the_day 这句话的意思是“当天捕捞的鱼”,它通常用于餐厅或鱼市场菜单中,表示当天供应的新鲜鱼类。 - Captain Giraffe
4
请注意,如果您使用此方法,某些值将比其他值更频繁地出现。这不是数学上的正确做法。 - James Kanze

16

使用<random>库要比使用rand简单得多(假设您已熟悉C++的语法,不会让您感到困惑)。

#include <random>
#include <iostream>

int main() {
  std::random_device r;
  std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
  std::mt19937 eng(seed);

  std::uniform_int_distribution<> dist(1, 10);

  for(int i = 0; i < 20; ++i)
    std::cout << dist(eng) << " ";
}

更容易吗?嗯,这很难说服 ;-). 使用<random>的主要原因是它为您提供了灵活性,可以插入不同的随机分布,例如正态或泊松分布,并插入不同的数字随机生成器,这些生成器可能更好或更快。 - Darren Cook
这里有一种使用<random>的替代方法:https://gist.github.com/1946214 我添加了一些注释。它不需要C++11。我希望它能展示正在发生的事情。 - Darren Cook
更难出错,更易读且意图更明显。当然,你可以将使用 rand() 的代码封装到一个函数中,使其与 <random> 一样易读,但既然已经有了 <random>,为什么还要费心呢? - bames53
我认为使用这种方法会更容易,因为存在这个问题的原因是将 rand() 转换为实际需要的输出存在微妙的困难。 - the_mandrill

11
random_integer = (rand() % 10) + 1 

这会给你一个介于1和10之间的伪随机数。


7
使用百分号符号(%)会影响概率分布,尽管在这种情况下影响不是很大。 - hochl
6
如果你有一个不错的随机数生成器,使用“%”实际上是首选方法。无论采用哪种方法,都必须丢弃一些值,或者接受结果中的某些偏差。 - James Kanze
4
在缩小范围之前,您会从 rand() 丢弃某些输出。如果您想要十个不同的值,并且 rand() 产生的值不是十的倍数,您需要丢弃一些值,或者更频繁地返回范围内的某些值以使得随机性更加平均。 - James Kanze
1
@JamesKanze 我不认识蒙特卡罗社区中任何一个主张以这种方式使用模数的人(无论是ryoungs所描述的两步过程还是您所描述的)。Rob帖子中的方法虽然阅读起来比较混乱,但是它是惯用法,因此你可以停止阅读它。 - dmckee --- ex-moderator kitten
2
如果你在使用随机数进行严肃的工作,并且生成的不同数字数量不是你感兴趣的元素数量的精确倍数,那么你必须丢弃一些生成的随机数,否则会引入偏差。我认识的做蒙特卡罗分析的人通常寻找一个双精度结果,并尽可能地给出许多不同的值;在这种情况下,当然不会使用 - James Kanze
显示剩余7条评论

9
一个有些晚的答案,但如果生成的质量很重要,它应该提供一些额外的信息。(并非所有应用程序都需要这个——轻微的偏差通常不是问题。)
首先,当然,原始代码中的问题在于range * rand()优先于后面的除法,并使用整数算术进行计算。根据RAND_MAX,这很容易导致溢出,产生实现定义的结果;在我所知道的所有实现中,如果它导致溢出(因为RAND_MAX > INT_MAX / range),实际结果几乎肯定小于RAND_MAX + 1.0,而除法将导致小于1.0的值。有几种避免这种情况的方法:最简单和最可靠的方法是rand() % range + lowest
请注意,这假设rand()具有合理的质量。许多早期的实现不是这样的,我看到过至少一个模拟掷骰子的rand() % 6 + 1交替奇偶性的实现。唯一正确的解决方案是获得更好的rand()实现;这导致人们尝试替代解决方案,例如(range * (rand() / (RAND_MAX + 1.0))) + lowest。这掩盖了问题,但它不会将坏的生成器变成好的。
第二个问题是,如果生成的质量很重要,当生成随机整数时,您正在离散化:例如,如果您模拟掷骰子,则有六个可能的值,您希望它们以相等的概率发生。随机生成器将生成RAND_MAX + 1个不同的值,并具有相等的概率。如果RAND_MAX + 1不是6的倍数,则无法在6个期望值之间均匀分配值。想象一下简单的情况,其中RAND_MAX + 1为10。使用上面的%方法,值1-4的可能性是值5和6的两倍。如果您使用更复杂的公式1 + int(6 * (rand() / (RAND_MAX + 1.0)))(在RAND_MAX + 1 == 10的情况下),结果表明3和6只有其他值的一半那么可能。从数学上讲,没有办法将10个不同的值均匀地分配到6个插槽中,每个插槽中具有相同数量的元素。

当然,RAND_MAX始终比10大得多,并且引入的偏差将会明显减少;如果范围显著小于RAND_MAX,那么可能是可以接受的。如果不是,通常的处理方法如下:

int limit = (RAND_MAX + 1LL) - (RAND_MAX + 1LL) % range;
            //  1LL will prevent overflow on most machines.
int result = rand();
while ( result >= limit ) {
    result = rand();
}
return result % range + lowest;

有几种方法可以确定要丢弃的值。这恰好是我使用的方法,但我记得Andy Koenig使用了完全不同的方法,但最终结果是相同的。

请注意,大多数情况下,您不会进入循环; 最坏的情况是range(RAND_MAX + 1) / 2 + 1,即使在这种情况下,您仍然平均只需循环一次。

请注意,这些注释仅适用于需要固定数量的离散结果的情况。对于常见的(其他)生成介于[0,1)范围内的随机浮点数的情况,rand() / (RAND_MAX + 1.0)是您能够获得的最佳结果。


1

我建议你用range*double(rand())/(RAND_MAX + 1.0))替换rand()/(RAND_MAX + 1.0)。因为我的解决方案似乎会引起一些问题...

参数的可能组合:

  • range*rand()是一个整数并且溢出。
  • 在将其转换为double之前,double(range*rand())会溢出。
  • range*double(rand())没有溢出并产生了预期的结果。

我的原始帖子有两个大括号,但它们没有改变任何东西(结果是相同的)。


这并不会改变任何事情,因为第二个操作数已经是“double”类型了。 - Konrad Rudolph
有趣的是,原帖版本会全部输出1,但使用这个区别版本会输出随机数。你试过了吗? - hochl
关键的区别在于您使用了额外的括号,而不是显式转换为“double”。我应该这样说。 - Konrad Rudolph
抱歉,我仍然不明白你的意思 - 对我来说,他的代码中的一个问题是range*rand()已经溢出了,将rand()转换为double可以避免这种情况,因为double具有57位精度。 - hochl
抱歉,我完全忘记了“range”乘数,我只看了你的代码片段并发现了一个多余的转换。你是对的。感谢你的坚持。 - Konrad Rudolph

1

Visual Studio 2008对该程序没有任何问题,并愉快地生成了一系列随机数。

我要小心的是/(RAND_MAX +1.0),因为这很可能会遇到整数问题,最终得出一个大而无当的零。

在除法之前转换为double类型,然后在之后转换回int类型。


1
RAND_MAX+1.0 一个双精度浮点数,根据C++03标准中的通常算术转换规则,第5节,第9段。 - Robᵩ

1
(rand() % highest) + lowest + 1

0

这是最简单的逻辑之一,我从博客中得到了它。在这个逻辑中,你可以用给定的模数(%)运算符在for循环内限制随机数,这只是从那个博客复制和粘贴过来的,但无论如何请查看它:

// random numbers generation in C++ using builtin functions
#include <iostream>

using namespace std;

#include <iomanip>

using std::setw;

#include <cstdlib>   // contains function prototype for rand

int main()
{
// loop 20 times
for ( int counter = 1; counter <= 20; counter++ ) {

    // pick random number from 1 to 6 and output it
    cout << setw( 10 ) << ( 1 + rand() % 6 );

    // if counter divisible by 5, begin new line of output
    if ( counter % 5 == 0 )
        cout << endl;

}

return 0;  // indicates successful termination

} // end main

- 请访问http://www.programmingtunes.com/generation-of-random-numbers-c/#sthash.BTZoT5ot.dpuf以获取更多信息。


0

那么使用条件来检查最后一个数字是否与当前数字相同呢?如果满足条件,则生成另一个随机数。这个解决方案是可行的,但需要更多时间。


0

您正在生成一个随机数(例如 (range*rand()/(RAND_MAX + 1.0))),其值介于-1和1之间(]-1,1[),然后将其转换为整数。这样的数字的整数值始终为0,因此您最终得到的是lower + 0

编辑:添加了公式以使我的答案更清晰


rand() 返回一个介于0和RAND_MAX之间的数字。 - Gandaro
@Gandaro:我不是在谈论rand(),而是这个语句(range*rand()/(RAND_MAX + 1.0))... - VirtualTroll
好的,那就没问题了。 ;) - Gandaro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接