C generiranje slučajnih brojeva u rasponu. Pseudoslučajni brojevi. Postavite gornju i donju granicu rand funkcije

U obrazovnim algoritamskim problemima, potreba za generiranjem nasumičnih cijelih brojeva je prilično uobičajena. Naravno, možete ih dobiti od korisnika, ali mogu nastati problemi s popunjavanjem niza sa 100 slučajnih brojeva.

Funkcija standardne biblioteke jezika C (ne C++) rand() dolazi nam u pomoć.

int rand(void);

Generiše pseudo-slučajni cijeli broj u rasponu vrijednosti od 0 do RAND_MAX. Ovo posljednje je konstanta koja varira ovisno o implementaciji jezika, ali u većini slučajeva je 32767.
Šta ako su nam potrebni slučajni brojevi od 0 do 9? Tipičan izlaz iz ove situacije je korištenje operacije dijeljenja po modulu.

Ako su nam potrebni brojevi od 1 (ne 0) do 9, onda možemo dodati jedan...

Ideja je sljedeća: generišemo nasumični broj od 0 do 8, a nakon dodavanja 1 on se pretvara u nasumični broj od 1 do 9.

I za kraj, ono najtužnije.
Nažalost, funkcija rand() generiše pseudoslučajne brojeve, tj. brojevi koji izgledaju nasumično, ali su zapravo niz vrijednosti izračunatih pomoću pametnog algoritma koji uzima tzv. zrno kao parametar. One. Brojevi koje generiše funkcija rand() zavisiće od vrednosti koju zrno ima u trenutku kada je pozvano. A zrnatost je uvijek postavljena od strane kompajlera na vrijednost 1. Drugim riječima, niz brojeva će biti pseudo-slučajan, ali uvijek isti.
A ovo nije ono što nam treba.

Funkcija srand() pomaže u ispravljanju situacije.

void srand (nepotpisano int sjeme);

Postavlja grain jednaku vrijednosti parametra s kojim je pozvan. I redoslijed brojeva će također biti drugačiji.

Ali problem ostaje. Kako napraviti zrno nasumično, jer sve zavisi od njega?
Tipičan izlaz iz ove situacije je korištenje funkcije time().

time_t time(time_t* timer);

Takođe je naslijeđen iz C jezika i, kada se pozove sa null pokazivačem kao parametrom, vraća broj sekundi koji su prošli od 1. januara 1970. godine. Ne, ovo nije šala.

Sada možemo prenijeti vrijednost ove funkcije u srand() funkciju (koja vrši implicitno prebacivanje) i imat ćemo divno nasumično zrno.
I brojevi će biti divni i neće se ponavljati.

Da biste koristili funkcije rand() i srand() morate uključiti datoteku zaglavlja , i koristiti time() - datoteku .

Evo kompletnog primjera.

#include
#include
#include

korištenje imenskog prostora std;

int main()
{
cout<< "10 random numbers (1..100): " << endl;
srand(vrijeme(NULL));
for(int i=0;i<10;i++) cout << rand() % 100 + 1 << " ";
cin.get();
return 0;
}

Tagovi: C slučajni, C slučajni brojevi, generisanje slučajnih brojeva, RNG, pseudoslučajni brojevi, Monte Carlo metoda

Pseudoslučajni brojevi

Generisanje pseudoslučajnih brojeva je složen matematički problem. Ovaj članak nema za cilj da pokrije ovu temu. U nastavku, koncept „slučajnog broja“ će značiti pseudoslučajan, osim ako nije drugačije navedeno.

Na primjere upotrebe slučajnih brojeva nailazite posvuda. Pseudoslučajni brojevi se koriste u dizajnu i grafici, za generiranje nivoa u kompjuterskim igrama i za simulaciju AI. Skupovi slučajnih brojeva se koriste u matematičkim algoritmima (vidi Monte Carlo metode).

Očigledno, problem generisanja slučajnih brojeva ne može se riješiti na klasičnom procesoru, budući da je rad računara po definiciji deterministički. Međutim, moguće je generirati vrlo dugačke skupove brojeva tako da njihova distribucija ima ista svojstva kao skupovi istinski slučajnih brojeva.

Važno je da za rješavanje određenog problema trebate odabrati pravi generator, ili barem znati njegova svojstva. Na primjer, prilikom modeliranja fizičkog procesa možete dobiti potpuno različite i često netačne rezultate, ovisno o izboru generatora slučajnih brojeva.

Pogledajmo standardni generator.

#include #include #include int main() ( int i, r; srand(42); for (i = 0; i< 10; i++) { r = rand(); printf("%d\n", r); } _getch(); return 0; }

Prvo, potrebno je inicijalizirati generator slučajnih brojeva (RNG, ili RNG - generator slučajnih brojeva), postaviti seme, na osnovu kojeg će se generiranje odvijati u budućnosti. Važno je da će za istu početnu vrijednost generator vratiti iste brojeve.

Srand(42);

Dodijelite varijablu r slučajnu vrijednost

R = rand();

Vrijednost će biti u rasponu od 0 do RAND_MAX.

Da biste dobili novi skup brojeva sljedeći put kada počnete, trebate svaki put inicijalizirati generator različitim vrijednostima. Na primjer, možete koristiti sistemsko vrijeme:

Srand(vrijeme(NULL));

Srand(_getpid());

Funkcija getpid biblioteke process.h vraća ID procesa (možete koristiti i getpid, verziju funkcije koja nije POSIX).

Centralna granična teorema

Vrlo je važno odmah podsjetiti ili uvesti središnju graničnu teoremu. Neformalna definicija: distribucija sume slabo zavisnih slučajnih varijabli teži normalnoj. Objašnjenje u obliku prsta: ako dodate nekoliko slučajnih varijabli, bez obzira na njihovu distribuciju, distribucija sume će biti normalna. Često možete vidjeti ovakav kod

#include #include #include int main() ( int i, r, r1, r2, r3; srand(vrijeme(NULL)); r1 = rand(); r2 = rand(); r3 = rand(); r = (r1 + r2 + r3 ) / 3 printf("%d", r);

Generisanje slučajnih brojeva u datom intervalu

Prvo, dobijamo slučajni broj od nula do jedan:

Const float RAND_MAX_F = RAND_MAX; float get_rand() (vrati rand() / RAND_MAX_F; )

Da biste dobili broj u intervalu od nule do N, pomnožite N sa slučajnim brojem od nule do jedan. Da bismo dobili slučajni broj od M do N, pomjerimo rezultirajući broj za M.

Float get_rand_range(const float min, const float max) ( return get_rand() * (max - min) + min; )

Da bismo dobili cijeli broj, uzet ćemo ostatak dijeljenja dužinom intervala. Ali ostatak dijeljenja će vratiti broj jedan manji od našeg intervala, pa ga povećavamo za jedan:

Int get_rand_range_int(const int min, const int max) (vrati rand() % (max - min + 1) + min; )

Primjer korištenja slučajnih brojeva za izračunavanje integrala. Neka nam je neka glatka funkcija jedne varijable. Ograničimo ga na kvadrat od a do b, i od 0 do neke tačke, koja je očito veća od naše funkcije.

Nasumično ćemo bacati tačke na naš kvadrat. Ako leže iznad funkcije (na slici prikazane kao zeleni križići), onda ćemo ih dodijeliti prvoj grupi A, ako ispod funkcije (crvena na slici), onda ćemo ih dodijeliti drugoj grupi B. pozicija tačaka je nasumična i ravnomerno raspoređena (pošto standard generator daje uniformnu raspodelu. Ovaj jednostavan primer, inače, već pokazuje koliko je važno poznavati svojstva RNG-a). Tada će omjer crvenih tačaka prema ukupnom broju tačaka biti jednak omjeru površine ispod grafikona prema ukupnoj površini. A ukupna površina je kvadrat (b-a) sa q.

Src="/images/c_random_integral.png" alt=" Sve što nasumično pada iznad naše funkcije je zeleno, sve ispod je crveno.
Odnos zelene i crvene će biti jednak omjeru površine iznad grafikona i površine ispod grafikona."> Всё, что случайно попадает выше нашей функции - зелёное, всё что ниже - красное. !}
Odnos zelene i crvene će biti jednak omjeru površine iznad grafikona i površine ispod grafikona.

Primijenimo naše proračune - pronađimo integral funkcije x^2 na intervalu od 0 do dva na dva načina.

#include #include #include #include #include const float RAND_MAX_F = RAND_MAX; float get_rand() ( return rand() / RAND_MAX_F; ) float get_rand_range(const float min, const float max) ( return get_rand() * (max - min) + min; ) #define ROUNDS 1000 float fun(float x) ( return x * x ) float square_square(float a, float b, float q) (float h = (b - a) / (float)ROUNDS; float suma = 0; za (; a);< b; a += h) { sum += fun(a) * h; } return sum; } float rand_square(float a, float b, float q) { float res; float x, y; int i; int lower = 0; float ratio; float square; srand(time(NULL)); for (i = 0; i < ROUNDS; i++) { x = get_rand_range(a, b); y = get_rand_range(0, q); res = fun(x); if (res >y) (niži++; ) ) odnos = (float)niži / (float)ROUNDS; kvadrat = (b - a) * q * odnos; povratni kvadrat; ) int main() (float abs_ans = 2.66667f; float sr = rand_square(0, 2, 4); float ss = square_square(0, 2, 4); printf("Rounds = %d\n", ROUNDS); printf("Sa = %.5f\n", abs_ans("Sr = %.5f\n", "Ss = %.5f\n", ss); ", fabs(sr - abs_ans)); printf("ds = %.5f\n", fabs(ss - abs_ans)); _getch(); vrati 0; )

Poigrajte se sa ROUNDS vrijednošću, promijenite je i pogledajte kako se mijenja tačnost izračuna.

Generiranje pravih slučajnih brojeva

Za generiranje stvarnih slučajnih brojeva koriste se generatori zasnovani na nekim slučajnim fizičkim procesima. Na primjer, o termalnoj buci, o brojanju broja fisija radioaktivne tvari, o atmosferskoj buci, itd. Nedostatak takvih generatora je njihova mala radna brzina (broj generiranih brojeva u sekundi); naravno, takvi generatori su obično zaseban uređaj.

Prijevod članka Slučajni brojevi Jona Skeetea, nadaleko poznatog u uskim krugovima. Zaustavio sam se na ovom članku jer sam svojevremeno i sam naišao na problem opisan u njemu.

Pretraživanje tema po .NET I C# na StackOverflow web stranici možete vidjeti bezbroj pitanja u kojima se spominje riječ "random", koja, u stvari, postavljaju isto vječno i "neuništivo" pitanje: zašto System.Random generator slučajnih brojeva "ne radi" i kako " popravi" " Ovaj članak je posvećen razmatranju ovog problema i načinima njegovog rješavanja.

Formulacija problema

Na StackOverflow-u, u news grupama i mailing listama, sva pitanja na temu “slučajno” zvuče otprilike ovako:
Koristim Random.Next za generiranje više slučajnih brojeva, ali metoda vraća isti broj kada se poziva više puta. Broj se mijenja svaki put kada se aplikacija pokrene, ali je u jednom izvršavanju programa konstantan.

Primjer koda je otprilike ovako:
// Loš kod! Nemojte koristiti! za (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit()); } ... static int GenerateDigit() { Random rng = new Random(); // Предположим, что здесь много логики return rng.Next(10); }
Pa šta nije u redu?

Objašnjenje

Klasa Random nije pravi generator slučajnih brojeva, ona sadrži generator pseudo slučajni brojevi. Svaka instanca klase Random sadrži neko interno stanje, a kada se pozove metoda Next (ili NextDouble, ili NextBytes), metoda koristi to stanje da vrati broj koji će se pojaviti nasumično. Interno stanje se tada mijenja tako da će sljedeći put kada se Next pozove, vratiti naizgled slučajni broj drugačiji od prethodno vraćenog.

Sve "unutrašnje" klase Random potpuno deterministički. To znači da ako uzmete nekoliko instanci klase Random sa istim početnim stanjem, koje je specificirano kroz konstruktorski parametar sjeme, i za svaku instancu pozovite određene metode istim redoslijedom i istim parametrima, tada ćete na kraju dobiti iste rezultate.

Dakle, šta nije u redu sa gornjim kodom? Loša stvar je što koristimo novu instancu klase Random unutar svake iteracije petlje. Random konstruktor, koji ne uzima nikakve parametre, uzima trenutni datum i vrijeme kao svoje seme. Iteracije u petlji će se „pomerati“ tako brzo da sistemsko vreme „neće imati vremena da se promeni“ po njihovom završetku; tako će sve instance Random-a dobiti istu vrijednost kao njihovo početno stanje i stoga će vratiti isti pseudo-slučajni broj.

Kako to popraviti?

Postoji mnogo rješenja za problem, svako ima svoje prednosti i nedostatke. Pogledaćemo neke od njih.
Korištenje kriptografskog generatora slučajnih brojeva
.NET sadrži apstraktnu klasu RandomNumberGenerator iz koje moraju naslijediti sve implementacije kriptografskih generatora slučajnih brojeva (u daljem tekstu cryptoRNG-ovi). .NET također sadrži jednu od ovih implementacija - upoznajte klasu RNGCryptoServiceProvider. Ideja kripto-RNG-a je da čak i ako je i dalje generator pseudo-slučajnih brojeva, on pruža prilično jaku nepredvidljivost rezultata. RNGCryptoServiceProvider koristi više izvora entropije, koji su u suštini "šum" u vašem računaru, a redosled brojeva koji generiše veoma je teško predvideti. Štaviše, šum "u računaru" se može koristiti ne samo kao početno stanje, već i između poziva na naredne slučajne brojeve; stoga, čak i znajući trenutno stanje klase, neće biti dovoljno izračunati i naredne brojeve koji će biti generisani u budućnosti, i one koji su generisani ranije. U stvari, tačno ponašanje zavisi od implementacije. Osim toga, Windows može koristiti specijalizirani hardver koji je izvor "prave slučajnosti" (na primjer, senzor raspada radioaktivnog izotopa) za generiranje još sigurnijih i pouzdanijih slučajnih brojeva.

Uporedimo ovo sa prethodno razmatranom Random klasom. Recimo da ste pozvali Random.Next(100) deset puta i sačuvali rezultate. Ako imate dovoljno računarske snage, možete, samo na osnovu ovih rezultata, izračunati početno stanje (seed) s kojim je Random instanca kreirana, predvidjeti sljedeće rezultate pozivanja Random.Next(100), pa čak i izračunati rezultate prethodni pozivi metoda. Ovo ponašanje je krajnje neprihvatljivo ako koristite nasumične brojeve u sigurnosne, finansijske svrhe itd. Crypto RNG-ovi rade znatno sporije od klase Random, ali generiraju niz brojeva, od kojih je svaki neovisniji i nepredvidiviji od vrijednosti ostalih.

U većini slučajeva, loša izvedba nije problem - loš API jeste. RandomNumberGenerator je dizajniran da generiše nizove bajtova - to je sve. Uporedite ovo sa metodama klase Random, gde je moguće dobiti nasumični ceo broj, razlomak, kao i skup bajtova. Još jedno korisno svojstvo je mogućnost dobivanja slučajnog broja u određenom rasponu. Uporedite ove mogućnosti sa nizom nasumičnih bajtova koje RandomNumberGenerator proizvodi. Situaciju možete ispraviti kreiranjem vlastitog omotača (wrapper-a) oko RandomNumberGeneratora, koji će nasumične bajtove pretvoriti u “zgodan” rezultat, ali ovo rješenje nije trivijalno.

Međutim, u većini slučajeva, "slabost" klase Random je u redu ako možete riješiti problem opisan na početku članka. Hajde da vidimo šta možemo da uradimo ovde.

Koristite jednu instancu klase Random za više poziva
Evo ga, korijen rješenja problema je korištenje samo jedne instance Random-a kada kreirate mnogo slučajnih brojeva koristeći Random.Next. I vrlo je jednostavno - pogledajte kako možete promijeniti gornji kod:
// Ovaj kod će biti bolji Random rng = new Random(); za (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit(rng)); } ... static int GenerateDigit(Random rng) { // Предположим, что здесь много логики return rng.Next(10); }
Sada će svaka iteracija imati različite brojeve... ali to nije sve. Šta se dešava ako ovaj blok koda pozovemo dvaput zaredom? Tako je, kreirat ćemo dvije slučajne instance sa istim sjemenom i dobiti dva identična skupa slučajnih brojeva. Brojevi u svakom skupu će biti različiti, ali će ovi skupovi biti međusobno jednaki.

Postoje dva načina za rješavanje problema. Prvo, možemo koristiti ne instancu, već statičko polje koje sadrži instancu Random, a zatim će gornji dio koda kreirati samo jednu instancu i koristiti je, pozivajući je onoliko puta koliko je potrebno. Drugo, odatle možemo u potpunosti ukloniti stvaranje Random instance, pomjerajući je "više", idealno na sam "vrh" programa, gdje će se kreirati jedna Random instanca, nakon čega će se prenositi na sva mjesta gdje su potrebni nasumični brojevi. Ovo je sjajna ideja, lijepo izražena ovisnostima, ali će funkcionirati sve dok koristimo samo jednu nit.

Sigurnost niti

Klasa Random nije sigurna niti. Uzimajući u obzir koliko volimo da kreiramo jednu instancu i da je koristimo u celom programu tokom celog trajanja njegovog izvršavanja (singleton, halo!), nedostatak sigurnosti niti postaje prava muka. Uostalom, ako koristimo jednu instancu istovremeno u nekoliko niti, onda postoji mogućnost da će se njeno unutrašnje stanje resetirati, a ako se to dogodi, od tog trenutka instanca će postati beskorisna.

Opet, postoje dva načina za rješavanje problema. Prvi put i dalje uključuje korištenje jedne instance, ali ovaj put korištenje zaključavanja resursa putem monitora. Da biste to učinili, trebate kreirati omotač oko Random-a koji će obaviti pozive svojih metoda u naredbu lock, osiguravajući isključivi pristup instanci za pozivatelja. Ovaj put je loš jer smanjuje performanse u scenarijima s intenzivnim nitima.

Drugi način, koji ću opisati u nastavku, je korištenje jedne instance po niti. Jedina stvar koju trebamo biti sigurni je da koristimo različite sjemenke kada kreiramo instance, tako da ne možemo koristiti zadane konstruktore. Inače, ovaj put je relativno jednostavan.

Siguran provajder

Na sreću, nova generička klasa ThreadLocal , uveden u .NET 4, olakšava pisanje dobavljača koji pružaju jednu instancu po niti. Potrebno je samo da prosledite delegata konstruktoru ThreadLocal, koji će se odnositi na dobijanje vrednosti same naše instance. U ovom slučaju, odlučio sam da koristim jednu seed vrijednost, inicijalizirajući je pomoću Environment.TickCount (što je upravo način na koji radi Random konstruktor bez parametara). Zatim, rezultirajući broj kvačica se povećava svaki put kada trebamo dobiti novu Random instancu za zasebnu nit.

Klasa ispod je potpuno statična i sadrži samo jednu javnu (otvorenu) metodu GetThreadRandom. Ova metoda je napravljena kao metoda, a ne kao svojstvo, uglavnom zbog pogodnosti: ovo će osigurati da sve klase kojima je potrebna instanca Random ovisi o Func (delegat koji ukazuje na metodu koja ne uzima parametre i vraća vrijednost tipa Random), a ne iz same klase Random. Ako je tip namijenjen za izvođenje na jednoj niti, može pozvati delegata da dobije jednu instancu Randoma i zatim ga koristi u cijelom; ako tip treba da radi u višenitnim scenarijima, može pozvati delegata svaki put kada mu zatreba generator slučajnih brojeva. Klasa u nastavku će kreirati onoliko instanci klase Random koliko ima niti, a svaka instanca će početi od različite početne vrijednosti. Ako trebamo koristiti dobavljača slučajnih brojeva kao ovisnost u drugim tipovima, možemo učiniti ovo: new TypeThatNeedsRandom(RandomProvider.GetThreadRandom) . Pa, evo samog koda:
korištenje sistema; koristeći System.Threading; javna statička klasa RandomProvider ( private static int seed = Environment.TickCount; privatni statički ThreadLocal randomWrapper = novi ThreadLocal (() => novi Random(Interlocked.Increment(ref seed))); javni statički Random GetThreadRandom() (vrati randomWrapper.Value; ) )
Dovoljno jednostavno, zar ne? To je zato što je cijeli kod usmjeren na proizvodnju ispravne instance Random-a. Jednom kada je instanca kreirana i vraćena, nije važno što ćete sljedeće učiniti s njom: sva daljnja izdavanja instanci su potpuno neovisna o trenutnom. Naravno, klijentski kod ima rupu za zlonamjernu zloupotrebu: može uzeti jednu instancu Random-a i proslijediti je drugim nitima umjesto da poziva naš RandomProvider na tim drugim nitima.

Problemi dizajna interfejsa

Jedan problem i dalje ostaje: koristimo slabo zaštićen generator slučajnih brojeva. Kao što je ranije spomenuto, postoji mnogo sigurnija verzija RNG-a u RandomNumberGeneratoru, čija je implementacija u klasi RNGCryptoServiceProvider. Međutim, njegov API je prilično teško koristiti u standardnim scenarijima.

Bilo bi jako lijepo kada bi RNG provajderi u okviru imali odvojene „izvore slučajnosti“. U ovom slučaju, mogli bismo imati jedan, jednostavan i zgodan API koji bi bio podržan i nesigurnom ali brzom implementacijom i sigurnom ali sporom implementacijom. Pa, nema štete u sanjanju. Možda će se slična funkcionalnost pojaviti u budućim verzijama .NET Frameworka. Možda će neko iz Microsofta ponuditi vlastitu implementaciju adaptera. (Nažalost, ja neću biti ta osoba... ispravna implementacija nečega poput ovoga je iznenađujuće složena.) Također možete kreirati svoju vlastitu klasu tako što ćete izvesti iz Random i nadjačati metode Sample i NextBytes, ali nije jasno kako bi točno trebali rad, pa čak i vaš vlastiti Uzorak za implementaciju može biti mnogo složeniji nego što se čini. Možda drugi put…

Programski jezici obično imaju funkcije koje vam omogućavaju generiranje slučajnih brojeva unutar zadanog raspona. U stvari, nisu generisani slučajni brojevi, već takozvani pseudo-slučajni brojevi; izgledaju nasumično, ali se izračunavaju pomoću vrlo specifične formule. Ali radi jednostavnosti, u nastavku ćemo ih ipak zvati slučajnim.

U programskom jeziku C možete dobiti slučajni broj pomoću funkcije rand(), koja je uključena u standardnu ​​biblioteku jezika. Ova funkcija ne prihvaća nikakve parametre.

Vježbajte
Napišite program koji dodeljuje rezultat funkcije rand() cjelobrojnoj varijabli. Prikažite vrijednost varijable na ekranu.

Funkcija rand() vraća cijeli broj između 0 i vrijednosti dodijeljene RAND_MAX konstanti. Vrijednost RAND_MAX ovisi o sistemu i definirana je u datoteci zaglavlja stdlib.h. Tako, na primjer, može biti 32767 (dvobajtni cijeli broj) ili 2147483647 (četvorobajtni cijeli broj).

Vježbajte
Odredite vrijednost RAND_MAX na vašem sistemu. Da biste to učinili, ne zaboravite uključiti datoteku zaglavlja stdlib.h u datoteku izvornog koda.

Kod ispod ispisuje 50 nasumičnih brojeva na ekranu:

#include #include main() ( char i; za (i = 1; i<= 50 ; i++ ) { printf ("%15d" , rand () ) ; if (i % 5 == 0 ) printf ("\n") ; } }

Tijelo petlje se pomiče u novi red nakon svakih pet brojeva prikazanih na ekranu. Da biste to učinili, koristi se izraz koji sadrži ostatak dijeljenja i sa 5, rezultat se uspoređuje sa 0. Da bi se spriječio prijelaz na novi red nakon prvog broja, i prvo se dodjeljuje jedan, a ne nula (pošto je 0 djeljivo sa 5 bez ostatka) .

Vježbajte
Kopirajte gornji kod. Pokrenite program nekoliko puta, primjećujući da li dobijate različite rezultate od pokretanja do pokretanja.

Trebali ste primijetiti da svaki put kada pokrenete program brojevi ostaju isti. Čak i ako ponovo kompajlirate program, rezultat se neće promijeniti. Ovaj efekat je zbog činjenice da je početni (inicijalizacijski) broj, koji je zamijenjen u formulu za izračunavanje prvog i sljedećih pseudoslučajnih brojeva, uvijek isti za svaki sistem. Međutim, ovo sjeme se može promijeniti pomoću funkcije srand(), kojoj se kao parametar prosljeđuje bilo koji cijeli broj. Jasno je da ako navedete određeni argument za funkciju, na primjer, srand(1000) , tada će brojevi također biti isti od poziva do poziva programa. Iako nisu isti kao što bi bili bez srand() . Dakle, javlja se problem, kako da argument za srand() takođe bude slučajan? Ispostavilo se da je to začarani krug.

Vježbajte
Preradite program koji ispisuje 50 nasumičnih brojeva kako bi prvo zatražio od korisnika bilo koji cijeli broj koristeći scanf() i proslijedio ga funkciji srand().

Korisnik programa može sam postaviti vrijednost za inicijalizaciju. Ali najčešće to nije potpuni izlaz iz situacije. Stoga je vrijednost inicijalizacije vezana za neki proces koji se odvija u operativnom sistemu, na primjer, za sat. Vrijeme (uzimajući u obzir ne samo doba dana, već i datum) nikada nije isto. To znači da će vrijednost za srand() pretvorena u cijeli broj iz sistemskog vremena biti drugačija.

Trenutno vrijeme se može pronaći pomoću funkcije time(), čiji je prototip opisan u datoteci time.h. Prodavanjem time() kao NULL parametra, dobijamo cijeli broj koji se može proslijediti srand() :
srand(vrijeme(NULL));

Vježbajte
Preradite svoj program tako da vrijednost inicijalizacije ovisi o sistemskom vremenu.

Dobivanje nasumičnih cijelih brojeva u određenim rasponima

Funkcija rand() proizvodi slučajni broj između 0 i RAND_MAX. Što trebate učiniti ako trebate primiti nasumične brojeve u drugim rasponima, na primjer, od 100 do 999?

Prvo, razmotrimo jednostavniju situaciju: dobijete nasumične brojeve od 0 do 5. Ako pokušate podijeliti bilo koji cijeli broj sa 5 u potpunosti, možete dobiti i 0 (kada je broj djeljiv sa 5 bez ostatka) i 1, 2, 3 , 4. Na primjer, rand() je vratio broj 283. Primjenjujući operaciju pronalaženja ostatka dijeljenja sa 5 na ovaj broj, dobijamo 3. To je izraz rand() % 5 daje bilo koji broj u rasponu ? Logično je pretpostaviti da biste trebali pronaći ostatak prilikom dijeljenja sa 6. U ovom slučaju, sljedeće rezonovanje bi bilo pismenije: trebate pronaći ostatak kada dijelite veličinom raspona. U ovom slučaju, jednaka je šest vrijednosti: 0, 1, 2, 3, 4, 5. Da biste pronašli veličinu raspona, potrebno je oduzeti prihvatljivi minimum od dozvoljenog maksimuma i dodati jedan: max - min + 1. Budite oprezni: ako, na primjer, trebate tako da maksimum koji je naveden u problemu ne padne u raspon, onda nema potrebe za dodavanjem ili se jedan mora oduzeti od maksimuma.

Vježbajte
Napišite program koji proizvodi 50 nasumičnih brojeva od 0 do 99 uključujući.

Dakle, znamo formulu za dobijanje dužine raspona: max - min + 1. Ako trebate dobiti broj od 6 do 10 uključujući, tada će dužina raspona biti jednaka 10 - 6 + 1 = 5 Izraz rand()% 5 će dati bilo koji broj od 0 do 4. Ali trebamo od 6 do 10. U ovom slučaju, dovoljno je dodati 6 rezultirajućem slučajnom ostatku, tj. minimum. Drugim riječima, morate izvršiti smjenu. Vrijedi za dati primjer:

  • ako je ostatak bio 0, onda dodavanjem 6 dobijamo 6;
  • ostatak 1, dodati 6, dobiti 7;
  • ostatak 4, dodati 6, dobiti 10;
  • ne može biti više od 4 ostatka.

U ovom slučaju, formula za dobivanje slučajnog broja u rasponu izgleda ovako:

Rand() % raspon_length + pomak

gdje se raspon_length izračunava kao b - a + 1, pomak je vrijednost a.

Ova formula uključuje i slučajeve kada je potrebno dobiti slučajni broj od 0 do N, tj. oni su posebni slučajevi toga.

Vježbajte
Prikažite niz nasumičnih brojeva koji pripadaju rasponu od 100 do 299 uključujući.

Jednako lako možete dobiti nasumične negativne brojeve. Zaista, ako je opseg specificiran kao [-35, -1], tada će njegova dužina biti jednaka -1 - (-35) + 1 = 35, što je tačno; Izraz za dobijanje slučajnog broja će izgledati ovako:

rand() % 35 - 35

Dakle, ako je ostatak dijeljenja 0, onda dobijamo -35, a ako je 34, onda -1. Preostali ostaci će dati vrijednosti u rasponu od -35 do -1.

Vježbajte
Prikažite niz nasumičnih brojeva koji pripadaju rasponu od -128 do 127 uključujući.

Dobivanje pravih slučajnih brojeva

Situacija sa realnim brojevima izgleda nešto drugačije. Prvo, ne možemo dobiti ostatak dijeljenja ako je dividenda ili djelitelj razlomak. Drugo, kada se izračunava dužina raspona, ne možete ga dodati.

Hajde da objasnimo drugi razlog. Recimo da je opseg specificiran kao . Ne sastoji se od određenog broja brojeva (kao u slučaju cijelih brojeva), već od neodređenog (moglo bi se reći beskonačnog) broja vrijednosti, jer realni brojevi mogu biti predstavljeni sa različitim stepenom preciznosti. Kasnije, prilikom zaokruživanja, i dalje će postojati šansa da se dobije maksimalna granica raspona, pa je za izračunavanje dužine raspona dovoljno oduzeti minimum od maksimuma.

Ako podijelite realno pretvoreni slučajni broj koji proizvodi funkcija rand() sa vrijednošću konstante RAND_MAX, dobićete pravi slučajni broj između 0 i 1. Sada, ako pomnožite ovaj broj sa dužinom raspona, vi ćete dobiti broj koji se nalazi u rasponu od 0 do vrijednosti dužine raspona. Zatim, ako mu dodate pomak na minimalnu granicu, broj će se sigurno uklopiti u traženi raspon. Dakle, formula za dobijanje slučajnog realnog broja izgleda ovako:

(float) rand() / RAND_MAX * (maks - min) + min

Vježbajte
Popunite niz slučajnim brojevima u rasponu od 0,51 do 1,00. Prikažite vrijednost elemenata niza na ekranu.

Jednako vjerovatni slučajni brojevi

Funkcija rand() generiše bilo koji slučajni broj od 0 do RAND_MAX sa jednakom vjerovatnoćom. Drugim riječima, broj 100 ima iste šanse da se pojavi kao i broj 25876.

Da biste to dokazali, dovoljno je napisati program koji broji broj pojavljivanja svake vrijednosti. Ako je uzorak (broj "subjekata") dovoljno velik, a raspon (raspršivanje vrijednosti) mali, onda bi trebali vidjeti da je postotak pojavljivanja jedne ili druge vrijednosti približno isti kao i kod drugih.

#include #include #define N 500 main () ( int i; int arr[ 5 ] = ( 0 ) ; srand (vrijeme (NULL) ) ; za (i= 0 ; i< N; i++ ) switch (rand () % 5 ) { case 0 : arr[ 0 ] ++; break ; case 1 : arr[ 1 ] ++; break ; case 2 : arr[ 2 ] ++; break ; case 3 : arr[ 3 ] ++; break ; case 4 : arr[ 4 ] ++; break ; } for (i= 0 ; i < 5 ; i++ ) printf ("%d - %.2f%%\n", i, ((float ) arr[ i] / N) * 100 ) ; )

U gornjem programu, niz od pet elemenata se prvo popunjava nulama. Nasumični brojevi se generišu od 0 do 4 uključujući. Ako je broj 0, tada se povećava vrijednost prvog elementa niza, ako je broj 1, onda drugog, itd. Na kraju, postotak svakog broja je prikazan na ekranu.

Vrlo često u programima postoji potreba za korištenjem nasumičnih brojeva - od popunjavanja niza do kriptografije. Za dobijanje niza slučajnih brojeva u jeziku C# postoji klasa Random. Ova klasa nudi dva konstruktora:

  • nasumično()- inicijalizira instancu klase Random sa početnom vrijednošću koja ovisi o trenutnom vremenu. Kao što je poznato, vrijeme se može predstaviti u krpelji- 100 nanosekundnih impulsa počevši od 1. januara 0001. A vrijednost vremena u tikovima je 64-bitni cijeli broj, koji će se koristiti za inicijalizaciju instance generatora slučajnih brojeva.
  • Slučajno (Int32)- inicijalizira instancu klase Random koristeći specificiranu početnu vrijednost. Inicijalizacija generatora slučajnih brojeva na ovaj način može biti zgodna prilikom otklanjanja grešaka u programu, jer će u ovom slučaju isti „slučajni“ brojevi biti generisani svaki put kada se program pokrene.
Glavna metoda ove klase je metoda Next(), koja vam omogućava da dobijete slučajni broj i ima niz preopterećenja:
  • Next() - vraća nasumični nenegativni cijeli broj u formatu Int32.
  • Sljedeći( Int32)- vraća nasumični nenegativan cijeli broj koji je manji od navedene vrijednosti.
  • Sljedeći( Int32 min, Int32 max)- vraća nasumični cijeli broj u navedenom rasponu. U ovom slučaju mora biti ispunjen uslov min
I takođe metode
  • NextBytes( bajt)- ispunjava elemente specificiranog niza bajtova nasumičnim brojevima.
  • NextDouble() - vraća nasumični broj s pomičnim zarezom, u rasponu)
    break ; // podudaranje pronađeno, element se ne podudara
    }
    ako (j == i)
    { // nema podudaranja
    a[i] = broj; // sačuvati element
    i++; // idemo na sljedeći element
    }
    }
    za (int i = 0; i< 100; i++)
    {

    ako (i % 10 == 9)
    Console.WriteLine();
    }
    Konzola .ReadKey();
    }
    }
    }

    Međutim, što se više približavate kraju niza, više generacija trebate napraviti da biste dobili vrijednost koja se ne ponavlja.
    Sljedeći primjer prikazuje broj poziva metodi Next() za dobivanje svakog elementa, kao i ukupan broj nasumičnih brojeva generiranih za popunjavanje niza od 100 elemenata s vrijednostima koje se ne ponavljaju.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53

    korištenje sistema;
    imenski prostor MyProgram
    {
    razred Program
    {
    static void Main (args niza)
    {
    Random rnd = novi Random();
    int a = novi int; // niz elemenata
    int count = novi int; // niz broja generacija
    a = rnd.Sljedeći(0, 101);
    int c = 0; // brojač broja generacija
    broj = 1; // a se generira samo jednom
    za (int i = 1; i< 100;)
    {
    int broj = rnd.Sljedeći(0, 101);
    c++; // generirao element još jednom
    int j;
    za (j = 0; j< i; j++)
    {
    ako (broj == a[j])
    break ;
    }
    ako (j == i)
    {
    a[i] = broj; i++;
    broj[i] = c; c = 0; // sačuvati broj generacija
    }
    }
    // Ispis vrijednosti elementa
    Console .WriteLine( "Vrijednosti elemenata");
    za (int i = 0; i< 100; i++)
    {
    Konzola .Write("(0,4) " , a[i]);
    ako (i % 10 == 9)
    Console.WriteLine();
    }
    Console.WriteLine();
    // Prikaz broja generacija
    Console .WriteLine( "Broj generacija elemenata");
    int suma = 0;
    za (int i = 0; i< 100; i++)
    {
    zbroj += count[i];
    Konzola .Write("(0,4) " , count[i]);
    ako (i % 10 == 9)
    Console.WriteLine();
    }
    Console .WriteLine( "Ukupan broj generacija - (0)", suma);
    Konzola .ReadKey();
    }
    }
    }

    void Main (args niza)
    {
    Random rnd = novi Random();
    int a = novi int;
    za (int i = 0; i< 100; i++)
    a[i] = i;
    za (int i = 0; i< 50; i++)
    {
    int i1 = rnd.Sljedeći(0, 100); // prvi indeks
    int i2 = rnd.Sljedeći(0, 100); // drugi indeks
    // razmjena vrijednosti elemenata sa indeksima i1 i i2
    int temp = a;
    a = a;
    a = temp;
    }
    Console .WriteLine( "Vrijednosti elemenata");
    za (int i = 0; i< 100; i++)
    {
    Konzola .Write("(0,4) " , a[i]);
    ako (i % 10 == 9)
    Console.WriteLine();
    }
    Konzola .ReadKey();
    }
    }
    }

    Miješanje vrijednosti je efikasnije ako se raspon vrijednosti podudara (ili je blizu) broju vrijednosti, jer se u ovom slučaju broj generiranja slučajnih elemenata značajno smanjuje.

     
Članci By tema:
Nove karakteristike interfejsa
Pozdrav Danas ćemo govoriti o interfejsima i formama u 1C: Enterprise 8.2. Dok sam pružao , primijetio sam koliko ljudi razlikuje komandno sučelje od običnog sučelja samo vizualno, pa sam odlučio da razjasnim. Redovno sučelje Regularno korisničko sučelje
Kako izbrisati fotografije sa Facebook-a Brisanje albuma sa fotografijama
Različite slike i video zapisi čine komunikaciju na društvenoj mreži mnogo zanimljivijom. Čisto tehnička stvar: prije nego što učitate sadržaj, morate ga kreirati ili nabaviti negdje. Ista fotografija mora već biti dostupna, na bilo kom dostupnom mediju.
Kako instalirati
Ovaj materijal je posvećen instalaciji Windows operativnih sistema sa USB fleš diska. Mnogi skeptici i dalje tvrde da je to nemoguće, a ako je moguće, vrlo je teško, ali samo postojanje ovog materijala dokazuje suprotno. Win instalaciju
Kako promijeniti temu u Firefoxu Firefox temi
Teme za Mozilu. Gdje preuzeti i kako instalirati. Postoje različiti načini za promjenu izgleda pretraživača. Reći ću vam o dva od njih. Besplatne teme za Mozilla Firefox Tema će u potpunosti promijeniti dizajn vašeg pretraživača. Ovo se radi vrlo jednostavno. Dosta