यादृच्छिक संख्या जनरेटर का उपयोग करते समय लोग क्यों कहते हैं कि मॉडुलो पूर्वाग्रह है?


277

मैंने देखा है कि यह सवाल बहुत पूछा गया है लेकिन कभी भी इसका सही ठोस जवाब नहीं देखा गया है। इसलिए मैं यहां एक पोस्ट करने जा रहा हूं, जो लोगों को यह समझने में मदद करेगा कि वास्तव rand()में सी + + जैसे यादृच्छिक संख्या जनरेटर का उपयोग करते समय "मॉडुलो पूर्वाग्रह" क्यों है ।

जवाबों:


394

तो rand()एक छद्म यादृच्छिक संख्या जनरेटर है जो 0 और के बीच एक प्राकृतिक संख्या चुनता है RAND_MAX, जो एक निरंतर परिभाषित है cstdlib(इस लेख को सामान्य अवलोकन के लिए देखें rand())।

अब क्या होगा यदि आप 0 और 2 के बीच एक यादृच्छिक संख्या उत्पन्न करना चाहते हैं? स्पष्टीकरण के लिए, मान लें कि RAND_MAX10 है और मैं कॉल करके 0 और 2 के बीच एक यादृच्छिक संख्या उत्पन्न करने का निर्णय लेता हूं rand()%3। हालाँकि, rand()%3समान संभावना वाले 0 और 2 के बीच संख्या का उत्पादन नहीं करता है!

जब rand()0, 3, 6 या 9 रिटर्न देता है rand()%3 == 0 । इसलिए, पी (0) = 4/11

जब rand()रिटर्न 1, 4, 7, या 10 rand()%3 == 1 ,। इसलिए, पी (1) = 4/11

जब rand()2, 5, या 8 रिटर्न देता है rand()%3 == 2 । इसलिए, पी (2) = 3/11

यह समान संभावना वाले 0 और 2 के बीच संख्या उत्पन्न नहीं करता है। बेशक छोटी श्रेणियों के लिए यह सबसे बड़ा मुद्दा नहीं हो सकता है, लेकिन बड़ी रेंज के लिए यह वितरण को कम कर सकता है, छोटी संख्याओं को कम करके।

तो rand()%nसमान संभावना वाले 0 से n-1 तक की संख्या को कब लौटाता है? कब RAND_MAX%n == n - 1? इस मामले में, हमारी पहले की धारणा के साथ rand()0 और के बीच एक संख्या वापस आती हैRAND_MAX समान संभावना के साथ, n के modulo वर्ग भी समान रूप से वितरित किए जाएंगे।

तो हम इस समस्या को कैसे हल करते हैं? जब तक आप अपनी वांछित सीमा में एक नंबर प्राप्त नहीं करते हैं, तब तक एक यादृच्छिक तरीका उत्पन्न करना जारी रहता है:

int x; 
do {
    x = rand();
} while (x >= n);

लेकिन यह निम्न मूल्यों के लिए अक्षम है n, क्योंकि आपके पास केवल n/RAND_MAXअपनी सीमा में मूल्य प्राप्त करने का एक मौका है, और इसलिए आपको RAND_MAX/nकॉल करने की आवश्यकता होगीrand() औसतन ।

एक अधिक कुशल फार्मूला अप्रोच कुछ बड़ी रेंज को विभाज्य लंबाई के साथ लेना होगा n, जैसे RAND_MAX - RAND_MAX % n, यादृच्छिक संख्या उत्पन्न करना जब तक कि आप एक सीमा में नहीं मिलते, और तब मापांक लें:

int x;

do {
    x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));

x %= n;

के छोटे मूल्यों के लिए n, इसके लिए शायद ही कभी एक से अधिक कॉल की आवश्यकता होगी rand()


उद्धृत और आगे पढ़ने का काम करता है:



6
__ के बारे में सोचने का दूसरा तरीका RAND_MAX%n == n - 1है (RAND_MAX + 1) % n == 0। कोड पढ़ते समय, मैं % something == 0इसकी गणना करने के अन्य तरीकों की तुलना में "समान रूप से विभाज्य" के रूप में आसानी से समझ सकता हूं । बेशक, अगर आपके C ++ stdlib RAND_MAXके समान मूल्य है INT_MAX, तो (RAND_MAX + 1)निश्चित रूप से काम नहीं करेगा; इसलिए मार्क की गणना सबसे सुरक्षित कार्यान्वयन है।
स्लिप डी। थॉम्पसन

बहुत अच्छा जवाब!
सयाली सोनावने

मैं नाइटपैकिंग हो सकता हूं, लेकिन अगर लक्ष्य व्यर्थ बिट्स को कम करना है तो हम इसे किनारे की स्थिति के लिए थोड़ा सुधार सकते हैं जहां RAND_MAX (RM) एन द्वारा समान रूप से विभाज्य होने से केवल 1 कम है। इस परिदृश्य में, किसी भी बिट्स को बर्बाद होने की आवश्यकता नहीं है X> करना = ((RM - RM% N)) जो कि N के छोटे मूल्यों के लिए कम मूल्य का है, लेकिन N के बड़े मूल्यों के लिए बड़े मूल्य का हो जाता है। जैसा कि स्लिप डी। थॉम्पसन द्वारा उल्लेख किया गया है, एक समाधान है जो केवल काम करेगा जब INT_MAX (IM)> RAND_MAX लेकिन टूट जाता है जब वे समान होते हैं। हालाँकि, इसके लिए एक सरल उपाय है कि हम गणना में संशोधन कर सकते हैं एक्स> = (आरएम - आरएम% एन) निम्नानुसार हैं:
बेन पर्सिक

एक्स> = आरएम - (((आरएम% एन) + 1)% एन)
बेन कार्मिक

मैंने एक अतिरिक्त उत्तर पोस्ट किया जिसमें समस्या को विस्तार से समझाया गया और उदाहरण कोड समाधान दिया गया।
बेन कार्मिक

36

एक यादृच्छिक का चयन करना पूर्वाग्रह को दूर करने का एक अच्छा तरीका है।

अपडेट करें

यदि हम किसी श्रेणी के x को विभाज्य से खोजते हैं तो हम कोड को तेजी से बना सकते हैं n

// Assumptions
// rand() in [0, RAND_MAX]
// n in (0, RAND_MAX]

int x; 

// Keep searching for an x in a range divisible by n 
do {
    x = rand();
} while (x >= RAND_MAX - (RAND_MAX % n)) 

x %= n;

उपरोक्त लूप बहुत तेज होना चाहिए, औसतन 1 पुनरावृत्ति कहें।


2
Yuck :-P एक डबल में कनवर्ट करना, फिर MAX_UPPER_LIMIT / RAND_MAX द्वारा गुणा करना अधिक क्लीनर है और बेहतर प्रदर्शन करता है।
बॉयसि

22
@ लोबिया: आप इस बिंदु से चूक गए हैं। यदि वे मान जो rand()वापस आ सकते हैं n, उनमें से एक भी नहीं है , तो आप जो भी करते हैं, आप अनिवार्य रूप से 'मॉडुलो बायस' प्राप्त करेंगे, जब तक कि आप उन कुछ मूल्यों को नहीं छोड़ते। user1413793 बताते हैं कि अच्छी तरह से (हालांकि उस जवाब में प्रस्तावित समाधान वास्तव में भाग्यशाली है)।
टोनीके

4
@ मेरे क्षमा याचना, मैं इस बिंदु को याद किया। पर्याप्त कठिन नहीं सोचा था, और सोचा कि पूर्वाग्रह केवल एक स्पष्ट मापांक ऑपरेशन का उपयोग करने वाले तरीकों के साथ लागू होगा। मुझे ठीक करने के लिए धन्यवाद :-)
ब्वॉयसी

ऑपरेटर पूर्वता RAND_MAX+1 - (RAND_MAX+1) % nकाम को सही ढंग से करता है, लेकिन मुझे अभी भी लगता है कि इसे RAND_MAX+1 - ((RAND_MAX+1) % n)स्पष्टता के लिए लिखा जाना चाहिए ।
लाइनस आरवर

4
यह काम नहीं करेगा RAND_MAX == INT_MAX (जैसा कि यह अधिकांश सिस्टम पर होता है) । मेरी दूसरी टिप्पणी @ user1413793 से ऊपर देखें।
ब्लूराजा -

19

@ user1413793 समस्या के बारे में सही है। मैं इस बात पर चर्चा नहीं करने जा रहा हूं कि एक बिंदु को छोड़कर: हाँ, के छोटे मूल्यों nऔर बड़े मूल्यों के लिए RAND_MAX, मोडुलो पूर्वाग्रह बहुत छोटा हो सकता है। लेकिन पूर्वाग्रह-उत्प्रेरण पैटर्न का उपयोग करने का अर्थ है कि हर बार जब आप एक यादृच्छिक संख्या की गणना करते हैं और विभिन्न मामलों के लिए विभिन्न पैटर्न चुनते हैं तो आपको पूर्वाग्रह पर विचार करना चाहिए। और अगर आप गलत विकल्प बनाते हैं, तो जो कीड़े इसे पेश करते हैं, वे सूक्ष्म और लगभग असंभव हैं इकाई परीक्षण। केवल उचित उपकरण का उपयोग करने की तुलना में (जैसे किarc4random_uniform ) , यह अतिरिक्त काम है, कम काम नहीं है। अधिक काम करना और एक बुरा समाधान प्राप्त करना भयानक इंजीनियरिंग है, खासकर जब हर बार सही करना ज्यादातर प्लेटफार्मों पर आसान होता है।

दुर्भाग्य से, समाधान के कार्यान्वयन सभी गलत या कम कुशल हैं जितना उन्हें होना चाहिए। (प्रत्येक समाधान में समस्याओं की व्याख्या करने वाली विभिन्न टिप्पणियाँ हैं, लेकिन उनमें से किसी भी समाधान को संबोधित करने के लिए निश्चित नहीं किया गया है।) यह आकस्मिक उत्तर देने वाले को भ्रमित करने की संभावना है, इसलिए मैं यहां एक ज्ञात-अच्छा कार्यान्वयन प्रदान कर रहा हूं।

फिर से, सबसे अच्छा समाधान सिर्फ उन arc4random_uniformप्लेटफार्मों पर उपयोग करना है जो इसे प्रदान करते हैं, या आपके प्लेटफॉर्म के लिए एक समान रंगा हुआ समाधान (जैसे Random.nextIntजावा पर)। यह आप के लिए कोई कोड कीमत पर सही काम करेंगे। यह लगभग हमेशा सही कॉल करने के लिए है।

यदि आपके पास नहीं है arc4random_uniform, तो आप ओपनसोर्स की शक्ति का उपयोग करके देख सकते हैं कि यह एक व्यापक श्रेणी के आरएनजी के शीर्ष पर कैसे लागू किया जाता है ( ar4randomइस मामले में, लेकिन एक समान दृष्टिकोण अन्य आरएनजी के शीर्ष पर भी काम कर सकता है)।

यहाँ OpenBSD कार्यान्वयन है :

/*
 * Calculate a uniformly distributed random number less than upper_bound
 * avoiding "modulo bias".
 *
 * Uniformity is achieved by generating new random numbers until the one
 * returned is outside the range [0, 2**32 % upper_bound).  This
 * guarantees the selected random number will be inside
 * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
 * after reduction modulo upper_bound.
 */
u_int32_t
arc4random_uniform(u_int32_t upper_bound)
{
    u_int32_t r, min;

    if (upper_bound < 2)
        return 0;

    /* 2**32 % x == (2**32 - x) % x */
    min = -upper_bound % upper_bound;

    /*
     * This could theoretically loop forever but each retry has
     * p > 0.5 (worst case, usually far better) of selecting a
     * number inside the range we need, so it should rarely need
     * to re-roll.
     */
    for (;;) {
        r = arc4random();
        if (r >= min)
            break;
    }

    return r % upper_bound;
}

उन लोगों के लिए इस कोड पर नवीनतम प्रतिबद्ध टिप्पणी ध्यान देने योग्य है जिन्हें समान चीजों को लागू करने की आवश्यकता है:

के 2**32 % upper_boundरूप में गणना करने के लिए arc4random_uniform () बदलें -upper_bound % upper_bound। कोड को सरल करता है और इसे ILP32 और LP64 दोनों आर्किटेक्चर पर समान बनाता है, और 64-बिट शेष के बजाय 32-बिट शेष का उपयोग करके LP64 आर्किटेक्चर पर भी थोड़ा तेज होता है।

टेक @ ओके डेराड्ट पर जोर्डन वेरवर द्वारा इंगित; djm या otto से कोई आपत्ति नहीं

जावा कार्यान्वयन भी आसानी से पता लगाने योग्य है (पिछले लिंक देखें):

public int nextInt(int n) {
   if (n <= 0)
     throw new IllegalArgumentException("n must be positive");

   if ((n & -n) == n)  // i.e., n is a power of 2
     return (int)((n * (long)next(31)) >> 31);

   int bits, val;
   do {
       bits = next(31);
       val = bits % n;
   } while (bits - val + (n-1) < 0);
   return val;
 }

ध्यान दें कि यदि arcfour_random() वास्तव में इसके कार्यान्वयन में वास्तविक RC4 एल्गोरिदम का उपयोग किया जाता है, तो आउटपुट में निश्चित रूप से कुछ पूर्वाग्रह होंगे। उम्मीद है कि आपके लाइब्रेरी लेखकों ने उसी इंटरफ़ेस के पीछे एक बेहतर CSPRNG का उपयोग करने के लिए स्विच किया है। मुझे याद है कि बीएसडी में से एक अब वास्तव में लागू करने के लिए ChaCha20 एल्गोरिथ्म का उपयोग करता है arcfour_random()। RC4 आउटपुट बायसेज़
rmalayter

2
iOS और OS X पर @rmalayter, आर्क 4 आयामी / देव / यादृच्छिक से पढ़ता है जो सिस्टम में उच्चतम गुणवत्ता वाली एंट्रॉपी है। (नाम में "आर्क 4" ऐतिहासिक और संगतता के लिए संरक्षित है।)
रोब नेपियर

@Rob_Napier पता करने के लिए अच्छा है, लेकिन /dev/randomअतीत में कुछ प्लेटफार्मों पर RC4 का उपयोग किया है (लिनक्स काउंटर मोड में SHA-1 का उपयोग करता है)। दुर्भाग्य से खोज के माध्यम से मुझे जो मैन पेज मिले, वे बताते हैं कि RC4 अभी भी विभिन्न प्लेटफार्मों पर उपयोग में है जो ऑफ़र करते हैं arc4random(हालांकि वास्तविक कोड अलग हो सकता है)।
ralayter

1
मैं उलझन में हूं। नहीं है -upper_bound % upper_bound == 0??
जॉन मैकक्लब

1
@JonMcClung -upper_bound % upper_boundवास्तव में 0 होगा यदि int32-बिट्स से अधिक चौड़ा हो। यह होना चाहिए (u_int32_t)-upper_bound % upper_bound)(संभालने के u_int32_tलिए एक BSD-ism है uint32_t)।
इयान एबॉट

14

परिभाषा

मोडुलो बायस इनपुट सेट के सबसेट पर सेट आउटपुट को कम करने के लिए मोडुलो अंकगणितीय का उपयोग करने में निहित पूर्वाग्रह है। सामान्य तौर पर, एक पूर्वाग्रह मौजूद होता है जब भी इनपुट और आउटपुट सेट के बीच मैपिंग समान रूप से वितरित नहीं की जाती है, जैसा कि आउटपुट सेट के आकार के विभाजक नहीं होने पर मोडुलो अंकगणितीय का उपयोग करने के मामले में।

यह पूर्वाग्रह कंप्यूटिंग में बचने के लिए विशेष रूप से कठिन है, जहां संख्याओं को बिट्स के तारों के रूप में दर्शाया जाता है: 0 एस और 1 एस। वास्तव में यादृच्छिकता के यादृच्छिक स्रोतों को खोजना भी बेहद मुश्किल है, लेकिन इस चर्चा के दायरे से परे है। इस उत्तर के शेष के लिए, मान लें कि वास्तव में यादृच्छिक बिट्स का असीमित स्रोत मौजूद है।

समस्या का उदाहरण

आइए इन यादृच्छिक बिट्स का उपयोग करके एक डाई रोल (0 से 5) का अनुकरण करने पर विचार करें। 6 संभावनाएं हैं, इसलिए हमें संख्या 6 का प्रतिनिधित्व करने के लिए पर्याप्त बिट्स की आवश्यकता है, जो 3 बिट्स है। दुर्भाग्य से, 3 यादृच्छिक बिट्स से 8 संभावित परिणाम मिलते हैं:

000 = 0, 001 = 1, 010 = 2, 011 = 3
100 = 4, 101 = 5, 110 = 6, 111 = 7

हम मान modulo 6 को लेते हुए सेट किए गए परिणाम के आकार को ठीक 6 तक कम कर सकते हैं, हालांकि यह modulo bias समस्या प्रस्तुत करता है: 110एक 0 111पैदावार , और 1 पैदावार। यह मर जाता है भरी हुई है।

संभावित समाधान

दृष्टिकोण 0:

यादृच्छिक बिट्स पर भरोसा करने के बजाय, सिद्धांत रूप में एक पूरे दिन पासा को रोल करने और एक डेटाबेस में परिणाम रिकॉर्ड करने के लिए एक छोटी सेना को रख सकता है, और फिर केवल एक बार प्रत्येक परिणाम का उपयोग कर सकता है। यह उतना ही व्यावहारिक है जितना कि यह लगता है, और संभावना से अधिक वास्तव में यादृच्छिक परिणाम नहीं देगा (सजा का उद्देश्य)।

दृष्टिकोण 1:

इसके बजाय मापांक का उपयोग कर के, एक अनुभवहीन लेकिन गणितीय सही समाधान है कि उपज छोड़ें परिणाम के लिए है 110और 111और केवल 3 नए बिट्स के साथ पुन: प्रयास करें। दुर्भाग्य से, इसका मतलब है कि प्रत्येक रोल पर 25% संभावना है कि एक पुन: रोल की आवश्यकता होगी, जिसमें से प्रत्येक रोल स्वयं भी शामिल है। यह स्पष्ट रूप से सभी के लिए अव्यावहारिक है लेकिन उपयोगों का सबसे तुच्छ है।

दृष्टिकोण 2:

अधिक बिट्स का उपयोग करें: 3 बिट्स के बजाय, 4 का उपयोग करें। यह 16 संभावित परिणाम देता है। बेशक, रि-रोलिंग कभी भी परिणाम 5 से अधिक होने से चीजें खराब हो जाती हैं (10/16 = 62.5%) ताकि अकेले मदद नहीं करेगा।

ध्यान दें कि 2 * 6 = 12 <16, इसलिए हम सुरक्षित रूप से 12 से कम किसी भी परिणाम को ले सकते हैं और परिणामों को समान रूप से वितरित करने के लिए उस modulo 6 को कम कर सकते हैं। अन्य 4 परिणामों को छोड़ दिया जाना चाहिए, और फिर पिछले दृष्टिकोण की तरह फिर से लुढ़क जाना चाहिए।

पहली बार में अच्छा लगता है, लेकिन चलो गणित की जाँच करें:

4 discarded results / 16 possibilities = 25%

इस मामले में, 1 अतिरिक्त बिट ने बिल्कुल भी मदद नहीं की !

यह परिणाम दुर्भाग्यपूर्ण है, लेकिन आइए 5 बिट्स के साथ फिर से प्रयास करें:

32 % 6 = 2 discarded results; and
2 discarded results / 32 possibilities = 6.25%

एक निश्चित सुधार, लेकिन बहुत से व्यावहारिक मामलों में अच्छा नहीं। अच्छी खबर यह है कि अधिक बिट्स जोड़ने से त्यागने और फिर से रोल करने की आवश्यकता की संभावना कभी नहीं बढ़ेगी । यह न केवल पासा के लिए है, बल्कि सभी मामलों में है।

जैसा कि प्रदर्शित किया गया है , लेकिन 1 अतिरिक्त बिट जोड़ने से कुछ भी नहीं बदल सकता है। वास्तव में अगर हम अपने रोल को 6 बिट तक बढ़ाते हैं, तो संभावना 6.25% रह जाती है।

यह 2 अतिरिक्त प्रश्न बताता है:

  1. यदि हम पर्याप्त बिट्स जोड़ते हैं, तो क्या गारंटी है कि एक त्याग की संभावना कम हो जाएगी?
  2. सामान्य मामले में कितने बिट्स पर्याप्त हैं ?

सामान्य समाधान

शुक्र है कि पहले प्रश्न का उत्तर हां में है। 6 के साथ समस्या यह है कि 2 ^ x मॉड 6 2 और 4 के बीच फ़्लिप करता है, जो संयोग से एक दूसरे से 2 के कई हैं, ताकि सम x> 1 के लिए भी।

[2^x mod 6] / 2^x == [2^(x+1) mod 6] / 2^(x+1)

इस प्रकार 6 नियम के बजाय एक अपवाद है। यह संभव है कि बड़े तौर-तरीकों से 2 की लगातार शक्तियां प्राप्त की जा सकें, लेकिन अंत में इसे चारों ओर से लपेटना चाहिए, और एक त्याग की संभावना कम हो जाएगी।

आगे के सबूत की पेशकश के बिना, सामान्य रूप से दोगुने बिट्स का उपयोग करके आवश्यक रूप से एक छोटा, आमतौर पर नगण्य, एक हार का मौका प्रदान करेगा।

अवधारणा के सुबूत

यहां एक उदाहरण कार्यक्रम है जो यादृच्छिक बाइट्स की आपूर्ति करने के लिए ओपनएसएसएल के लिबासरीपो का उपयोग करता है। संकलन करते समय, उस लाइब्रेरी से लिंक करना सुनिश्चित करें, -lcryptoजिसके साथ सभी को उपलब्ध होना चाहिए।

#include <iostream>
#include <assert.h>
#include <limits>
#include <openssl/rand.h>

volatile uint32_t dummy;
uint64_t discardCount;

uint32_t uniformRandomUint32(uint32_t upperBound)
{
    assert(RAND_status() == 1);
    uint64_t discard = (std::numeric_limits<uint64_t>::max() - upperBound) % upperBound;
    uint64_t randomPool = RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));

    while(randomPool > (std::numeric_limits<uint64_t>::max() - discard)) {
        RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));
        ++discardCount;
    }

    return randomPool % upperBound;
}

int main() {
    discardCount = 0;

    const uint32_t MODULUS = (1ul << 31)-1;
    const uint32_t ROLLS = 10000000;

    for(uint32_t i = 0; i < ROLLS; ++i) {
        dummy = uniformRandomUint32(MODULUS);
    }
    std::cout << "Discard count = " << discardCount << std::endl;
}

मैं यह देखने के लिए MODULUSऔर ROLLSमूल्यों के साथ खेलने को प्रोत्साहित करता हूं कि वास्तव में अधिकांश परिस्थितियों में कितने रोल फिर से होते हैं। एक संशयवादी व्यक्ति फ़ाइल को दर्ज करने के लिए गणना किए गए मूल्यों को सहेजने और वितरण सामान्य होने की पुष्टि कर सकता है।


मैं वास्तव में आशा करता हूं कि किसी ने भी नेत्रहीन रूप से आपके समान यादृच्छिक कार्यान्वयन की नकल नहीं की होगी। randomPool = RAND_bytes(...)लाइन हमेशा में परिणाम होगा randomPool == 1दावे की वजह से। यह हमेशा एक त्याग और फिर से रोल में परिणत होता है । मुझे लगता है कि आप एक अलग लाइन पर घोषणा करना चाहते थे। नतीजतन, इसके कारण RNG को 1हर पुनरावृत्ति के लिए वापस लौटना पड़ा ।
Qix - मोनिका ने ३२

स्पष्ट होने के लिए, OpenSSL प्रलेखन के अनुसार randomPoolहमेशा मूल्यांकन करेंगे क्योंकि यह हमेशा दावे के लिए धन्यवाद सफल होगा । 1RAND_bytes()RAND_status()
Qix - मोनिका ने

9

मोडुलो के उपयोग के साथ दो सामान्य शिकायतें हैं।

  • सभी जनरेटर के लिए एक वैध है। एक सीमा मामले में देखना आसान है। यदि आपके जनरेटर में एक RAND_MAX है जो 2 है (जो कि C मानक के अनुरूप नहीं है) और आप केवल 0 या 1 को मान के रूप में चाहते हैं, modulo का उपयोग करते हुए अक्सर दो बार 0 उत्पन्न करेगा (जब जनरेटर 0 और 2 उत्पन्न करता है) जैसा कि यह होगा उत्पन्न 1 (जब जनरेटर 1 उत्पन्न करता है)। ध्यान दें कि यह सच है जैसे ही आप मूल्यों को नहीं छोड़ते हैं, जो भी आप जनरेटर मूल्यों से वांछित एक का उपयोग कर रहे हैं, एक दूसरे के रूप में अक्सर दो बार होता है।

  • कुछ प्रकार के जनरेटर में उनके कम महत्वपूर्ण बिट्स दूसरे की तुलना में कम यादृच्छिक होते हैं, कम से कम उनके कुछ मापदंडों के लिए, लेकिन दुख की बात है कि उन मापदंडों में अन्य दिलचस्प विशेषता हैं (जैसे कि RAND_MAX को 2 की शक्ति से कम करने में सक्षम है)। समस्या अच्छी तरह से जानी जाती है और लंबे समय तक पुस्तकालय कार्यान्वयन संभवतः समस्या से बचने के लिए (उदाहरण के लिए नमूना रैंड) सी मानक में इस तरह के जनरेटर का उपयोग करते हैं, लेकिन 16 कम महत्वपूर्ण बिट्स को छोड़ देते हैं), लेकिन कुछ इस बारे में शिकायत करना पसंद करते हैं वह और आपकी किस्मत खराब हो सकती है

जैसे कुछ का उपयोग करना

int alea(int n){ 
 assert (0 < n && n <= RAND_MAX); 
 int partSize = 
      n == RAND_MAX ? 1 : 1 + (RAND_MAX-n)/(n+1); 
 int maxUsefull = partSize * n + (partSize-1); 
 int draw; 
 do { 
   draw = rand(); 
 } while (draw > maxUsefull); 
 return draw/partSize; 
}

0 और n के बीच एक यादृच्छिक संख्या उत्पन्न करने के लिए दोनों समस्याओं से बचना होगा (और यह RAND_MAX == INTMM के साथ अतिप्रवाह से बचा जाता है)

BTW, C ++ 11 ने रैंड () की तुलना में कमी और अन्य जनरेटर के लिए मानक तरीके पेश किए।


n == RAND_MAX? 1: (RAND_MAX-1) / (n + 1): मुझे समझ में आया कि यहाँ RAND_MAX को समान पृष्ठ आकार N में विभाजित करना है, फिर N के भीतर विचलन लौटाएं, लेकिन मैं इस कोड को ठीक से मैप नहीं कर सकता।
zinking

1
भोली संस्करण होना चाहिए (RAND_MAX + 1) / (n + 1) क्योंकि N + 1 बाल्टियों में विभाजित करने के लिए RAND_MAX + 1 मान हैं। यदि RAND_MAX + 1 की गणना करते समय अतिप्रवाह से बचने के लिए, इसे 1+ (RAND_MAX-n) / (n + 1) में बदला जा सकता है। N + 1 की गणना करते समय ओवरफ्लो से बचने के लिए, केस n == RAND_MAX को पहले चेक किया जाता है।
एपीग्रामग्राम

+ इसके अलावा, विभाजित करना पुनर्जनन संख्याओं की तुलना में अधिक महंगा लगता है।
zinking

4
मोडुलो लेने और विभाजित करने में समान लागत होती है। कुछ ISA केवल एक निर्देश भी प्रदान करते हैं जो हमेशा दोनों प्रदान करते हैं। पुनर्जनन की लागत n और RAND_MAX पर निर्भर करेगी। यदि n RAND_MAX के संबंध में छोटा है, तो इसमें बहुत खर्च हो सकता है। और स्पष्ट रूप से आप यह तय कर सकते हैं कि पक्षपात आपके आवेदन के लिए महत्वपूर्ण नहीं है; मैं सिर्फ उनसे बचने का एक तरीका देता हूं।
एपीग्रामग्राम

9

मार्क का समाधान (स्वीकृत समाधान) लगभग पूर्ण है।

int x;

do {
    x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));

x %= n;

संपादित करें 25 मार्च 16 को 23:16 पर

मार्क अमेरी 39k21170211

हालाँकि, यह एक चेतावनी है जो किसी भी परिदृश्य में परिणामों के 1 वैध सेट को छोड़ देता है, जहां RAND_MAX( RM) बहुविध N(जहाँ N= संभव वैध परिणामों की संख्या ) से कई गुना कम है।

यानी, जब 'मानों की गणना छोड़ दी गई' ( D) के बराबर है N, तो वे वास्तव में एक वैध सेट ( V), एक अवैध सेट नहीं है! I)।

क्या इस का कारण बनता है कुछ बिंदु मार्क के बीच अंतर की दृष्टि खो देता है Nऔर Rand_Max

Nएक ऐसा सेट है, जिसके वैध सदस्य केवल पॉजिटिव इंटेगर शामिल होते हैं, क्योंकि इसमें उन प्रतिक्रियाओं की गिनती होती है जो मान्य होगी। (उदाहरण: सेट N= {1, 2, 3, ... n })

Rand_max हालाँकि, एक सेट है जो (हमारे उद्देश्यों के लिए परिभाषित किया गया है) में किसी भी प्रकार के गैर-नकारात्मक पूर्णांक शामिल हैं।

यह सबसे सामान्य रूप में है, जो यहां परिभाषित किया गया Rand Maxहै वह सभी मान्य परिणामों का सेट है, जिसमें सैद्धांतिक रूप से नकारात्मक संख्या या गैर-संख्यात्मक मान शामिल हो सकते हैं।

इसलिए Rand_Maxबेहतर "संभावित प्रतिक्रियाओं" के सेट के रूप में परिभाषित किया गया है।

हालांकि Nमान्य प्रतिक्रियाओं के सेट के भीतर मूल्यों की गिनती के खिलाफ काम करता है, इसलिए यहां तक ​​कि हमारे विशिष्ट मामले में परिभाषित होने के बावजूद, Rand_Maxइसमें शामिल कुल संख्या की तुलना में एक मूल्य कम होगा।

मार्क के समाधान का उपयोग करते हुए, मानों को छोड़ दिया जाता है जब: X => RM - RM% N

EG: 

Ran Max Value (RM) = 255
Valid Outcome (N) = 4

When X => 252, Discarded values for X are: 252, 253, 254, 255

So, if Random Value Selected (X) = {252, 253, 254, 255}

Number of discarded Values (I) = RM % N + 1 == N

 IE:

 I = RM % N + 1
 I = 255 % 4 + 1
 I = 3 + 1
 I = 4

   X => ( RM - RM % N )
 255 => (255 - 255 % 4) 
 255 => (255 - 3)
 255 => (252)

 Discard Returns $True

जैसा कि आप ऊपर के उदाहरण में देख सकते हैं, जब X का मान (प्रारंभिक फ़ंक्शन से प्राप्त यादृच्छिक संख्या) 252, 253, 254, या 255 है, तो हम इसे त्याग देंगे भले ही इन चार मूल्यों में लौटे मूल्यों का एक वैध सेट शामिल हो। ।

IE: जब मानों की गणना को त्याग दिया गया (I) = N (वैध परिणामों की संख्या) तो रिटर्न वैल्यूज़ का एक वैध सेट मूल फ़ंक्शन द्वारा छोड़ दिया जाएगा।

यदि हम N और RM के बीच के अंतर को D के रूप में वर्णित करते हैं, अर्थात:

D = (RM - N)

फिर जैसे-जैसे D का मान छोटा होता जाता है, इस पद्धति के कारण अनावश्यक री-रोल का प्रतिशत प्रत्येक प्राकृतिक गुणक में बढ़ता जाता है। (जब RAND_MAX एक अभाज्य संख्या के बराबर नहीं है तो यह वैध चिंता का विषय है)

ईजी:

RM=255 , N=2 Then: D = 253, Lost percentage = 0.78125%

RM=255 , N=4 Then: D = 251, Lost percentage = 1.5625%
RM=255 , N=8 Then: D = 247, Lost percentage = 3.125%
RM=255 , N=16 Then: D = 239, Lost percentage = 6.25%
RM=255 , N=32 Then: D = 223, Lost percentage = 12.5%
RM=255 , N=64 Then: D = 191, Lost percentage = 25%
RM=255 , N= 128 Then D = 127, Lost percentage = 50%

चूँकि Rerolls के प्रतिशत में वृद्धि होती है, N के RM के करीब आता है, यह कई अलग-अलग मूल्यों पर वैध चिंता का विषय हो सकता है, क्योंकि वह उस कोड को चलाने वाले सिस्टम की बाधाओं और मूल्यों की तलाश में होता है।

इसे नकारने के लिए हम एक सरल संशोधन कर सकते हैं जैसा कि यहाँ दिखाया गया है:

 int x;

 do {
     x = rand();
 } while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );

 x %= n;

यह सूत्र का एक अधिक सामान्य संस्करण प्रदान करता है जो आपके अधिकतम मूल्यों को परिभाषित करने के लिए मापांक का उपयोग करने की अतिरिक्त विशिष्टताओं के लिए खाता है।

RAND_MAX के लिए एक छोटे मूल्य का उपयोग करने के उदाहरण जो कि एन का गुणक है।

Mark'original संस्करण:

RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X >= (RAND_MAX - ( RAND_MAX % n ) )
When X >= 2 the value will be discarded, even though the set is valid.

सामान्यीकृत संस्करण 1:

RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X > (RAND_MAX - ( ( RAND_MAX % n  ) + 1 ) % n )
When X > 3 the value would be discarded, but this is not a vlue in the set RAND_MAX so there will be no discard.

इसके अतिरिक्त, उस स्थिति में जहां N को RAND_MAX में मानों की संख्या होनी चाहिए; इस स्थिति में, आप N = RAND_MAX +1 सेट कर सकते हैं, जब तक कि RAND_MAX = INT_MAX।

लूप-वार आप केवल एन = 1 का उपयोग कर सकते हैं, और एक्स के किसी भी मूल्य को स्वीकार किया जाएगा, हालांकि, और अपने अंतिम गुणक के लिए एक IF स्टेटमेंट डाल सकते हैं। लेकिन शायद आपके पास कोड है जो फ़ंक्शन 1 को n = 1 के साथ कॉल करने पर एक वैध कारण हो सकता है ...

तो 0 का उपयोग करना बेहतर हो सकता है, जो सामान्य रूप से एक डिव त्रुटि प्रदान करेगा, जब आप n = RAND_MAX 1 चाहते हैं

सामान्यीकृत संस्करण 2:

int x;

if n != 0 {
    do {
        x = rand();
    } while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );

    x %= n;
} else {
    x = rand();
}

इन दोनों समाधानों ने समस्या का समाधान अनावश्यक रूप से खारिज किए गए वैध परिणामों के साथ किया है जो तब होगा जब आरएम + 1 एन का एक उत्पाद है।

जब आप RAND_MAX में निहित मानों के कुल संभव सेट के बराबर n की आवश्यकता होती है, तो दूसरा संस्करण भी एज केस परिदृश्य को कवर करता है।

दोनों में संशोधित दृष्टिकोण समान है और मान्य यादृच्छिक संख्या प्रदान करने और त्याग किए गए मूल्यों को कम करने की आवश्यकता के लिए एक अधिक सामान्य समाधान की अनुमति देता है।

बार बार कहना:

बेसिक जनरल सॉल्यूशन जो निशान के उदाहरण का विस्तार करता है:

// Assumes:
//  RAND_MAX is a globally defined constant, returned from the environment.
//  int n; // User input, or externally defined, number of valid choices.

 int x;

 do {
     x = rand();
 } while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );

 x %= n;

विस्तारित सामान्य समाधान जो RAND_MAX + 1 = n के एक अतिरिक्त परिदृश्य की अनुमति देता है:

// Assumes:
//  RAND_MAX is a globally defined constant, returned from the environment.
//  int n; // User input, or externally defined, number of valid choices.

int x;

if n != 0 {
    do {
        x = rand();
    } while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );

    x %= n;
} else {
    x = rand();
}

कुछ भाषाओं में (विशेष रूप से व्याख्या की गई भाषाओं में) समतुल्य स्थिति के बाहर तुलना-संचालन की गणना करते हुए तेजी से परिणाम प्राप्त हो सकते हैं क्योंकि यह एक बार की गणना है, चाहे कितने भी प्रयास की आवश्यकता हो। YMMV!

// Assumes:
//  RAND_MAX is a globally defined constant, returned from the environment.
//  int n; // User input, or externally defined, number of valid choices.

int x; // Resulting random number
int y; // One-time calculation of the compare value for x

if n != 0 {
    y = RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) 
    do {
        x = rand();
    } while (x > y);

    x %= n;
} else {
    x = rand();
}

क्या यह कहना सुरक्षित नहीं है कि मार्क के समाधान के साथ समस्या यह है कि वह RAND_MAX और n को "माप की इकाई" के रूप में मानते हैं जब वास्तव में उनका मतलब दो अलग-अलग चीजों से होता है? जबकि n परिणामी "संभावनाओं की संख्या" का प्रतिनिधित्व करता है, RAND_MAX केवल मूल संभावना के अधिकतम मूल्य का प्रतिनिधित्व करता है, जहां RAND_MAX + 1 संभावनाओं की मूल संख्या होगी। मुझे आश्चर्य है कि वह आपके निष्कर्ष पर नहीं पहुंचा क्योंकि उसने ऐसा माना था कि n और RAND_MAX समीकरण के साथ एक ही बात नहीं थी:RAND_MAX%n = n - 1
Danilo Souza Morães

@ DaniloSouzaMorães धन्यवाद डैनिलो, आपने मामले को बहुत गंभीरता से रखा है। मैं यह दिखाने के लिए गया कि वह क्यों और कैसे के साथ क्या कर रहा था, लेकिन मुझे नहीं लगता कि मैं कभी भी यह बता पा रहा था कि वह गलत तरीके से क्या कर रहा था, क्योंकि मैं तर्क के विवरण में लिपटा हुआ हूं कि कैसे और कैसे एक मुद्दा क्यों है, कि मैं इस मुद्दे पर स्पष्ट रूप से नहीं बता रहा हूं। क्या आपको बुरा लगता है अगर मैं अपने उत्तर में से कुछ का उपयोग करने के लिए अपने उत्तर में संशोधन करता हूं, तो इस मुद्दे के बारे में मेरे अपने सारांश के रूप में क्या और जहां स्वीकृत समाधान वह कर रहा है जिसे शीर्ष के पास संबोधित करने की आवश्यकता है?
बेन पर्सिक

वह तो जबर्दस्त होगा। इसके लिए जाओ
Danilo Souza Morães

1

एक साथ RAND_MAXका मूल्य 3(वास्तविकता में यह है कि तुलना में बहुत अधिक होना चाहिए लेकिन पूर्वाग्रह अभी भी मौजूद हैं) यह इन गणनाओं एक पूर्वाग्रह है कि वहाँ से समझ में आता है:

1 % 2 = 1 2 % 2 = 0 3 % 2 = 1 random_between(1, 3) % 2 = more likely a 1

इस स्थिति में, % 2आप के बीच एक यादृच्छिक संख्या चाहते हैं 0और क्या नहीं है 1। आप इस बीच 0और इसके 2द्वारा एक यादृच्छिक संख्या प्राप्त कर सकते हैं % 3, क्योंकि इस मामले में: RAND_MAXएक से अधिक है 3

एक और तरीका

बहुत सरल है, लेकिन अन्य उत्तरों को जोड़ने के लिए, यहाँ मेरे समाधान के बीच एक यादृच्छिक संख्या प्राप्त करने के लिए समाधान है , 0और n - 1इसलिए nविभिन्न संभावनाएं, बिना पूर्वाग्रह के।

  • संभावनाओं की संख्या को एनकोड करने के लिए आवश्यक बिट्स (बाइट्स) की संख्या यादृच्छिक डेटा के बिट्स की संख्या है जिसकी आपको आवश्यकता होगी
  • यादृच्छिक बिट्स से संख्या सांकेतिक शब्दों में बदलना
  • यदि यह संख्या है >= n, तो पुनरारंभ करें (कोई मोडुलो)।

वास्तव में यादृच्छिक डेटा प्राप्त करना आसान नहीं है, इसलिए आवश्यकता से अधिक बिट्स का उपयोग क्यों करें।

एक छद्म यादृच्छिक संख्या जनरेटर से बिट्स के कैश का उपयोग करके, स्मॉलटाक में एक उदाहरण दिया गया है। मैं कोई सुरक्षा विशेषज्ञ नहीं हूं इसलिए अपने जोखिम पर उपयोग करें।

next: n

    | bitSize r from to |
    n < 0 ifTrue: [^0 - (self next: 0 - n)].
    n = 0 ifTrue: [^nil].
    n = 1 ifTrue: [^0].
    cache isNil ifTrue: [cache := OrderedCollection new].
    cache size < (self randmax highBit) ifTrue: [
        Security.DSSRandom default next asByteArray do: [ :byte |
            (1 to: 8) do: [ :i |    cache add: (byte bitAt: i)]
        ]
    ].
    r := 0.
    bitSize := n highBit.
    to := cache size.
    from := to - bitSize + 1.
    (from to: to) do: [ :i |
        r := r bitAt: i - from + 1 put: (cache at: i)
    ].
    cache removeFrom: from to: to.
    r >= n ifTrue: [^self next: n].
    ^r

-1

जैसा कि स्वीकृत उत्तर बताता है, "modulo bias" की जड़ें कम मूल्य में हैं RAND_MAX। वह RAND_MAX(10) के एक बहुत छोटे मूल्य का उपयोग यह दिखाने के लिए करता है कि यदि RAND_MAX 10 था, तो आपने% का उपयोग करके 0 और 2 के बीच एक संख्या उत्पन्न करने की कोशिश की, निम्नलिखित परिणाम परिणाम होंगे:

rand() % 3   // if RAND_MAX were only 10, gives
output of rand()   |   rand()%3
0                  |   0
1                  |   1
2                  |   2
3                  |   0
4                  |   1
5                  |   2
6                  |   0
7                  |   1
8                  |   2
9                  |   0

तो 0 के 4 आउटपुट (4/10 मौका) और 1 और 2 के केवल 3 आउटपुट (प्रत्येक 3/10 संभावनाएं) हैं।

तो यह पक्षपातपूर्ण है। कम संख्या में बाहर आने का बेहतर मौका है।

लेकिन RAND_MAXयह छोटा होने पर ही जाहिर होता है । या अधिक विशेष रूप से, जब आपके द्वारा modding संख्या बड़ी है की तुलना मेंRAND_MAX

लूपिंग की तुलना में एक बेहतर उपाय (जो पागलपन से अक्षम है और इसका सुझाव भी नहीं दिया जाना चाहिए) एक बहुत बड़े आउटपुट रेंज के साथ एक PRNG का उपयोग करना है। Mersenne ट्विस्टर एल्गोरिथ्म ४२९४९६७२९५ की एक अधिकतम उत्पादन है। जैसा कि MersenneTwister::genrand_int32() % 10सभी इरादों और उद्देश्यों के लिए किया जाता है, समान रूप से वितरित किया जाएगा और modulo पूर्वाग्रह प्रभाव सभी लेकिन गायब हो जाएगा।


3
तुम्हारा अधिक कुशल है और यह शायद सच है कि अगर RAND_MAX काफी बड़ा है तो आप जिस संख्या को संशोधित कर रहे हैं, हालांकि आपका अभी भी पक्षपाती होगा। दी ये सभी छद्म यादृच्छिक संख्या जनरेटर वैसे भी हैं और अपने आप में एक अलग विषय है, लेकिन यदि आप पूरी तरह से यादृच्छिक संख्या जनरेटर मानते हैं, तो आपका रास्ता अभी भी कम मूल्यों को मिटा देता है।
user1413793

क्योंकि उच्चतम मान विषम है, समय का MT::genrand_int32()%20 (50 + 2.3e-8)% और समय का 1 (50 - 2.3e-8)% है। जब तक आप कैसिनो का RGN (जो शायद आप एक बहुत बड़ी रेंज RGN के लिए उपयोग करते हैं) का निर्माण कर रहे हैं, तब तक कोई भी उपयोगकर्ता अतिरिक्त 2.3e-8% नोटिस नहीं करने वाला है। आप यहां बात करने के लिए बहुत छोटी संख्या के बारे में बात कर रहे हैं।
बॉबोबोबो

7
लूपिंग सबसे अच्छा उपाय है। यह "पागलपन से अक्षम" नहीं है; सबसे खराब औसत मामले में दो बार से कम पुनरावृत्तियों की आवश्यकता होती है। उच्च RAND_MAXमूल्य का उपयोग करने से मोडुलो पूर्वाग्रह में कमी आएगी, लेकिन इसे खत्म नहीं करना चाहिए। लोपिंग करेंगे।
जेरेड नील्सन

5
यदि RAND_MAXआप उस संख्या से पर्याप्त रूप से बड़े हैं, जिसे आप संशोधित कर रहे हैं, तो यादृच्छिक संख्या को पुन: उत्पन्न करने के लिए आपको जितनी बार आवश्यकता होती है, वह गायब हो जाती है और दक्षता को प्रभावित नहीं करेगी। मैं पाशन के सबसे बड़े कई के खिलाफ रहे हैं परीक्षण रखने के लिए, आप के रूप में लंबे समय के रूप कहते हैं कि nबस के बजाय nके रूप में स्वीकार किए जाते हैं जवाब द्वारा प्रस्तावित।
मार्क रैनसम

-3

मैंने सिर्फ वॉन न्यूमैन के निष्पक्ष सिक्का फ्लिप विधि के लिए एक कोड लिखा था, जो कि यादृच्छिक संख्या पीढ़ी प्रक्रिया में किसी भी पूर्वाग्रह को सैद्धांतिक रूप से समाप्त करना चाहिए। अधिक जानकारी ( http://en.wikipedia.org/wiki/Fair_coin ) पर मिल सकती है

int unbiased_random_bit() {    
    int x1, x2, prev;
    prev = 2;
    x1 = rand() % 2;
    x2 = rand() % 2;

    for (;; x1 = rand() % 2, x2 = rand() % 2)
    {
        if (x1 ^ x2)      // 01 -> 1, or 10 -> 0.
        {
            return x2;        
        }
        else if (x1 & x2)
        {
            if (!prev)    // 0011
                return 1;
            else
                prev = 1; // 1111 -> continue, bias unresolved
        }
        else
        {
            if (prev == 1)// 1100
                return 0;
            else          // 0000 -> continue, bias unresolved
                prev = 0;
        }
    }
}

यह मोडुलो पूर्वाग्रह को संबोधित नहीं करता है। इस प्रक्रिया का उपयोग साक्षरता को थोड़ा सा करने के लिए किया जा सकता है। हालाँकि, एक बिट स्ट्रीम से समान वितरण के लिए 0 से n तक जहाँ n दो में से एक से कम नहीं है, को modulo bias को संबोधित करने की आवश्यकता होती है। इस प्रकार यह समाधान यादृच्छिक संख्या पीढ़ी प्रक्रिया में किसी भी पूर्वाग्रह को
रिक

2
@ क्लिक करें हम्म। वॉन न्यूमैन की विधि का तार्किक विस्तार, मोडुलो पूर्वाग्रह को समाप्त करने के लिए, जब, 1 और 100 के बीच एक यादृच्छिक संख्या उत्पन्न होती है, तो यह होगा: ए) rand() % 100100 बार कॉल करता है। बी) यदि सभी परिणाम अलग-अलग हैं, तो पहले एक को लें। C) अन्यथा, GOTO A. यह काम करेगा, लेकिन लगभग 10 ^ 42 की पुनरावृत्तियों की अपेक्षित संख्या के साथ, आपको काफी धैर्य रखना होगा। और अमर है।
मार्क अमेरी

@MarkAmery वास्तव में जो काम करना चाहिए। इस एल्गोरिथ्म को देखते हुए हालांकि इसे सही ढंग से लागू नहीं किया गया है। सबसे पहले होना चाहिए:else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}
रिक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.