สร้างตัวเลขสุ่มอย่างสม่ำเสมอตลอดทั้งช่วง


95

ฉันต้องการสร้างตัวเลขสุ่มภายในช่วงเวลาที่กำหนด [max; min]

นอกจากนี้ควรกระจายตัวเลขสุ่มอย่างสม่ำเสมอตลอดช่วงเวลาไม่ได้อยู่ที่จุดใดจุดหนึ่ง

Currenly ฉันกำลังสร้างเป็น:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

จากการทดสอบของฉันตัวเลขสุ่มจะถูกสร้างขึ้นเพียงจุดเดียวเท่านั้น

Example
min = 3604607;
max = 7654607;

สร้างตัวเลขสุ่ม:

3631594
3609293
3630000
3628441
3636376
3621404

จากคำตอบด้านล่าง: ตกลง RAND_MAX คือ 32767 ฉันอยู่บนแพลตฟอร์ม C ++ Windows มีวิธีอื่นในการสร้างตัวเลขสุ่มที่มีการแจกแจงสม่ำเสมอหรือไม่?


2
สร้าง Dice-O-Matic: gamesbyemail.com/News/DiceOMatic
Jarett Millard

1
ฉันไม่รู้เลยว่า C ++ rand()นั้นเหมือนกัน คุณใช้ห้องสมุดใด cstdlib.h's rand()ไม่สม่ำเสมอ: cplusplus.com/reference/cstdlib/rand
ไมค์วอร์เรน

3
ไม่ Rand () จะเหมือนกัน (ยกเว้นในการใช้งานบั๊กกี้ในช่วงแรก ๆ ) สิ่งที่ไม่สม่ำเสมอคือการใช้ตัวดำเนินการโมดูลัส '%' เพื่อ จำกัด ช่วง โปรดดูstackoverflow.com/questions/2999075/…สำหรับวิธีแก้ปัญหาที่เหมาะสมหรือหากคุณมี "arc4random_uniform" ก็สามารถใช้งานได้โดยตรงเช่นกัน
John Meacham

@ Alien01: คุณจะพิจารณาเปลี่ยนคำตอบที่ยอมรับเป็น "Shoe" ("ทำไมแรนด์ถึงเป็นความคิดที่ไม่ดี" ฯลฯ .. ) คำตอบของฉันล้าสมัยจริงๆและทุกครั้งที่ฉันได้รับการโหวตฉันรู้สึกเหมือนมีคนวิ่งไปตามทางเดินที่ไม่ถูกต้อง
peterchen

กระดาษสีขาวที่ดีเกี่ยวกับการสุ่มใน c ++ 11
Pupsik

คำตอบ:


159

ทำไมrandความคิดที่ไม่ดี

คำตอบส่วนใหญ่ที่คุณได้รับที่นี่ใช้ประโยชน์จากrandฟังก์ชันและตัวดำเนินการโมดูลัส วิธีนี้อาจสร้างตัวเลขไม่เท่ากัน (ขึ้นอยู่กับช่วงและค่าของRAND_MAX) ดังนั้นจึงไม่แนะนำ

C ++ 11 และการสร้างในช่วง

ด้วย C ++ 11 ตัวเลือกอื่น ๆ ได้เพิ่มขึ้น std::uniform_int_distributionซึ่งหนึ่งในนั้นเหมาะกับความต้องการของคุณสำหรับการสร้างตัวเลขแบบสุ่มในช่วงสวยอย่าง: นี่คือตัวอย่าง:

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  rand_dev;
std::mt19937                        generator(rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

และนี่คือตัวอย่างการทำงาน

ฟังก์ชันเทมเพลตอาจช่วยได้บ้าง:

template<typename T>
T random(T range_from, T range_to) {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<T>    distr(range_from, range_to);
    return distr(generator);
}

เครื่องกำเนิดไฟฟ้าแบบสุ่มอื่น ๆ

<random>ส่วนหัวมีอื่น ๆ อีกนับไม่ถ้วนกำเนิดจำนวนสุ่มที่มีชนิดที่แตกต่างกันของการกระจายรวมทั้ง Bernoulli, Poisson และปกติ

ฉันจะสับเปลี่ยนคอนเทนเนอร์ได้อย่างไร

มาตรฐานให้std::shuffleซึ่งสามารถใช้ได้ดังต่อไปนี้:

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

อัลกอริทึมจะเรียงลำดับองค์ประกอบใหม่แบบสุ่มโดยมีความซับซ้อนเชิงเส้น

เพิ่ม. สุ่ม

อีกหนึ่งทางเลือกในกรณีที่คุณไม่ได้เข้าสู่ C ++ 11 + คอมไพเลอร์คือการใช้Boost.Random อินเทอร์เฟซคล้ายกับ C ++ 11 อย่างมาก


24
ให้ความสนใจกับคำตอบนี้เนื่องจากมันทันสมัยกว่ามาก
gsamaras

นี่คือคำตอบที่ใช่ ขอบคุณ! อย่างไรก็ตามฉันต้องการดูคำอธิบายเชิงลึกในทุกขั้นตอนของโค้ดนั้น เช่นmt19937ประเภทคืออะไร?
Apollo

@ Apollo เอกสารระบุว่า "Mersenne Twister 32 บิตโดย Matsumoto and Nishimura, 1998" ฉันสมมติว่าเป็นอัลกอริทึมในการสร้างตัวเลขสุ่มหลอก
รองเท้า

@Shoe 1 9 6 2 8 7 1 4 7 7สำหรับช่วงที่กำหนดจะสร้างตัวเลขในลำดับเดียวกัน คุณจะสุ่มทุกครั้งที่เรารันโปรแกรมได้อย่างไร?

1
@ ริชาร์ดทางเลือกคืออะไร?
รองเท้า

61

[แก้ไข] คำเตือน: ห้ามใช้rand()เพื่อสถิติการจำลองการเข้ารหัสหรือสิ่งที่ร้ายแรง

มันดีพอที่จะทำให้ตัวเลขดูสุ่มสำหรับมนุษย์ทั่วไปที่กำลังเร่งรีบไม่ต้องรออีกต่อไป

ดูคำตอบของ @Jefffreyสำหรับตัวเลือกที่ดีกว่าหรือคำตอบนี้สำหรับตัวเลขสุ่มที่ปลอดภัยด้วยการเข้ารหัสลับ


โดยทั่วไปบิตสูงจะแสดงการกระจายที่ดีกว่าบิตต่ำดังนั้นวิธีที่แนะนำในการสร้างตัวเลขสุ่มของช่วงเพื่อจุดประสงค์ง่ายๆคือ:

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

หมายเหตุ : ตรวจสอบให้แน่ใจว่า RAND_MAX + 1 ไม่ล้น (ขอบคุณ Demi)!

การหารสร้างตัวเลขสุ่มในช่วงเวลา [0, 1); "ยืด" เป็นช่วงที่ต้องการ เฉพาะเมื่อสูงสุด - นาที + 1 เข้าใกล้ RAND_MAX คุณต้องมีฟังก์ชัน "BigRand ()" เหมือนที่โพสต์โดย Mark Ransom

นอกจากนี้ยังช่วยหลีกเลี่ยงปัญหาการหั่นบางอย่างเนื่องจากโมดูโลซึ่งอาจทำให้ตัวเลขของคุณแย่ลงมากขึ้น


เครื่องสร้างตัวเลขสุ่มในตัวไม่รับประกันว่าจะมีคุณภาพที่จำเป็นสำหรับการจำลองทางสถิติ เป็นเรื่องปกติสำหรับตัวเลขที่จะ "ดูสุ่ม" สำหรับมนุษย์ แต่สำหรับการใช้งานที่จริงจังคุณควรใช้สิ่งที่ดีกว่าหรืออย่างน้อยก็ตรวจสอบคุณสมบัติของมัน (การแจกแจงแบบสม่ำเสมอมักจะดี แต่ค่ามักจะสัมพันธ์กันและลำดับจะถูกกำหนด ). Knuth มีบทความที่ยอดเยี่ยม (ถ้าอ่านยาก) เกี่ยวกับเครื่องกำเนิดตัวเลขแบบสุ่มและเมื่อเร็ว ๆ นี้ฉันพบว่าLFSRนั้นยอดเยี่ยมและใช้งานง่ายเนื่องจากคุณสมบัติของมันนั้นใช้ได้สำหรับคุณ


4
BigRand สามารถให้ผลลัพธ์ที่ดีกว่าแม้ว่าช่วงที่ต้องการจะไม่เกิน RAND_MAX ก็ตาม พิจารณาเมื่อ RAND_MAX เป็น 32767 และคุณต้องการ 32767 ค่าที่เป็นไปได้ - ตัวเลขสุ่ม 32768 สองตัว (รวมศูนย์) จะแมปกับเอาต์พุตเดียวกันและจะมีโอกาสเกิดขึ้นเป็นสองเท่าเมื่อเทียบกับค่าอื่น ๆ แทบจะเป็นคุณสมบัติสุ่มในอุดมคติ!
Mark Ransom

8
(RAND_MAX + 1) เป็นความคิดที่ไม่ดี ซึ่งสามารถโรลโอเวอร์และให้ค่าติดลบ ดีกว่าที่จะทำสิ่งที่ชอบ: ((double) RAND_MAX) + 1.0
Demi

4
@peterchen: ฉันคิดว่าคุณเข้าใจผิดว่าเดมี่พูดอะไร เธอหมายถึงสิ่งนี้: ( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min เพียงแค่ย้าย Conversion เป็นสองเท่าและหลีกเลี่ยงปัญหา
Mooing Duck

3
นอกจากนี้สิ่งนี้เป็นเพียงการเปลี่ยนการแจกแจงจากค่า 32767 ด้านล่างในช่วงเป็นการกระจายค่า 32767 เท่า ๆ กันในช่วงและค่า 4017233 ที่เหลือจะไม่ถูกเลือกโดยอัลกอริทึมนี้
Mooing Duck

2
คำตอบที่ให้มาคือปิดด้วย 1 สมการที่ถูกต้องคือ: ((double) rand () / (RAND_MAX + 1.0)) * (max-min) + min "max-min + 1" ถูกใช้เมื่อใช้% not * . คุณจะเห็นว่าทำไมเมื่อคุณทำ min = 0, max = 1 peterchen หรือ @ peter-mortensen สามารถแก้ไขได้
davepc

18

ฉันต้องการเติมเต็มคำตอบที่ยอดเยี่ยมของ Angry Shoe และ peterchen ด้วยภาพรวมสั้น ๆ เกี่ยวกับความทันสมัยในปี 2015:

ทางเลือกที่ดีบางอย่าง

randutils

randutilsห้องสมุด(นำเสนอ)เป็นความแปลกใหม่ที่น่าสนใจที่นำเสนออินเตอร์เฟซที่เรียบง่ายและ (ประกาศ) ความสามารถในการสุ่มที่แข็งแกร่ง มีข้อเสียที่เพิ่มการพึ่งพาโครงการของคุณและยังใหม่ไม่ได้รับการทดสอบอย่างกว้างขวาง อย่างไรก็ตามการฟรี (ใบอนุญาต MIT) และส่วนหัวเท่านั้นฉันคิดว่ามันคุ้มค่าที่จะลอง

ตัวอย่างน้อยที่สุด: ม้วนตาย

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

แม้ว่าใครจะไม่สนใจห้องสมุด แต่เว็บไซต์ ( http://www.pcg-random.org/ ) มีบทความที่น่าสนใจมากมายเกี่ยวกับรูปแบบของการสร้างตัวเลขสุ่มโดยทั่วไปและไลบรารี C ++ โดยเฉพาะ

เพิ่ม. สุ่ม

Boost.Random (เอกสาร)เป็นห้องสมุดที่เป็นแรงบันดาลใจC++11's <random>ซึ่งหุ้นมากของอินเตอร์เฟซ ในขณะที่ในทางทฤษฎียังเป็นการพึ่งพาภายนอก แต่Boostตอนนี้มีสถานะเป็นไลบรารี "กึ่งมาตรฐาน" และRandomโมดูลของมันอาจถือได้ว่าเป็นตัวเลือกคลาสสิกสำหรับการสร้างตัวเลขสุ่มที่มีคุณภาพดี มีข้อดีสองประการเกี่ยวกับC++11โซลูชัน:

  • พกพาสะดวกมากขึ้นเพียงแค่ต้องการการสนับสนุนคอมไพเลอร์สำหรับ C ++ 03
  • random_deviceใช้วิธีการเฉพาะของระบบเพื่อให้ได้เมล็ดที่มีคุณภาพดี

ข้อบกพร่องเล็ก ๆ เพียงอย่างเดียวคือการเสนอขายโมดูลไม่ได้เป็นส่วนหัวเท่านั้นหนึ่งมีการรวบรวมและเชื่อมโยงrandom_deviceboost_random

ตัวอย่างน้อยที่สุด: ม้วนตาย

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

แม้ว่าตัวอย่างขั้นต่ำจะทำงานได้ดี แต่โปรแกรมจริงควรใช้การปรับปรุงสองอย่าง:

  • ทำให้: กำเนิดคืออวบค่อนข้าง (> 2 KB) และจะดีกว่าไม่ได้จัดสรรในกองmt19937thread_local
  • เมล็ดพันธุ์ที่mt19937มีจำนวนเต็มมากกว่าหนึ่งจำนวน: Mersenne Twister มีสถานะใหญ่และสามารถใช้ประโยชน์จากเอนโทรปีได้มากขึ้นในระหว่างการเริ่มต้น

ทางเลือกที่ไม่ดีบางอย่าง

ไลบรารี C ++ 11

แม้ว่าจะเป็นวิธีการแก้ปัญหาที่เป็นสำนวนที่สุด แต่<random>ห้องสมุดก็ไม่ได้เสนออะไรมากมายเพื่อแลกกับความซับซ้อนของอินเทอร์เฟซแม้แต่กับความต้องการขั้นพื้นฐาน ข้อบกพร่องอยู่ที่std::random_device: Standard ไม่ได้กำหนดคุณภาพขั้นต่ำสำหรับเอาต์พุต (ตราบเท่าที่entropy()ส่งคืน0) และในปี 2015 MinGW (ไม่ใช่คอมไพเลอร์ที่ใช้มากที่สุด แต่แทบจะไม่เป็นตัวเลือกที่ลึกลับ) จะพิมพ์4บนตัวอย่างที่น้อยที่สุดเสมอ

ตัวอย่างน้อยที่สุด: ม้วนตาย

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

หากการใช้งานไม่เน่าเสียโซลูชันนี้ควรเทียบเท่ากับ Boost หนึ่งและใช้คำแนะนำเดียวกัน

วิธีแก้ปัญหาของ Godot

ตัวอย่างน้อยที่สุด: ม้วนตาย

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

นี่เป็นวิธีแก้ปัญหาที่ง่ายมีประสิทธิภาพและเรียบร้อย มีข้อบกพร่องเท่านั้นจะใช้เวลารวบรวมสักครู่ - ประมาณสองปีโดยให้ C ++ 17 ออกตามเวลาและrandintฟังก์ชันการทดลองได้รับการอนุมัติในมาตรฐานใหม่ ในเวลานั้นการรับประกันคุณภาพการเพาะเมล็ดจะดีขึ้นด้วย

เลวร้ายเป็นดีกว่าการแก้ปัญหา

ตัวอย่างน้อยที่สุด: ม้วนตาย

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
    std::srand(std::time(nullptr));
    std::cout << (std::rand() % 6 + 1);
}

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

โซลูชันการหมุนรอบบัญชี

ตัวอย่างน้อยที่สุด: ม้วนตาย

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

ในขณะที่ 9 เป็นผลลัพธ์ที่ค่อนข้างผิดปกติสำหรับการม้วนแบบธรรมดา แต่เราต้องชื่นชมการผสมผสานที่ยอดเยี่ยมของคุณสมบัติที่ดีในโซลูชันนี้ซึ่งจัดการได้เร็วที่สุดง่ายที่สุดเป็นมิตรกับแคชและพกพาได้มากที่สุด ด้วยการแทนที่ 9 ด้วย 4 อันจะได้เครื่องกำเนิดไฟฟ้าที่สมบูรณ์แบบสำหรับ Dungeons and Dragons ทุกชนิดในขณะที่ยังคงหลีกเลี่ยงค่าสัญลักษณ์ที่รับภาระ 1, 2 และ 3 ข้อบกพร่องเล็ก ๆ เพียงอย่างเดียวคือเนื่องจากอารมณ์ไม่ดีของโทรลล์บัญชีของ Dilbert โปรแกรมนี้ก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนด


randutilsห้องสมุดเรียกว่า PCG ในขณะนี้
tay10r

10

ถ้าRAND_MAXเป็น 32767 คุณสามารถเพิ่มจำนวนบิตเป็นสองเท่าได้อย่างง่ายดาย

int BigRand()
{
    assert(INT_MAX/(RAND_MAX+1) > RAND_MAX);
    return rand() * (RAND_MAX+1) + rand();
}

ฉันไม่คิดว่าจะได้ผล เครื่องกำเนิดตัวเลขสุ่มหลอกมักจะถูกกำหนด ตัวอย่างเช่นหากการrandโทรครั้งแรกส่งกลับ0x1234และครั้งที่สอง0x56780x12345678แล้วที่คุณได้รับ นั่นคือเพียงตัวเลขที่คุณจะได้รับการเริ่มต้นด้วยเพราะหมายเลขถัดไปจะเป็น0x1234 0x5678คุณจะได้ผลลัพธ์ 32 บิต แต่คุณมีตัวเลขที่เป็นไปได้ 32768 เท่านั้น
user694733

@ user694733 เครื่องกำเนิดไฟฟ้าจำนวนสุ่มที่ดีมีระยะเวลาที่มากกว่าจำนวนของผลก็สามารถสร้างดังนั้น 0x1234 จะไม่เสมอจะตามมาด้วย 0x5678
Mark Ransom

9

ถ้าคุณมีความสามารถที่จะใช้เพิ่ม ฉันมีโชคดีกับพวกเขาห้องสมุดสุ่ม

uniform_int ควรทำในสิ่งที่คุณต้องการ


ฉันได้ทำงานบางอย่างกับ uniform_int ด้วย merseinne twister และน่าเสียดายสำหรับบางช่วงค่าที่ส่งคืนโดย uniform_int ไม่สม่ำเสมอเท่าที่ฉันคาดหวัง ตัวอย่างเช่น uniform_int <> (0, 3) มีแนวโน้มที่จะสร้างมากกว่า 0 มากกว่า 1 หรือ 2
ScaryAardvark

@ScaryAardvark เสียงที่เหมือนการดำเนินงานที่ไม่ดีของuniform_intแล้ว มันค่อนข้างง่ายในการสร้างผลลัพธ์ที่เป็นกลางมีคำถามมากมายที่นี่ซึ่งแสดงให้เห็นถึงวิธีการ
Mark Ransom

@ มาร์คไถ่. ใช่ฉันเห็นด้วยอย่างยิ่ง
ScaryAardvark

8

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

คุณยังสามารถเขียนของคุณเองโดยใช้อัลกอริทึมการเข้ารหัส (เช่นAES ) โดยการเลือกเมล็ดพันธุ์และIVจากนั้นเข้ารหัสเอาต์พุตของฟังก์ชันการเข้ารหัสใหม่อย่างต่อเนื่อง การใช้ OpenSSL นั้นง่ายกว่า แต่มีความเป็นผู้ชายน้อยกว่า


ฉันไม่สามารถใช้ไลบรารีของบุคคลที่สามได้ฉันถูก จำกัด ไว้ที่ C ++ เท่านั้น
anand

จากนั้นไปตามเส้นทางของลูกผู้ชายใช้ AES หรืออัลกอริทึมการเข้ารหัสอื่น ๆ
SoapBox

2
RC4 มีความสำคัญต่อรหัสและสุ่มเพียงพอสำหรับวัตถุประสงค์ในทางปฏิบัติทั้งหมด (ยกเว้น WEP แต่นั่นไม่ใช่ความผิดของ RC4 ทั้งหมด) ฉันหมายความว่ามันเป็นรหัสที่ไม่สำคัญอย่างยิ่ง ชอบ 20 บรรทัดหรือมากกว่านั้น รายการ Wikipedia มีรหัสหลอก
Steve Jessop

4
ทำไมคุณไม่สามารถใช้รหัสของบุคคลที่สามได้? หากเป็นคำถามสำหรับการบ้านคุณควรพูดเช่นนั้นเพราะหลายคนอยากจะให้คำแนะนำที่เป็นประโยชน์แทนที่จะให้คำตอบที่สมบูรณ์ในกรณีนี้ ถ้าไม่ใช่การบ้านให้ไปเตะผู้ชายที่บอกว่า "ไม่มีรหัสบุคคลที่สาม" เพราะเขาเป็นคนปัญญาอ่อน
DevSolar

ลิงก์โดยตรงเพิ่มเติมไปยังเอกสารฟังก์ชัน OpenSSL rand (): openssl.org/docs/crypto/rand.html#
DevSolar

5

คุณควรมองหาRAND_MAXคอมไพเลอร์ / สภาพแวดล้อมเฉพาะของคุณ ฉันคิดว่าคุณจะเห็นผลลัพธ์เหล่านี้ถ้าrand()สร้างตัวเลข 16 บิตแบบสุ่ม (ดูเหมือนคุณจะสมมติว่ามันเป็นตัวเลข 32 บิต)

ฉันไม่สามารถสัญญาว่านี่คือคำตอบ แต่โปรดโพสต์คุณค่าของRAND_MAXคุณและรายละเอียดเพิ่มเติมเล็กน้อยเกี่ยวกับสภาพแวดล้อมของคุณ


3

ตรวจสอบอะไร RAND_MAXอยู่ในระบบของคุณ - ฉันเดาว่ามันเป็นเพียง 16 บิตและช่วงของคุณใหญ่เกินไปสำหรับมัน

นอกเหนือจากนั้นดูการอภิปรายนี้เมื่อ: ฝ่ายผลิตสุ่มจำนวนเต็มภายในที่ต้องการอยากมีช่วงและบันทึกเกี่ยวกับการใช้ (หรือไม่) เดอะแรนด์ C () ฟังก์ชั่น


ตกลง RAND_MAX คือ 32767 ฉันอยู่บนแพลตฟอร์ม Windows C ++ .. มีวิธีอื่นในการสร้างตัวเลขสุ่มที่มีการกระจายสม่ำเสมอหรือไม่?
anand

2

นี่ไม่ใช่รหัส แต่ตรรกะนี้อาจช่วยคุณได้

static double rnd(void)
{
   return (1.0 / (RAND_MAX + 1.0) * ((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
    register int i;
    srand( seed );
    for( i = 0; i < POOLSIZE; i++){
        pool[i] = rnd();
    }
}

 // This function returns a number between 0 and 1
 static double rnd0_1(void)
 {
    static int i = POOLSIZE-1;
    double r;

    i = (int)(POOLSIZE*pool[i]);
    r = pool[i];
    pool[i] = rnd();
    return (r);
}

2

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

ในฐานะที่เป็นข้อสังเกตอื่นคุณไม่ควรใช้rand()เนื่องจากการสร้างตัวเลขสุ่มนั้นไม่ดีนัก ฉันไม่รู้ว่าคุณกำลังทำงานบนแพลตฟอร์มอะไร แต่อาจมีฟังก์ชันที่ดีกว่าที่คุณสามารถเรียกrandom()ใช้ได้


1

สิ่งนี้ควรให้การกระจายที่สม่ำเสมอตลอดช่วง[low, high)โดยไม่ใช้การลอยตราบใดที่ช่วงโดยรวมน้อยกว่า RAND_MAX

uint32_t rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= RAND_MAX
    assert(low < high);
    assert(high - low <= RAND_MAX);

    uint32_t range = high-low;
    uint32_t scale = RAND_MAX/range;
    do {
        val = rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

และสำหรับค่าที่มากกว่า RAND_MAX ที่คุณต้องการ

uint32_t rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < RAND_MAX)
        return rand_range_low(low, high);
    uint32_t scale = range/RAND_MAX;
    do {
        val = rand() + rand_range(0, scale) * RAND_MAX; // scale the initial range in RAND_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

นี่คือคร่าวๆว่า std :: uniform_int_distribution ทำสิ่งต่างๆอย่างไร


0

โดยธรรมชาติแล้วตัวอย่างตัวเลขสุ่มขนาดเล็กไม่จำเป็นต้องกระจายอย่างสม่ำเสมอ พวกเขาเป็นแบบสุ่มหลังจากทั้งหมด ฉันยอมรับว่าหากตัวสร้างตัวเลขสุ่มสร้างตัวเลขที่ดูเหมือนจะถูกจัดกลุ่มอย่างต่อเนื่องแสดงว่าอาจมีบางอย่างผิดปกติ

แต่จำไว้ว่าการสุ่มไม่จำเป็นต้องเหมือนกัน

แก้ไข: ฉันเพิ่ม "ตัวอย่างเล็ก ๆ " เพื่อชี้แจง


"กระจายสม่ำเสมอ" มีความหมายที่ชัดเจนและเครื่องกำเนิดไฟฟ้าแบบสุ่มมาตรฐานมักจะเข้ามาใกล้
peterchen

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

Kluge พูดถูก การแจกแจงแบบสม่ำเสมอในตัวอย่างขนาดเล็กบ่งชี้ว่าตัวอย่างนั้นไม่ใช่การสุ่มอย่างแน่นอน
Bill the Lizard

1
บิลมันบ่งบอกว่าไม่มีสิ่งนั้น ตัวอย่างขนาดเล็กส่วนใหญ่ไม่มีความหมาย แต่ถ้า RNG ควรจะสม่ำเสมอและเอาต์พุตมีความสม่ำเสมอเหตุใดจึงเลวร้ายยิ่งกว่าตัวอย่างขนาดเล็กที่ไม่สม่ำเสมอ
Dan Dyer

2
การแจกแจงอย่างมีนัยสำคัญทางใดทางหนึ่งบ่งชี้ว่าไม่ใช่การสุ่ม: ฉันคิดว่าบิลแค่หมายความว่า 6 ผลลัพธ์ที่เว้นระยะเท่ากันก็น่าสงสัยเช่นกัน ใน OP ค่า 6 จะอยู่ในช่วง 32k / 4M หรือ <1% ของช่วงที่ต้องการ ความน่าจะเป็นที่จะเป็นผลบวกลวงนั้นน้อยเกินไปที่จะโต้แย้ง
Steve Jessop

0

คำตอบที่กำหนดโดยman 3 randสำหรับตัวเลขระหว่าง 1 ถึง 10 ได้แก่ :

j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

ในกรณีของคุณมันจะเป็น:

j = min + (int) ((max-min+1) * (rand() / (RAND_MAX + 1.0)));

แน่นอนว่านี่ไม่ใช่การสุ่มหรือความสม่ำเสมอที่สมบูรณ์แบบตามที่ข้อความอื่น ๆ ชี้ให้เห็น แต่ก็เพียงพอสำหรับกรณีส่วนใหญ่


1
นี้เป็นเพียงการจัดเรียงกระจายไปปรากฏมากยิ่งขึ้น แต่มันไม่จริงใด ๆ มากยิ่งขึ้นสำหรับช่วงที่มีขนาดใหญ่ (เช่นกรณีของ OP)
มอเป็ด

0

@วิธีการแก้ ((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

คำเตือน : อย่าลืมเนื่องจากข้อผิดพลาดในการยืดและความแม่นยำที่อาจเกิดขึ้นได้ (แม้ว่า RAND_MAX จะมีขนาดใหญ่พอก็ตาม) คุณจะสามารถสร้าง "ถังขยะ" แบบกระจายเท่า ๆ กันเท่านั้นและไม่ใช่ตัวเลขทั้งหมดใน [min, max]


@ การแก้ปัญหา: Bigrand

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


ข้อมูล : แนวทางของคุณ (โมดูโล) "ดี" ตราบใดที่ช่วงของแรนด์ () เกินช่วงช่วงเวลาของคุณและค่าแรนด์ () เป็น "สม่ำเสมอ" ข้อผิดพลาดสำหรับจำนวนสูงสุด - ต่ำสุดแรกคือ 1 / (RAND_MAX +1)

นอกจากนี้ฉันขอแนะนำให้เปลี่ยนไปใช้แพ็คเกจแบบสุ่มใหม่ใน C ++ 11 ด้วยซึ่งมีการใช้งานที่ดีกว่าและหลากหลายกว่า rand ()


0

นี่คือวิธีแก้ปัญหาที่ฉันคิดขึ้น:

#include "<stdlib.h>"

int32_t RandomRange(int32_t min, int32_t max) {
    return (rand() * (max - min + 1) / (RAND_MAX + 1)) + min;
}

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

มันทำให้เกิดข้อสันนิษฐานเล็กน้อย ขั้นแรกให้สันนิษฐานว่าRAND_MAX * (max - min + 1)จะพอดีกับint32_tไฟล์. หากRAND_MAXใช้การคำนวณ int 32767 และ 32 บิตช่วงสูงสุดที่คุณสามารถมีได้คือ 32767 หากการใช้งานของคุณมี RAND_MAX ที่ใหญ่กว่ามากคุณสามารถเอาชนะสิ่งนี้ได้โดยใช้จำนวนเต็มที่มากขึ้น (เช่นint64_t) ในการคำนวณ ประการที่สองถ้าint64_tใช้ แต่RAND_MAXยังคงเป็น 32767 ในช่วงที่มากกว่าRAND_MAXคุณจะเริ่มได้รับ "หลุม" ในตัวเลขเอาต์พุตที่เป็นไปได้ นี่อาจเป็นปัญหาใหญ่ที่สุดสำหรับโซลูชันที่ได้จากการปรับขนาดrand()ที่ได้มาจากการปรับ

การทดสอบด้วยการทำซ้ำจำนวนมากอย่างไรก็ตามแสดงให้เห็นว่าวิธีนี้มีความสม่ำเสมอมากสำหรับช่วงขนาดเล็ก แต่ก็เป็นไปได้ (และมีแนวโน้ม) ที่ทางคณิตศาสตร์นี้มีบางส่วนมีอคติเล็ก ๆ RAND_MAXและอาจจะพัฒนาปัญหาเมื่อช่วงใกล้ ทดสอบด้วยตัวคุณเองและตัดสินใจว่าตรงกับความต้องการของคุณหรือไม่


-1

แน่นอนว่ารหัสต่อไปนี้จะไม่ให้หมายเลขสุ่ม แต่เป็นหมายเลขสุ่มหลอก ใช้รหัสต่อไปนี้

#define QUICK_RAND(m,n) m + ( std::rand() % ( (n) - (m) + 1 ) )

ตัวอย่างเช่น:

int myRand = QUICK_RAND(10, 20);

คุณต้องโทร

srand(time(0));  // Initialize random number generator.

มิฉะนั้นตัวเลขจะไม่อยู่ใกล้สุ่ม


1
คำถามคือขอให้กระจายสม่ำเสมอ โซลูชันที่เสนอนี้จะไม่ก่อให้เกิดการกระจายสม่ำเสมอ ไลบรารี c ++ มาตรฐานมีสิ่งอำนวยความสะดวกสำหรับการสร้างเลขสุ่มหลอก สิ่งเหล่านี้จะให้การแจกจ่ายที่สม่ำเสมอหากมีการร้องขอ
IInspectable

-3

ฉันเพิ่งพบสิ่งนี้บนอินเทอร์เน็ต สิ่งนี้ควรใช้งานได้:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));

โปรดชี้แจงว่าคุณต้องการอะไรมีอัลกอริทึมมากมายสำหรับ PRNG อยู่ที่นั่น นอกจากนี้มันจะง่ายกว่าถ้าคุณแก้ไขคำถามหลักแทนการโพสต์คำตอบ
peterchen

วิธีนี้ได้ผลดีที่สุดสำหรับฉัน ... ฉันสามารถรับหมายเลขสุ่มแบบกระจายได้ดีขึ้นด้วยสูตรนี้ ..
anand

4
หากช่วงของคุณเกิน RAND_MAX ผลลัพธ์อาจไม่สม่ำเสมอ นั่นคือมีค่าในช่วงที่จะไม่แสดงไม่ว่าฟังก์ชันของคุณจะเรียกกี่ครั้งก็ตาม
dmckee --- อดีตผู้ดูแลลูกแมว

4
นอกจากนี้หาก max และ min เป็น int ที่ไม่ได้ลงนามและ min คือ 0 และ max คือ MAX_UINT ดังนั้น ((max) - (min) +1) จะเป็น 0 และผลลัพธ์จะเป็น 0 เสมอ ระวังการทำคณิตศาสตร์ประเภทนี้มากเกินไป! ตามที่ระบุไว้โดย dmckee สิ่งนี้จะขยายการกระจายไปตามช่วงปลายทาง แต่ไม่รับประกันค่าที่ไม่ซ้ำกันมากกว่า RAND_MAX
jesup
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.