การสร้างเลขทศนิยมแบบสุ่ม


271

ฉันจะสร้างการสุ่มลอยตัวใน C ++ ได้อย่างไร

ฉันคิดว่าฉันสามารถเอาแรนด์จำนวนเต็มแล้วหารด้วยบางอย่างนั่นจะเพียงพอหรือไม่


2
มันขึ้นอยู่กับสิ่งที่คุณต้องการจำนวนและวิธีการสุ่ม โดยทั่วไปแล้วแรนด์ () จะให้ 15 บิตของการสุ่ม แต่ลอยมีความแม่นยำ 23 บิตดังนั้นมันจะพลาดบางค่า
Pete Kirkham

1
ฉันมีการปรับปรุงคำตอบของฉันที่จะรวมทุกตัวเลือกที่สำคัญที่มีอยู่และตัวเลือกของฉันให้ความสำคัญกับrandomส่วนหัวเพิ่มใน C ++ 11 หนุนต่อไปโดยเอกสารมาตรฐานN3924: ท้อใจ Rand () ใน C ฉันรวมrand()ไว้ในคำตอบสำหรับการพิจารณาในอดีตส่วนใหญ่ แต่ยังตระหนักถึงแอปพลิเคชันแบบดั้งเดิมที่มีอยู่
Shafik Yaghmour

คำตอบของฉันรวมถึงวิธีการหลีกเลี่ยงการรับหมายเลขเดียวกันทุกครั้งที่มี<random>ส่วนหัว
Andreas DM

คำตอบ:


381

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


สิ่งนี้จะสร้างตัวเลขตั้งแต่ 0.0 ถึง 1.0 โดยรวม

float r = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);

นี้จะสร้างตัวเลขตั้งแต่ 0.0 ถึงพลบางfloat, X:

float r2 = static_cast <float> (rand()) / (static_cast <float> (RAND_MAX/X));

สิ่งนี้จะสร้างจำนวนจากบางส่วนLOไปเป็นบางส่วนโดยพลการHI:

float r3 = LO + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(HI-LO)));

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


ก่อนที่จะโทรrand()คุณต้อง "seed" ตัวสร้างหมายเลขสุ่มโดยการโทรsrand()ก่อน นี้ควรจะทำครั้งเดียวในช่วงรันโปรแกรมของคุณ - rand()ไม่ได้ครั้งเดียวทุกครั้งที่คุณโทร มักจะทำเช่นนี้:

srand (static_cast <unsigned> (time(0)));

เพื่อเรียกrandหรือคุณต้องsrand#include <cstdlib>

เพื่อที่จะโทรคุณต้องtime#include <ctime>


22
อย่าลืมที่จะเพาะเมล็ดก่อน!
Klaim

14
ดีที่สุดที่จะต้องทราบว่าข้อ จำกัด ทั้งสองรวม
dmckee --- ผู้ดูแลอดีตลูกแมว

14
คำตอบนี้ทำให้เข้าใจผิด มันครอบคลุมใน Going Native 2013 เมื่อสัปดาห์ที่แล้ว rand () พิจารณาว่าเป็นอันตราย, channel9.msdn.com/Events/GoingNative/2013/สำหรับคำอธิบายโดยละเอียด
Ade Miller

14
ฉันไม่เข้าใจว่าทำไมผู้คนมากมายตอบคำตอบนี้ มันไม่ถูกต้องทางคณิตศาสตร์ RAND_MAX เป็นจำนวนน้อยมาก (โดยทั่วไปคือ 2 ^ 16) นั่นหมายความว่าจากจุดลอยตัว 23 บิตคุณสุ่มเพียง 15 ครั้ง คนอื่นอาจจะเป็นศูนย์ คุณจะได้รับตัวเลขสุ่มในการแจกแจงแบบสม่ำเสมอ แต่มีความแม่นยำต่ำ ตัวอย่างเช่นตัวสร้างแบบสุ่มของคุณสามารถสร้าง 0.00001 และ 0.00002 แต่ไม่สามารถสร้าง 0.000017 คุณมีการกระจายแบบสม่ำเสมอ แต่มีความแม่นยำต่ำ (ความแม่นยำน้อยกว่า 256 เท่าของจุดลอยตัวจริง)
DanielHsH

10
@DanielHsH: rand()สหกรณ์โดยเฉพาะถามว่าสิ่งที่กลศาสตร์สามารถนำมาใช้ในการสร้างลอยสุ่มที่มี คำถามนี้และคำตอบของฉันมุ่งเน้นไปที่การเรียนรู้พื้นฐานและไม่กังวลเกี่ยวกับความแม่นยำระดับสูง คุณต้องหัดเดินก่อนที่จะเรียนรู้ที่จะวิ่ง
John Dibling

137

C ++ 11 มอบทางเลือกใหม่มากมายให้กับrandomคุณ เอกสารที่เป็นที่ยอมรับในหัวข้อนี้จะเป็นN3551 การสร้างหมายเลขสุ่มใน C ++ 11

เพื่อดูว่าทำไมใช้rand()อาจเป็นปัญหาได้ดูแรนด์ () พิจารณาอันตรายวัสดุที่นำเสนอโดยสเตฟานตัน Lavavejได้รับในช่วงGoingNative 2013เหตุการณ์ สไลด์อยู่ในการแสดงความคิดเห็น แต่ที่นี่คือการเชื่อมโยงโดยตรง

ฉันยังครอบคลุมboostรวมถึงการใช้งานrandเนื่องจากรหัสดั้งเดิมอาจยังต้องการการสนับสนุน

ตัวอย่างด้านล่างกลั่นจากไซต์ cppreference และใช้เครื่องยนต์std :: mersenne_twister_engineและstd :: uniform_real_distributionซึ่งสร้างตัวเลขใน[0,10)ช่วงเวลาโดยมีเอ็นจินและการแจกแจงแสดงความคิดเห็น ( ดูสด ):

#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <random>

int main()
{
    std::random_device rd;

    //
    // Engines 
    //
    std::mt19937 e2(rd());
    //std::knuth_b e2(rd());
    //std::default_random_engine e2(rd()) ;

    //
    // Distribtuions
    //
    std::uniform_real_distribution<> dist(0, 10);
    //std::normal_distribution<> dist(2, 2);
    //std::student_t_distribution<> dist(5);
    //std::poisson_distribution<> dist(2);
    //std::extreme_value_distribution<> dist(0,2);

    std::map<int, int> hist;
    for (int n = 0; n < 10000; ++n) {
        ++hist[std::floor(dist(e2))];
    }

    for (auto p : hist) {
        std::cout << std::fixed << std::setprecision(1) << std::setw(2)
                  << p.first << ' ' << std::string(p.second/200, '*') << '\n';
    }
}

เอาต์พุตจะคล้ายกับที่แสดงต่อไปนี้:

0 ****
1 ****
2 ****
3 ****
4 *****
5 ****
6 *****
7 ****
8 *****
9 ****

ผลลัพธ์จะแตกต่างกันไปขึ้นอยู่กับการกระจายที่คุณเลือกดังนั้นถ้าเราตัดสินใจใช้std :: normal_distributionด้วยค่าของ2ทั้งmeanและstddevเช่นdist(2, 2)แทน output จะคล้ายกับสิ่งนี้ ( ดูสด ):

-6 
-5 
-4 
-3 
-2 **
-1 ****
 0 *******
 1 *********
 2 *********
 3 *******
 4 ****
 5 **
 6 
 7 
 8 
 9 

ต่อไปนี้เป็นรุ่นที่แก้ไขของรหัสบางส่วนที่นำเสนอในN3551( ดูสด ):

#include <algorithm>
#include <array>
#include <iostream>
#include <random>

std::default_random_engine & global_urng( )
{
    static std::default_random_engine u{};
    return u ;
}

void randomize( )
{
    static std::random_device rd{};
    global_urng().seed( rd() );
}

int main( )
{
  // Manufacture a deck of cards:
  using card = int;
  std::array<card,52> deck{};
  std::iota(deck.begin(), deck.end(), 0);

  randomize( ) ;  

  std::shuffle(deck.begin(), deck.end(), global_urng());
  // Display each card in the shuffled deck:
  auto suit = []( card c ) { return "SHDC"[c / 13]; };
  auto rank = []( card c ) { return "AKQJT98765432"[c % 13]; };

  for( card c : deck )
      std::cout << ' ' << rank(c) << suit(c);

   std::cout << std::endl;
}

ผลลัพธ์จะคล้ายกับ:

5H 5 วินาทีเป็น 9 วินาที 4D 6 H TH 6D KH 2 วินาที QS 9 H 8 H 3D KC TD 7H 2D KS 3C TC 7D 4C QH QC QD JD AH JC AC KD 9D 5C 2 H 4H 9C 8C JH 5D 4 วินาที 7C โฆษณา 3 วินาที 8 วินาที TS 7S 6S

การส่งเสริม

แน่นอนBoost.Randomเป็นตัวเลือกเสมอเช่นกันที่นี่ฉันใช้boost :: random :: uniform_real_distribution :

#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>

int main()
{
    boost::random::mt19937 gen;
    boost::random::uniform_real_distribution<> dist(0, 10);

    std::map<int, int> hist;
    for (int n = 0; n < 10000; ++n) {
        ++hist[std::floor(dist(gen))];
    }

    for (auto p : hist) {
        std::cout << std::fixed << std::setprecision(1) << std::setw(2)
                  << p.first << ' ' << std::string(p.second/200, '*') << '\n';
    }
}

แรนด์ ()

หากคุณต้องใช้งานrand()เราสามารถไปที่C FAQสำหรับคำแนะนำเกี่ยวกับฉันจะสร้างตัวเลขสุ่มแบบลอยตัวได้อย่างไร ซึ่งโดยทั่วไปให้ตัวอย่างที่คล้ายกับสิ่งนี้เพื่อสร้างช่วงเวลา[0,1):

#include <stdlib.h>

double randZeroToOne()
{
    return rand() / (RAND_MAX + 1.);
}

และเพื่อสร้างตัวเลขสุ่มในช่วงจาก[M,N):

double randMToN(double M, double N)
{
    return M + (rand() / ( RAND_MAX / (N-M) ) ) ;  
}

1
คุณสามารถแก้ไขrandMToNpls ของคุณได้ไหม ทั้งทราบว่ามันเป็น[M,N]หรือเพิ่มกลับมาจากด้านบน+ 1. randZeroToOne-> คิดว่าจะเรียกแบบนี้:randMToN(0.0, 1.0);
BeyelerStudios

1
(N-M)นอกจากนี้จะต้องระมัดระวังกับการหารด้วยศูนย์ที่ วิธีที่ดีในการจัดการกับข้อผิดพลาดนี้พบได้ที่นี่: stackoverflow.com/questions/33058848//
ดร. เบ

61

ลองดูที่Boost.Random คุณสามารถทำสิ่งนี้:

float gen_random_float(float min, float max)
{
    boost::mt19937 rng;
    boost::uniform_real<float> u(min, max);
    boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > gen(rng, u);
    return gen();
}

ลองเล่นไปเรื่อย ๆ คุณอาจจะผ่านวัตถุ mt19937 เดียวกันได้ดีกว่าแทนที่จะสร้างใหม่ทุกครั้ง แต่หวังว่าคุณจะได้แนวคิด


1
uniform_real ใช้ช่วงเวลาเปิดครึ่ง [นาทีสูงสุด) ซึ่งหมายความว่าคุณจะได้รับค่าต่ำสุด แต่จะไม่ถึงค่าสูงสุด มันเป็นเรื่องที่ต้องพิจารณาถึงแม้ว่าคุณจะปัดเศษไปบ้างคุณก็สามารถแก้ไขปัญหานี้ได้
TehWan

20
นี่คือส่วนหนึ่งของ C ++ 11
Tomas Andrle

@ หมาป่าในแอปพลิเคชั่นที่ใช้งานได้จริงอัตราต่อรองของการกดค่าทศนิยมเฉพาะใด ๆ นั้นต่ำมากจนไม่สำคัญว่าจะรวมหรือแยกจุดปลาย หากคุณต้องการmaxแต่สามารถใช้ปลายเปิดคุณสามารถย้อนกลับช่วงเวลาได้อย่างง่ายดาย:min return min + max - gen();
Mark Ransom

26

ในปัจจุบันc++คุณอาจจะใช้ส่วนหัวที่มาพร้อมกับ<random> ที่จะได้รับการสุ่ม's คุณสามารถใช้c++11
floatstd::uniform_real_distribution<>

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

float get_random()
{
    static std::default_random_engine e;
    static std::uniform_real_distribution<> dis(0, 1); // rage 0 - 1
    return dis(e);
}

เหมาะอย่างยิ่งที่จะวางสิ่งเหล่าfloatนี้ไว้ในภาชนะบรรจุเช่นstd::vector:

int main()
{
    std::vector<float> nums;
    for (int i{}; i != 5; ++i) // Generate 5 random floats
        nums.emplace_back(get_random());

    for (const auto& i : nums) std::cout << i << " ";
}

ตัวอย่างผลลัพธ์:

0.0518757 0.969106 0.0985112 0.0895674 0.895542

std::uniform_real_distribution<> dis(0, 1); // rage 0 - 1ไม่ถูกต้องทางเทคนิคจะไม่สร้าง 1.0 ดูen.cppreference.com/w/cpp/numeric/random/ ...... To create a distribution over the closed interval [a,b], std::nextafter(b, std::numeric_limits<RealType>::max()) may be used as the second parameter.
Troyseph

1
นี่ควรเป็นคำตอบที่ได้รับการยอมรับซึ่งเป็นสิ่งที่เลวร้ายในปี 2020
อเล็กซ์

25

เรียกรหัสที่มีสองfloatค่ารหัสทำงานในช่วงใด ๆ

float rand_FloatRange(float a, float b)
{
    return ((b - a) * ((float)rand() / RAND_MAX)) + a;
}

มันอาจคุ้มค่าที่จะกล่าวถึงว่านี่เป็นกรณีการใช้งานที่เป็นไปได้สำหรับfmaf()(หรือการfma()โอเวอร์โหลดเกินพิกัดใน C ++) ใน C99 หรือ C ++ 11 ซึ่งอาจรักษาความแม่นยำได้มากขึ้น fmaf((float)rand() / RAND_MAX, b - a, a)ในขณะที่
ทิมČas

22

หากคุณใช้ C ++ ไม่ใช่ C โปรดจำไว้ว่าในรายงานทางเทคนิค 1 (TR1) และในร่าง C ++ 0x พวกเขาได้เพิ่มสิ่งอำนวยความสะดวกสำหรับตัวสร้างตัวเลขสุ่มในไฟล์ส่วนหัวฉันเชื่อว่ามันเหมือนกับ Boost ไลบรารีแบบสุ่มและแน่นอนยืดหยุ่นและ "ทันสมัย" กว่าฟังก์ชั่นห้องสมุด C แรนด์

ไวยากรณ์นี้มีความสามารถในการเลือกเครื่องกำเนิด (เช่นmersenne twister mt19937) จากนั้นเลือกการกระจาย (ปกติ, เบอนูลลี, ทวินาม ฯลฯ )

ไวยากรณ์มีดังนี้ (ยืมตัวไร้ยางอายจากเว็บไซต์นี้ ):

  #include <iostream>
  #include <random>

  ...

  std::tr1::mt19937 eng;  // a core engine class 
  std::tr1::normal_distribution<float> dist;     

  for (int i = 0; i < 10; ++i)        
      std::cout << dist(eng) << std::endl;

2
นี่คือตอนนี้ใน C ++ 11, dist สามารถเริ่มต้นได้ด้วยค่า min และ max
Étienne

ดูเหมือนว่าแปลกที่ฉันจะใส่ min และ max ใน initializer และให้กำเนิดเมื่อได้รับค่า - ฉันต้องการถ้าเป็นวิธีอื่น ๆ รอบดี
yoyo

6

ในบางระบบ (Windows ที่มี VC สปริงให้ความสำคัญในปัจจุบัน) RAND_MAXมีขนาดเล็กอย่างน่าขันฉัน อี เพียง 15 บิต เมื่อทำการหารโดยRAND_MAXคุณจะสร้าง mantissa เพียง 15 บิตแทนที่จะเป็น 23 บิตที่เป็นไปได้ สิ่งนี้อาจหรืออาจไม่ใช่ปัญหาสำหรับคุณ แต่คุณพลาดค่าบางอย่างในกรณีนั้น

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

float r = (float)((rand() << 15 + rand()) & ((1 << 24) - 1)) / (1 << 24);

ยังไม่ทดลอง แต่อาจใช้งานได้ :-)


สิ่งที่เกี่ยวกับลอย r = (ลอย) ((แรนด์ () << 9) | แรนด์ ()) / RAND_MAX? (ยังไม่ได้ทดสอบ)
กับดัก

ขอโทษด้วยการหารด้วย RAND_MAX จะไม่นำพาคุณไปทุกที่ ... จุดรวมของเคล็ดลับนี้คือการมีบางสิ่งที่ใหญ่กว่า RAND_MAX ... แก้ไขให้ฉันด้วย
Joey

2
ระมัดระวังในการเขียนตัวเลขสุ่มโดยไม่มีทฤษฎี ... การโทรไปยัง rand () อย่างต่อเนื่องอาจไม่เป็นอิสระอย่างสมบูรณ์ คำแนะนำ: ถ้าเส้น congruential กำเนิดชมบิตต่ำในการโทรติดต่อกันมันสลับกันไปมาระหว่าง 0 และ 1
RBerteig

ฉันรู้ว่า. สำหรับบางแอปพลิเคชั่นนี้อาจจะเพียงพอ แต่ใช่คุณควรใช้มากกว่าสองสายในกรณีนี้ ในกรณีนี้ไม่มีกระสุนเงินคุณไม่สามารถเชื่อได้ว่าเป็น LCG PRNG อื่น ๆ มีบิตสูงที่อ่อนแอ โซลูชัน Boost ควรดีที่สุดที่นี่
Joey

(nb: บิตต่ำที่ส่งคืนโดย rand ใน MSVC ไม่ใช่บิตต่ำสุดของสถานะ RNG สำหรับ 100 rand () โทรหาฉันได้รับสิ่งต่อไปนี้: 1100100000111111111010100100110101011101101101111101111 Java มีการใช้บิต 48 บิตต่ำสุดที่ส่งคืนโดย rand ใน MSVC ไม่ใช่บิตต่ำสุดของสถานะ RNG ทำมันในทำนองเดียวกัน)
Joey

4

drand48(3)เป็นวิธีมาตรฐาน POSIX GLibC ยังมีเวอร์ชัน reentrantdrand48_r(3)ที่

ฟังก์ชันถูกประกาศล้าสมัยใน SVID 3 แต่ไม่มีตัวเลือกอื่นเพียงพอดังนั้นIEEE Std 1003.1-2013ยังคงรวมอยู่และไม่มีโน้ตที่จะไปทุกที่ในไม่ช้า

ใน Windows วิธีมาตรฐานคือCryptGenRandom ()


2

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

//Returns a random number in the range [0.0f, 1.0f).  Every
//bit of the mantissa is randomized.
float rnd(void){
  //Generate a random number in the range [0.5f, 1.0f).
  unsigned int ret = 0x3F000000 | (0x7FFFFF & ((rand() << 8) ^ rand()));
  unsigned short coinFlips;

  //If the coin is tails, return the number, otherwise
  //divide the random number by two by decrementing the
  //exponent and keep going. The exponent starts at 63.
  //Each loop represents 15 random bits, a.k.a. 'coin flips'.
  #define RND_INNER_LOOP() \
    if( coinFlips & 1 ) break; \
    coinFlips >>= 1; \
    ret -= 0x800000
  for(;;){
    coinFlips = rand();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    //At this point, the exponent is 60, 45, 30, 15, or 0.
    //If the exponent is 0, then the number equals 0.0f.
    if( ! (ret & 0x3F800000) ) return 0.0f;
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
  }
  return *((float *)(&ret));
}

7
วิธีการที่น่าสนใจผมอยากจะ upvote แต่ผมไม่เข้าใจสิ่งที่เกิดขึ้น
hasen

2

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

| sign (1-bit) | e (8-bits) | f (23- บิต) |

จำนวนที่แสดงโดยคำนี้คือ (-1 * sign) * 2 ^ e * 1.f

สังเกตว่า 'e' number เป็น biased (ด้วยอคติ 127) ดังนั้นตั้งแต่ -127 to 126 ฟังก์ชั่นที่ง่ายที่สุด (และสุ่มมากที่สุด) คือการเขียนข้อมูลของ int สุ่มลงในโฟลต ดังนั้น

int tmp = rand();
float f = (float)*((float*)&tmp);

โปรดทราบว่าถ้าคุณทำfloat f = (float)rand();มันจะแปลงจำนวนเต็มเป็นลอย (เช่น 10 จะกลายเป็น 10.0)

ดังนั้นถ้าคุณต้องการ จำกัด ค่าสูงสุดคุณสามารถทำสิ่งที่ชอบ (ไม่แน่ใจว่ามันใช้งานได้)

int tmp = rand();
float f = *((float*)&tmp);
tmp = (unsigned int)f       // note float to int conversion!
tmp %= max_number;
f -= tmp;

แต่ถ้าคุณดูที่โครงสร้างของ float คุณจะเห็นว่าค่าสูงสุดของ float คือ (ประมาณ) 2 ^ 127 ซึ่งมีขนาดใหญ่กว่าค่าสูงสุดของ int (2 ^ 32) ดังนั้นจึงพิจารณาส่วนสำคัญของ ตัวเลขที่สามารถแทนได้ด้วยการลอย นี่คือการดำเนินการขั้นสุดท้ายของฉัน:

/**
 * Function generates a random float using the upper_bound float to determine 
 * the upper bound for the exponent and for the fractional part.
 * @param min_exp sets the minimum number (closest to 0) to 1 * e^min_exp (min -127)
 * @param max_exp sets the maximum number to 2 * e^max_exp (max 126)
 * @param sign_flag if sign_flag = 0 the random number is always positive, if 
 *              sign_flag = 1 then the sign bit is random as well
 * @return a random float
 */
float randf(int min_exp, int max_exp, char sign_flag) {
    assert(min_exp <= max_exp);

    int min_exp_mod = min_exp + 126;

    int sign_mod = sign_flag + 1;
    int frac_mod = (1 << 23);

    int s = rand() % sign_mod;  // note x % 1 = 0
    int e = (rand() % max_exp) + min_exp_mod;
    int f = rand() % frac_mod;

    int tmp = (s << 31) | (e << 23) | f;

    float r = (float)*((float*)(&tmp));

    /** uncomment if you want to see the structure of the float. */
//    printf("%x, %x, %x, %x, %f\n", (s << 31), (e << 23), f, tmp, r);

    return r;
}

ใช้ฟังก์ชั่นนี้randf(0, 8, 0)จะส่งกลับตัวเลขสุ่มระหว่าง 0.0 และ 255.0


1
คุณมีข้อผิดพลาด rand ()% frac_mod จะไม่ทำงานเนื่องจาก MAX_RAND มักจะต่ำกว่า (1 << 23)
DanielHsH

ฉันต้องยอมรับว่าฉันไม่ทราบขนาดที่แน่นอนของ MAX_RAND ไม่น้อยกว่าที่มันจะยังคงทำงานอาจเป็นคำสั่งที่ไร้ประโยชน์ แต่มันจะยังคงทำงาน 8% 10 = 8 ดังนั้นก็ดี แต่ถ้า MAX_RAND มีขนาดเล็กกว่าเสมอ (1 << 23) คุณสามารถลบออกได้อย่างแน่นอน
2546926

2
ไม่ผิดนะ โดยปกติ RandMax จะมีค่าประมาณ 65,000 นั่นหมายความว่าจาก 23 บิตคุณสุ่มเพียง 15 ครั้ง คนอื่นอาจจะเป็นศูนย์ คุณจะได้รับตัวเลขสุ่ม แต่มีความแม่นยำต่ำ ตัวอย่างเช่นเครื่องกำเนิดไฟฟ้าแบบสุ่มของคุณสามารถสร้าง 0.001 และ 0.002 แต่ไม่สามารถสร้าง 0.0017 ดังนั้นคุณมีการกระจายแบบสม่ำเสมอ แต่มีความแม่นยำต่ำ (ความแม่นยำน้อยกว่าการลอยแบบ 256 เท่า)
DanielHsH

คำตอบนี้มีข้อผิดพลาดสองประการ แก้ไขช่วงเลขชี้กำลัง: int e = (rand() % (max_exp - min_exp)) + min_exp_mod;และ mantissa: int f = (int)(frac_mod * (float)rand() / RAND_MAX);แทนที่บรรทัดตามลำดับด้านบน โปรดทราบว่าความผิดพลาดของแมนทิสซานั้นสำคัญ: สำหรับRAND_MAXขนาดเล็กกว่า1 << 23คุณจะสุ่มบิตบิตที่สำคัญต่ำกว่าและรับ 0s สำหรับบิตที่สำคัญที่สุดตลอดเวลา!
BeyelerStudios

2

หากคุณรู้ว่ารูปแบบจุดลอยตัวของคุณคือ IEEE 754 (เกือบทุกซีพียูสมัยใหม่รวมถึง Intel และ ARM) คุณสามารถสร้างเลขทศนิยมแบบสุ่มจากจำนวนเต็มแบบสุ่มโดยใช้วิธีการที่ชาญฉลาด สิ่งนี้ควรได้รับการพิจารณาหากคุณไม่มีสิทธิ์เข้าถึง C ++ 11 randomหรือBoost.Randomดีกว่ามาก

float rand_float()
{
    // returns a random value in the range [0.0-1.0)

    // start with a bit pattern equating to 1.0
    uint32_t pattern = 0x3f800000;

    // get 23 bits of random integer
    uint32_t random23 = 0x7fffff & (rand() << 8 ^ rand());

    // replace the mantissa, resulting in a number [1.0-2.0)
    pattern |= random23;

    // convert from int to float without undefined behavior
    assert(sizeof(float) == sizeof(uint32_t));
    char buffer[sizeof(float)];
    memcpy(buffer, &pattern, sizeof(float));
    float f;
    memcpy(&f, buffer, sizeof(float));

    return f - 1.0;
}

สิ่งนี้จะให้การกระจายที่ดีกว่าการใช้การหาร


8
ฉันไม่แน่ใจว่าทำไมคุณพูดว่าสิ่งนี้จะให้ "การกระจายที่ดีขึ้น" ในความเป็นจริงนี้จะให้เหมือนกันreturn (float)random23 / (1 << 23)กระจายเป็นเพียงแค่ (ใช่ฉันเพิ่งทดสอบสิ่งนี้ปรับเปลี่ยนการทำงานของคุณเพื่อใช้random32เป็นพารามิเตอร์และเรียกใช้สำหรับค่าทั้งหมดจากศูนย์ถึง(1 << 23)-1และใช่วิธีการของคุณให้ผลเหมือนกับการหารด้วย1 << 23)
Ilmari Karonen

1

สำหรับ C ++ มันสามารถสร้างจำนวนลอยจริงภายในช่วงที่ระบุโดยdistตัวแปร

#include <random>  //If it doesnt work then use   #include <tr1/random>
#include <iostream>

using namespace std;

typedef std::tr1::ranlux64_base_01 Myeng; 
typedef std::tr1::normal_distribution<double> Mydist;

int main() { 
       Myeng eng; 
       eng.seed((unsigned int) time(NULL)); //initializing generator to January 1, 1970);
       Mydist dist(1,10); 

       dist.reset(); // discard any cached values 
       for (int i = 0; i < 10; i++)
       {
           std::cout << "a random value == " << (int)dist(eng) << std::endl; 
       }

       return (0);
}

1
คุณเพิ่งคัดลอกและวางรหัสจากคำตอบนี้หรือไม่? stackoverflow.com/a/1118739/1538531
ดีเร็ก

ที่จริงไม่ฉันประหลาดใจนิดหน่อยที่เห็นว่าพวกเขาดูเหมือนกันมากแค่ไหน! แต่ฉันเริ่มต้นเครื่องยนต์กำเนิด 1, ม.ค. 1970
Marco167

ยุติธรรมพอสมควร ฉันสังเกตเห็นว่าคุณเริ่มต้นกำเนิดไปยังยุค แต่สาปรหัสที่คล้ายกัน!
Derek

ฉันคิดว่ามันแปลกเล็กน้อยที่จะให้ตัวอย่าง TR1 คุณสามารถอธิบายได้ในกรณีใดที่บางคนจะต้องใช้ TR1 แทน C ++ 11
Shafik Yaghmour

0

rand () ส่งคืน int ระหว่าง 0 ถึง RAND_MAX ในการรับตัวเลขสุ่มระหว่าง 0.0 ถึง 1.0 อันดับแรกให้ส่งคืน int โดยแรนด์ () เป็นทุ่นแล้วหารด้วย RAND_MAX


0
#include <cstdint>
#include <cstdlib>
#include <ctime>

using namespace std;

/* single precision float offers 24bit worth of linear distance from 1.0f to 0.0f */
float getval() {
    /* rand() has min 16bit, but we need a 24bit random number. */
    uint_least32_t r = (rand() & 0xffff) + ((rand() & 0x00ff) << 16);
    /* 5.9604645E-8 is (1f - 0.99999994f), 0.99999994f is the first value less than 1f. */
    return (double)r * 5.9604645E-8;
}

int main()
{
    srand(time(NULL));
...

ฉันไม่สามารถโพสต์คำตอบได้สองข้อดังนั้นนี่คือทางออกที่สอง ตัวเลขสุ่ม log2, อคติขนาดใหญ่ต่อ 0.0f แต่แท้จริงแล้วมันคือการสุ่มลอยตัว 1.0f ถึง 0.0f

#include <cstdint>
#include <cstdlib>
#include <ctime>

using namespace std;

float getval () {
    union UNION {
        uint32_t i;
        float f;
    } r;
    /* 3 because it's 0011, the first bit is the float's sign.
     * Clearing the second bit eliminates values > 1.0f.
     */
    r.i = (rand () & 0xffff) + ((rand () & 0x3fff) << 16);
    return r.f;
}

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