如何在C++中获取1到12之间的随机值?

4

我如何在C++中获取1到12的随机值?

这样我就可以得到3、6或11?


17
还有其他人认为 int Random(){ return 4; /*Guaranteed random -- Determined by dice roll */ } 这段代码吗? - Earlz
我认为解释说明既是多余的,又会增加歧义;实际上,在1和12之间还有其他值(无限多)。 - Clifford
我需要在某些应用程序中自动填充日期时,在3或4之前加上一个零,因此我需要输入01而不是1的日期,以及08而不是8的月份。 - MicheleIce
啊,所以你只需要将这些生成的数字转换为两位定宽数字(作为字符串)? - Earlz
7
哎呀。数值的格式与数值本身无关。生成数值后,您可以按任何想要的方式进行格式化。 - Tim
5个回答

16

请使用以下公式:

M + rand() / (RAND_MAX / (N - M + 1) + 1), M = 1, N = 12

阅读此FAQ,以了解更多信息。

编辑:这个问题的大多数答案没有考虑到差劲的伪随机数生成器(通常由库函数rand()提供)在低位上不太随机。因此:

rand() % 12 + 1

不够好。


2
仅使用除法本身只会带来微小的改进--结果可能更随机,但通常仍然倾斜。假设rand()以相等的概率产生所有数字直到RAND_MAX,除非RAND_MAX恰好是12的倍数(在消失得很少的情况下),否则这将不会产生1到12的数字具有相等的概率。 - Jerry Coffin
3
使用朴素的百分比方法存在统计偏差问题,提出这个问题值得加1。 - Clifford
@Jerry:公平地说,链接的常见问题解答提供了其他解决方案。 - Clifford
@Clifford:我还没有检查这个链接,但你是对的。不过,如果我的记忆没有出错,FAQ中的部分内容是我参与撰写的。 - Jerry Coffin
实际上,重新检查一下,看起来我可能没有。对于任何关心我在说什么的人,请参见:http://groups.google.com/group/comp.lang.c/browse_frm/thread/5a034f20e4981e53/8b4393f2ec2e40e7?hl=en#8b4393f2ec2e40e7 - Jerry Coffin

9
#include <iomanip>
#include <iostream>
#include <stdlib.h>
#include <time.h>

// initialize random seed
srand( time(NULL) );

// generate random number
int randomNumber = rand() % 12 + 1;

// output, as you seem to wan a '0'
cout << setfill ('0') << setw (2) << randomNumber;

为了解决DirkGently的问题,也许采用类似的方法会更好?
// generate random number
int randomNumber = rand()>>4; // get rid of the first 4 bits

// get the value
randomNumer = randomNumer % 12 + 1;

编辑:在mre和dirkgently的评论后进行了修改。


2
这种方法很差,因为许多随机数生成器的低位比特令人不安地非随机。请阅读我在答案中发布的常见问题解答。 - dirkgently
2
@dirkgently 我敢打赌这只是一份作业,所以他并不在乎这些数字是否真正随机。 - Earlz
+1 你太快了 ;) 或许改用 std::cout。 - mre
4
@Earlz:完成作业是为了正确学习知识,而不是抄袭。因此,我的答案并没有给出完整解决方案,只是一个提示。这是一个非常、非常重要的提示,需要记在心里。 - dirkgently
1
<Pedantic> 缺少 #include <iomanip> 以及它的 std::setfill 和 std::setw </Pedantic> - mre

4

在这种情况下,前导零有什么意义吗?您是否打算将其转换为八进制,因此12实际上是10(在十进制中)?

在指定范围内获取随机数相当简单:

int rand_lim(int limit) {
/* return a random number between 0 and limit inclusive.
 */

    int divisor = RAND_MAX/(limit+1);
    int retval;

    do { 
        retval = rand() / divisor;
    } while (retval > limit);

    return retval;
}

while循环是为了防止偏斜的结果--一些输出比其他输出更常见。当你直接使用除法(或其余数)时,会出现偏斜的结果几乎是不可避免的。

如果想要打印出来使得个位数也显示两位数(即前导0),可以这样做:

std::cout << std::setw(2) << std::setprecision(2) << std::setfill('0') << number;

编辑:为什么这样可以工作,以及为什么需要while循环(或类似的东西),考虑一个非常有限的生成器版本,它只产生从0到9的数字。进一步假设我们希望在0到2的范围内获得数字。我们可以将数字基本上排列在一个表格中:

0 1 2
3 4 5 
6 7 8 
9

根据我们的喜好,我们可以将数字排列在列中:

0 3 6  
1 4 7
2 5 8
    9

无论如何,我们最终都会得到一个列比其他任何列都多一个数字的情况。10除以3总会有余数1,因此无论我们如何分配这些数字,我们总会有一个余数使其中一个输出比其他输出更常见。
上面代码的基本思路非常简单:在获取数字并确定该数字在类似上面的“表格”中应落在哪里之后,它检查我们拥有的数字是否是“奇数”。如果是,将执行另一次循环迭代以获得另一个数字。
还有其他方式可以做到这一点。例如,您可以首先计算范围大小的最大倍数,该最大倍数仍在随机数生成器的范围内,并重复生成数字,直到您获得小于该数字,然后除以接收到的数字以将其分为正确的范围。理论上,这甚至可能略微更有效(它避免了将随机数除以它使用的随机数范围外的数来将其带入正确范围)。实际上,循环大部分时间只会执行一次迭代,因此执行循环内部或外部的操作几乎没有区别。

这对我来说看起来是正确的,但它并不显而易见。您能否详细说明一下为什么它是正确的? - Jørgen Fogh

1

你可以这样做:

#include <cstdlib>
#include <cstdio>
#include <time.h>

int main(int argc, char **argv)
{
    srand(time(0));

    printf("Random number between 1 and 12: %d", (rand() % 12) + 1);
}

srand函数将使用当前时间(以秒为单位)来初始化随机数生成器 - 这通常是这样做的方式,但也有更安全的解决方案。

rand() % 12将给你一个0到11之间的数字(%是模运算符),所以我们在这里加1以得到你想要的1到12的范围。

为了打印出01 02 03等而不是1 2 3,你可以格式化输出:

printf("Random number between 01 and 12: %02d", (rand() % 12) + 1);

-2
"(rand() % 12 + 1).ToString("D2")" 是一个程序相关的代码。

4
这个问题标记为 C++,不是 C# 或其他代码。 - AndiDog
如果有人不知道如何将其转换为C ++,那么我会质疑他们进行任何编程的能力。 - Tim
2
@tim:但此时,它基本上只是回显问题而已。如何打印两位小数?使用两位小数进行转换!哦,还有,除了至少还有六个人给出相同(糟糕)的关于如何缩小范围的建议... - Jerry Coffin

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