ทำไมผู้คนถึงบอกว่ามีโมดูโล่แบบอคติเมื่อใช้ตัวสร้างตัวเลขสุ่ม?


277

ฉันได้เห็นคำถามนี้ถามมาก แต่ไม่เคยเห็นคำตอบที่เป็นรูปธรรมจริง ดังนั้นฉันจะโพสต์หนึ่งที่นี่ซึ่งหวังว่าจะช่วยให้ผู้คนเข้าใจว่าทำไมมี "modulo bias" เมื่อใช้ตัวสร้างตัวเลขสุ่มเช่นrand()ใน C ++

คำตอบ:


394

ดังนั้นrand()เป็นตัวสร้างตัวเลขสุ่มหลอกซึ่งเลือกจำนวนธรรมชาติระหว่าง 0 และRAND_MAXซึ่งเป็นค่าคงที่ที่กำหนดไว้ในcstdlib(ดูบทความนี้สำหรับภาพรวมทั่วไปบนrand())

ตอนนี้จะเกิดอะไรขึ้นถ้าคุณต้องการสร้างตัวเลขสุ่มระหว่างพูด 0 ถึง 2 เพื่อประโยชน์ในการอธิบายให้พูดของRAND_MAX10 และฉันตัดสินใจที่จะสร้างตัวเลขสุ่มระหว่าง 0 และ 2 rand()%3โดยการเรียก อย่างไรก็ตามrand()%3ไม่ได้สร้างตัวเลขระหว่าง 0 ถึง 2 ด้วยความน่าจะเป็นที่เท่ากัน!

เมื่อrand()ผลตอบแทน 0, 3, 6, rand()%3 == 09, ดังนั้น P (0) = 4/11

เมื่อrand()ผลตอบแทนที่ 1, 4, 7, rand()%3 == 110, ดังนั้น P (1) = 4/11

เมื่อrand()ผลตอบแทนที่ 2, 5, rand()%3 == 28, ดังนั้น P (2) = 3/11

สิ่งนี้จะไม่สร้างตัวเลขระหว่าง 0 ถึง 2 ที่มีความน่าจะเป็นเท่ากัน แน่นอนว่าสำหรับช่วงขนาดเล็กนี่อาจไม่ใช่ปัญหาที่ใหญ่ที่สุด แต่สำหรับช่วงที่มีขนาดใหญ่กว่านี้อาจทำให้การกระจายกระจายไป

ดังนั้นเมื่อใดที่rand()%nส่งกลับช่วงของตัวเลขจาก 0 ถึง n-1 ที่มีความน่าจะเป็นที่เท่ากัน? RAND_MAX%n == n - 1เมื่อ ในกรณีนี้พร้อมกับข้อสันนิษฐานก่อนหน้าของเราrand()จะส่งคืนตัวเลขระหว่าง 0 และRAND_MAXด้วยความน่าจะเป็นที่เท่ากันคลาสโมดูโลของ n จะได้รับการแจกแจงเท่ากัน

แล้วเราจะแก้ปัญหานี้อย่างไร วิธีที่หยาบคือการสร้างตัวเลขสุ่มจนกว่าคุณจะได้ตัวเลขในช่วงที่คุณต้องการ:

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
อีกวิธีหนึ่งของการคิด about_ RAND_MAX%n == n - 1_ (RAND_MAX + 1) % n == 0คือ เมื่ออ่านรหัสฉันมักจะเข้าใจ% something == 0ว่า "หารอย่างสม่ำเสมอ" ได้ง่ายกว่าวิธีการคำนวณอื่น ๆ แน่นอนถ้า C ++ stdlib ของคุณมีRAND_MAXมูลค่าเท่าINT_MAXกัน(RAND_MAX + 1)แน่นอนจะไม่ทำงาน ดังนั้นการคำนวณของ Mark ยังคงเป็นการใช้งานที่ปลอดภัยที่สุด
Slipp D. Thompson

คำตอบที่ดีมาก!
Sayali Sonawane

ฉันอาจจะ nitpicking แต่ถ้าเป้าหมายคือการลดบิตที่สูญเปล่าเราสามารถปรับปรุงสิ่งนี้เล็กน้อยสำหรับสภาพขอบที่ RAND_MAX (RM) เพียง 1 น้อยกว่าการหารด้วย N อย่างเท่าเทียมกันในสถานการณ์นี้ไม่จำเป็นต้องเสียบิตโดย ทำ X> = (RM - RM% N)) ซึ่งมีค่าน้อยสำหรับค่าขนาดเล็กของ N แต่จะกลายเป็นมูลค่าที่มากกว่าสำหรับค่าขนาดใหญ่ของ N ตามที่กล่าวไว้โดย Slipp D. Thompson มีวิธีแก้ปัญหาที่จะทำงานเท่านั้น เมื่อ INT_MAX (IM)> RAND_MAX แต่หยุดพักเมื่อเท่ากัน อย่างไรก็ตามมีทางออกที่ง่ายสำหรับสิ่งนี้เราสามารถแก้ไขการคำนวณ X> = (RM - RM% N) ดังนี้:
Ben Personick

X> = RM - (((RM% N) + 1)% N)
Ben Personick

ฉันโพสต์คำตอบเพิ่มเติมเพื่ออธิบายปัญหาโดยละเอียดและให้โซลูชันโค้ดตัวอย่าง
Ben Personick

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 นั้นสะอาดกว่ามากและทำงานได้ดีขึ้น
boycy

22
@ boycy: คุณพลาดจุดไปแล้ว หากจำนวนของค่าที่rand()สามารถส่งคืนไม่ใช่จำนวนnมากดังนั้นสิ่งที่คุณทำคุณจะได้รับ 'โมดูโล่อคติ' อย่างหลีกเลี่ยงไม่ได้เว้นแต่คุณจะละทิ้งค่าเหล่านั้น user1413793 อธิบายว่าเป็นอย่างดี (แม้ว่าวิธีการแก้ปัญหาที่เสนอในคำตอบนั้นช่างยากจริงๆ)
TonyK

4
@ TonyK ขอโทษของฉันฉันพลาดจุด ไม่ได้คิดหนักพอและคิดว่าอคติจะใช้กับวิธีการที่ใช้การดำเนินการโมดูลัสอย่างชัดเจนเท่านั้น ขอบคุณที่ซ่อมฉัน :-)
boycy

ตัวดำเนินการมาก่อนRAND_MAX+1 - (RAND_MAX+1) % nทำงานได้อย่างถูกต้อง แต่ฉันก็ยังคิดว่าควรเขียนRAND_MAX+1 - ((RAND_MAX+1) % n)เพื่อความชัดเจน
Linus Arver

4
นี้จะไม่ทำงานถ้า(มันไม่เกี่ยวกับระบบส่วนใหญ่)RAND_MAX == INT_MAX ดูความคิดเห็นที่สองของฉันต่อ @ user1413793 ด้านบน
BlueRaja - Danny Pflughoeft

19

@ user1413793 ถูกต้องเกี่ยวกับปัญหา ฉันจะไม่พูดคุยเรื่องนี้ต่อไปนอกจากจะทำให้ประเด็นหนึ่ง: ใช่สำหรับค่าขนาดเล็กnและค่าขนาดใหญ่ของRAND_MAXอคติโมดูโลอาจมีขนาดเล็กมาก แต่การใช้รูปแบบที่ทำให้เกิดอคติหมายความว่าคุณต้องพิจารณาความเอนเอียงทุกครั้งที่คุณคำนวณตัวเลขสุ่มและเลือกรูปแบบที่แตกต่างกันสำหรับกรณีที่แตกต่างกัน และถ้าคุณทำการเลือกผิดข้อบกพร่องที่มันนำเสนอนั้นบอบบางและแทบจะเป็นไปไม่ได้เลยที่จะทำการทดสอบหน่วย เมื่อเทียบกับการใช้เครื่องมือที่เหมาะสม (เช่นarc4random_uniform) นั่นเป็นงานพิเศษไม่ทำงานน้อยลง การทำงานมากขึ้นและการแก้ปัญหาที่แย่กว่านั้นคือวิศวกรรมที่แย่มากโดยเฉพาะอย่างยิ่งเมื่อทำในสิ่งที่ถูกต้องทุกครั้งจะง่ายบนแพลตฟอร์มส่วนใหญ่

น่าเสียดายที่การใช้งานโซลูชันไม่ถูกต้องหรือมีประสิทธิภาพน้อยกว่าที่ควรจะเป็น (แต่ละวิธีมีความคิดเห็นที่อธิบายถึงปัญหาต่าง ๆ แต่ไม่มีการแก้ไขวิธีใดที่จะแก้ไขปัญหาเหล่านี้) มีแนวโน้มที่จะสร้างความสับสนให้กับผู้ตอบคำถามแบบไม่เป็นทางการ

อีกครั้งทางออกที่ดีที่สุดคือการใช้งานarc4random_uniformบนแพลตฟอร์มที่ให้บริการหรือโซลูชันที่มีระยะคล้ายคลึงกันสำหรับแพลตฟอร์มของคุณ (เช่นRandom.nextIntบน Java) มันจะทำสิ่งที่ถูกต้องโดยไม่มีค่าใช้จ่ายสำหรับคุณ นี่เป็นการโทรที่ถูกต้องเกือบทุกครั้ง

หากคุณไม่มีarc4random_uniformคุณสามารถใช้พลังของ opensource เพื่อดูว่ามีการใช้งานอย่างไรบน RNG ar4randomในวงกว้าง ( ในกรณีนี้ แต่วิธีการที่คล้ายกันอาจใช้งานได้กับRNG อื่น ๆ )

นี่คือการใช้งาน 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;
}

เป็นสิ่งที่ควรสังเกตเมื่อคอมเม้นต์นี้มีไว้สำหรับผู้ที่ต้องการใช้สิ่งที่คล้ายกัน:

เปลี่ยน arc4random_uniform () เพื่อคำนวณเป็น2**32 % upper_bound -upper_bound % upper_boundลดความซับซ้อนของรหัสและทำให้มันเหมือนกันทั้งในสถาปัตยกรรม ILP32 และ LP64 และยังเร็วขึ้นเล็กน้อยในสถาปัตยกรรม LP64 โดยใช้ส่วนที่เหลือแบบ 32 บิตแทนที่จะเป็นส่วนที่เหลือ 64 บิต

ชี้ให้เห็นโดย Jorden Verwer บนเทคโนโลยี @ ok deraadt; ไม่มีการคัดค้านจาก 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 ที่ดีกว่าในอินเทอร์เฟซเดียวกัน ฉันจำได้ว่าหนึ่งใน BSDs ตอนนี้ใช้อัลกอริทึม ChaCha20 เพื่อนำไปใช้arcfour_random()จริง ข้อมูลเพิ่มเติมเกี่ยวกับอคติ RC4 ซึ่งทำให้ไร้ประโยชน์สำหรับความปลอดภัยหรือแอปพลิเคชั่นที่สำคัญอื่น ๆ เช่นวิดีโอโปกเกอร์
rmalayter

2
@rmalayter บน iOS และ OS X, arc4random อ่านจาก / dev / random ซึ่งเป็นเอนโทรปีคุณภาพสูงสุดในระบบ (ชื่อ "arc4" ในชื่อนั้นมีคุณค่าทางประวัติศาสตร์และเก็บรักษาไว้เพื่อความเข้ากันได้)
Rob Napier

@Rob_Napier รู้ดี แต่/dev/randomเคยใช้ RC4 ในบางแพลตฟอร์มในอดีต (Linux ใช้ SHA-1 ในโหมดตัวนับ) น่าเสียดายที่ man page ที่ฉันพบผ่านการค้นหาระบุว่า RC4 ยังคงใช้งานได้บนแพลตฟอร์มต่าง ๆ ที่มีให้arc4random(แม้ว่ารหัสจริงอาจแตกต่างกัน)
rmalayter

1
ฉันสับสน ไม่ใช่-upper_bound % upper_bound == 0เหรอ
Jon McClung

1
@JonMcClung -upper_bound % upper_boundจะเป็น 0 จริงถ้าintกว้างกว่า 32 บิต มันควรจะเป็น(u_int32_t)-upper_bound % upper_bound)(สมมติว่าu_int32_tเป็น BSD-ism สำหรับuint32_t)
เอียนแอ็บบอต

14

คำนิยาม

Modulo Biasคืออคติโดยธรรมชาติในการใช้ modulo arithmetic เพื่อลดชุดเอาต์พุตเป็นชุดย่อยของชุดอินพุต โดยทั่วไปแล้วอคตินั้นจะเกิดขึ้นทุกครั้งที่การแมประหว่างชุดอินพุตและเอาต์พุตไม่ได้กระจายเท่า ๆ กันเช่นในกรณีของการใช้เลขคณิตโมดูโลเมื่อขนาดของชุดเอาต์พุตไม่ได้เป็นตัวหารของขนาดของชุดอินพุต

อคตินี้ยากที่จะหลีกเลี่ยงในการคำนวณโดยที่ตัวเลขจะแสดงเป็นสตริงของบิต: 0s และ 1s การหาแหล่งที่มาของการสุ่มอย่างแท้จริงนั้นยากมากเช่นกัน แต่อยู่นอกเหนือขอบเขตของการสนทนานี้ สำหรับส่วนที่เหลือของคำตอบนี้สมมติว่ามีแหล่งสุ่มบิตอย่างไม่ จำกัด

ตัวอย่างปัญหา

ลองพิจารณาจำลองการหมุนตาย (0 ถึง 5) โดยใช้บิตสุ่มเหล่านี้ มีความเป็นไปได้ 6 อย่างดังนั้นเราต้องการบิตมากพอที่จะเป็นตัวแทนของหมายเลข 6 ซึ่งก็คือ 3 บิต น่าเสียดายที่ 3 บิตสุ่มมี 8 ผลลัพธ์ที่เป็นไปได้:

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

เราสามารถลดขนาดของผลลัพธ์ที่ตั้งค่าเป็น 6 อย่างแน่นอนโดยการใช้ค่าโมดูโล 6 แต่สิ่งนี้นำเสนอปัญหาแบบโมดูโลอคติ : 110ให้ผลเป็น 0 และ111ให้ผลเป็น 1 ตายนี้ถูกโหลด

โซลูชั่นที่เป็นไปได้

วิธีการ 0:

แทนที่จะใช้บิตสุ่มในทางทฤษฎีเราสามารถจ้างกองทัพขนาดเล็กหมุนลูกเต๋าได้ตลอดวันและบันทึกผลลัพธ์ลงในฐานข้อมูลแล้วใช้ผลลัพธ์แต่ละรายการเพียงครั้งเดียว เรื่องนี้เกี่ยวกับการปฏิบัติเท่าที่ฟังและมากกว่าจะไม่ให้ผลลัพธ์ที่สุ่มอย่างแท้จริงอยู่แล้ว (ปุนตั้งใจ)

วิธีที่ 1:

แทนที่จะใช้โมดูลัสโซลูชันที่ไร้เดียงสา แต่ถูกต้องทางคณิตศาสตร์คือการทิ้งผลลัพธ์ที่ให้ผล110และ111ลองอีกครั้งด้วย 3 บิตใหม่ แต่น่าเสียดายที่นี้หมายถึงว่ามีโอกาส 25% ในแต่ละม้วนที่ re-ม้วนจะต้องรวมถึงแต่ละม้วนอีกครั้งตัวเอง สิ่งนี้ทำไม่ได้อย่างเห็นได้ชัดสำหรับทุกคน แต่มีประโยชน์น้อยที่สุด

วิธีที่ 2:

ใช้บิตเพิ่มเติม: แทน 3 บิตใช้ 4 ผลนี้ให้ผลลัพธ์ที่เป็นไปได้ 16 ข้อ แน่นอนว่าการกลิ้งซ้ำเมื่อใดก็ตามที่ผลลัพธ์มากกว่า 5 ทำให้สิ่งเลวร้ายลง (10/16 = 62.5%) เพื่อที่จะไม่ช่วยคนเดียว

ขอให้สังเกตว่า 2 * 6 = 12 <16 ดังนั้นเราสามารถรับผลได้อย่างปลอดภัยน้อยกว่า 12 และลดโมดูโล 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 mod 6 พลิกระหว่าง 2 และ 4 ซึ่งบังเอิญเป็นทวีคูณของ 2 จากกันและกันดังนั้นสำหรับ x คู่> 1

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

ดังนั้น 6 เป็นข้อยกเว้นมากกว่ากฎ มีความเป็นไปได้ที่จะพบโมดูลัสที่ใหญ่กว่าซึ่งให้พลังต่อเนื่องเป็น 2 ในลักษณะเดียวกัน แต่ในที่สุดสิ่งนี้จะต้องล้อมรอบและความน่าจะเป็นของการละทิ้งจะลดลง

โดยไม่ต้องเสนอการพิสูจน์เพิ่มเติมโดยทั่วไปการใช้จำนวนบิตที่ต้องการเป็นสองเท่าจะทำให้มีโอกาสน้อยลงในการทิ้ง

พิสูจน์แนวคิด

นี่คือตัวอย่างโปรแกรมที่ใช้ libcrypo ของ OpenSSL ในการจัดหาไบต์แบบสุ่ม เมื่อทำการรวบรวมให้แน่ใจว่าได้ลิงค์ไปยังไลบรารี-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 - MONICA ถูกยกเลิก

เพื่อความชัดเจนrandomPoolเราจะประเมิน1ตามเอกสารRAND_bytes() OpenSSL เสมอเพราะจะประสบความสำเร็จเสมอเนื่องจากการRAND_status()ยืนยัน
Qix - MONICA ถูกยกเลิก

9

มีสองเรื่องร้องเรียนปกติด้วยการใช้โมดูโล

  • หนึ่งที่ถูกต้องสำหรับเครื่องกำเนิดไฟฟ้าทั้งหมด มันง่ายกว่าที่จะเห็นในกรณีขีด จำกัด หากเครื่องกำเนิดของคุณมี RAND_MAX ซึ่งเป็น 2 (ที่ไม่สอดคล้องกับมาตรฐาน C) และคุณต้องการเพียง 0 หรือ 1 เป็นค่าการใช้ modulo จะสร้าง 0 สองครั้งบ่อยครั้ง (เมื่อเครื่องกำเนิดสร้าง 0 และ 2) ตามที่มันจะ สร้าง 1 (เมื่อเครื่องกำเนิดสร้าง 1) โปรดทราบว่านี่เป็นความจริงทันทีที่คุณไม่ปล่อยค่าใด ๆ ที่การแมปที่คุณใช้จากค่าตัวกำเนิดไปยังค่าที่ต้องการหนึ่งจะเกิดขึ้นสองครั้งบ่อยเท่าที่อื่น ๆ

  • เครื่องกำเนิดไฟฟ้าบางชนิดมีบิตที่มีนัยสำคัญน้อยกว่าซึ่งสุ่มน้อยกว่าเครื่องอื่นอย่างน้อยสำหรับพารามิเตอร์บางตัว แต่น่าเสียดายที่พารามิเตอร์เหล่านั้นมีคุณลักษณะที่น่าสนใจอื่น ๆ (เช่นมีความสามารถในการมี RAND_MAX น้อยกว่าพลัง 2) ปัญหานี้เป็นที่รู้จักกันดีและการใช้งานห้องสมุดเป็นเวลานานอาจหลีกเลี่ยงปัญหาได้ (ตัวอย่างเช่น rand ตัวอย่าง () การใช้งานในมาตรฐาน C ใช้ตัวกำเนิดชนิดนี้ แต่ลดบิตที่สำคัญน้อยกว่า 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 == INT_MAX)

BTW, C ++ 11 แนะนำวิธีมาตรฐานในการลดและตัวกำเนิดอื่นที่ไม่ใช่แรนด์ ()


n == RAND_MAX? 1: (RAND_MAX-1) / (n + 1): ฉันเข้าใจความคิดที่นี่คือการแบ่ง RAND_MAX แรกเป็นขนาดหน้าเท่ากัน N จากนั้นคืนค่าเบี่ยงเบนภายใน N แต่ฉันไม่สามารถแมปรหัสกับสิ่งนี้ได้อย่างแม่นยำ
zinking

1
รุ่นที่ไร้เดียงสาควรเป็น (RAND_MAX + 1) / (n + 1) เนื่องจากมีค่า RAND_MAX + 1 เพื่อแบ่งในที่เก็บข้อมูล n + 1 หากต้องการหลีกเลี่ยงการโอเวอร์โฟลว์ขณะคำนวณ RAND_MAX + 1 สามารถแปลงได้ใน 1+ (RAND_MAX-n) / (n + 1) เพื่อหลีกเลี่ยงการโอเวอร์โฟลว์เมื่อคำนวณ n + 1 เคส n == RAND_MAX จะถูกตรวจสอบก่อน
AProgrammer

+ บวกการแบ่งคือการคิดต้นทุนมากขึ้นเมื่อเทียบกับการสร้างตัวเลขซ้ำ
zinking

4
การโมดูโลและการหารมีค่าเดียวกัน ISA บางตัวให้คำแนะนำเพียงคำเดียวซึ่งให้ทั้งสองอย่างเสมอ ค่าใช้จ่ายในการสร้างตัวเลขจะขึ้นอยู่กับ n และ RAND_MAX หาก n มีขนาดเล็กเมื่อเทียบกับ RAND_MAX อาจมีราคาสูง และแน่นอนคุณอาจตัดสินใจว่าอคตินั้นไม่สำคัญสำหรับการสมัครของคุณ ฉันเพียงแค่หลีกเลี่ยงพวกเขา
AProgrammer

9

โซลูชันของ Mark (โซลูชันที่ยอมรับ) นั้นสมบูรณ์แบบเกือบ

int x;

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

x %= n;

แก้ไข Mar 25 '16 เวลา 23:16

Mark Amery 39k21170211

อย่างไรก็ตามมันมีข้อแม้ที่แยกผลลัพธ์ที่ถูกต้อง 1 ชุดในสถานการณ์ใด ๆ ที่RAND_MAX( RM) เป็น 1 น้อยกว่าผลคูณของN(ที่ไหนN= จำนวนผลลัพธ์ที่ใช้ได้ที่เป็นไปได้)

กล่าวคือเมื่อ 'จำนวนค่าที่ละทิ้ง' ( D) เท่ากับNแล้วพวกเขาจะเป็นชุดที่ถูกต้อง ( V)ไม่ใช่ชุดที่ไม่ถูกต้องI)

สิ่งที่ทำให้เกิดนี้อยู่ในบางจุดมาร์คสูญเสียการมองเห็นความแตกต่างระหว่างและNRand_Max

Nเป็นชุดที่สมาชิกที่ถูกต้องของจะประกอบด้วยเฉพาะจำนวนเต็มบวกเนื่องจากประกอบด้วยจำนวนการตอบสนองที่จะถูกต้อง (เช่น: Set N= {1, 2, 3, ... n })

Rand_max อย่างไรก็ตามเป็นชุดที่ (ตามที่กำหนดไว้สำหรับวัตถุประสงค์ของเรา) รวมถึงจำนวนเต็มที่ไม่เป็นลบ

ในรูปแบบทั่วไปที่สุดสิ่งที่กำหนดไว้ที่นี่เป็นRand Maxชุดของผลลัพธ์ที่ถูกต้องทั้งหมดซึ่งในทางทฤษฎีอาจรวมถึงจำนวนลบหรือค่าที่ไม่ใช่ตัวเลข

ดังนั้นจึงRand_Maxถูกกำหนดให้ดีขึ้นเป็นชุดของ "การตอบสนองที่เป็นไปได้"

อย่างไรก็ตามNดำเนินการกับการนับจำนวนของค่าภายในชุดของการตอบสนองที่ถูกต้องดังนั้นแม้ตามที่กำหนดไว้ในกรณีเฉพาะของเราRand_Maxจะเป็นค่าหนึ่งน้อยกว่าจำนวนทั้งหมดที่มี

การใช้โซลูชันของ Mark จะถูกยกเลิกค่าเมื่อ: 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 ซึ่งเป็นตัวคูณของ N

Mark'original Version:

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

การวนรอบคุณสามารถใช้ N = 1 ได้และค่าใด ๆ ของ X จะได้รับการยอมรับอย่างไรก็ตามและใส่คำสั่ง IF สำหรับตัวคูณสุดท้ายของคุณ แต่บางทีคุณอาจมีรหัสที่อาจมีเหตุผลที่ถูกต้องในการส่งคืน 1 เมื่อเรียกใช้ฟังก์ชันด้วย n = 1 ...

ดังนั้นจึงควรใช้ 0 ซึ่งปกติจะให้ข้อผิดพลาด Div 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();
}

โซลูชันทั้งสองนี้แก้ปัญหาด้วยผลลัพธ์ที่ถูกต้องโดยไม่จำเป็นซึ่งจะเกิดขึ้นเมื่อ RM + 1 เป็นผลิตภัณฑ์ของ n

เวอร์ชันที่สองยังครอบคลุมสถานการณ์ของเคสขอบเมื่อคุณต้องการ n ให้เท่ากับชุดของค่าที่เป็นไปได้ทั้งหมดที่มีอยู่ใน RAND_MAX

วิธีการแก้ไขในทั้งสองนั้นเหมือนกันและอนุญาตให้มีวิธีแก้ปัญหาทั่วไปเพิ่มเติมเพื่อความต้องการในการให้ตัวเลขสุ่มที่ถูกต้องและลดค่าที่ถูกทิ้ง

หากต้องการย้ำ:

โซลูชันทั่วไปพื้นฐานซึ่งขยายตัวอย่างของเครื่องหมาย:

// 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();
}

ปลอดภัยหรือไม่ที่จะบอกว่าปัญหาเกี่ยวกับวิธีแก้ปัญหาของ Mark คือเขาปฏิบัติต่อ RAND_MAX และ n ว่าเป็น "หน่วยวัด" เดียวกันเมื่อจริง ๆ แล้วมันหมายถึงสองสิ่งที่แตกต่างกันหรือไม่ ในขณะที่ n แทน "จำนวนของความเป็นไปได้" ที่เกิดขึ้น RAND_MAX จะแสดงเฉพาะค่าสูงสุดของความเป็นไปได้ดั้งเดิมโดยที่ RAND_MAX + 1 จะเป็นจำนวนความเป็นไปได้ดั้งเดิม ฉันประหลาดใจที่เขาไม่ได้รับข้อสรุปของคุณเนื่องจากดูเหมือนว่าเขาได้รับการยอมรับและ RAND_MAX ไม่เหมือนกันกับสมการ:RAND_MAX%n = n - 1
Danilo Souza Morães

@ DaniloSouzaMorãesขอบคุณ Danilo คุณให้ความสำคัญกับเรื่องนี้อย่างมาก ฉันไปเพื่อแสดงให้เห็นถึงสิ่งที่เขาทำพร้อมกับทำไมและอย่างไร แต่ไม่คิดว่าฉันสามารถระบุได้ว่าเขาทำอะไรผิดอย่างละเอียดขณะที่ฉันได้รับรายละเอียดของตรรกะเกี่ยวกับวิธีการและ ทำไมมีปัญหาฉันไม่ได้ระบุอย่างชัดเจนว่ามีปัญหาอะไร คุณทราบหรือไม่ว่าฉันแก้ไขคำตอบเพื่อใช้บางสิ่งที่คุณเขียนที่นี่เป็นบทสรุปของฉันเองเกี่ยวกับปัญหาของสิ่งที่และวิธีแก้ปัญหาที่ยอมรับได้ทำในสิ่งที่ต้องแก้ไขใกล้ด้านบนหรือไม่
Ben Personick

นั่นจะยอดเยี่ยม ไปหามัน
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_MAX3

วิธีอื่น

นอกจากนี้ง่ายมาก แต่จะเพิ่มให้กับคำตอบอื่น ๆ นี่คือคำตอบของฉันที่จะได้รับจำนวนสุ่มระหว่าง0และn - 1ดังนั้นnความเป็นไปได้ที่แตกต่างกันโดยไม่มีอคติ

  • จำนวนบิต (ไม่ใช่ไบต์) ที่จำเป็นสำหรับการเข้ารหัสจำนวนความเป็นไปได้คือจำนวนบิตของข้อมูลสุ่มที่คุณต้องการ
  • เข้ารหัสตัวเลขจากบิตสุ่ม
  • ถ้าหมายเลขนี้เป็น>= nรีสตาร์ท (ไม่มีแบบโมดูโล)

ข้อมูลสุ่มจริง ๆ ไม่ใช่เรื่องง่ายดังนั้นทำไมใช้บิตมากกว่าที่ต้องการ

ด้านล่างเป็นตัวอย่างใน Smalltalk โดยใช้แคชบิตจากเครื่องกำเนิดตัวเลขแบบหลอกเทียม ฉันไม่มีผู้เชี่ยวชาญด้านความปลอดภัยใช้ความเสี่ยงของคุณเอง

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

ในฐานะที่เป็นคำตอบที่ได้รับการยอมรับบ่งชี้ว่า "โมดูโลอคติ" 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

ดังนั้นจึงมี 4 เอาต์พุตของ 0 (โอกาส 4/10) และมีเพียง 3 เอาต์พุตของ 1 และ 2 (โอกาสแต่ละ 3/10)

ดังนั้นมันมีอคติ ตัวเลขที่ต่ำกว่ามีโอกาสที่ดีกว่าในการออกมา

แต่นั่นจะปรากฏให้เห็นอย่างชัดเจนเมื่อRAND_MAXมีขนาดเล็กมีขนาดเล็กหรือมากขึ้นโดยเฉพาะเมื่อจำนวนของคุณจะ modding RAND_MAXโดยมีขนาดใหญ่เมื่อเทียบกับ

ทางออกที่ดีกว่าการวนซ้ำ (ซึ่งไม่มีประสิทธิภาพอย่างไม่น่าเชื่อและไม่ควรแม้แต่จะแนะนำ) คือการใช้ PRNG ที่มีช่วงเอาต์พุตที่ใหญ่กว่ามาก Mersenne Twisterอัลกอริทึมที่มีการส่งออกสูงสุดของ 4294967295 การทำเช่นนี้MersenneTwister::genrand_int32() % 10เพื่อจุดประสงค์และจุดประสงค์ทั้งหมดจะได้รับการเผยแพร่อย่างเท่าเทียมกันและผลของโมดูโลจะมี แต่จะหายไป


3
ของคุณมีประสิทธิภาพมากขึ้นและอาจเป็นความจริงที่ว่าหาก RAND_MAX มีขนาดใหญ่กว่าจำนวนที่คุณดัดแปลงด้วยอย่างไรก็ตามคุณจะยังคงลำเอียง จริงอยู่ที่สิ่งเหล่านี้ล้วนเป็นเครื่องกำเนิดเลขสุ่มหลอกอยู่แล้วและในตัวของมันเองก็เป็นหัวข้อที่แตกต่างกัน แต่ถ้าคุณคิดว่าเครื่องกำเนิดเลขสุ่มเต็มวิธีที่คุณยังคงมีอคติต่ำ
user1413793

เนื่องจากค่าสูงสุดเป็นเลขคี่ให้MT::genrand_int32()%2เลือก 0 (50 + 2.3e-8)% ของเวลาและ 1 (50 - 2.3e-8)% ของเวลา นอกจากว่าคุณกำลังสร้าง RGN ของคาสิโน (ซึ่งคุณอาจจะใช้ช่วง RGN ที่ใหญ่กว่า) ผู้ใช้ทุกคนจะไม่สังเกตเห็นพิเศษ 2.3e-8% ของเวลา คุณกำลังพูดถึงตัวเลขน้อยเกินไปที่จะสำคัญที่นี่
bobobobo

7
การวนลูปเป็นทางออกที่ดีที่สุด มันไม่ได้เป็น "ไม่มีประสิทธิภาพอย่างบ้าคลั่ง"; ต้องการการทำซ้ำน้อยกว่าสองเท่าในกรณีที่แย่ที่สุด การใช้RAND_MAXค่าที่สูงจะลดอคติแบบโมดูโล แต่จะไม่กำจัดมัน จะวนรอบ
Jared Nielsen

5
หากRAND_MAXมีจำนวนมากกว่าจำนวนที่คุณ modding อย่างเพียงพอจำนวนครั้งที่คุณจำเป็นต้องสร้างหมายเลขสุ่มจะหายไปเล็กน้อยและจะไม่มีผลกับประสิทธิภาพ ฉันบอกว่าให้วนซ้ำไปเรื่อย ๆ ตราบใดที่คุณกำลังทดสอบตัวคูณที่ใหญ่ที่สุดnแทนที่จะnตอบตามคำตอบที่ยอมรับ
Mark Ransom

-3

ฉันเพิ่งเขียนรหัสสำหรับวิธีการหยอดเหรียญแบบไม่เอนเอียงของ Von Neumann ซึ่งในทางทฤษฎีควรกำจัดอคติใด ๆ ในกระบวนการสร้างหมายเลขแบบสุ่ม ข้อมูลเพิ่มเติมสามารถดูได้ที่ ( 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 ไม่น้อยกว่ากำลังสองที่ต้องใช้การปรับแบบโมดูโล ดังนั้นวิธีนี้ไม่สามารถกำจัดอคติใด ๆ ในกระบวนการสร้างหมายเลขสุ่ม
Rick

2
@Rick hmm ส่วนขยายแบบลอจิคัลของวิธีของ Von Neumann ในการกำจัดแบบโมดูโลเมื่อสร้างตัวเลขสุ่มระหว่างพูด 1 และ 100 จะเป็น: A) เรียกrand() % 100100 ครั้ง B) หากผลลัพธ์ทั้งหมดต่างกันให้จดผลลัพธ์แรกไว้ C) มิฉะนั้น GOTO A. สิ่งนี้จะได้ผล แต่ด้วยจำนวนการทำซ้ำประมาณ 10 ^ 42 คุณจะต้องอดทน และเป็นอมตะ
Mark Amery

@ MarkAmery แน่นอนว่าควรจะทำงาน ดูอัลกอริทึมนี้แม้ว่ามันจะไม่ได้ถูกใช้ สิ่งอื่นควรเป็น:else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}
Rick
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.