Phân phối các chữ số cuối cùng của các số ngẫu nhiên trong Python


24

Có hai cách rõ ràng để tạo một chữ số ngẫu nhiên từ 0 đến 9 trong Python. Người ta có thể tạo một số dấu phẩy động ngẫu nhiên trong khoảng từ 0 đến 1, nhân với 10 và làm tròn xuống. Ngoài ra, người ta có thể sử dụng random.randintphương pháp.

import random

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

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

Tôi tò mò về những gì sẽ xảy ra nếu một người tạo ra một số ngẫu nhiên trong khoảng từ 0 đến 1 và giữ chữ số cuối cùng . Tôi không nhất thiết mong đợi sự phân phối là thống nhất, nhưng tôi thấy kết quả khá đáng ngạc nhiên.

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)

Đầu ra:

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

Một biểu đồ được hiển thị dưới đây. Lưu ý rằng 0 không xuất hiện, vì các số 0 ở cuối được cắt bớt. Nhưng bất cứ ai cũng có thể giải thích tại sao các chữ số 4, 5 và 6 phổ biến hơn các chữ số còn lại? Tôi đã sử dụng Python 3.6.10, nhưng kết quả tương tự trong Python 3.8.0a4.

Phân phối các chữ số cuối cùng của phao ngẫu nhiên


4
Điều này có liên quan đến cách các biểu diễn chuỗi của float được tính bằng Python. Xem docs.python.org/3/tutorial/floatingpoint.html . Bạn sẽ nhận được nhiều kết quả hơn nữa nếu bạn sử dụng chữ số thứ mười (đầu tiên sau số thập phân) thay vì chữ số cuối cùng.
Dennis

1
Chúng tôi lưu trữ float trong biểu diễn nhị phân (vì bộ nhớ của chúng tôi cũng là nhị phân). strchuyển đổi nó thành cơ sở 10 bị ràng buộc gây ra vấn đề. ví dụ: mantissa phao 1 bit b0 -> 1.0b1 -> 1.5. "Chữ số cuối" sẽ luôn là 0hoặc 5.
Mateen Ulhaq

1
random.randrange(10)thậm chí còn rõ ràng hơn, IMHO. random.randint(gọi random.randrangedưới mui xe) là một bổ sung sau này cho randommô-đun dành cho những người không hiểu cách hoạt động của phạm vi trong Python. ;)
PM 2Ring

2
@ PM2Ring: randrangethực sự đứng thứ hai, sau khi họ quyết định randintgiao diện là một lỗi.
user2357112 hỗ trợ Monica

@ user2357112supportsMonica Ồ, ok. Tôi đứng sửa. Tôi chắc chắn rằng randrange là số 1, nhưng trí nhớ của tôi không còn tốt như trước. ;)
PM 2Ring

Câu trả lời:


21

Đó không phải là "chữ số cuối" của số. Đó là chữ số cuối cùng của chuỗi strđã cho bạn khi truyền số.

Khi bạn gọi strmột số float, Python cung cấp cho bạn đủ các chữ số mà việc gọi floattrên chuỗi sẽ cung cấp cho bạn số float ban đầu. Với mục đích này, một số 1 hoặc 9 theo dõi ít ​​có khả năng là cần thiết hơn các chữ số khác, bởi vì một số 1 hoặc 9 có nghĩa là số rất gần với giá trị bạn nhận được bằng cách làm tròn chữ số đó. Có một cơ hội tốt không có phao nào khác gần hơn, và nếu vậy, chữ số đó có thể bị loại bỏ mà không phải hy sinh float(str(original_float))hành vi.

Nếu strcung cấp cho bạn đủ các chữ số để thể hiện chính xác đối số, chữ số cuối cùng sẽ luôn luôn là 5, ngoại trừ khi random.random()trả về 0,0, trong trường hợp đó, chữ số cuối cùng sẽ là 0. (Phao chỉ có thể biểu thị các số hữu tỷ dyadic và chữ số thập phân khác không cuối cùng của một tỷ lệ hợp lý dyadic không nguyên luôn luôn là 5.) Các đầu ra cũng sẽ rất dài, trông giống như

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

đó là một trong những lý do strkhông làm điều đó.

Nếu strđưa cho bạn chính xác 17 chữ số có nghĩa (đủ để phân biệt tất cả các giá trị nổi với nhau, nhưng đôi khi nhiều chữ số hơn mức cần thiết), thì hiệu ứng bạn nhìn thấy sẽ biến mất. Sẽ có sự phân phối gần như thống nhất các chữ số ở cuối (bao gồm 0).

(Ngoài ra, bạn quên rằng strđôi khi trả về một chuỗi trong ký hiệu khoa học, nhưng đó là một hiệu ứng nhỏ, bởi vì có một xác suất thấp nhận được một phao nơi đó sẽ xảy ra ra khỏi random.random().)


5

TL; DR Ví dụ của bạn không thực sự nhìn vào chữ số cuối cùng. Chữ số cuối cùng của một mantissa đại diện nhị phân hữu hạn được chuyển đổi thành cơ sở 10 phải luôn luôn là 0hoặc 5.


Hãy xem 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);

    // ...
}

Và bây giờ tại 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 xác nhận điều này:

Độ chính xác có ý nghĩa 53 bit cho độ chính xác từ 15 đến 17 chữ số thập phân có ý nghĩa (2 -53 1,11 × 10 -16 ). Nếu một chuỗi thập phân có tối đa 15 chữ số có nghĩa được chuyển đổi thành biểu diễn chính xác kép của IEEE 754 và sau đó được chuyển đổi thành chuỗi thập phân có cùng số chữ số, kết quả cuối cùng sẽ khớp với chuỗi gốc. Nếu một số chính xác kép của IEEE 754 được chuyển đổi thành một chuỗi thập phân có ít nhất 17 chữ số có nghĩa, và sau đó được chuyển đổi thành biểu diễn chính xác kép, kết quả cuối cùng phải khớp với số ban đầu.

Do đó, khi chúng tôi sử dụng str(hoặc repr), chúng tôi chỉ đại diện cho 17 chữ số có nghĩa trong cơ sở 10. Điều này có nghĩa là một số số dấu phẩy động sẽ bị cắt ngắn. Trong thực tế, để có được đại diện chính xác, bạn cần độ chính xác của 53 chữ số có nghĩa! Bạn có thể xác minh điều này như sau:

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

Bây giờ sử dụng độ chính xác tối đa, đây là cách thích hợp để tìm "chữ số cuối":

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

Chú ý: Như đã chỉ ra bởi user2357112, việc triển khai đúng để xem xét là PyOS_double_to_stringformat_float_short, nhưng tôi sẽ để lại những cái hiện tại bởi vì họ đang sư phạm thú vị hơn.


"Do đó, khi chúng tôi sử dụng str (hoặc repr), chúng tôi chỉ đại diện cho 17 chữ số có nghĩa trong cơ sở 10." - 17 là mức tối đa. Nếu nó thực sự là 17 chữ số cố định, hiệu ứng trong câu hỏi sẽ không xuất hiện. Hiệu quả trong câu hỏi đến từ việc str(some_float)sử dụng làm tròn số vừa đủ cho các chuyến đi .
user2357112 hỗ trợ Monica

1
Bạn đang nhìn vào việc thực hiện sai PyOS_double_to_string. Việc triển khai đó được xử lý trước theo hướng có lợi cho việc này
user2357112 hỗ trợ Monica

Về nhận xét đầu tiên: Như đã đề cập, việc biểu diễn chính xác số dấu phẩy động (EDIT: với số mũ bằng 0) yêu cầu 53 chữ số có nghĩa, mặc dù 17 là đủ để đảm bảo float(str(x)) == x. Hầu hết, câu trả lời này chỉ là để hiển thị giả định ("chữ số cuối cùng của biểu diễn chính xác") được đưa ra trong câu hỏi là sai, vì kết quả đúng chỉ là 5s (và không chắc 0).
Mateen Ulhaq

53 chữ số thập phân có ý nghĩa là không đủ. Đây là một ví dụ cần nhiều hơn nữa.
user2357112 hỗ trợ Monica

@ user2357112supportsMonica Xin lỗi, ý tôi là với số mũ bằng 0. (Điều này là cần thiết để đảm bảo tính đồng nhất trong khoảng [0, 1].)
Mateen Ulhaq
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.