【问题标题】:Distribution of final digits of random numbers in PythonPython中随机数的最终数字分布
【发布时间】:2020-04-25 01:39:35
【问题描述】:

在 Python 中有两种明显的方法可以生成从 0 到 9 的随机数字。可以生成一个介于 0 和 1 之间的随机浮点数,乘以 10,然后向下舍入。或者,可以使用random.randint 方法。

import random

def random_digit_1():
    return int(10 * random.random())

def random_digit_2():
    return random.randint(0, 9)

我很好奇如果生成一个介于 0 和 1 之间的随机数并保留 最后一个位会发生什么。我并不一定期望分布是均匀的,但我发现结果非常令人惊讶。

from random import random, seed
from collections import Counter

seed(0)
counts = Counter(int(str(random())[-1]) for _ in range(1_000_000))
print(counts)

输出:

Counter({1: 84206,
         5: 130245,
         3: 119433,
         6: 129835,
         8: 101488,
         2: 100861,
         9: 84796,
         4: 129088,
         7: 120048})

直方图如下所示。请注意,不会出现 0,因为尾随零被截断。但是谁能解释为什么数字 4、5 和 6 比其他数字更常见?我用了 Python 3.6.10,但 Python 3.8.0a4 中的结果相似。

【问题讨论】:

  • 这与在 Python 中计算浮点数的字符串表示的方式有关。见docs.python.org/3/tutorial/floatingpoint.html。如果您使用十分位(小数点后的第一位)而不是最后一位,您会得到更均匀的结果。
  • 我们以二进制表示形式存储浮点数(因为我们的内存也是二进制的)。 str 将其转换为 base-10,这必然会导致问题。例如1 位浮点尾数 b0 -> 1.0b1 -> 1.5。 “最后一位”将始终为 05
  • random.randrange(10) 更明显,恕我直言。 random.randint(在后台调用 random.randrange)是后来添加到 random 模块的,适用于不了解 Python 中范围如何工作的人。 ;)
  • @PM2Ring: randrange 实际上排在第二位,因为他们认为 randint 接口是一个错误。
  • @user2357112supportsMonica 哦,好的。我站得更正了。我确信 randrange 是第一名,但我的记忆力不如以前了。 ;)

标签: python random


【解决方案1】:

这不是数字的“最后一位”。这是传递数字时str 给你的字符串的最后一位

当您在浮点数上调用 str 时,Python 会为您提供足够的数字,以至于在字符串上调用 float 会为您提供原始浮点数。为此,与其他数字相比,尾随 1 或 9 不太可能是必需的,因为尾随 1 或 9 表示该数字非常接近您通过四舍五入该数字获得的值。很有可能没有其他浮点数更接近,如果是这样,则可以丢弃该数字而不会牺牲 float(str(original_float)) 行为。

如果str 给了你足够的数字来准确地表示参数,最后一个数字几乎总是5,除非random.random() 返回0.0,在这种情况下最后一个数字是0。(浮点数只能表示@ 987654321@,并且非整数二元有理数的最后一个非零十进制数字始终为 5。)输出也将非常长,看起来像

>>> import decimal, random
>>> print(decimal.Decimal(random.random()))
0.29711195452007921335990658917580731213092803955078125

这是str 不这样做的原因之一。

如果str 为您提供了 17 个有效数字(足以区分所有浮点值,但有时数字多于必要的数字),那么您看到的效果就会消失。尾随数字(包括 0)几乎均匀分布。

(另外,您忘记了str 有时会以科学计数法返回字符串,但这是一个很小的影响,因为在random.random() 之外发生这种情况的浮动概率很低。)

【讨论】:

    【解决方案2】:

    TL;DR 您的示例实际上并没有查看最后一位数字。转换为 base-10 的有限二进制表示的尾数的最后一位应始终为 05


    看看cpython/pystrtod.c中的评论:

    char * PyOS_double_to_string(double val,
                                             char format_code,
                                             int precision,
                                             int flags,
                                             int *type)
    {
        char format[32];
        Py_ssize_t bufsize;
        char *buf;
        int t, exp;
        int upper = 0;
    
        /* Validate format_code, and map upper and lower case */
        switch (format_code) {
        // ...
        case 'r':          /* repr format */
            /* Supplied precision is unused, must be 0. */
            if (precision != 0) {
                PyErr_BadInternalCall();
                return NULL;
            }
            /* The repr() precision (17 significant decimal digits) is the
               minimal number that is guaranteed to have enough precision
               so that if the number is read back in the exact same binary
               value is recreated.  This is true for IEEE floating point
               by design, and also happens to work for all other modern
               hardware. */
            precision = 17;
            format_code = 'g';
            break;
        // ...
    }
    

    Wikipedia 证实了这一点:

    53 位有效数字精度提供 15 到 17 位有效十进制数字精度(2-53 ≈ 1.11 × 10-16)。如果将最多 15 位有效数字的十进制字符串转换为 IEEE 754 双精度表示,然后再转换回具有相同位数的十进制字符串,则最终结果应与原始字符串匹配。 如果将 IEEE 754 双精度数转换为具有至少 17 位有效数字的十进制字符串,然后再转换回双精度表示,则最终结果必须与原始数匹配。

    因此,当我们使用str(或repr)时,我们仅表示以 10 为底的 17 位有效数字。这意味着一些浮点数将被截断。事实上,要获得准确的表示,您需要 53 位有效数字的精度!您可以按如下方式进行验证:

    >>> counts = Counter(
    ...     len(f"{random():.99f}".lstrip("0.").rstrip("0"))
    ...     for _ in range(1000000)
    ... )
    >>> counts
    Counter({53: 449833,
             52: 270000,
             51: 139796,
             50: 70341,
             49: 35030,
             48: 17507,
             47: 8610,
             46: 4405,
             45: 2231,
             44: 1120,
             43: 583,
             42: 272,
             41: 155,
             40: 60,
             39: 25,
             38: 13,
             37: 6,
             36: 5,
             35: 4,
             34: 3,
             32: 1})
    >>> max(counts)
    53
    

    现在使用最大精度,这是找到“最后一位”的正确方法:

    >>> counts = Counter(
    ...     int(f"{random():.53f}".lstrip("0.").rstrip("0")[-1])
    ...     for _ in range(1000000)
    ... )
    >>> counts
    Counter({5: 1000000})
    

    因此,最后一位数字始终是5。 (或者,在极少数情况下,0。)这是有道理的,因为:

    2**0  == 1.0
    2**-1 == 0.5
    2**-2 == 0.25
    2**-3 == 0.125
    2**-4 == 0.0625
    2**-5 == 0.03125
    2**-6 == 0.015625
    2**-7 == 0.0078125
    2**-8 == 0.00390625
    2**-9 == 0.001953125
    ...
    2**-k == 0.[k-1 digits]5
    

    所有尾数都是这些系数的部分和。


    注意:正如 user2357112 所指出的,要查看的正确实现是 PyOS_double_to_stringformat_float_short,但我将保留当前的实现,因为它在教学上更有趣。

    【讨论】:

    • “因此,当我们使用 str(或 repr)时,我们仅表示 17 位有效数字以 base-10 为基础。” - 17 是最大值。如果它实际上是一个固定的 17 位数字,则不会出现问题中的效果。问题中的效果来自刚刚足够的数字到往返四舍五入str(some_float) 使用。
    • 您正在查看 PyOS_double_to_string 的错误实现。该实现已被预处理以支持this one
    • 关于第一条评论:如前所述,浮点数的精确表示(编辑:指数为 0)需要 53 个有效数字,尽管 17 足以保证 float(str(x)) == x。大多数情况下,这个答案只是为了表明问题中的假设(“精确表示的最后一位数字”)是错误的,因为正确的结果只是5s(不太可能是0)。
    • 53 个有效十进制数字是不够的。 Here's an example that takes a lot more.
    • @user2357112supportsMonica 抱歉,我的意思是指数为 0。(这是保证区间 [0, 1] 内的一致性所必需的。)
    猜你喜欢
    • 2016-12-25
    • 1970-01-01
    • 1970-01-01
    • 2016-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-16
    相关资源
    最近更新 更多