ทำไม rand () ทำซ้ำตัวเลขบ่อยกว่าบน Linux มากกว่า Mac?


87

ฉันใช้ hashmap ใน C เป็นส่วนหนึ่งของโครงการที่ฉันกำลังทำงานและใช้การแทรกแบบสุ่มเพื่อทดสอบเมื่อฉันสังเกตเห็นว่าrand()บน Linux ดูเหมือนว่าจะซ้ำตัวเลขบ่อยกว่าบน Mac RAND_MAXคือ 2147483647 / 0x7FFFFFFF บนทั้งสองแพลตฟอร์ม ฉันได้ลดขนาดลงในโปรแกรมทดสอบนี้ที่ทำให้อาร์เรย์แบบRAND_MAX+1ยาว - ยาวสร้างRAND_MAXตัวเลขแบบสุ่มบันทึกว่าแต่ละรายการซ้ำกันหรือไม่และตรวจสอบออกจากรายการตามที่เห็น

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main() {
    size_t size = ((size_t)RAND_MAX) + 1;
    char *randoms = calloc(size, sizeof(char));
    int dups = 0;
    srand(time(0));
    for (int i = 0; i < RAND_MAX; i++) {
        int r = rand();
        if (randoms[r]) {
            // printf("duplicate at %d\n", r);
            dups++;
        }
        randoms[r] = 1;
    }
    printf("duplicates: %d\n", dups);
}

Linux สร้างข้อมูลซ้ำประมาณ 790 ล้านรายการอย่างสม่ำเสมอ Mac อย่างต่อเนื่องเพียงสร้างหนึ่งจึง loops ผ่านจำนวนสุ่มทุกที่จะสามารถสร้างเกือบได้โดยไม่ต้องทำซ้ำ ใครช่วยอธิบายให้ฉันฟังได้ว่ามันใช้งานได้อย่างไร? ฉันไม่สามารถบอกอะไรที่แตกต่างจากหน้าคนไม่สามารถบอกได้ว่า RNG ใดที่ใช้กันและไม่พบสิ่งใดทางออนไลน์ ขอบคุณ!


4
เนื่องจาก rand () ส่งคืนค่าจาก 0..RAND_MAX แบบรวมอาเรย์ของคุณจะต้องมีขนาด RAND_MAX + 1
Blastfurnace

21
คุณอาจสังเกตว่า RAND_MAX / e ~ = 790 ล้าน นอกจากนี้ขีด จำกัด ของ (1-1 / n) ^ n เมื่อ n เข้าใกล้อนันต์คือ 1 / e
David Schwartz

3
@DavidSchwartz ถ้าฉันเข้าใจคุณถูกต้องนั่นอาจอธิบายได้ว่าทำไมตัวเลขบน Linux ถึงประมาณ 790 ล้านอย่างต่อเนื่อง ฉันเดาคำถามแล้วว่าทำไม / ทำไม Mac ถึงไม่ทำซ้ำหลายครั้ง?
Theron S

26
ไม่มีข้อกำหนดด้านคุณภาพสำหรับ PRNG ในไลบรารีรันไทม์ ความต้องการที่แท้จริงเท่านั้นคือการทำซ้ำด้วยเมล็ดเดียวกัน เห็นได้ชัดว่าคุณภาพของ PRNG ใน linux ของคุณดีกว่าใน Mac ของคุณ
pmg

4
@ chux ใช่ แต่เนื่องจากมันขึ้นอยู่กับการคูณรัฐจะไม่มีวันเป็นศูนย์หรือผลลัพธ์ (สถานะถัดไป) ก็จะเป็นศูนย์เช่นกัน ตามซอร์สโค้ดมันจะตรวจสอบศูนย์เป็นกรณีพิเศษหาก seeded กับศูนย์ แต่มันไม่เคยผลิตศูนย์เป็นส่วนหนึ่งของลำดับ
Arkku

คำตอบ:


119

ในขณะที่ตอนแรกมันอาจฟังดูเหมือน macOS rand()จะดีกว่าที่จะไม่ทำซ้ำตัวเลขใด ๆ แต่อย่างใดควรทราบว่าด้วยจำนวนตัวเลขที่สร้างขึ้นนี้คาดว่าจะเห็นข้อมูลซ้ำซ้อนมากมาย (อันที่จริงประมาณ 790 ล้านหรือ (2 31 -1 ) / e ) ในทำนองเดียวกันการวนซ้ำตามลำดับจะไม่ทำให้เกิดการซ้ำซ้อน แต่จะไม่ถือว่าสุ่มมากนัก ดังนั้นการrand()ใช้งานลีนุกซ์จึงอยู่ในการทดสอบนี้ซึ่งแยกไม่ออกจากแหล่งสุ่มที่แท้จริงในขณะที่ macOS rand()ไม่ใช่

อีกสิ่งหนึ่งที่น่าประหลาดใจในแวบแรกคือวิธีที่ MacOS rand()สามารถจัดการเพื่อหลีกเลี่ยงการซ้ำซ้อนได้ดี เมื่อมองไปที่ซอร์สโค้ดเราพบว่าการติดตั้งมีดังนี้:

/*
 * Compute x = (7^5 * x) mod (2^31 - 1)
 * without overflowing 31 bits:
 *      (2^31 - 1) = 127773 * (7^5) + 2836
 * From "Random number generators: good ones are hard to find",
 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
 * October 1988, p. 1195.
 */
    long hi, lo, x;

    /* Can't be initialized with 0, so use another value. */
    if (*ctx == 0)
        *ctx = 123459876;
    hi = *ctx / 127773;
    lo = *ctx % 127773;
    x = 16807 * lo - 2836 * hi;
    if (x < 0)
        x += 0x7fffffff;
    return ((*ctx = x) % ((unsigned long) RAND_MAX + 1));

สิ่งนี้จะส่งผลให้ตัวเลขทั้งหมดมีค่าระหว่าง 1 ถึงRAND_MAX, รวมทุกครั้งก่อนที่ลำดับจะซ้ำอีกครั้ง เนื่องจากสถานะถัดไปขึ้นอยู่กับการคูณรัฐจึงไม่มีทางเป็นศูนย์ (หรือสถานะในอนาคตทั้งหมดจะเป็นศูนย์ด้วย) ดังนั้นตัวเลขซ้ำที่คุณเห็นคือตัวเลขแรกและศูนย์คือตัวเลขที่ไม่เคยส่งคืน

Apple ได้ส่งเสริมการใช้ตัวสร้างหมายเลขสุ่มที่ดีขึ้นในเอกสารและตัวอย่างอย่างน้อยตราบใดที่ macOS (หรือ OS X) มีอยู่ดังนั้นคุณภาพของrand()อาจไม่ถือว่ามีความสำคัญและพวกเขาติดอยู่กับหนึ่งใน เครื่องกำเนิดไฟฟ้าหลอกง่ายที่สุดที่มีอยู่ (ตามที่คุณบันทึกไว้พวกเขาrand()จะได้รับความคิดเห็นพร้อมกับข้อเสนอแนะเพื่อใช้arc4random()แทน)

ในบันทึกที่เกี่ยวข้องตัวสร้างหมายเลขปลอมที่ง่ายที่สุดที่ฉันสามารถหาได้ที่ให้ผลลัพธ์ที่ดีในการทดสอบแบบสุ่ม (และอื่น ๆ อีกมากมาย) สำหรับการสุ่มคือxorshift * :

uint64_t x = *ctx;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
*ctx = x;
return (x * 0x2545F4914F6CDD1DUL) >> 33;

การใช้งานนี้ส่งผลให้เกิดซ้ำซ้อนกันเกือบ 790 ล้านในการทดสอบของคุณ


5
บทความวารสารที่ตีพิมพ์ในปี 1980 นำเสนอการทดสอบทางสถิติสำหรับ PRNGs อยู่บนพื้นฐานของปัญหา "วันเกิด"
pjs

14
"Apple ได้ส่งเสริมการใช้เครื่องกำเนิดเลขสุ่มที่ดีขึ้นในเอกสารของพวกเขา" -> แน่นอนว่า Apple อาจใช้arc4random()รหัสที่ล้าหลังrand()และได้รับrand()ผลลัพธ์ที่ดี แทนที่จะพยายามบังคับให้โปรแกรมเมอร์เขียนโค้ดต่างกันเพียงแค่สร้างฟังก์ชั่นห้องสมุดที่ดีขึ้น "พวกเขาเพิ่งติด" เป็นตัวเลือกของพวกเขา
chux - Reinstate Monica

23
การขาดออฟเซ็ตคงที่ใน mac rand()ทำให้แย่มากจนไม่มีประโยชน์สำหรับการใช้งานจริง: ทำไม rand ()% 7 ส่งคืน 0 เสมอ , Rand ()% 14 เพียงสร้างค่า 6 หรือ 13
phuclv

4
@PeterCordes: มีความต้องการดังกล่าวอยู่randที่เรียกใช้อีกครั้งด้วยเมล็ดเดียวกันผลิตลำดับเดียวกัน OpenBSD randเสียและไม่เชื่อฟังสัญญานี้
.. GitHub หยุดช่วยน้ำแข็ง

8
@ R..GitHubSTOPHELPINGICE คุณเห็นความต้องการ C ที่rand()มีเมล็ดเดียวกันสร้างลำดับเดียวกันระหว่างไลบรารีรุ่นต่างๆหรือไม่? การรับประกันดังกล่าวอาจมีประโยชน์สำหรับการทดสอบการถดถอยระหว่างเวอร์ชันห้องสมุด แต่ฉันไม่พบข้อกำหนด C สำหรับมัน
chux - Reinstate Monica

34

MacOS จัดเตรียมฟังก์ชัน rand () ที่ไม่มีเอกสารใน stdlib หากคุณไม่ได้ใส่มันไว้ค่าแรกที่ส่งออกคือ 16807, 282475249, 1622650073, 984943658 และ 1144108930 การค้นหาอย่างรวดเร็วจะแสดงให้เห็นว่าลำดับนี้สอดคล้องกับตัวสร้างตัวเลขสุ่ม LCG พื้นฐานมากที่ทำซ้ำสูตรต่อไปนี้:

x n +1 = 7 5 · x n (mod 2 31 - 1)

เนื่องจากสถานะของ RNG นี้ถูกอธิบายโดยสิ้นเชิงด้วยค่าของจำนวนเต็ม 32 บิตเดียวระยะเวลาจึงไม่นานนัก เพื่อความแม่นยำมันจะทำซ้ำทุก 2 31 - 2 ซ้ำการส่งออกทุกค่าจาก 1 ถึง 2 31 - 2

ฉันไม่คิดว่าจะมีการติดตั้งมาตรฐานของ rand () สำหรับ Linux ทุกรุ่น แต่มีฟังก์ชั่น glibc rand ()ที่ใช้บ่อย แทนที่จะเป็นตัวแปรสถานะ 32 บิตเดียวสิ่งนี้ใช้พูลมากกว่า 1,000 บิตซึ่งสำหรับทุกเจตนาและวัตถุประสงค์จะไม่สร้างลำดับการทำซ้ำอย่างสมบูรณ์ อีกครั้งคุณสามารถค้นหารุ่นที่คุณมีโดยพิมพ์ผลลัพธ์สองสามตัวแรกจาก RNG นี้โดยไม่ต้องเริ่มต้นก่อน (ฟังก์ชัน glibc rand () สร้างหมายเลข 1804289383, 846930886, 1681692777, 1714636915 และ 1957747793)

ดังนั้นเหตุผลที่คุณได้รับความขัดแย้งมากขึ้นใน Linux (และแทบจะไม่ได้ใน MacOS) คือรุ่น Linux ของ rand () นั้นสุ่มมากกว่า


5
ผู้ที่ไม่ถูกrand()ต้องจะต้องทำตัวเหมือนคนที่มีsrand(1);
pmg

5
มีซอร์สโค้ดสำหรับrand()ใน macOS: opensource.apple.com/source/Libc/Libc-1353.11.2/stdlib/FreeBSD/ … FWIW ฉันรันการทดสอบเดียวกันกับคอมไพล์จากซอร์สโค้ดและแน่นอนมันส่งผลให้ ซ้ำหนึ่งเดียว Apple ได้ส่งเสริมการใช้ตัวสร้างหมายเลขสุ่มอื่น ๆ (เช่นarc4random()ก่อนที่ Swift จะเข้ามาแทนที่) ในตัวอย่างและเอกสารประกอบดังนั้นการใช้งานrand()อาจไม่ธรรมดามากในแอพพื้นฐานบนแพลตฟอร์มซึ่งอาจอธิบายได้ว่าทำไมมันถึงไม่ดี
Arkku

ขอบคุณสำหรับการตอบกลับที่ตอบคำถามของฉัน และช่วงเวลาของ (2 ^ 31) -2 อธิบายว่าทำไมมันจะเริ่มทำซ้ำในตอนท้ายอย่างที่ฉันสังเกต คุณ (@ r3mainer) พูดว่าrand()ไม่มีเอกสาร แต่ @Arkku ได้ให้ลิงก์ไปยังแหล่งที่มาที่ชัดเจน คุณรู้หรือไม่ว่าทำไมฉันถึงไม่สามารถค้นหาไฟล์นั้นในระบบของฉันและทำไมฉันเห็นเฉพาะint rand(void) __swift_unavailable("Use arc4random instead.");ใน Mac's stdlib.h? ฉันคิดว่ารหัส @Arkku ที่เชื่อมโยงกับจะถูกรวบรวมเป็น ... ห้องสมุดอะไร?
Theron S

1
@TheronS มันเรียบเรียงห้องสมุดซี /usr/lib/libc.dyliblibc, =)
Arkku

5
ซึ่งรุ่นrand()ที่กำหนด C ใช้โปรแกรมไม่ได้กำหนดโดย "คอมไพเลอร์" หรือ "ระบบปฏิบัติการ" แต่การดำเนินงานของห้องสมุดมาตรฐาน C (เช่นglibc, libc.dylib, msvcrt*.dll)
Peter O.

10

rand()ถูกกำหนดโดยมาตรฐาน C และมาตรฐาน C ไม่ได้ระบุอัลกอริทึมที่จะใช้ เห็นได้ชัดว่า Apple กำลังใช้อัลกอริทึมที่ด้อยกว่าสำหรับการใช้งาน GNU / Linux ของคุณ: Linux นั้นแยกไม่ออกจากแหล่งสุ่มที่แท้จริงในการทดสอบของคุณในขณะที่การใช้งานของ Apple นั้นจะสับตัวเลข

หากคุณต้องการตัวเลขสุ่มใด ๆ ที่มีคุณภาพให้ใช้ PRNG ที่ดีกว่าซึ่งให้การรับประกันอย่างน้อยกับคุณภาพของตัวเลขที่ส่งคืนหรือเพียงแค่อ่านจาก/dev/urandomหรือคล้ายกัน ในภายหลังจะให้หมายเลขคุณภาพการเข้ารหัสลับ แต่ช้า แม้ว่าตัวมันเองจะช้าเกินไป แต่/dev/urandomก็สามารถให้เมล็ดที่ยอดเยี่ยมแก่กันและกันได้เร็วกว่า PRNG


ขอบคุณสำหรับการตอบกลับ. ฉันไม่ต้องการ PRNG ที่ดีจริงๆเพียงแค่กังวลว่ามีพฤติกรรมบางอย่างที่ไม่ได้กำหนดซึ่งแฝงตัวอยู่ใน hashmap ของฉันจากนั้นก็อยากรู้อยากเห็นเมื่อฉันตัดความเป็นไปได้นั้นออกไป
Theron S

btw นี่คือตัวอย่างของตัวสร้างหมายเลขสุ่มที่ปลอดภัย cryptographically: github.com/divinity76/phpcpp/commit/ … - แต่มันคือ C ++ แทน C และฉันปล่อยให้ผู้ใช้งาน STL ทำหน้าที่ยกของหนักทั้งหมด ..
hanshenrik

3
@hanshenrik crypto RNG โดยทั่วไปแล้ว overkill & ช้าเกินไปสำหรับตารางแฮชธรรมดา
PM 2Ring

1
@ PM2Ring อย่างแน่นอน ตารางแฮชส่วนใหญ่ต้องรวดเร็วไม่ดี อย่างไรก็ตามหากคุณต้องการพัฒนาอัลกอริธึมตารางแฮชที่ไม่เพียง แต่รวดเร็ว แต่ก็เหมาะสมฉันเชื่อว่ามันเป็นประโยชน์ที่จะได้รู้ถึงเทคนิคบางอย่างของอัลกอริทึมแฮชเข้ารหัส มันจะช่วยให้คุณหลีกเลี่ยงข้อผิดพลาดที่เห็นได้ชัดที่สุดที่ไขปริศนาแฮชที่รวดเร็วที่สุด อย่างไรก็ตามฉันจะไม่โฆษณาสำหรับการติดตั้งเฉพาะที่นี่
cmaster - คืนสถานะโมนิก้า

@cmaster จริงเพียงพอ ก็แน่นอนเป็นความคิดที่ดีที่จะรู้เล็กน้อยเกี่ยวกับสิ่งที่ต้องการฟังก์ชั่นการผสมและผลหิมะถล่ม โชคดีที่มีฟังก์ชั่นแฮ็ชที่ไม่ใช่ crypto ที่มีคุณสมบัติที่ดีซึ่งไม่เสียสละความเร็วมากเกินไป (เมื่อใช้งานอย่างถูกต้อง) เช่น xxhash, murmur3 หรือ siphash
PM 2Ring

5

โดยทั่วไปคู่แรนด์ / srand ได้รับการพิจารณาว่าเป็นประเภทที่คัดค้านมาเป็นเวลานานเนื่องจากบิตลำดับต่ำแสดงการสุ่มน้อยกว่าบิตลำดับสูงในผลลัพธ์ สิ่งนี้อาจหรืออาจจะไม่มีอะไรเกี่ยวข้องกับผลลัพธ์ของคุณ แต่ฉันคิดว่านี่เป็นโอกาสที่ดีที่จะจำไว้ว่าแม้ว่าการใช้งาน rand / srand บางอย่างจะมีความทันสมัยมากขึ้น แต่การใช้งานแบบเก่ายังคงดีกว่า ) ในกล่อง Arch Linux ของฉันข้อความต่อไปนี้ยังคงอยู่ใน man page สำหรับ rand (3):

  The versions of rand() and srand() in the Linux C Library use the  same
   random number generator as random(3) and srandom(3), so the lower-order
   bits should be as random as the higher-order bits.  However,  on  older
   rand()  implementations,  and  on  current implementations on different
   systems, the lower-order bits are much less random than the  higher-or-
   der bits.  Do not use this function in applications intended to be por-
   table when good randomness is needed.  (Use random(3) instead.)

ด้านล่างนั้นหน้า man จะให้ตัวอย่างสั้น ๆ ที่ง่ายมากการใช้งานของ rand และ srand ที่เกี่ยวกับ LC RNG ที่ง่ายที่สุดที่คุณเคยเห็นและมี RAND_MAX ขนาดเล็ก ฉันไม่คิดว่าพวกเขาจะจับคู่สิ่งที่อยู่ในไลบรารีมาตรฐาน C หากพวกเขาเคยทำ หรืออย่างน้อยฉันก็หวังว่าจะไม่

โดยทั่วไปถ้าคุณจะใช้บางอย่างจากไลบรารีมาตรฐานให้ใช้แบบสุ่มถ้าคุณทำได้ (หน้ารายการแสดงว่าเป็นมาตรฐาน POSIX กลับไปเป็น POSIX.1-2001 แต่ rand เป็นวิธีมาตรฐานก่อนที่ C จะเป็นมาตรฐาน) . หรือดีกว่ายังให้เปิดสูตรตัวเลข (หรือค้นหาทางออนไลน์) หรือ Knuth แล้วนำไปใช้ มันง่ายมากและคุณต้องทำเพียงครั้งเดียวเพื่อมีวัตถุประสงค์ทั่วไป RNG พร้อมคุณสมบัติที่คุณต้องการบ่อยที่สุดและคุณภาพที่เป็นที่รู้จัก


ขอบคุณสำหรับบริบท ฉันไม่ต้องการการสุ่มที่มีคุณภาพสูงและใช้งาน MT19937 แม้ว่าใน Rust ส่วนใหญ่เป็นเพียงแค่อยากรู้เกี่ยวกับวิธีการค้นหาสาเหตุที่ทั้งสองแพลตฟอร์มทำงานแตกต่างกัน
Theron S

1
บางครั้งคำถามที่ดีที่สุดจะถูกถามโดยให้ความสนใจอย่างง่าย ๆ แทนความต้องการที่เข้มงวด - ดูเหมือนว่าคำถามเหล่านั้นมักจะเป็นคำถามที่ได้รับชุดของคำตอบที่ดีจากจุดที่อยากรู้อยากเห็น คุณเป็นหนึ่งในพวกเขา นี่คือคนที่อยากรู้อยากเห็นแฮ็กเกอร์ตัวจริงและตัวจริง
Thomas Kammeyer

มันตลกที่คำแนะนำคือ "หยุดใช้ rand ()" แทนที่จะทำให้ rand () ดีขึ้น ไม่มีอะไรในมาตรฐานเคยบอกว่ามันจะต้องเป็นเครื่องกำเนิดไฟฟ้าที่เฉพาะเจาะจง
ท่อ

2
@pipe หากการทำให้rand()ดีขึ้นหมายถึงการทำให้ช้าลง (ซึ่งอาจเป็นไปได้ - ตัวเลขสุ่มแบบเข้ารหัสลับที่มีความปลอดภัยต้องใช้ความพยายามอย่างมาก) ก็คงจะดีกว่าถ้าจะทำให้เร็วขึ้นแม้ว่าจะคาดเดาได้มากกว่าก็ตาม ตัวอย่างในประเด็น: เรามีแอปพลิเคชันที่ใช้เวลานานในการเริ่มต้นซึ่งเราโยงไปถึง RNG ที่มีการเริ่มต้นจำเป็นต้องรอเอนโทรปีเพียงพอที่จะสร้าง ... กลับกลายเป็นว่ามันไม่จำเป็นต้องปลอดภัยมากนักดังนั้นแทนที่ด้วย RNG ที่แย่กว่านั้นเป็นการปรับปรุงครั้งใหญ่
gidds
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.