วิธีสร้างตัวเลขจำนวนเต็มสุ่มจากภายในช่วง


108

นี่คือการติดตามจากคำถามที่โพสต์ไว้ก่อนหน้านี้:

จะสร้างตัวเลขสุ่มใน C ได้อย่างไร?

ฉันต้องการสร้างตัวเลขสุ่มจากภายในช่วงที่กำหนดเช่น 1 ถึง 6 เพื่อเลียนแบบด้านข้างของดาย

ฉันจะทำสิ่งนี้ได้อย่างไร


3
หากคุณดูคำตอบที่สองสำหรับคำถามที่คุณอ้างถึงคุณมีคำตอบ Rand ()% 6.
Mats Fredriksson

2
ฉันไม่เข้าใจวิธีการทำงานดังนั้นฉันจึงตัดสินใจแยกคำถามเพื่อความชัดเจน
Jamie Keeling

2
ความคิดแบบสุ่ม: หากคุณสำรวจความคิดเห็นของโปรแกรมเมอร์แบบสุ่มคุณจะพบว่าพวกเขาสุ่มจำนวนหนึ่งกำลังคิดหาวิธีสร้างตัวเลขแบบสุ่ม เมื่อพิจารณาว่าจักรวาลอยู่ภายใต้กฎที่แม่นยำและคาดเดาได้มันไม่น่าสนใจที่เราจะพยายามสร้างสิ่งต่างๆแบบสุ่มมากขึ้น? คำถามเช่นนี้มักจะนำโปสเตอร์ 10k + ออกมา
Armstrongest

2
@ แมทแรนด์ ()% 6 สามารถคืนค่าเป็น 0 ไม่ดีสำหรับการตาย
new123456

คุณสามารถทำเครื่องหมายstackoverflow.com/a/6852396/419เป็นคำตอบที่ยอมรับแทนคำตอบที่เชื่อมโยงไปได้ไหม :) ขอบคุณ
Kev

คำตอบ:


173

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

ซึ่งหมายความว่าวิธีเดียวที่ถูกต้องในการเปลี่ยนช่วงrand()คือการแบ่งออกเป็นกล่อง ตัวอย่างเช่นหากRAND_MAX == 11คุณต้องการช่วง1..6คุณควรกำหนดให้{0,1}เป็น 1 {2,3}ถึง 2 และอื่น ๆ สิ่งเหล่านี้ไม่ปะติดปะต่อช่วงเวลาที่มีขนาดเท่ากันและกระจายอย่างสม่ำเสมอและเป็นอิสระ

ข้อเสนอแนะในการใช้การแบ่งจุดลอยตัวมีความเป็นไปได้ทางคณิตศาสตร์ แต่โดยหลักการแล้วต้องทนทุกข์ทรมานจากการปัดเศษ บางทีอาจจะdoubleเป็นความแม่นยำสูงมากพอที่จะทำให้การทำงาน; อาจจะไม่. ฉันไม่รู้และฉันไม่ต้องการที่จะคิดออก ไม่ว่าในกรณีใดคำตอบขึ้นอยู่กับระบบ

วิธีที่ถูกต้องคือการใช้เลขคณิตจำนวนเต็ม นั่นคือคุณต้องการสิ่งต่อไปนี้:

#include <stdlib.h> // For random(), RAND_MAX

// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
  unsigned long
    // max <= RAND_MAX < ULONG_MAX, so this is okay.
    num_bins = (unsigned long) max + 1,
    num_rand = (unsigned long) RAND_MAX + 1,
    bin_size = num_rand / num_bins,
    defect   = num_rand % num_bins;

  long x;
  do {
   x = random();
  }
  // This is carefully written not to overflow
  while (num_rand - defect <= (unsigned long)x);

  // Truncated division is intentional
  return x/bin_size;
}

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

หากคุณต้องการรับค่าสุ่มนอกช่วงเริ่มต้น[0, RAND_MAX]คุณต้องทำสิ่งที่ยุ่งยาก บางทีสมควรที่สุดคือการกำหนดฟังก์ชั่นrandom_extended()ที่ดึงnบิต (ใช้random_at_most()) และผลตอบแทนใน[0, 2**n)และจากนั้นให้ใช้random_at_most()กับrandom_extended()ในสถานที่ของrandom()(และ2**n - 1ในสถานที่ของRAND_MAX) เพื่อดึงค่าสุ่มน้อยกว่า2**nสมมติว่าคุณมีประเภทตัวเลขที่สามารถถือดังกล่าว ค่า ในที่สุดแน่นอนคุณจะได้รับค่าในการ[min, max]ใช้min + random_at_most(max - min)รวมถึงค่าลบ


1
@ Adam Rosenfield, @ Ryan Reich: ในคำถามที่เกี่ยวข้องซึ่ง Adam ได้ตอบ: stackoverflow.com/questions/137783/…คำตอบที่โหวตมากที่สุด: การใช้ 'โมดูลัส' จะไม่ถูกต้องใช่หรือไม่? ในการสร้าง 1..7 จาก 1..21 ควรใช้ขั้นตอนที่ Ryan อธิบายไว้โปรดแก้ไขฉันหากฉันผิด
Arvind

1
ในการตรวจสอบเพิ่มเติมปัญหาอื่นที่นี่คือจะใช้ไม่ได้เมื่อmax - min > RAND_MAXซึ่งร้ายแรงกว่าปัญหาที่ฉันระบุไว้ข้างต้น (เช่น VC ++ มีRAND_MAXเพียง 32767)
interjay

2
while loop สามารถอ่านได้มากขึ้น แทนที่จะดำเนินการมอบหมายตามเงื่อนไขคุณอาจต้องการไฟล์do {} while().
theJPster

4
เฮ้คำตอบนี้อ้างถึงโดยหนังสือ Comet OS;) ครั้งแรกที่ฉันเห็นสิ่งนั้นในหนังสือการสอน
vpuente

3
นอกจากนี้ยังอ้างถึงในหนังสือ OSTEP :) pages.cs.wisc.edu/~remzi/OSTEP (บทที่ 9 หน้า 4)
rafascar

33

จากคำตอบของ @Ryan Reich ฉันคิดว่าฉันจะเสนอเวอร์ชันที่ล้างแล้ว การตรวจสอบขอบเขตแรกไม่จำเป็นต้องใช้ในการตรวจสอบขอบเขตครั้งที่สองและฉันได้ทำซ้ำแล้วซ้ำอีกแทนที่จะเรียกซ้ำ ก็จะส่งกลับค่าในช่วง [นาที, สูงสุด] ที่และmax >= min1+max-min < RAND_MAX

unsigned int rand_interval(unsigned int min, unsigned int max)
{
    int r;
    const unsigned int range = 1 + max - min;
    const unsigned int buckets = RAND_MAX / range;
    const unsigned int limit = buckets * range;

    /* Create equal size buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    do
    {
        r = rand();
    } while (r >= limit);

    return min + (r / buckets);
}

28
โปรดทราบว่าสิ่งนี้จะติดอยู่ในลูปที่ไม่มีที่สิ้นสุดหาก range> = RAND_MAX ถามฉันว่าฉันรู้ได้อย่างไร: /
theJPster

24
คุณรู้ได้อย่างไร!?
Fantastic Mr Fox

1
โปรดทราบว่าคุณกำลังเปรียบเทียบ int กับ int ที่ไม่ได้ลงนาม (r> = limit) ปัญหาจะแก้ไขได้อย่างง่ายดายโดยการทำให้limitเป็น int (และเลือกที่bucketมากเกินไป) ตั้งแต่RAND_MAX / range< INT_MAXและ<=buckets * range RAND_MAXแก้ไข: ฉันได้ส่งและแก้ไขข้อเสนอแล้ว
rrrrrrrrrrrrrrr

วิธีแก้ปัญหาจาก @Ryan Reich ยังคงให้การกระจายที่ดีกว่า (อคติน้อยกว่า) แก่ฉัน
Vladimir

20

นี่คือสูตรหากคุณทราบค่าสูงสุดและต่ำสุดของช่วงและคุณต้องการสร้างตัวเลขที่รวมอยู่ระหว่างช่วง:

r = (rand() % (max + 1 - min)) + min

9
ดังที่ระบุไว้ในคำตอบของ Ryan สิ่งนี้ก่อให้เกิดผลลัพธ์ที่เอนเอียง
David Wolever

6
ผลลำเอียงศักยภาพล้นกับint max+1-min
chux - คืนสถานะ Monica

1
ใช้งานได้กับจำนวนเต็มต่ำสุดและสูงสุดเท่านั้น หากค่าต่ำสุดและสูงสุดเป็นแบบลอยจะไม่สามารถทำการดำเนินการ% ได้
Taioli Francesco

17
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)rand()/RAND_MAX;

       return (max - min +1)*scaled + min;
}

ดูตัวเลือกอื่น ๆ ได้ที่นี่


2
@ S.Lott - ไม่จริง แต่ละรายจะแจกแจงกรณีอัตราต่อรองที่สูงกว่าเล็กน้อยแตกต่างกันนั่นคือทั้งหมด คณิตศาสตร์คู่ให้ความรู้สึกว่ามีความแม่นยำมากกว่าที่นั่น แต่คุณสามารถใช้(((max-min+1)*rand())/RAND_MAX)+minและรับการแจกแจงแบบเดียวกันได้อย่างง่ายดาย(สมมติว่า RAND_MAX มีขนาดเล็กพอเมื่อเทียบกับ int เพื่อไม่ให้ล้น)
Steve314

4
นี้เป็นอันตรายเล็กน้อย: มันเป็นไปได้สำหรับการ (น้อยมาก) ผลตอบแทนmax + 1ถ้าอย่างใดอย่างหนึ่งrand() == RAND_MAXหรือrand()อยู่ใกล้กับและข้อผิดพลาดจุดลอยตัวผลักดันผลสุดท้ายที่ผ่านมาRAND_MAX max + 1เพื่อความปลอดภัยคุณควรตรวจสอบว่าผลลัพธ์อยู่ในระยะก่อนส่งคืน
Mark Dickinson

1
@ คริสตอฟ: ฉันเห็นด้วยเกี่ยวกับRAND_MAX + 1.0. ฉันยังไม่แน่ใจว่ามันดีพอที่จะป้องกันmax + 1ผลตอบแทนได้แม้ว่าโดยเฉพาะอย่างยิ่ง+ minในตอนท้ายนั้นเกี่ยวข้องกับรอบที่อาจทำให้เกิดmax + 1ค่า Rand () จำนวนมาก ปลอดภัยกว่าที่จะละทิ้งแนวทางนี้โดยสิ้นเชิงและใช้เลขคณิตจำนวนเต็ม
Mark Dickinson

3
หากRAND_MAXจะถูกแทนที่ด้วยRAND_MAX+1.0ขณะที่คริสโตแสดงให้เห็นแล้วผมเชื่อว่าสิ่งนี้มีความปลอดภัยโดยมีเงื่อนไขว่าจะกระทำโดยใช้จำนวนเต็มคณิตศาสตร์:+ min return (unsigned int)((max - min + 1) * scaled) + minเหตุผล (ไม่ชัดเจน) ก็คือสมมติว่า IEEE 754 เลขคณิตและครึ่งวงกลมถึงคู่ (และยังmax - min + 1สามารถแทนค่าได้ว่าเป็นคู่ แต่จะเป็นจริงในเครื่องทั่วไป) มันเป็นความจริงเสมอx * scaled < xสำหรับ บวกใด ๆ คู่xและคู่ใด ๆที่น่าพอใจscaled 0.0 <= scaled && scaled < 1.0
Mark Dickinson

1
ล้มเหลวสำหรับrandr(0, UINT_MAX): สร้าง 0 เสมอ
chux - คืนสถานะโมนิกา

12

คุณจะไม่ทำ:

srand(time(NULL));
int r = ( rand() % 6 ) + 1;

%เป็นตัวดำเนินการโมดูลัส โดยพื้นฐานแล้วมันจะหารด้วย 6 และส่งกลับส่วนที่เหลือ ... จาก 0 - 5


1
มันจะให้ผลลัพธ์ตั้งแต่ 1 - 6 นั่นคือสิ่งที่ +1 มีไว้สำหรับ
Armstrongest

4
Simon แสดง libc ที่ใช้งานได้ทุกที่ที่rand()มีบิตลำดับต่ำของสถานะของเครื่องกำเนิดไฟฟ้า (ถ้าใช้ LCG) ฉันยังไม่เคยเห็นเลย - ทั้งหมด (ใช่รวมถึง MSVC ที่ RAND_MAX เป็นเพียง 32767) ลบบิตลำดับต่ำออก ไม่แนะนำให้ใช้โมดูลัสด้วยเหตุผลอื่นกล่าวคือทำให้การแจกแจงเบ้ไปตามจำนวนที่น้อยกว่า
โจอี้

@ โจฮันเนส: ปลอดภัยที่จะบอกว่าเครื่องสล็อตไม่ใช้โมดูลัส?
Armstrongest

ฉันจะไม่รวม 0 ได้อย่างไร ดูเหมือนว่าถ้าฉันวิ่งวนเป็น 30 รอบบางทีครั้งที่สองหรือสามที่มันวิ่งจะมี 0 ประมาณครึ่งทาง นี่เป็นความบังเอิญบ้างไหม?
Jamie Keeling

@ โจฮันเนส: อาจจะไม่ใช่ปัญหามากนักในปัจจุบัน แต่โดยปกติแล้วไม่แนะนำให้ใช้บิตลำดับต่ำ c-faq.com/lib/randrange.html
jamesdlin

9

สำหรับผู้ที่เข้าใจปัญหาอคติ แต่ไม่สามารถทนต่อเวลาทำงานที่ไม่สามารถคาดเดาได้ของวิธีการที่ใช้การปฏิเสธชุดนี้จะสร้างจำนวนเต็มสุ่มแบบเอนเอียงน้อยลงใน[0, n-1]ช่วงเวลา:

r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...

มันไม่ได้โดย synthesising ความแม่นยำสูงคงที่จุดสุ่มi * log_2(RAND_MAX + 1)บิต (ซึ่งiคือจำนวนของการทำซ้ำ) nและการดำเนินการคูณยาวจาก

เมื่อจำนวนบิตมากพอเมื่อเทียบกับnอคติจะมีขนาดเล็กมาก

ไม่สำคัญว่าRAND_MAX + 1จะน้อยกว่าn(เช่นในคำถามนี้ ) หรือไม่ใช่ยกกำลังสอง แต่ต้องใช้ความระมัดระวังเพื่อหลีกเลี่ยงการล้นจำนวนเต็มหากRAND_MAX * nมีขนาดใหญ่


2
RAND_MAXมักจะเป็นINT_MAXเช่นนั้นRAND_MAX + 1-> UB (เช่น INT_MIN)
- Reinstate Monica

@chux นั่นคือสิ่งที่ฉันหมายถึง "ต้องระมัดระวังเพื่อหลีกเลี่ยงการล้นจำนวนเต็มหากRAND_MAX * nมีขนาดใหญ่" คุณต้องจัดให้ใช้ประเภทที่เหมาะสมกับความต้องการของคุณ
sh1

@chux " RAND_MAXมักจะINT_MAX" ใช่ แต่ในระบบ 16 บิตเท่านั้น! การเก็บถาวรที่ทันสมัยพอสมควรจะใส่ไว้INT_MAXที่ 2 ^ 32/2 และRAND_MAXที่ 2 ^ 16/2 เป็นสมมติฐานที่ไม่ถูกต้องหรือไม่?
แมว

2
@cat ทดสอบวันนี้ 2 intคอมไพเลอร์32 บิตฉันพบRAND_MAX == 32767ในหนึ่งและRAND_MAX == 2147483647ในอีกอันหนึ่ง ประสบการณ์โดยรวมของฉัน (ทศวรรษ) นั้น RAND_MAX == INT_MAXบ่อยขึ้น ดังนั้นไม่เห็นด้วยที่สถาปัตยกรรม 32 บิตที่ทันสมัยพอสมควรจะมีRAND_MAXที่2^16 / 2. เนื่องจากข้อมูลจำเพาะ C อนุญาต32767 <= RAND_MAX <= INT_MAXฉันจึงเขียนโค้ดไปตามนั้นแทนที่จะเป็นแนวโน้ม
chux - คืนสถานะ Monica

3
"ต้องใช้ความระมัดระวังเพื่อไม่ให้จำนวนเต็มล้น"
sh1

4

เพื่อหลีกเลี่ยงความลำเอียงของโมดูโล (แนะนำในคำตอบอื่น ๆ ) คุณสามารถใช้:

arc4random_uniform(MAX-MIN)+MIN

โดยที่ "MAX" คือขอบเขตบนและ "MIN" คือขอบเขตล่าง ตัวอย่างเช่นสำหรับตัวเลขระหว่าง 10 ถึง 20:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

วิธีง่ายๆและดีกว่าการใช้ "rand ()% N"


1
วู้ฮู้ดีกว่าคำตอบอื่น ๆ พันล้านเท่า สิ่งที่ควรทราบ#include <bsd/stdlib.h>ก่อนอื่น นอกจากนี้ความคิดใด ๆ ที่จะรับสิ่งนี้บน Windows โดยไม่ใช้ MinGW หรือ CygWin?
แมว

1
ไม่มันไม่ได้ดีไปกว่าคำตอบอื่น ๆ เพราะคำตอบอื่น ๆ นั้นเป็นแบบทั่วไปมากกว่า ที่นี่คุณถูก จำกัด ไว้ที่ arc4random คำตอบอื่น ๆ ช่วยให้คุณสามารถเลือกแหล่งที่มาแบบสุ่มอื่นใช้งานกับประเภทตัวเลขที่แตกต่างกัน ... และสุดท้าย แต่ไม่ท้ายสุดอาจช่วยให้ใครบางคนเข้าใจปัญหาได้ อย่าลืมว่าคำถามนี้น่าสนใจสำหรับคนอื่น ๆ ที่อาจมีความต้องการพิเศษบางอย่างหรือไม่มีสิทธิ์เข้าถึง arc4random ... อย่างไรก็ตามหากคุณสามารถเข้าถึงได้และต้องการวิธีแก้ปัญหาที่รวดเร็วมันก็เป็นคำตอบที่ดีมาก😊
K. Biermann

4

นี่คืออัลกอริทึมที่ง่ายกว่าโซลูชันของ Ryan Reich เล็กน้อย:

/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
    uint32_t range = (end - begin) + 1;
    uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);

    /* Imagine range-sized buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    uint32_t randVal = rand();
    while (randVal >= limit) randVal = rand();

    /// Return the position you hit in the bucket + begin as random number
    return (randVal % range) + begin;
}

Example (RAND_MAX := 16, begin := 2, end := 7)
    => range := 6  (1 + end - begin)
    => limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)

The limit is always a multiple of the range,
so we can split it into range-sized buckets:
    Possible-rand-output: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
    Buckets:             [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
    Buckets + begin:     [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]

1st call to rand() => 13
     13 is not in the bucket-range anymore (>= limit), while-condition is true
         retry...
2nd call to rand() => 7
     7 is in the bucket-range (< limit), while-condition is false
         Get the corresponding bucket-value 1 (randVal % range) and add begin
    => 3

1
RAND_MAX + 1สามารถintเติมล้นได้อย่างง่ายดาย ในกรณีนั้น(RAND_MAX + 1) % rangeจะสร้างผลลัพธ์ที่น่าสงสัย พิจารณา(RAND_MAX + (uint32_t)1)
chux - คืนสถานะ Monica

2

แม้ว่า Ryan จะถูกต้อง แต่การแก้ปัญหาอาจง่ายกว่ามากโดยอาศัยสิ่งที่ทราบเกี่ยวกับแหล่งที่มาของการสุ่ม ในการระบุปัญหาอีกครั้ง:

  • มีแหล่งที่มาของการสุ่มแสดงตัวเลขจำนวนเต็มในช่วงที่[0, MAX)มีการแจกแจงแบบสม่ำเสมอ
  • เป้าหมายของเราคือการผลิตการกระจายอย่างสม่ำเสมอตัวเลขจำนวนเต็มแบบสุ่มในช่วงที่[rmin, rmax]0 <= rmin < rmax < MAX

จากประสบการณ์ของฉันถ้าจำนวนถังขยะ (หรือ "กล่อง") มีขนาดเล็กกว่าช่วงของตัวเลขดั้งเดิมอย่างมีนัยสำคัญและแหล่งที่มาดั้งเดิมมีความแข็งแกร่งในการเข้ารหัส - ไม่จำเป็นต้องทำผ่าน rigamarole ทั้งหมดและการแบ่งโมดูโลอย่างง่ายจะ เพียงพอ (เช่นoutput = rnd.next() % (rmax+1)ถ้าrmin == 0) และสร้างตัวเลขสุ่มที่กระจายอย่างสม่ำเสมอ "เพียงพอ" และไม่มีการสูญเสียความเร็วใด ๆ ปัจจัยสำคัญคือแหล่งที่มาของการสุ่ม (เช่นเด็ก ๆ อย่าลองทำที่บ้านด้วยrand())

นี่คือตัวอย่าง / หลักฐานว่ามันทำงานอย่างไรในทางปฏิบัติ ฉันต้องการสร้างตัวเลขสุ่มตั้งแต่ 1 ถึง 22 โดยมีแหล่งที่มาที่แข็งแกร่งในการเข้ารหัสที่สร้างไบต์แบบสุ่ม (อิงจาก Intel RDRAND) ผลลัพธ์คือ:

Rnd distribution test (22 boxes, numbers of entries in each box):     
 1: 409443    4.55%
 2: 408736    4.54%
 3: 408557    4.54%
 4: 409125    4.55%
 5: 408812    4.54%
 6: 409418    4.55%
 7: 408365    4.54%
 8: 407992    4.53%
 9: 409262    4.55%
10: 408112    4.53%
11: 409995    4.56%
12: 409810    4.55%
13: 409638    4.55%
14: 408905    4.54%
15: 408484    4.54%
16: 408211    4.54%
17: 409773    4.55%
18: 409597    4.55%
19: 409727    4.55%
20: 409062    4.55%
21: 409634    4.55%
22: 409342    4.55%   
total: 100.00%

สิ่งนี้ใกล้เคียงกับเครื่องแบบเท่าที่ฉันต้องการสำหรับจุดประสงค์ของฉัน (การโยนลูกเต๋าที่ยุติธรรมการสร้างโค้ดบุ๊กที่มีการเข้ารหัสที่แข็งแกร่งสำหรับเครื่องเข้ารหัสสมัยสงครามโลกครั้งที่สองเช่นhttp://users.telenet.be/d.rijmenants/en/kl-7sim.htmฯลฯ ). ผลลัพธ์ไม่แสดงอคติใด ๆ ที่เห็นได้ชัด

นี่คือที่มาของตัวสร้างตัวเลขสุ่มที่แข็งแกร่ง (จริง) ที่เข้ารหัสลับ: Intel Digital Random Number Generator และโค้ดตัวอย่างที่สร้างตัวเลขสุ่ม 64 บิต (ไม่ได้ลงชื่อ)

int rdrand64_step(unsigned long long int *therand)
{
  unsigned long long int foo;
  int cf_error_status;

  asm("rdrand %%rax; \
        mov $1,%%edx; \
        cmovae %%rax,%%rdx; \
        mov %%edx,%1; \
        mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
        *therand = foo;
  return cf_error_status;
}

ฉันรวบรวมบน Mac OS X ด้วย clang-6.0.1 (ตรง) และด้วย gcc-4.8.3 โดยใช้แฟล็ก "-Wa, q" (เนื่องจาก GAS ไม่รองรับคำแนะนำใหม่เหล่านี้)


คอมไพล์ด้วยgcc randu.c -o randu -Wa,q(GCC 5.3.1 บน Ubuntu 16) หรือclang randu.c -o randu(Clang 3.8.0) ใช้งานได้ แต่ทิ้งคอร์ที่รันไทม์ด้วยIllegal instruction (core dumped). ความคิดใด ๆ ?
แมว

ก่อนอื่นฉันไม่รู้ว่า CPU ของคุณรองรับคำสั่ง RDRAND จริงหรือไม่ ระบบปฏิบัติการของคุณค่อนข้างใหม่ล่าสุด แต่ CPU อาจไม่ใช่ ประการที่สอง (แต่มีโอกาสน้อยกว่า) - ฉันไม่รู้ว่า Ubuntu มีแอสเซมเบลอร์ประเภทใด (และ Ubuntu มีแนวโน้มที่จะย้อนกลับไปข้างหลังการอัปเดตแพ็คเกจ) ตรวจสอบไซต์ Intel ที่ฉันอ้างถึงเพื่อดูวิธีทดสอบว่า CPU ของคุณรองรับ RDRAND หรือไม่
เมาส์

คุณมีจุดที่ดีแน่นอน rand()สิ่งที่ฉันยังไม่สามารถรับคือสิ่งที่เป็นไปอย่างผิดปกติกับ ฉันลองทดสอบและโพสต์คำถามนี้แล้วแต่ยังหาคำตอบที่ชัดเจนไม่ได้
myradio

1

อย่างที่กล่าวไว้ก่อนหน้านี้ว่าโมดูโลไม่เพียงพอเพราะมันทำให้การกระจายตัวเบ้ นี่คือรหัสของฉันซึ่งปิดบังบิตและใช้เพื่อให้แน่ใจว่าการกระจายไม่บิดเบี้ยว

static uint32_t randomInRange(uint32_t a,uint32_t b) {
    uint32_t v;
    uint32_t range;
    uint32_t upper;
    uint32_t lower;
    uint32_t mask;

    if(a == b) {
        return a;
    }

    if(a > b) {
        upper = a;
        lower = b;
    } else {
        upper = b;
        lower = a; 
    }

    range = upper - lower;

    mask = 0;
    //XXX calculate range with log and mask? nah, too lazy :).
    while(1) {
        if(mask >= range) {
            break;
        }
        mask = (mask << 1) | 1;
    }


    while(1) {
        v = rand() & mask;
        if(v <= range) {
            return lower + v;
        }
    }

}

รหัสง่ายๆต่อไปนี้ให้คุณดูการกระจาย:

int main() {

    unsigned long long int i;


    unsigned int n = 10;
    unsigned int numbers[n];


    for (i = 0; i < n; i++) {
        numbers[i] = 0;
    }

    for (i = 0 ; i < 10000000 ; i++){
        uint32_t rand = random_in_range(0,n - 1);
        if(rand >= n){
            printf("bug: rand out of range %u\n",(unsigned int)rand);
            return 1;
        }
        numbers[rand] += 1;
    }

    for(i = 0; i < n; i++) {
        printf("%u: %u\n",i,numbers[i]);
    }

}

กลายเป็นไม่มีประสิทธิภาพมากเมื่อคุณปฏิเสธตัวเลขจาก Rand () สิ่งนี้จะไม่มีประสิทธิภาพโดยเฉพาะอย่างยิ่งเมื่อช่วงมีขนาดที่สามารถเขียนได้เป็น 2 ^ k + 1 จากนั้นเกือบครึ่งหนึ่งของความพยายามทั้งหมดของคุณจากการเรียก Rand () ที่ช้าจะถูกปฏิเสธโดยเงื่อนไข จะดีกว่าไหมถ้าคำนวณช่วงโมดูโล RAND_MAX ชอบ: v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;ฉันเข้าใจว่า modulo นั้นทำงานได้ช้ากว่าการมาสก์ แต่ฉันก็ยังคิดว่า ..... มันควรจะทดสอบ
ØysteinSchønning-Johansen

rand()ส่งกลับในช่วงint [0..RAND_MAX]ช่วงนั้นสามารถเป็นช่วงย่อยได้อย่างง่ายดายuint32_tและrandomInRange(0, ,b)ไม่สร้างค่าในช่วง(INT_MAX...b]นั้น
chux - คืนสถานะ Monica

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.