[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이 잘 리므로 0이 표시되지 않습니다. 그러나 왜 숫자 4, 5 및 6이 나머지보다 더 일반적인지 설명 할 수 있습니까? Python 3.6.10을 사용했지만 결과는 Python 3.8.0a4와 비슷했습니다.

랜덤 플로트의 최종 자릿수 분포



답변

그것은 숫자의 “마지막 숫자”가 아닙니다. 즉,의 마지막 자리의 문자열을 str 수를 통과 할 때.

strfloat 를 호출하면 Python은 float문자열 을 호출 하면 원래 float을 제공 할 수있는 충분한 자릿수를 제공합니다. 이 목적을 위해 후행 1 또는 9는 다른 숫자보다 필요하지 않을 것입니다. 후행 1 또는 9는 숫자가 해당 숫자를 반올림하여 얻는 값에 매우 가깝다는 것을 의미하기 때문입니다. 다른 수레가 더 가까이 있지 않을 가능성이 높으며, 그렇다면 해당 숫자를 희생하지 않고 버릴 수 있습니다.float(str(original_float)) 행동 .

경우 str당신에게 정확하게 인수를 나타냅니다 충분히 자리를 준, 마지막 자리는 거의 항상 때를 제외하고 5 것 random.random()수레 만 표현할 수있다 (이 경우 마지막 숫자가 0이 될 것이다 수익률 0.0, 이진 유리수를 , 마지막 제로가 아닌 소수점 자리의 정수가 아닌 2 차원 이항은 항상 5입니다.) 출력도 매우 길어 보입니다.

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

str그렇게하지 않는 이유 중 하나입니다 .

경우 str당신에게 정확히 17 (서로 모든 부동 소수점 값을 구별하기에 충분하지만, 필요 이상으로 때로는 이상의 숫자) 유효 숫자가 준 후, 효과는 넌 보는 사라질 것입니다. 후행 숫자 (0 포함)가 거의 균일하게 분포합니다.

(또한 str과학적 표기법으로 문자열을 반환하는 것을 잊었 지 만 사소한 영향을 미칩니다. 왜냐하면 부동 소수점을 얻을 가능성이 낮기 때문입니다 random.random().)


답변

TL; DR 예제는 실제로 마지막 숫자를보고 있지 않습니다. 10 진수로 변환 된 유한 이진 표현 가수의 마지막 숫자는 항상 0또는 이어야합니다 5.


살펴보십시오 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;
    // ...
}

Wikipedia는 이것을 확인합니다 :

53 비트의 유효 정밀도는 15에서 17의 유효 소수 자릿수 정밀도 (2 -53 ≈ 1.11 × 10 -16 )를 제공합니다. 최대 유효 자릿수가 15 자리 인 10 진수 문자열이 IEEE 754 배정 밀도 표현으로 변환 된 다음 같은 자릿수의 10 진수 문자열로 다시 변환되면 최종 결과는 원래 문자열과 일치해야합니다. IEEE 754 배정 밀도 숫자가 17 자리 이상의 유효 자릿수를 가진 10 진수 문자열로 변환 된 다음 다시 배정 밀도 표현으로 변환되는 경우 최종 결과는 원래 숫자와 일치해야합니다.

따라서 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_stringand입니다 format_float_short.하지만 더 교육적으로 흥미 롭기 때문에 현재 구현을 남겨 두겠습니다.


답변