random.randint(1,10)会返回11吗?

10 投票
3 回答
2905 浏览
提问于 2025-04-15 23:57

在研究这个问题并查看random.py的源代码时,我开始怀疑randrangerandint的表现是否真的和宣传的一样。我很倾向于相信它们是这样,但根据我的理解,randrange的实现基本上是这样的:

start + int(random.random()*(stop-start))

(假设startstop都是整数值),所以randrange(1, 10)应该会返回一个在1到9之间的随机数。

randint(start, stop)实际上是调用randrange(start, stop+1),因此返回的数字会在1到10之间。

我现在的问题是:

如果random()有可能返回1.0,那么randint(1,10)就会返回11,对吧?

3 个回答

3

来自Python文档:

几乎所有模块的功能都依赖于一个基本的函数random(),这个函数会生成一个随机的小数,范围是从0.0到1.0,但不包括1.0。

就像几乎所有生成随机小数的算法一样……

12

其他回答提到,random() 的结果总是严格小于 1.0;不过,这只是部分真相。

如果你用 int(random() * n) 来计算 randrange(n),你还需要知道,对于任何满足 0.0 <= x < 1.0 的 Python 浮点数 x 和任何正整数 n,都有 0.0 <= x * n < n,因此 int(x * n) 一定小于 n

这里可能出错的地方有两个:首先,当我们计算 x * n 时,n 会被隐式转换为浮点数。对于足够大的 n,这种转换可能会改变值。但是如果你查看 Python 的源代码,会发现它只在 n 小于 2**53 的情况下使用 int(random() * n) 方法(这里和下面假设平台使用的是 IEEE 754 双精度浮点数),这是转换 n 为浮点数时保证不会丢失信息的范围(因为 n 可以被准确表示为浮点数)。

第二个可能出错的地方是,乘法 x * n 的结果(现在是浮点数相乘)可能无法精确表示,因此会涉及一些舍入。如果 x 足够接近 1.0,那么舍入可能会把结果向上舍入到 n 本身。

为了证明这种情况不会发生,我们只需要考虑 x 的最大可能值,即在几乎所有运行 Python 的机器上都是 1 - 2**-53。所以我们需要证明 (1 - 2**-53) * n < n 对于我们的正整数 n 是成立的,因为总是会有 random() * n <= (1 - 2**-53) * n

证明(简述)设 k 为唯一的整数,使得 2**(k-1) < n <= 2**k。那么 n 的下一个浮点数是 n - 2**(k-53)。我们需要证明 n*(1-2**-53)(即实际未舍入的乘积值)离 n - 2**(k-53) 更近,而不是离 n 更近,这样它就总是会被舍入到下方。但简单的算术表明,从 n*(1-2**-53)n 的距离是 2**-53 * n,而从 n*(1-2**-53)n - 2**(k-53) 的距离是 (2**k - n) * 2**-53。但是 2**k - n < n(因为我们选择了 k 使得 2**(k-1) < n),所以这个乘积确实离 n - 2**(k-53) 更近,因此它会被舍入到下方(假设平台采用某种形式的四舍五入)。

所以我们是安全的。呼!


附录(2015-07-04):上述假设使用的是 IEEE 754 binary64 算法,采用四舍五入到偶数的舍入模式。在许多机器上,这个假设是相当安全的。然而,在使用 x87 FPU 进行浮点运算的 x86 机器上(例如各种 32 位 Linux),在乘法中可能会发生 双重舍入,这使得 random() * nrandom() 返回最大可能值的情况下有可能向上舍入到 n。发生这种情况的最小 nn = 2049。更多讨论请见 http://bugs.python.org/issue24546

27

来自 random.py 和文档的内容:

"""Get the next random number in the range [0.0, 1.0)."""

这里的 ) 表示这个区间是不包括 1.0 的。也就是说,它永远不会返回 1.0。

这是数学中的一个通用规则,[] 表示包括这个数,而 () 表示不包括这个数。这两种括号可以混合使用,比如 (a, b][a, b)。想了解更多,可以看看 维基百科:区间(数学) 上的正式解释。

撰写回答