rand()在0和1之间的随机数生成函数

78

因此,以下代码使得 0 < r < 1。

r = ((double) rand() / (RAND_MAX))
为什么使用r = ((double) rand() / (RAND_MAX + 1))会使得结果在-1和0之间?
添加一到RAND_MAX难道不应该让结果在1和2之间吗?
编辑:我在这行代码上得到了一个"integer overflow in expression"的警告,所以可能是这个问题。我刚刚用cout << r << endl测试了一下,它确实给我返回了在-1和0之间的值。

2
尝试在最后一个 ) 后添加 1 - Vincenzo Pii
15
无论如何,你都应该使用uniform_real_distribution。如果你没有C++11,可以使用boost版本 - KillianDS
代数错误。如果r是从0到m范围内的随机数,则比率r/m将在范围(0,1)内,但是r/(m+1)将在范围(0,m/(m+1))内,而不是范围(1,2)内。如果m非常大(与1相比),则m/(m+1)近似于1,因此您的表达式r = ((double) rand() / (RAND_MAX + 1))将给出一个随机数,其范围大约为(0,1)-也就是说,如果没有溢出的话。 - flies
7个回答

94

这完全是实现特定的,但似乎在你所用的C++环境中,RAND_MAX等于INT_MAX

因此,RAND_MAX + 1会出现未定义(溢出)行为,并变为INT_MIN。虽然您最初的语句是将(0到INT_MAX之间的随机数)/(INT_MAX)进行除法运算,并生成一个值0 <= r < 1,但现在它正在将(0到INT_MAX之间的随机数)/(INT_MIN)进行除法运算,生成一个值-1 < r <= 0

为了生成一个随机数1 <= r < 2,您需要

r = ((double) rand() / (RAND_MAX)) + 1

2
为什么要在 RAND_MAX 周围加上括号? - ssc
1
@ssc 为了强调与原始代码的区别,这些括号没有技术上的必要。 - Mad Physicist
1
OP 尝试的原因是错误的,但如果必要的话,可以通过添加 1.0 而不是 1 来避免 UB,这将强制 RAND_MAX 转换为 double 类型,从而避免整数溢出。 - underscore_d
@ssc 因为它是宏。除非您确切知道其定义,否则应始终使用宏进行操作。考虑这样的定义 #define RAND_MAX 1+100,上述表达式的结果将是什么?;-) - Valentin H
@MadPhysicist 除非你确切知道,否则宏不是一个表达式而是一个单独的数字。 - Valentin H
r = ((double) rand() / (RAND_MAX)) + 0.5; 使用0.5进行四舍五入,而不是+1,因为无论如何都会是1,除非你恰好rand() RAND_MAX,在这种情况下它将是2。这将生成一个0或1,然后您可以将其加1。我想0或1的随机值对于在调试器中进行数字引脚值测试很有用。否则,解决方案不错! - developer68

40

rand() / double(RAND_MAX) 生成一个浮点随机数,范围在0(包括)到1(包括)。但由于以下原因,这不是一个好的方法(因为 RAND_MAX 通常为32767):

  1. 可以生成的不同随机数的数量太少:32768。如果你需要更多不同的随机数,你需要另一种方法(下面给出了一个代码示例)。
  2. 生成的数字粒度太大:你可能得到1/32768、2/32768、3/32768,但永远不会得到其中任何其他值。
  3. 随机数生成器引擎的状态有限:在生成RAND_MAX个随机数之后,实现通常开始重复相同的随机数序列。

由于rand()的上述限制,一个更好的选择是生成0(包括)到1(不包括)之间的随机数,可以使用以下代码段(类似于http://en.cppreference.com/w/cpp/numeric/random/uniform_real_distribution中的示例):

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    std::mt19937_64 rng;
    // initialize the random number generator with time-dependent seed
    uint64_t timeSeed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
    std::seed_seq ss{uint32_t(timeSeed & 0xffffffff), uint32_t(timeSeed>>32)};
    rng.seed(ss);
    // initialize a uniform distribution between 0 and 1
    std::uniform_real_distribution<double> unif(0, 1);
    // ready to generate random numbers
    const int nSimulations = 10;
    for (int i = 0; i < nSimulations; i++)
    {
        double currentRandomNumber = unif(rng);
        std::cout << currentRandomNumber << std::endl;
    }
    return 0;
}

通过将unif(0, 1)替换为unif(1, 2),可以轻松修改以生成介于1(包括)和2(不包括)之间的随机数。


天哪,我一直在尝试找到一个零到一的随机数生成器来在 while 循环中使用,这个方法可能真的可行。我尝试了每种可能的 random_device 方法,但得到了相同的输出结果。我不知道 0xfffffffff 是什么(十六进制代码?),以及右移 32 位...这两个都超出了我的理解范围。(我对 C++ 还比较新手,但我认为它比 Rust 容易多了。)感谢您提供这个方法! - Mark Moretto

18
不行,因为RAND_MAX通常会被扩展为MAX_INT。所以加一(显然)会将其放置在MIN_INT处(虽然据我所说应该是未定义的行为),因此需要改变符号。
要获得您想要的结果,您需要将+1移动到计算之外。
r = ((double) rand() / (RAND_MAX)) + 1;

2
添加一个值可能会将其放置在MIN_INT位置,但仍然是未定义行为。 - Pubby
2
将一个数加一会导致未定义的行为,因为所有溢出都会发生。这是错误和误导性的,因为它似乎是可以工作的。请修正您的答案或将其删除。 - Luchian Grigore
我同意,我已经相应地进行了编辑。然而,这似乎是观察到的行为。 - Tudor
1
@LuchianGrigore:只针对有符号溢出进行澄清。 - GManNickG

3
这是正确的方式:
double randd() {
  return (double)rand() / ((double)RAND_MAX + 1);
}

或者

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

这样做可以修复有符号整数溢出 UB 的问题,同时保留 OP 其余语义不变,但通过阅读他们真正想要完成的描述,我们可以清楚地看到它仍然不能产生期望的结果,因为他们在错误的位置进行了加法。 - underscore_d
为什么要加1?假设RAND_MAX=2,那么rand()会返回0或1或2。如果你想让它返回0到1之间的数字,难道不应该将其除以2而不是3吗? - Tomer Wolberg
0包含在内,1不包含在内。如果1也要包含在内,那么1的概率非常小。 - KIM Taegyoon
这对我来说使用g++(gcc版本10.2.1 20210110(Debian 10.2.1-6))有效。对于测试程序而言,简单易行且不太复杂。 - Agguro

3
它并没有。它会使得0 <= r < 1,但是你原来的是0 <= r <= 1
需要注意的是,如果RAND_MAX + 1溢出,这可能导致未定义的行为

1
RAND_MAX无法溢出无符号类型,因此您可以将其转换为无符号类型。 - phuclv

2

我的猜测是RAND_MAX等于INT_MAX,所以你正在将其溢出为负数。

只需执行以下操作:

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

或者更好的方法是,使用C++11的随机数生成器。


正如你在别人的回答中指出的,“溢出到负数”只是在 OP 观察到的情况下发生了,而且不是一个可靠的描述,因为溢出是未定义的,所以结果可能会做任何事情。 - underscore_d

0

this->value = rand() % (this->max + 1);

看起来在0到1++之间工作得很好。


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