การสร้างจำนวนเต็มแบบสุ่มจากช่วง


157

ฉันต้องการฟังก์ชั่นที่จะสร้างจำนวนเต็มแบบสุ่มในช่วงที่กำหนด (รวมถึงค่าเส้นขอบ) ฉันไม่ต้องการคุณภาพ / การสุ่มที่ไม่มีเหตุผลฉันมีข้อกำหนดสี่ข้อ:

  • ฉันต้องการมันเร็ว โครงการของฉันต้องสร้างตัวเลขสุ่มนับล้าน (หรือบางครั้งก็เป็นสิบล้าน) และฟังก์ชันตัวสร้างปัจจุบันของฉันได้พิสูจน์แล้วว่าเป็นคอขวด
  • ฉันต้องการมันเพื่อให้มีความสม่ำเสมอ (การใช้แรนด์ () นั้นดีมาก)
  • ช่วง min-max สามารถเป็นอะไรก็ได้ตั้งแต่ <0, 1> ถึง <-32727, 32727>
  • มันจะต้องมีเมล็ด

ขณะนี้ฉันมีรหัส C ++ ดังต่อไปนี้:

output = min + (rand() * (int)(max - min) / RAND_MAX)

ปัญหาคือว่ามันไม่เหมือนกันจริงๆ - max จะถูกส่งคืนเมื่อ rand () = RAND_MAX (สำหรับ Visual C ++ เป็น 1/32727) นี่เป็นปัญหาสำคัญสำหรับช่วงขนาดเล็กเช่น <-1, 1> ซึ่งค่าสุดท้ายแทบไม่เคยส่งคืน

ดังนั้นฉันจึงคว้าปากกาและกระดาษและมาพร้อมกับสูตรต่อไปนี้ (ซึ่งสร้างขึ้นบนเคล็ดลับการปัดเศษ (จำนวนเต็ม) (n + 0.5) จำนวนเต็ม):

ป้อนคำอธิบายรูปภาพที่นี่

แต่มันก็ไม่ได้ให้การกระจายที่เหมือนกัน การรันซ้ำกับ 10,000 ตัวอย่างให้ฉันอัตราส่วน 37:50:13 สำหรับค่า -1, 0. 1

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



3
@ เรียกเก็บเงิน MaGriff: ใช่ มันมีปัญหาเดียวกัน เวอร์ชั่นง่ายคือคุณจะแบ่งขนม 10 ชิ้นในเด็ก 3 คนได้อย่างไร (โดยไม่ทำลายขนม) คำตอบก็คือคุณไม่สามารถทำได้ - คุณต้องให้ลูกสามคนและไม่ต้องให้ใครกับคนที่สิบ
Jerry Coffin

5
คุณดูที่Boost.Randomหรือไม่
Fred Nurk

3
ตรวจสอบบทความ Andrew Koenig "ปัญหาง่าย ๆ ที่แทบจะไม่สามารถแก้ไขได้อย่างถูกต้อง": drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev

1
@ ยีน Bushuyev: ทั้งแอนดรูและฉันเคยพิณเรื่องนี้มาระยะหนึ่งแล้ว ดู: groups.google.com/group/comp.lang.c++/browse_frm/thread/…และ: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Coffin

คำตอบ:


105

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

output = min + (rand() % static_cast<int>(max - min + 1))

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


2
ขอบคุณดูเหมือนว่าจะดีพอสำหรับฉันจากการทดสอบอย่างรวดเร็ว - การแจกแจงสำหรับ -1, 0, 1 เกือบ 33:33:33
MatějZábský

3
ส่งคืนค่าสูงสุดเสมอ ฉันทำอะไรบางอย่างหายไปหรือเปล่า : |
rohan-patel

15
rand()ควรได้รับการพิจารณาว่าเป็นอันตรายใน C ++มีวิธีที่ดีกว่ามากในการได้รับสิ่งที่กระจายอย่างสม่ำเสมอและสุ่ม
Mgetz

1
มันส่งกลับตัวเลขที่ถูกต้องภายในช่วง 100% ของเวลาจริง ๆ หรือไม่ ฉันพบคำตอบ stackoverflow อื่น ๆ ที่นี่ซึ่งใช้การเรียกซ้ำเพื่อทำ "วิธีการที่ถูกต้อง": stackoverflow.com/a/6852396/623622
Czarek Tomczak

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

296

คำตอบที่ง่ายที่สุด (และดีที่สุด) C ++ (โดยใช้มาตรฐาน 2011) คือ

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);

ไม่จำเป็นต้องคิดค้นล้อใหม่อีกครั้ง ไม่จำเป็นต้องกังวลเรื่องอคติ ไม่ต้องกังวลกับการใช้เวลาเป็นเมล็ดสุ่ม


1
ปัจจุบันนี้ควรจะเป็นคำตอบ การสร้างตัวเลขสุ่มหลอกอ้างอิงสำหรับคุณสมบัติเพิ่มเติม
alextoind

8
ฉันเห็นด้วยกับ "ง่ายที่สุด" (และเป็นไปได้มากที่สุด) ไม่ใช่ "ดีที่สุด" แต่น่าเสียดายที่มาตรฐานการให้การรับประกันเกี่ยวกับrandom_deviceซึ่งอาจจะมีการเสียอย่างสมบูรณ์ในบางกรณี ยิ่งไปกว่านั้นmt19937ในขณะที่ตัวเลือกวัตถุประสงค์ทั่วไปที่ดีมากไม่ใช่เครื่องกำเนิดไฟฟ้าคุณภาพดีที่เร็วที่สุด (ดูการเปรียบเทียบนี้ ) ดังนั้นจึงอาจไม่ใช่ตัวเลือกที่เหมาะสมที่สุดสำหรับ OP
Alberto M

1
@AlbertoM น่าเสียดายที่การเปรียบเทียบการอ้างถึงของคุณไม่ได้ให้รายละเอียดเพียงพอและไม่สามารถทำซ้ำได้ซึ่งทำให้ดูน่าสงสัย (ยิ่งกว่านั้นมาจากปี 2015 ในขณะที่คำตอบของฉันกลับไปที่ปี 2556) อาจเป็นเรื่องจริงที่มีวิธีการที่ดีกว่า (และหวังว่าในอนาคตminstdจะเป็นวิธีการดังกล่าว) แต่นั่นเป็นความคืบหน้า สำหรับการนำไปใช้ที่ไม่ดีของrandom_device- มันน่ากลัวและควรได้รับการพิจารณาข้อผิดพลาด (อาจเป็นไปได้ของมาตรฐาน C ++ หากอนุญาต)
วอลเตอร์

1
ฉันเห็นด้วยกับคุณโดยสิ้นเชิง ฉันไม่ต้องการที่จะวิพากษ์วิจารณ์การแก้ปัญหาของคุณต่อเพียงแค่เตือนผู้อ่านชั่วคราวว่าคำตอบที่ชัดเจนเกี่ยวกับเรื่องนี้แม้จะมีสัญญาของ C ++ 11 ยังไม่ได้เขียน ฉันกำลังจะโพสต์ภาพรวมของเรื่องเป็นปี 2015 เป็นคำตอบของคำถามที่เกี่ยวข้อง
Alberto M

1
นั่นคือ "ง่ายที่สุด"? คุณช่วยอธิบายได้ไหมว่าทำไมrand()ตัวเลือกที่เรียบง่ายอย่างชัดเจนไม่ใช่ตัวเลือกและสำคัญสำหรับการใช้ที่ไม่สำคัญเช่นการสร้างดัชนี pivot แบบสุ่มหรือไม่ นอกจากนี้ฉันต้องกังวลเกี่ยวกับการสร้างrandom_device/ mt19937/ uniform_int_distributionในฟังก์ชั่นการวนรอบ / อินไลน์ที่แคบหรือไม่? ฉันควรจะชอบที่จะส่งพวกเขาไปรอบ ๆ ?
bluenote10

60

หากคอมไพเลอร์ของคุณรองรับ C ++ 0x และการใช้มันเป็นตัวเลือกสำหรับคุณ<random>ส่วนหัวมาตรฐานใหม่น่าจะตรงกับความต้องการของคุณ มันมีคุณภาพสูงuniform_int_distributionซึ่งจะยอมรับขอบเขตขั้นต่ำและสูงสุด (รวมตามที่คุณต้องการ) และคุณสามารถเลือกเครื่องกำเนิดตัวเลขสุ่มแบบต่างๆเพื่อเชื่อมต่อกับการกระจายนั้น

นี่คือโค้ดที่สร้างการแจกแจงแบบสุ่มล้านintตัวใน [-57, 365] ฉันได้ใช้<chrono>สิ่งอำนวยความสะดวกมาตรฐานใหม่เพื่อให้ทันเวลาตามที่คุณกล่าวถึงประสิทธิภาพเป็นข้อกังวลสำคัญสำหรับคุณ

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

สำหรับฉัน (2.8 GHz Intel Core i5) สิ่งนี้จะพิมพ์ออกมา:

2.10268e + 07 ตัวเลขสุ่มต่อวินาที

คุณสามารถ seed ตัวกำเนิดโดยส่งผ่าน int ไปยัง constructor:

    G g(seed);

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

    typedef std::uniform_int_distribution<long long> D;

หากคุณพบในภายหลังว่าตัวminstd_randกำเนิดที่มีคุณภาพไม่สูงพอคุณสามารถเปลี่ยนได้อย่างง่ายดาย เช่น:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine

มีการควบคุมแยกตัวสร้างตัวเลขสุ่มและการกระจายแบบสุ่มนั้นสามารถปลดปล่อยได้อย่างอิสระ

ฉันยังคำนวณ (ไม่แสดง) ช่วงเวลา 4 ช่วงแรกของการแจกแจงนี้ (โดยใช้minstd_rand) และเปรียบเทียบกับค่าทางทฤษฎีเพื่อพยายามประเมินคุณภาพของการแจกแจง:

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

( x_คำนำหน้าหมายถึง "คาดหวัง")


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

ปัญหานี้เกิดขึ้นได้ง่ายขึ้นเนื่องจากการกระจายขั้นต่ำและสูงสุดไม่เคยเปลี่ยนแปลง ถ้าคุณต้องสร้างdซ้ำทุกครั้งที่มีขอบเขตแตกต่างกันอย่างไร มันจะช้าลงไหม?
quant_dev

15

ลองแบ่งปัญหาออกเป็นสองส่วน:

  • สร้างตัวเลขสุ่มnในช่วง 0 ถึง (สูงสุด - นาที)
  • เพิ่มขั้นต่ำลงในหมายเลขนั้น

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

ปรากฎว่าเราสามารถใช้สัญชาตญาณนี้ถ้าเรายินดีที่จะอนุญาตให้หลอก - nondeterminism เป็นเวลาทำงานของอัลกอริทึมของเรา เมื่อใดก็ตามที่ rand () ส่งกลับตัวเลขที่มีขนาดใหญ่เกินไปเราก็จะขอหมายเลขสุ่มอีกจนกว่าเราจะได้หมายเลขที่มีขนาดเล็กพอ

ขณะนี้เวลาทำงานมีการกระจายแบบเรขาคณิตด้วยค่า1/pที่คาดไว้ซึ่งpเป็นความน่าจะเป็นที่จะได้รับจำนวนที่น้อยพอในการลองครั้งแรก เนื่องจากRAND_MAX - (RAND_MAX + 1) % (max-min+1)น้อยกว่า(RAND_MAX + 1) / 2เรารู้อยู่เสมอp > 1/2ดังนั้นจำนวนการทำซ้ำที่คาดไว้จะน้อยกว่าสองเท่าสำหรับช่วงใด ๆ มันเป็นไปได้ที่จะสร้างตัวเลขสุ่มนับล้านในเวลาน้อยกว่าหนึ่งวินาทีบน CPU มาตรฐานด้วยเทคนิคนี้

แก้ไข:

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


เพื่อความสมบูรณ์: นี่คือการสุ่มตัวอย่างการปฏิเสธ
etarion

3
ความจริงแล้วสนุก: Joel Spolsky เคยกล่าวถึงรุ่นของคำถามนี้ว่าเป็นตัวอย่างของ StackOverflow ที่ตอบคำถามได้ดี ผมมองผ่านคำตอบในเว็บไซต์ที่เกี่ยวข้องกับการสุ่มตัวอย่างการปฏิเสธในเวลานั้นและทุกคน เดียว หนึ่งไม่ถูกต้อง
Jørgen Fogh

13

วิธีการเกี่ยวกับMersenne Twister ? การใช้งานบูสต์นั้นค่อนข้างง่ายต่อการใช้งานและได้รับการทดสอบอย่างดีในแอปพลิเคชันในโลกแห่งความเป็นจริงมากมาย ฉันใช้มันด้วยตัวเองในโครงการทางวิชาการหลายอย่างเช่นปัญญาประดิษฐ์และอัลกอริธึมวิวัฒนาการ

นี่คือตัวอย่างของพวกเขาที่พวกเขาสร้างฟังก์ชั่นง่าย ๆ ในการหมุนแม่พิมพ์แบบหกด้าน:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}

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

Mersenne Twister เป็นเครื่องกำเนิดไฟฟ้า "หมายเลขสุ่ม" ที่คิดค้นโดย Makoto Matsumoto และ Takuji Nishimura; เว็บไซต์ของพวกเขารวมถึงการใช้อัลกอริทึมมากมาย

โดยพื้นฐานแล้ว Mersenne Twister เป็นระบบลงทะเบียนกะตอบรับแบบเส้นตรงขนาดใหญ่มาก อัลกอริทึมทำงานกับเมล็ด 19,937 บิตซึ่งเก็บในอาร์เรย์ 624 องค์ประกอบของจำนวนเต็ม 32 บิตที่ไม่ได้ลงชื่อ ค่า 2 ^ 19937-1 คือ Mersenne prime เทคนิคสำหรับการจัดการเมล็ดพันธุ์นั้นขึ้นอยู่กับอัลกอริทึม "การบิด" ที่เก่ากว่า - ดังนั้นชื่อ "Mersenne Twister"

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


1
Twers Mersenne เป็นเครื่องกำเนิดไฟฟ้าที่ดี แต่ปัญหาที่เขาจัดการกับซากโดยไม่คำนึงถึงเครื่องกำเนิดไฟฟ้าต้นแบบ
Jerry Coffin

ฉันไม่ต้องการใช้ Boost สำหรับเครื่องกำเนิดไฟฟ้าแบบสุ่มเพราะ (เนื่องจากโครงการของฉันเป็นห้องสมุด) มันหมายถึงการแนะนำการพึ่งพาอื่นให้กับโครงการ ฉันอาจจะถูกบังคับให้ใช้มันต่อไปในอนาคตดังนั้นฉันจึงสามารถเปลี่ยนไปใช้ตัวสร้างนี้ได้
MatějZábský

1
@Jerry Coffin มีปัญหาอะไร? ฉันเสนอให้เพราะมันตอบสนองทุกความต้องการของเขา: มันเร็วมันเป็นแบบเดียวกัน (ใช้การboost::uniform_intกระจาย) คุณสามารถแปลง min max range เป็นอะไรก็ได้ที่คุณชอบและมันเป็นเมล็ด
Aphex

@mzabsky ฉันอาจจะไม่ยอมให้หยุดฉันเมื่อฉันต้องส่งโครงการของฉันไปยังอาจารย์เพื่อขอความช่วยเหลือฉันเพิ่งรวมไฟล์ส่วนหัวบูสต์ที่เกี่ยวข้องที่ฉันใช้อยู่ คุณไม่จำเป็นต้องทำแพคเกจไลบรารีเพิ่ม 40mb ทั้งหมดด้วยรหัสของคุณ แน่นอนว่าในกรณีของคุณอาจไม่สามารถทำได้ด้วยเหตุผลอื่นเช่นลิขสิทธิ์ ...
Aphex

@Aphex โครงการของฉันไม่ได้เป็นการจำลองทางวิทยาศาสตร์หรือสิ่งที่ต้องการการกระจายที่สม่ำเสมอ ฉันใช้เครื่องกำเนิดไฟฟ้าเก่าเป็นเวลา 1.5 ปีโดยไม่มีปัญหาใด ๆ ฉันสังเกตเห็นการกระจายแบบเอนเอียงเมื่อฉันต้องการให้มันสร้างตัวเลขจากช่วงที่เล็กมาก (3 ในกรณีนี้) ความเร็วยังคงเป็นข้อถกเถียงที่จะต้องพิจารณาโซลูชันเพิ่ม ฉันจะตรวจสอบใบอนุญาตเพื่อดูว่าฉันสามารถเพิ่มไฟล์ที่จำเป็นบางอย่างลงในโครงการของฉันได้ไหม - ฉันชอบ "Checkout -> F5 -> พร้อมใช้" ในขณะนี้
MatějZábský

11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

นี่คือการแม็พจำนวนเต็ม 32768 ถึง (nMax-nMin + 1) การทำแผนที่จะค่อนข้างดีถ้า (nMax-nMin + 1) มีขนาดเล็ก (ตามที่คุณต้องการ) อย่างไรก็ตามโปรดทราบว่าหาก (nMax-nMin + 1) มีขนาดใหญ่การทำแผนที่จะไม่ทำงาน (ตัวอย่างเช่น - คุณไม่สามารถแมปค่า 32768 กับ 30000 ค่าด้วยความน่าจะเป็นที่เท่ากัน) หากจำเป็นต้องใช้ช่วงดังกล่าวคุณควรใช้แหล่งข้อมูลแบบสุ่ม 32 บิตหรือ 64 บิตแทนที่จะเป็น rand 15- บิต () หรือละเว้นผลลัพธ์ rand () ซึ่งอยู่นอกช่วง


แม้จะไม่เป็นที่นิยม แต่ก็เป็นสิ่งที่ฉันใช้สำหรับโครงการที่ไม่ใช่วิทยาศาสตร์ ง่ายต่อการเข้าใจ (คุณไม่ต้องการระดับคณิตศาสตร์) และทำงานอย่างเพียงพอ (ไม่ต้องใช้รหัสใด ๆ กับโปรไฟล์) :) ในกรณีของช่วงขนาดใหญ่ผมคิดว่าเราจะได้สองสายแรนด์ () ค่าเข้าด้วยกันและได้รับค่า 30 บิตเพื่อทำงานร่วมกับ (สมมติ RAND_MAX = 0x7FFF คือ 15 บิตสุ่ม)
efotinis

เปลี่ยนRAND_MAXเป็น(double) RAND_MAXเพื่อหลีกเลี่ยงคำเตือนการล้นของจำนวนเต็ม
alex

4

นี่คือเวอร์ชันที่ไม่เอนเอียงที่สร้างตัวเลขใน[low, high]:

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

หากช่วงของคุณมีขนาดเล็กพอสมควรจะไม่มีเหตุผลใดที่แคชด้านขวามือของการเปรียบเทียบในdoลูป


IMO ไม่มีคำตอบใด ๆ ที่นำเสนอว่ามีการปรับปรุงอย่างมาก โซลูชันแบบวนซ้ำของเขาทำงานได้ แต่มีแนวโน้มที่จะไม่มีประสิทธิภาพโดยเฉพาะอย่างยิ่งสำหรับช่วงขนาดเล็กเช่น OP อธิบาย สารละลายเบี่ยงเบนสม่ำเสมอของเขาไม่ได้สร้างความเบี่ยงเบนเหมือนกันเลย อย่างมากมันพรางตัวแบบที่ไม่มีความสม่ำเสมอ
Jerry Coffin

@Jerry: โปรดตรวจสอบเวอร์ชั่นใหม่
Jeremiah Willcock

ฉันไม่แน่ใจเล็กน้อยเกี่ยวกับการทำงานอย่างถูกต้อง อาจ แต่ความถูกต้องดูเหมือนไม่ชัดเจนสำหรับฉันอย่างน้อย
Jerry Coffin

@Jerry: นี่คือเหตุผลของฉัน: สมมติว่าช่วงคือ[0, h)เพื่อความเรียบง่าย การโทรrand()มีRAND_MAX + 1ค่าส่งคืนที่เป็นไปได้ การrand() % hยุบ(RAND_MAX + 1) / hของพวกเขาไปยังhค่าเอาต์พุตแต่ละค่ายกเว้นค่าที่(RAND_MAX + 1) / h + 1แม็พกับค่าที่น้อยกว่า(RAND_MAX + 1) % h(เนื่องจากรอบบางส่วนสุดท้ายผ่านhเอาต์พุต) ดังนั้นเราจึงลบ(RAND_MAX + 1) % hผลลัพธ์ที่เป็นไปได้เพื่อให้ได้การแจกแจงแบบไม่มีอคติ
Jeremiah Willcock

3

ฉันแนะนำไลบรารี Boost.Randomมันมีรายละเอียดและมีเอกสารที่ดีมากช่วยให้คุณระบุการกระจายที่คุณต้องการได้อย่างชัดเจนและในสถานการณ์ที่ไม่ใช่การเข้ารหัสลับอาจมีประสิทธิภาพเหนือกว่าการใช้งานไลบรารี C ทั่วไป


1

สมมติว่า min และ max เป็นค่า int [และ] หมายถึงรวมค่านี้ (และ) หมายความว่าไม่รวมค่านี้โดยใช้ด้านบนเพื่อรับค่าที่ถูกต้องโดยใช้ c ++ rand ()

การอ้างอิง: สำหรับ () [] define ไปที่:

https://en.wikipedia.org/wiki/Interval_(mathematics)

สำหรับฟังก์ชั่น rand และ srand หรือ RAND_MAX define ให้ไปที่:

http://en.cppreference.com/w/cpp/numeric/random/rand

[ต่ำสุดสูงสุด]

int randNum = rand() % (max - min + 1) + min

(นาที, สูงสุด]

int randNum = rand() % (max - min) + min + 1

[ต่ำสุดสูงสุด)

int randNum = rand() % (max - min) + min

(ขั้นต่ำสูงสุด)

int randNum = rand() % (max - min - 1) + min + 1

0

ในการสุ่มตัวอย่างการปฏิเสธเธรดนี้ได้ถูกกล่าวถึงแล้ว แต่ฉันต้องการแนะนำการปรับให้เหมาะสมอย่างใดอย่างหนึ่งตามความจริงที่rand() % 2^somethingไม่แนะนำอคติใด ๆ ดังที่ได้กล่าวมาแล้ว

อัลกอริทึมนั้นง่ายมาก:

  • คำนวณพลังงานที่เล็กที่สุดของ 2 มากกว่าความยาวช่วงเวลา
  • สุ่มตัวเลขหนึ่งตัวในช่วง "ใหม่"
  • คืนค่าจำนวนนั้นถ้าน้อยกว่าความยาวของช่วงเวลาเดิม
    • ปฏิเสธเป็นอย่างอื่น

นี่คือตัวอย่างรหัสของฉัน:

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

วิธีนี้ใช้งานได้ดีโดยเฉพาะอย่างยิ่งสำหรับช่วงเวลาเล็ก ๆ เพราะพลังของ 2 จะเป็น "ใกล้" ถึงความยาวช่วงเวลาจริงดังนั้นจำนวนของการผิดพลาดจะน้อยลง

PS
เห็นได้ชัดว่าการหลีกเลี่ยงการเรียกซ้ำจะมีประสิทธิภาพมากขึ้น (ไม่จำเป็นต้องคำนวณซ้ำไปซ้ำมา .. ) แต่ฉันคิดว่ามันอ่านได้ง่ายกว่าสำหรับตัวอย่างนี้


0

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

สมมติว่าคุณต้องการ [min, max] ขอบเขตของตัวเลขสุ่มจำนวนเต็ม เราเริ่มต้นจาก [0, สูงสุด - นาที]

ใช้ฐาน b = max-min + 1

เริ่มต้นจากการแทนตัวเลขที่คุณได้จาก rand () ในฐาน b

ด้วยวิธีนี้คุณจะมีพื้น (log (b, RAND_MAX)) เพราะแต่ละหลักในฐาน b ยกเว้นตัวเลขสุดท้ายอาจแทนตัวเลขสุ่มในช่วง [0, max-min]

แน่นอนว่าการเปลี่ยนครั้งสุดท้ายเป็น [min, max] นั้นง่ายสำหรับการสุ่มหมายเลข r + min

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

ถ้า NUM_DIGIT เป็นจำนวนหลักในฐาน b ที่คุณสามารถแยกและนั่นคือ

NUM_DIGIT = floor(log(b,RAND_MAX))

จากนั้นข้างต้นเป็นการใช้งานง่าย ๆ ในการดึงตัวเลขสุ่ม NUM_DIGIT จาก 0 ถึง b-1 จากหมายเลขสุ่ม RAND_MAX หนึ่งหมายเลขโดยให้ b <RAND_MAX


-1

สูตรนี้ง่ายมากดังนั้นลองใช้นิพจน์นี้ดู

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0

2
ปัญหาทั้งหมดกำลังใช้แรนด์ของ C / C ++ ซึ่งคืนค่าจำนวนเต็มในช่วงที่ระบุโดยรันไทม์ ดังที่แสดงในเธรดนี้การแม็พจำนวนเต็มแบบสุ่มจาก [0, RAND_MAX] ถึง [MIN, MAX] นั้นไม่ตรงไปตรงมาทั้งหมดหากคุณต้องการหลีกเลี่ยงการทำลายคุณสมบัติทางสถิติหรือประสิทธิภาพ หากคุณมีค่าเป็นสองเท่าในช่วง [0, 1] การทำแผนที่เป็นเรื่องง่าย
MatějZábský

2
คำตอบของคุณผิดคุณควรใช้โมดูลัสแทน:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes

-2

การแสดงออกต่อไปนี้ควรเป็นกลางถ้าฉันไม่ผิด:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

ฉันสมมุติว่า rand () ให้ค่าสุ่มในช่วงระหว่าง 0.0 ถึง 1.0 ไม่รวม 1.0 และ max และ min นั้นเป็นจำนวนเต็มโดยมีเงื่อนไขว่า min <max


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