पायथन में यादृच्छिक संख्याओं के अंतिम अंकों का वितरण


24

पायथन में 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 बाकी की तुलना में अधिक सामान्य क्यों हैं? मैंने पायथन 3.6.10 का उपयोग किया, लेकिन परिणाम पायथन 3.8.0a4 में समान थे।

यादृच्छिक फ़्लोट्स के अंतिम अंकों का वितरण


4
इसका अर्थ यह है कि पाइथन में फ्लोट्स के स्ट्रिंग निरूपण की गणना की जाती है। Docs.python.org/3/tutorial/floatingpoint.html देखें । यदि आपने अंतिम अंक के बजाय दसवें अंक (दशमलव के बाद पहली बार) का उपयोग किया है तो आपको और भी अधिक परिणाम मिलेंगे।
डेनिस

1
हम बाइनरी प्रतिनिधित्व में फ्लोट को स्टोर करते हैं (चूंकि हमारी मेमोरी भी बाइनरी है)। strइसे बेस -10 में परिवर्तित करता है जो समस्याओं का कारण है। उदाहरण के लिए 1-बिट फ्लोट मंटिसा b0 -> 1.0और b1 -> 1.5। "पिछले अंक" हमेशा हो जाएगा 0या 5
मतीन उल्हाक

1
random.randrange(10)और भी स्पष्ट है, IMHO। random.randint(जो random.randrangeहुड के तहत कॉल करता है) बाद में randomउन लोगों के लिए मॉड्यूल के अलावा था, जो यह नहीं समझते कि पायथन में रेंज कैसे काम करते हैं। ;)
पीएम 2 रिंग

2
@ PM2Ring: इंटरफ़ेस में गलती randrangeहोने के बाद उन्होंने वास्तव में दूसरा फैसला किया randint
user2357112

@ user2357112supportsMonica ओह, ठीक है। मुझे सही साबित होना है। मुझे यकीन था कि रैंड्रेंज 1 था, लेकिन मेरी मेमोरी उतनी अच्छी नहीं है जितनी पहले हुआ करती थी। ;)
पीएम 2 रिंग

जवाबों:


21

यह संख्या का "अंतिम अंक" नहीं है। यह स्ट्रिंगstr का अंतिम अंक आपको दिया गया है जब आपने नंबर दिया था।

जब आप strएक फ्लोट पर कॉल करते हैं, तो पायथन आपको पर्याप्त अंक देता है कि 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 में परिवर्तित एक परिमित बाइनरी-प्रतिनिधित्व वाले मंटिसा का अंतिम अंक हमेशा 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;
    // ...
}

विकिपीडिया इसकी पुष्टि करता है:

53-बिट महत्व वाली परिशुद्धता 15 से 17 महत्वपूर्ण दशमलव अंकों की सटीकता (2 -53 bit 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_stringऔर format_float_short, लेकिन मैं वर्तमान वाले को छोड़ दूंगा क्योंकि वे अधिक दिलचस्प हैं।


"इस प्रकार, जब हम str (या repr) का उपयोग करते हैं, हम केवल बेस -10 में 17 महत्वपूर्ण अंकों का प्रतिनिधित्व कर रहे हैं।" - 17 अधिकतम है। यदि यह वास्तव में एक निश्चित 17 अंक था, तो प्रश्न में प्रभाव दिखाई नहीं देगा। प्रश्न में प्रभाव बस-पर्याप्त-अंकों-से-राउंड-ट्रिप राउंडिंग str(some_float)उपयोगों से आता है।
user2357112

1
आप के गलत कार्यान्वयन को देख रहे हैं PyOS_double_to_string। यह कार्यान्वयन इस के
पूर्व निर्धारित है

पहली टिप्पणी के बारे में: जैसा कि उल्लेख किया गया है, एक अस्थायी बिंदु संख्या (EDIT: 0 के एक घातांक के साथ) के सटीक प्रतिनिधित्व के लिए 53 महत्वपूर्ण अंकों की आवश्यकता होती है, हालांकि 17 की गारंटी के लिए पर्याप्त है float(str(x)) == x। अधिकतर, यह उत्तर केवल प्रश्न में किए गए अनुमान ("सटीक प्रतिनिधित्व का अंतिम अंक") को गलत दिखाने के लिए था, क्योंकि सही परिणाम सिर्फ 5s (और एक संभावना नहीं 0) है।
मतीन उल्हाक

53 महत्वपूर्ण दशमलव अंक पर्याप्त नहीं हैं। यहाँ एक उदाहरण है जो बहुत अधिक लेता है।
user2357112 मोनिका

@ user2357112supportsMonica क्षमा करें, मेरा मतलब 0. के प्रतिपादक से है (जो कि अंतराल [0, 1] के भीतर एकरूपता की गारंटी के लिए आवश्यक है।)
मेटेन उल्हाक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.