Python中随机数最终数字的分布


24

在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类似。

随机浮点数最终数字的分布


4
这与在Python中计算float的字符串表示形式有关。请参阅docs.python.org/3/tutorial/floatingpoint.html。如果使用十分之一的数字(小数点后的第一位)而不是最后一位,您将获得更均匀的结果。
丹尼斯

1
我们以二进制表示形式存储浮点数(因为我们的内存也是二进制的)。str将其转换为以10为基数,势必会引起问题。例如1位浮点尾数b0 -> 1.0b1 -> 1.5。“最后一位”将始终为05
Mateen Ulhaq

1
random.randrange(10)恕我直言,这一点更加明显。random.randintrandom.randrange在幕后调用)是该random模块的新增内容,适用于不了解范围在Python中如何工作的人们。;)
2

2
@ PM2Ring:randrange在他们认为randint界面有误之后,实际上排在第二位。
user2357112支持Monica

@ user2357112supportsMonica哦,好的。我站得住了。我确定randrange是第一名,但我的记忆却不如从前。;)
2

Answers:


21

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

当您调用str浮点数时,Python为您提供了足够的数字,因此调用float字符串将为您提供原始的浮点数。为此,与其他数字相比,后缀1或9不太可能是必需的,因为后缀1或9表示该数字非常接近您舍入该数字所得到的值。很有可能没有其他浮标更接近,如果是这样,则可以在不牺牲float(str(original_float))行为的情况下丢弃该数字。

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

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

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

如果str给您恰好17个有效数字(足以将所有浮点值彼此区分开,但有时会超出必要数字),那么您看到的效果将消失。尾随数字(包括0)几乎是均匀分布的。

(此外,您忘记了str有时会以科学计数法返回字符串,但这是次要的效果,因为在这种情况下发生浮动的可能性很小random.random()。)


5

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


看一下cpython/floatobject.c

static PyObject *
float_repr(PyFloatObject *v)
{
    PyObject *result;
    char *buf;

    buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v),
                                'r', 0,
                                Py_DTSF_ADD_DOT_0,
                                NULL);

    // ...
}

现在在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;
    // ...
}

维基百科确认了这一点:

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})

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


“因此,当我们使用str(或repr)时,我们仅以10为基数表示17个有效数字。” -最大值为17。如果实际上是固定的17位数字,则不会出现问题中的效果。问题中的影响来自于足够的数字到往返舍入str(some_float)用途。
user2357112支持Monica

1
您正在查看的错误实现PyOS_double_to_string对该
user2357112支持Monica

关于第一个评论:如上所述,浮点数的精确表示(编辑:指数为0)需要53个有效数字,尽管17足以保证float(str(x)) == x。通常,这个答案只是为了表明问题中的假设(“精确表示的最后一位数字”)是错误的,因为正确的结果只是5s(而且不太可能0)。
Mateen Ulhaq

53位有效的十进制数字是不够的。这是一个需要花费更多时间的示例。
user2357112支持Monica

@ user2357112supportsMonica对不起,我的意思是指数为0。(必须保证间隔[0,1]内的均匀性。)
Mateen Ulhaq
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.