Python'da rasgele sayıların son rakamlarının dağılımı


24

Python'da 0 ile 9 arasında rastgele bir rakam oluşturmanın iki belirgin yolu vardır. 0 ile 1 arasında rastgele bir kayan nokta sayısı üretilebilir, 10 ile çarpılabilir ve yuvarlanabilir. Alternatif olarak, random.randintyöntem kullanılabilir.

import random

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

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

Birisi 0 ile 1 arasında rastgele bir sayı üretip son basamağı tutarsa ​​ne olacağını merak ediyordum . Dağıtımın tek tip olmasını beklemiyordum, ama sonucu oldukça şaşırtıcı buldum.

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)

Çıktı:

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

Bir histogram aşağıda gösterilmiştir. Sondaki sıfırlar kesildiğinden 0'ın görünmediğine dikkat edin. Ancak 4, 5 ve 6 basamaklarının neden diğerlerinden daha yaygın olduğunu açıklayabilir mi? Python 3.6.10 kullandım, ancak sonuçlar Python 3.8.0a4'te benzerdi.

Rasgele yüzerlerin son basamaklarının dağılımı


4
Bu, şamandıraların dize temsillerinin Python'da hesaplanma şekli ile ilgilidir. Bkz. Docs.python.org/3/tutorial/floatingpoint.html . Son basamaktan ziyade onuncu basamağı (ondalıktan sonra ilk olarak) kullanırsanız çok daha eşit sonuçlar elde edersiniz.
Dennis

1
Şamandıraları ikili gösterimde saklarız (hafızamız da ikili olduğu için). strproblemlere neden olan temel-10'a dönüştürür. örneğin, 1 bit şamandıralı mantis b0 -> 1.0ve b1 -> 1.5. "Son basamak" her zaman 0veya olur 5.
Mateen Ulhaq

1
random.randrange(10)daha açıktır, IMHO. random.randint( random.randrangebaşlık altında aramalar ) randomPython'da aralıkların nasıl çalıştığını anlamayan insanlar için modüle daha sonraki bir eklemeydi. ;)
PM 2Ring

2
@ PM2Ring: arayüzün bir hata randrangeolduğuna karar verdikten sonra aslında ikinci randintoldu.
user2357112 Monica

@ user2357112supportsMonica Oh, tamam. Ben düzeltilmiş duruyorum. Randrange'in 1 olduğundan emindim, ama hafızam eskisi kadar iyi değil. ;)
PM 2Ring

Yanıtlar:


21

Bu sayının "son basamağı" değil. Yani son rakamı var dize strsana verdiği numarayı geçerken.

Bir strkayan noktaya çağrı yaptığınızda , Python size floatdize çağrıldığında orijinal kayan nokta verecek yeterli basamak verir . Bu amaçla, 1 veya 9 sonunun diğer rakamlardan daha az gerekli olması muhtemeldir, çünkü 1 veya 9 sonundaki sayı, bu rakamı yuvarlayarak elde ettiğiniz değere çok yakın olduğu anlamına gelir. Başka hiçbir yüzmenin daha yakın olmama ihtimali yüksektir ve eğer öyleyse, bu rakam float(str(original_float))davranıştan ödün vermeden atılabilir .

Eğer strsize tam argümanı temsil etmek yeterli sayıda basamak verdi son basamak hemen her zaman dışında, 5 olurdu random.random()Şamandıralar sadece temsil edebilir (bu durumda son basamak 0. olurdu döner 0.0, diyadik rationals ve son sıfırdan farklı ondalık basamak arasında tamsayı olmayan bir ikili rasyon her zaman 5'tir.) Çıktılar da son derece uzun olurdu,

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

hangi sebeplerden strbiri bunu yapmaz.

Eğer strsize tam 17 (birbirinden tüm float değerleri ayırt etmek yeterli, ancak gerekli olandan bazen fazla basamak) önemli basamak verdi, o zaman etkisi Sen gören ortadan kalkacaktır. Sondaki rakamların neredeyse eşit dağılımı (0 dahil) olacaktır.

(Ayrıca, strbilimsel gösterimde bazen bir dize döndürdüğünü unutuyorsunuz , ancak bu küçük bir etki, çünkü bunun gerçekleşeceği yerde bir şamandıra alma olasılığı düşük random.random().)


5

TL; DR Örneğiniz aslında son basamağa bakmıyor. Baz-10'a dönüştürülmüş sonlu bir ikili temsil edilen mantisin son basamağı daima 0veya olmalıdır 5.


Şuna bir göz atın 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);

    // ...
}

Ve şimdi 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 bunu onaylar:

53 bit anlamlılık hassasiyeti 15 ila 17 arasında önemli ondalık basamak hassasiyeti sağlar (2 -53 ≈ 1,11 × 10 -16 ). En fazla 15 anlamlı basamağa sahip bir ondalık dize, IEEE 754 çift kesinlikli gösterime dönüştürülür ve sonra aynı sayıda basamak içeren bir ondalık dizeye dönüştürülürse, nihai sonuç orijinal dizeyle eşleşmelidir. Bir IEEE 754 çift kesinlikli sayı, en az 17 anlamlı basamaklı bir ondalık dizgeye dönüştürülür ve sonra yeniden çift kesinlikli gösterime dönüştürülürse, nihai sonuç orijinal sayıyla eşleşmelidir.

Bu nedenle, str(veya repr) kullandığımızda , baz-10'da sadece 17 önemli basamağı temsil ediyoruz. Bu, kayan nokta sayısının bir kısmının kesileceği anlamına gelir. Aslında, tam temsili elde etmek için 53 önemli basamak hassasiyetine ihtiyacınız var! Bunu aşağıdaki gibi doğrulayabilirsiniz:

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

Şimdi maksimum hassasiyeti kullanarak "son basamağı" bulmanın doğru yolu:

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

NOT: user2357112 tarafından işaret edildiği gibi, bakmak için doğru uygulamalar vardır PyOS_double_to_stringve format_float_short, ama mevcut olanları bırakacağım çünkü daha pedagojik olarak ilginçler.


"Bu nedenle, str (veya repr) kullandığımızda, taban-10'da sadece 17 önemli basamağı temsil ediyoruz." - Maksimum 17'dir. Aslında sabit bir 17 basamak olsaydı, sorudaki etki görünmezdi. Sorudaki etki, yeterince basamaklı-gidiş-dönüş yuvarlama str(some_float)kullanımlarından geliyor.
user2357112 Monica

1
Uygulamasının yanlış uygulanışına bakıyorsunuz PyOS_double_to_string. Bu uygulama, bunun
user2357112 Monica

İlk yoruma ilişkin olarak: Belirtildiği gibi, kayan nokta sayısının (EDIT: 0 üslü) tam olarak gösterilmesi 53 anlamlı basamak gerektirir, ancak 17 garanti için yeterlidir float(str(x)) == x. Çoğunlukla, bu cevap sadece soruda yapılan varsayımı ("tam temsilin son basamağı") yanlış göstermekti, çünkü doğru sonuç sadece 5s (ve olası değil 0).
Mateen Ulhaq

53 önemli ondalık basamak yeterli değil. İşte çok daha fazlasını gerektiren bir örnek.
user2357112 Monica

Monica Üzgünüz, 0 üssü ile kastediyorum. ([0, 1] aralığında eşitliği garanti etmek için gereklidir.)
Mateen Ulhaq
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.