Generování náhodných čísel v jazyce C. Náhodná čísla ve standardním generátoru náhodných čísel C C

Překlad článku Náhodná čísla Jon Skeete, široce známý v úzkých kruzích. U tohoto článku jsem se zastavil, protože jsem svého času sám narazil na problém v něm popsaný.

Procházení témat podle .SÍŤ A C# na webu StackOverflow můžete vidět nespočet otázek zmiňujících slovo „random“, které ve skutečnosti vyvolávají stejnou věčnou a „nezničitelnou“ otázku: proč generátor náhodných čísel System.Random „nefunguje“ a jak „ opravit" " Tento článek je věnován zvážení tohoto problému a způsobům jeho řešení.

Formulace problému

Na StackOverflow, v diskusních skupinách a seznamech adresátů, všechny otázky na téma „náhodné“ zní asi takto:
Používám Random.Next ke generování více náhodných čísel, ale metoda vrací stejné číslo, když je voláno vícekrát. Číslo se mění při každém spuštění aplikace, ale v rámci jednoho spuštění programu je konstantní.

Příklad kódu je něco takového:
// Špatný kód! Nepoužívat! for (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit()); } ... static int GenerateDigit() { Random rng = new Random(); // Предположим, что здесь много логики return rng.Next(10); }
Tak co je tady špatně?

Vysvětlení

Třída Random není skutečný generátor náhodných čísel, obsahuje generátor pseudo náhodná čísla. Každá instance třídy Random obsahuje nějaký vnitřní stav, a když je zavolána metoda Next (nebo NextDouble nebo NextBytes), metoda použije tento stav k vrácení čísla, které se bude jevit jako náhodné. Vnitřní stav se pak změní tak, že při příštím zavolání Next vrátí jiné zdánlivě náhodné číslo, než které bylo vráceno dříve.

Všechny „vnitřnosti“ třídy Random zcela deterministický. To znamená, že pokud vezmete několik instancí třídy Random se stejným počátečním stavem, který je určen parametrem konstruktoru semínko a pro každou instanci zavolejte určité metody ve stejném pořadí a se stejnými parametry, pak nakonec dostanete stejné výsledky.

Co je tedy špatného na výše uvedeném kódu? Špatná věc je, že v každé iteraci cyklu používáme novou instanci třídy Random. Náhodný konstruktor, který nebere žádné parametry, bere jako výchozí hodnotu aktuální datum a čas. Iterace ve smyčce budou „rolovat“ tak rychle, že systémový čas „nebude mít čas se změnit“ po jejich dokončení; všechny instance Náhodného tedy obdrží stejnou hodnotu jako jejich počáteční stav, a proto vrátí stejné pseudonáhodné číslo.

jak to opravit?

Existuje mnoho řešení problému, z nichž každé má své klady a zápory. Na pár z nich se podíváme.
Použití kryptografického generátoru náhodných čísel
.NET obsahuje abstraktní třídu RandomNumberGenerator, ze které musí dědit všechny implementace kryptografických generátorů náhodných čísel (dále jen cryptoRNG). .NET také obsahuje jednu z těchto implementací - splňují třídu RNGCryptoServiceProvider. Myšlenka krypto-RNG je, že i když je to stále generátor pseudonáhodných čísel, poskytuje poměrně silnou nepředvídatelnost výsledků. RNGCryptoServiceProvider využívá více zdrojů entropie, které jsou v podstatě „šumem“ ve vašem počítači a posloupnost čísel, kterou generuje, je velmi obtížné předvídat. Navíc "in-computer" šum může být použit nejen jako počáteční stav, ale také mezi voláními na následující náhodná čísla; tedy dokonce vědět Současný stav třídy, nebude to stačit k výpočtu jak dalších čísel, která budou vygenerována v budoucnu, tak čísel, která byla vygenerována dříve. Ve skutečnosti je přesné chování závislé na implementaci. Kromě toho mohou Windows používat specializované Hardware, který je zdrojem „skutečné náhodnosti“ (například by to mohl být snímač radioaktivního rozpadu izotopů), který generuje ještě bezpečnější a spolehlivější náhodná čísla.

Srovnejme to s dříve diskutovanou třídou Random. Řekněme, že jste desetkrát zavolali Random.Next(100) a uložili výsledky. Pokud máte dostatečný výpočetní výkon, můžete pouze na základě těchto výsledků vypočítat počáteční stav (seed), se kterým byla instance Random vytvořena, předpovědět další výsledky volání Random.Next(100) a dokonce vypočítat výsledky předchozí volání metod. Toto chování je extrémně nepřijatelné, pokud používáte náhodná čísla pro bezpečnostní, finanční účely atd. Crypto RNG fungují výrazně pomaleji než třída Random, ale generují posloupnost čísel, z nichž každé je nezávislejší a nepředvídatelné na hodnotách ostatních.

Ve většině případů více nízký výkon není překážkou - špatné API ano. RandomNumberGenerator je navržen tak, aby generoval sekvence bajtů - to je vše. Porovnejte to s metodami třídy Random, kde je možné získat náhodné celé číslo, zlomkové číslo, stejně jako sadu bajtů. Další užitečnou vlastností je možnost získat náhodné číslo v určeném rozsahu. Porovnejte tyto možnosti s polem náhodných bajtů, které vytváří RandomNumberGenerator. Situaci můžete napravit vytvořením vlastního obalu (wrapper) kolem RandomNumberGenerator, který převede náhodné bajty na „pohodlný“ výsledek, ale toto řešení není triviální.

Ve většině případů je však „slabost“ třídy Random v pořádku, pokud dokážete vyřešit problém popsaný na začátku článku. Podívejme se, co zde můžeme dělat.

Použijte jednu instanci třídy Random pro více volání
Tady to je, kořenem řešení problému je použití pouze jedné instance Random při vytváření mnoha náhodných čísel pomocí Random.Next. A je to velmi jednoduché - podívejte se, jak můžete změnit výše uvedený kód:
// Tento kód bude lepší Random rng = new Random(); for (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit(rng)); } ... static int GenerateDigit(Random rng) { // Предположим, что здесь много логики return rng.Next(10); }
Nyní bude mít každá iterace jiná čísla... ale to není vše. Co se stane, když tento blok kódu zavoláme dvakrát za sebou? Správně, vytvoříme dvě náhodné instance se stejným semínkem a získáme dvě stejné sady náhodných čísel. Čísla v každé sadě se budou lišit, ale tyto sady budou mezi sebou stejné.

Problém lze vyřešit dvěma způsoby. Za prvé, můžeme použít nikoli instanci, ale statické pole obsahující instanci Random, a pak výše uvedený kus kódu vytvoří pouze jednu instanci a bude ji používat a bude ji volat tolikrát, kolikrát je potřeba. Zadruhé můžeme odtamtud vytvoření náhodné instance zcela odstranit a přesunout ji „výše“, ideálně až na samý „vrchol“ programu, kde se vytvoří jediná náhodná instance, po které se přenese na všechna místa. kde jsou potřeba náhodná čísla. Tento skvělý nápad, což je hezky vyjádřeno závislostmi, ale bude to fungovat, dokud budeme používat pouze jedno vlákno.

Bezpečnost závitu

Třída Random není bezpečná pro vlákna. Vzhledem k tomu, jak rádi vytváříme jedinou instanci a používáme ji v rámci programu po celou dobu jeho provádění (singleton, ahoj!), nedostatek bezpečnosti vláken se stává skutečnou bolestí v zadku. Pokud totiž použijeme jednu instanci současně ve více vláknech, pak existuje možnost, že se její vnitřní stav resetuje, a pokud se tak stane, tak od toho okamžiku bude instance zbytečná.

Opět existují dva způsoby, jak problém vyřešit. První cesta stále zahrnuje použití jediné instance, ale tentokrát pomocí zamykání prostředků prostřednictvím monitoru. Chcete-li to provést, musíte kolem Random vytvořit obal, který zabalí volání jeho metod do příkazu zámku a zajistí tak výhradní přístup k instanci pro volajícího. Tato cesta je špatná, protože snižuje výkon ve scénářích náročných na vlákna.

Dalším způsobem, který popíšu níže, je použití jedné instance na vlákno. Jediná věc, kterou musíme zajistit, je, že při vytváření instancí používáme různá semena, takže nemůžeme používat výchozí konstruktory. Jinak je tato cesta poměrně přímočará.

Bezpečný poskytovatel

Naštěstí nová generická třída ThreadLocal , představený v .NET 4, velmi usnadňuje zápis poskytovatelů, kteří poskytují jednu instanci na vlákno. Stačí předat delegáta konstruktoru ThreadLocal, který bude odkazovat na získání hodnoty naší instance samotné. V tomto případě jsem se rozhodl použít jedinou počáteční hodnotu a inicializoval jsem ji pomocí Environment.TickCount (což je přesně to, jak funguje konstruktor Random bez parametrů). Dále se výsledný počet tiků zvýší pokaždé, když potřebujeme získat novou náhodnou instanci pro samostatné vlákno.

Níže uvedená třída je zcela statická a obsahuje pouze jednu veřejnou (otevřenou) metodu GetThreadRandom. Tato metoda je vytvořena spíše metodou než vlastností, hlavně pro pohodlí: to zajistí, že všechny třídy, které potřebují instanci Random, budou záviset na Func (delegát ukazující na metodu, která nebere žádné parametry a vrací hodnotu typu Random), a nikoli ze samotné třídy Random. Pokud je typ určen ke spuštění v jediném vláknu, může zavolat delegáta, aby získal jedinou instanci Random a poté ji mohl používat v celém průběhu; pokud typ potřebuje pracovat ve vícevláknových scénářích, může zavolat delegátovi pokaždé, když potřebuje generátor náhodných čísel. Níže uvedená třída vytvoří tolik instancí třídy Random, kolik je vláken, a každá instance bude začínat od jiné počáteční hodnoty. Pokud potřebujeme použít poskytovatele náhodných čísel jako závislost v jiných typech, můžeme udělat toto: new TypeThatNeedsRandom(RandomProvider.GetThreadRandom) . No, tady je samotný kód:
pomocí systému; pomocí System.Threading; public static class RandomProvider ( private static int seed = Environment.TickCount; private static ThreadLocal randomWrapper = nový ThreadLocal (() => new Random(Interlocked.Increment(ref seed))); public static Random GetThreadRandom() ( return randomWrapper.Value; ) )
Dost jednoduché, že? Je to proto, že veškerý kód je zaměřen na vytvoření správné instance Random. Jakmile je instance vytvořena a vrácena, nezáleží na tom, co s ní dále uděláte: všechna další vydání instancí jsou zcela nezávislá na aktuální. Klientský kód má samozřejmě mezeru pro škodlivé zneužití: může vzít jednu instanci Random a předat ji jiným vláknům namísto volání našeho RandomProvider v těchto jiných vláknech.

Problémy s návrhem rozhraní

Jeden problém stále zůstává: používáme slabě chráněný generátor náhodných čísel. Jak již bylo zmíněno dříve, v RandomNumberGenerator existuje mnohem bezpečnější verze RNG, jejíž implementace je ve třídě RNGCryptoServiceProvider. Jeho API se však ve standardních scénářích používá poměrně obtížně.

Bylo by velmi hezké, kdyby poskytovatelé RNG v rámci měli samostatné „zdroje náhodnosti“. V tomto případě bychom mohli mít jediné, jednoduché a pohodlné API, které by podporovala jak nezabezpečená, ale rychlá implementace, tak zabezpečená, ale pomalá. No, není na škodu snít. Možná se podobná funkce objeví v budoucích verzích .NET Framework. Možná někdo mimo Microsoft nabídne vlastní implementaci adaptéru. (Bohužel tím člověkem nebudu...implementovat něco takového správně je překvapivě složité.) Můžete si také vytvořit vlastní třídu odvozením z Random a přepsáním metod Sample a NextBytes, ale není jasné, jak přesně by měly práce nebo dokonce váš vlastní Implementační vzorek může být mnohem složitější, než se zdá. Možná příště…

Překlad článku Náhodná čísla Jon Skeete, široce známý v úzkých kruzích. U tohoto článku jsem se zastavil, protože jsem svého času sám narazil na problém v něm popsaný.

Procházení témat podle .SÍŤ A C# na webu StackOverflow můžete vidět nespočet otázek zmiňujících slovo „random“, které ve skutečnosti vyvolávají stejnou věčnou a „nezničitelnou“ otázku: proč generátor náhodných čísel System.Random „nefunguje“ a jak „ opravit" " Tento článek je věnován zvážení tohoto problému a způsobům jeho řešení.

Formulace problému

Na StackOverflow, v diskusních skupinách a seznamech adresátů, všechny otázky na téma „náhodné“ zní asi takto:
Používám Random.Next ke generování více náhodných čísel, ale metoda vrací stejné číslo, když je voláno vícekrát. Číslo se mění při každém spuštění aplikace, ale v rámci jednoho spuštění programu je konstantní.

Příklad kódu je něco takového:
// Špatný kód! Nepoužívat! for (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit()); } ... static int GenerateDigit() { Random rng = new Random(); // Предположим, что здесь много логики return rng.Next(10); }
Tak co je tady špatně?

Vysvětlení

Třída Random není skutečný generátor náhodných čísel, obsahuje generátor pseudo náhodná čísla. Každá instance třídy Random obsahuje nějaký vnitřní stav, a když je zavolána metoda Next (nebo NextDouble nebo NextBytes), metoda použije tento stav k vrácení čísla, které se bude jevit jako náhodné. Vnitřní stav se pak změní tak, že při příštím zavolání Next vrátí jiné zdánlivě náhodné číslo, než které bylo vráceno dříve.

Všechny „vnitřnosti“ třídy Random zcela deterministický. To znamená, že pokud vezmete několik instancí třídy Random se stejným počátečním stavem, který je určen parametrem konstruktoru semínko a pro každou instanci zavolejte určité metody ve stejném pořadí a se stejnými parametry, pak nakonec dostanete stejné výsledky.

Co je tedy špatného na výše uvedeném kódu? Špatná věc je, že v každé iteraci cyklu používáme novou instanci třídy Random. Náhodný konstruktor, který nebere žádné parametry, bere jako výchozí hodnotu aktuální datum a čas. Iterace ve smyčce budou „rolovat“ tak rychle, že systémový čas „nebude mít čas se změnit“ po jejich dokončení; všechny instance Náhodného tedy obdrží stejnou hodnotu jako jejich počáteční stav, a proto vrátí stejné pseudonáhodné číslo.

jak to opravit?

Existuje mnoho řešení problému, z nichž každé má své klady a zápory. Na pár z nich se podíváme.
Použití kryptografického generátoru náhodných čísel
.NET obsahuje abstraktní třídu RandomNumberGenerator, ze které musí dědit všechny implementace kryptografických generátorů náhodných čísel (dále jen cryptoRNG). .NET také obsahuje jednu z těchto implementací - splňují třídu RNGCryptoServiceProvider. Myšlenka krypto-RNG je, že i když je to stále generátor pseudonáhodných čísel, poskytuje poměrně silnou nepředvídatelnost výsledků. RNGCryptoServiceProvider využívá více zdrojů entropie, které jsou v podstatě „šumem“ ve vašem počítači a posloupnost čísel, kterou generuje, je velmi obtížné předvídat. Navíc "in-computer" šum může být použit nejen jako počáteční stav, ale také mezi voláními na následující náhodná čísla; takže i když budeme znát aktuální stav třídy, nebude stačit vypočítat jak další čísla, která budou generována v budoucnu, tak ta, která byla vygenerována dříve. Ve skutečnosti je přesné chování závislé na implementaci. Kromě toho může systém Windows používat specializovaný hardware, který je zdrojem „skutečné náhodnosti“ (například snímač radioaktivního izotopového rozpadu), aby generoval ještě bezpečnější a spolehlivější náhodná čísla.

Srovnejme to s dříve diskutovanou třídou Random. Řekněme, že jste desetkrát zavolali Random.Next(100) a uložili výsledky. Pokud máte dostatečný výpočetní výkon, můžete pouze na základě těchto výsledků vypočítat počáteční stav (seed), se kterým byla instance Random vytvořena, předpovědět další výsledky volání Random.Next(100) a dokonce vypočítat výsledky předchozí volání metod. Toto chování je extrémně nepřijatelné, pokud používáte náhodná čísla pro bezpečnostní, finanční účely atd. Crypto RNG fungují výrazně pomaleji než třída Random, ale generují posloupnost čísel, z nichž každé je nezávislejší a nepředvídatelné na hodnotách ostatních.

Ve většině případů není špatný výkon porušením dohody – je to špatné API. RandomNumberGenerator je navržen tak, aby generoval sekvence bajtů - to je vše. Porovnejte to s metodami třídy Random, kde je možné získat náhodné celé číslo, zlomkové číslo a také sadu bajtů. Další užitečnou vlastností je možnost získat náhodné číslo v určeném rozsahu. Porovnejte tyto možnosti s polem náhodných bajtů, které vytváří RandomNumberGenerator. Situaci můžete napravit vytvořením vlastního obalu (wrapper) kolem RandomNumberGenerator, který převede náhodné bajty na „pohodlný“ výsledek, ale toto řešení není triviální.

Ve většině případů je však „slabost“ třídy Random v pořádku, pokud dokážete vyřešit problém popsaný na začátku článku. Podívejme se, co zde můžeme dělat.

Použijte jednu instanci třídy Random pro více volání
Tady to je, kořenem řešení problému je použití pouze jedné instance Random při vytváření mnoha náhodných čísel pomocí Random.Next. A je to velmi jednoduché - podívejte se, jak můžete změnit výše uvedený kód:
// Tento kód bude lepší Random rng = new Random(); for (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit(rng)); } ... static int GenerateDigit(Random rng) { // Предположим, что здесь много логики return rng.Next(10); }
Nyní bude mít každá iterace jiná čísla... ale to není vše. Co se stane, když tento blok kódu zavoláme dvakrát za sebou? Správně, vytvoříme dvě náhodné instance se stejným semínkem a získáme dvě stejné sady náhodných čísel. Čísla v každé sadě se budou lišit, ale tyto sady budou mezi sebou stejné.

Problém lze vyřešit dvěma způsoby. Za prvé, můžeme použít nikoli instanci, ale statické pole obsahující instanci Random, a pak výše uvedený kus kódu vytvoří pouze jednu instanci a bude ji používat a bude ji volat tolikrát, kolikrát je potřeba. Zadruhé můžeme odtamtud vytvoření náhodné instance zcela odstranit a přesunout ji „výše“, ideálně až na samý „vrchol“ programu, kde se vytvoří jediná náhodná instance, po které se přenese na všechna místa. kde jsou potřeba náhodná čísla. To je skvělý nápad, pěkně vyjádřený závislostmi, ale bude fungovat, pokud budeme používat pouze jedno vlákno.

Bezpečnost závitu

Třída Random není bezpečná pro vlákna. Vzhledem k tomu, jak rádi vytváříme jedinou instanci a používáme ji v rámci programu po celou dobu jeho provádění (singleton, ahoj!), nedostatek bezpečnosti vláken se stává skutečnou bolestí v zadku. Pokud totiž použijeme jednu instanci současně ve více vláknech, pak existuje možnost, že se její vnitřní stav resetuje, a pokud se tak stane, tak od toho okamžiku bude instance zbytečná.

Opět existují dva způsoby, jak problém vyřešit. První cesta stále zahrnuje použití jediné instance, ale tentokrát pomocí zamykání prostředků prostřednictvím monitoru. Chcete-li to provést, musíte kolem Random vytvořit obal, který zabalí volání jeho metod do příkazu zámku a zajistí tak výhradní přístup k instanci pro volajícího. Tato cesta je špatná, protože snižuje výkon ve scénářích náročných na vlákna.

Dalším způsobem, který popíšu níže, je použití jedné instance na vlákno. Jediná věc, kterou musíme zajistit, je, že při vytváření instancí používáme různá semena, takže nemůžeme používat výchozí konstruktory. Jinak je tato cesta poměrně přímočará.

Bezpečný poskytovatel

Naštěstí nová generická třída ThreadLocal , představený v .NET 4, velmi usnadňuje zápis poskytovatelů, kteří poskytují jednu instanci na vlákno. Stačí předat delegáta konstruktoru ThreadLocal, který bude odkazovat na získání hodnoty naší instance samotné. V tomto případě jsem se rozhodl použít jedinou počáteční hodnotu a inicializoval jsem ji pomocí Environment.TickCount (což je přesně to, jak funguje konstruktor Random bez parametrů). Dále se výsledný počet tiků zvýší pokaždé, když potřebujeme získat novou náhodnou instanci pro samostatné vlákno.

Níže uvedená třída je zcela statická a obsahuje pouze jednu veřejnou (otevřenou) metodu GetThreadRandom. Tato metoda je vytvořena spíše metodou než vlastností, hlavně pro pohodlí: to zajistí, že všechny třídy, které potřebují instanci Random, budou záviset na Func (delegát ukazující na metodu, která nebere žádné parametry a vrací hodnotu typu Random), a nikoli ze samotné třídy Random. Pokud je typ určen ke spuštění v jediném vláknu, může zavolat delegáta, aby získal jedinou instanci Random a poté ji mohl používat v celém průběhu; pokud typ potřebuje pracovat ve vícevláknových scénářích, může zavolat delegátovi pokaždé, když potřebuje generátor náhodných čísel. Níže uvedená třída vytvoří tolik instancí třídy Random, kolik je vláken, a každá instance bude začínat od jiné počáteční hodnoty. Pokud potřebujeme použít poskytovatele náhodných čísel jako závislost v jiných typech, můžeme udělat toto: new TypeThatNeedsRandom(RandomProvider.GetThreadRandom) . No, tady je samotný kód:
pomocí systému; pomocí System.Threading; public static class RandomProvider ( private static int seed = Environment.TickCount; private static ThreadLocal randomWrapper = nový ThreadLocal (() => new Random(Interlocked.Increment(ref seed))); public static Random GetThreadRandom() ( return randomWrapper.Value; ) )
Dost jednoduché, že? Je to proto, že veškerý kód je zaměřen na vytvoření správné instance Random. Jakmile je instance vytvořena a vrácena, nezáleží na tom, co s ní dále uděláte: všechna další vydání instancí jsou zcela nezávislá na aktuální. Klientský kód má samozřejmě mezeru pro škodlivé zneužití: může vzít jednu instanci Random a předat ji jiným vláknům namísto volání našeho RandomProvider v těchto jiných vláknech.

Problémy s návrhem rozhraní

Jeden problém stále zůstává: používáme slabě chráněný generátor náhodných čísel. Jak již bylo zmíněno dříve, v RandomNumberGenerator existuje mnohem bezpečnější verze RNG, jejíž implementace je ve třídě RNGCryptoServiceProvider. Jeho API se však ve standardních scénářích používá poměrně obtížně.

Bylo by velmi hezké, kdyby poskytovatelé RNG v rámci měli samostatné „zdroje náhodnosti“. V tomto případě bychom mohli mít jediné, jednoduché a pohodlné API, které by podporovala jak nezabezpečená, ale rychlá implementace, tak zabezpečená, ale pomalá. No, není na škodu snít. Možná se podobná funkce objeví v budoucích verzích .NET Framework. Možná někdo mimo Microsoft nabídne vlastní implementaci adaptéru. (Bohužel tím člověkem nebudu...implementovat něco takového správně je překvapivě složité.) Můžete si také vytvořit vlastní třídu odvozením z Random a přepsáním metod Sample a NextBytes, ale není jasné, jak přesně by měly práce nebo dokonce váš vlastní Implementační vzorek může být mnohem složitější, než se zdá. Možná příště…

Velmi často je v programech potřeba používat náhodná čísla - od vyplňování pole až po kryptografii. Pro získání posloupnosti náhodných čísel má jazyk C# třídu Random. Tato třída poskytuje dva konstruktory:

  • Náhodný()- inicializuje instanci třídy Random s počáteční hodnotou, která závisí na aktuálním čase. Jak je známo, čas může být reprezentován v klíšťata- 100 nanosekundových pulzů počínaje 1. lednem 0001. A časová hodnota v ticks je 64bitové celé číslo, které bude použito k inicializaci instance generátoru náhodných čísel.
  • Náhodné (Int32)- inicializuje instanci třídy Random pomocí zadané počáteční hodnoty. Inicializace generátoru náhodných čísel tímto způsobem může být pohodlná při ladění programu, protože v tomto případě budou při každém spuštění programu generována stejná „náhodná“ čísla.
Hlavní metodou této třídy je metoda Next(), která vám umožňuje získat náhodné číslo a má řadu přetížení:
  • Next() - vrací náhodné nezáporné celé číslo ve formátu Int32.
  • Další( Int32)- vrátí náhodné nezáporné celé číslo, které je menší než zadaná hodnota.
  • Další( Int32 min, Int32 max)- vrátí náhodné celé číslo v zadaném rozsahu. V tomto případě musí být splněna podmínka min
A také metody
  • NextBytes( Byte)- vyplní prvky zadaného bajtového pole náhodnými čísly.
  • NextDouble() - vrací náhodné číslo s plovoucí desetinnou čárkou v rozsahu )
    přestávka ; // nalezena shoda, prvek se neshoduje
    }
    jestliže (j == i)
    { // žádná shoda nenalezena
    a[i] = num; // uložit prvek
    i++; // přejít na další prvek
    }
    }
    for (int i = 0; i< 100; i++)
    {

    if (i % 10 == 9)
    Console.WriteLine();
    }
    Konzole .ReadKey();
    }
    }
    }

    Čím blíže ke konci pole, tím více generací musí být provedeno, aby se získala neopakující se hodnota.
    Následující příklad zobrazuje počet volání metody Next() k získání každého prvku a také celkový počet náhodných čísel vygenerovaných k vyplnění pole 100 prvků neopakujícími se hodnotami.

    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

    pomocí systému;
    jmenný prostor MyProgram
    {
    třídní program
    {
    static void Main (string args)
    {
    Random rnd = new Random();
    int a = new int ; // pole prvků
    int pocet = new int ; // pole počtu generací
    a = rnd.Další(0, 101);
    int c = 0; // čítač počtu generací
    počet = 1; // a je generováno pouze jednou
    for (int i = 1; i< 100;)
    {
    int num = rnd.Next(0, 101);
    c++; // vygeneroval prvek ještě jednou
    int j;
    pro (j = 0; j< i; j++)
    {
    if (num == a[j])
    přestávka ;
    }
    jestliže (j == i)
    {
    a[i] = num; i++;
    počet[i] = c; c = 0; // uložit počet generací
    }
    }
    // Tisk hodnot prvků
    Konzole .WriteLine( "Hodnoty prvků");
    for (int i = 0; i< 100; i++)
    {
    Console .Write("(0,4) " , a[i]);
    if (i % 10 == 9)
    Console.WriteLine();
    }
    Console.WriteLine();
    // Zobrazení počtu generací
    Konzole .WriteLine( "Počet generace prvků");
    int součet = 0;
    for (int i = 0; i< 100; i++)
    {
    součet += počet[i];
    Console .Write("(0,4) " , count[i]);
    if (i % 10 == 9)
    Console.WriteLine();
    }
    Konzole .WriteLine( "Celkový počet generací - (0)", součet);
    Konzole .ReadKey();
    }
    }
    }

    void Main (string args)
    {
    Random rnd = new Random();
    int a = nový int ;
    for (int i = 0; i< 100; i++)
    a[i] = i;
    for (int i = 0; i< 50; i++)
    {
    int i1 = rnd.Next(0, 100); // první index
    int i2 = rnd.Next(0, 100); // druhý index
    // výměna hodnot prvků s indexy i1 a i2
    int temp = a;
    a = a;
    a = teplota;
    }
    Konzole .WriteLine( "Hodnoty prvků");
    for (int i = 0; i< 100; i++)
    {
    Console .Write("(0,4) " , a[i]);
    if (i % 10 == 9)
    Console.WriteLine();
    }
    Konzole .ReadKey();
    }
    }
    }

    Přesouvání hodnot je efektivnější, pokud se rozsah hodnot shoduje (nebo se blíží) počtu hodnot, protože v tomto případě je počet generování náhodných prvků výrazně snížen.

    Funkce, která generuje pseudonáhodná čísla, má prototyp v souboru knihovny stdlib.h:

    1
    2
    3
    4
    5
    6

    unsigned long int next = 1;
    int rand (neplatný)
    {
    další = další * 1103515245;
    return ((unsigned int )(další / 65536) * 2768);
    }


    Funkce rand() nebere žádné argumenty, ale pracuje s další proměnnou s globálním rozsahem.

    Pokud potřebujete vygenerovat sekvenci v rozsahu , pak se použije vzorec:

    Číslo = rand() % (M2-M1+1) + M1;

    Kde Číslo– vygenerované číslo. M2-M1+1– plný rozsah reprezentace čísel. M1– offset zadaného rozsahu vzhledem k 0; % - zbytek divize.

    Pokud například potřebujete vygenerovat sekvenci v rozsahu [-10;10], bude volání funkce vypadat

    Číslo = rand()%(10+10+1)-10

    Číslo = rand()%(21)-10

    V důsledku získání zbytku z dělení 21 máme číslo od 0 do 20. Odečtením 10 od výsledného čísla získáme číslo v požadovaném rozsahu [-10;10].

    Sekvence generovaná funkcí rand() však bude vypadat stejně při každém spuštění programu.

    Pro generování různých sekvencí při každém spuštění programu je nutné inicializovat globální proměnnou next s hodnotou jinou než 1. K tomuto účelu použijte funkci
    void srand (nepodepsané int seed)
    (další = semeno; )

    Aby bylo zajištěno, že inicializace next bude při každém spuštění programu jiná, je jako výchozí argument nejčastěji používán aktuální čas.

    Příklad Vyplňte pole 20 prvků náhodnými čísly v rozsahu od 0 do 99.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    #zahrnout
    #zahrnout
    #zahrnout
    #definujte VELIKOST 20
    int main() (
    int a;
    srand(čas(NULL));
    for (int i = 0; i {
    a[i] = rand() % 100;
    printf("%d" , a[i]);
    }
    getchar();
    návrat 0;
    }


    Výsledek provedení

    Často vzniká úkol uspořádat existující sadu hodnot v náhodném pořadí. K tomuto účelu se také používá generátor pseudonáhodných čísel. Tím se vytvoří pole a naplní se hodnotami.
    Samotný postup míchání je následující. Dvě hodnoty indexu pole jsou generovány náhodně a hodnoty prvků s výslednými indexy jsou prohozeny. Postup se opakuje alespoň Nkrát, kde N je počet prvků pole.
    Jako příklad zvažte zamíchání 20 hodnot (od 1 do 20) a opakování postupu 20krát.

    Implementace v C

    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

    #zahrnout
    #zahrnout
    #zahrnout
    #definujte VELIKOST 20
    int main() (
    int a;
    srand(čas(NULL));

    for (int i = 0; i< SIZE; i++)
    {
    a[i] = i + 1;
    printf("%2d " , a[i]);
    }
    for (int i = 0; i< SIZE; i++)
    {
    // Náhodně vygeneruje dva indexy prvků
    int ind1 = rand() % 20;
    int ind2 = rand() % 20;
    // a zaměňte prvky s těmito indexy
    int temp = a;
    a = a;
    a = teplota;
    }
    printf("\n" );

    for (int i = 0; i< SIZE; i++)
    printf("%2d " , a[i]);
    getchar();
    návrat 0;
    }


    Výsledek provedení


    Často vyvstává úkol náhodného výběru dříve specifikovaných prvků pole. Navíc je nutné zajistit absenci opakování při výběru těchto prvků.
    Algoritmus pro tuto volbu je následující:

    • Náhodně vybereme index prvku pole
    • Pokud již byl vybrán prvek se stejným indexem, posuňte se doprava, dokud nedosáhneme dalšího nevybraného prvku. Zároveň dbáme na to, aby „pohyb doprava“ nepřesáhl hranice pole. Pokud je detekován přesah pole, začneme prvky pole prohlížet od začátku.
    • Výběr prvku
    • Oprava prvku podle výběru
    • Opakujte tyto kroky pro všechny ostatní prvky

    Implementace v C
    Výsledkem je nové pole b, vytvořené náhodným výběrem prvků pole a.

    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

    #zahrnout
    #zahrnout
    #zahrnout
    #definujte VELIKOST 20
    int main() (
    int a;
    int b; // výsledné pole
    srand(čas(NULL));
    // Vyplňte pole po sobě jdoucími hodnotami od 1 do 20
    for (int i = 0; i< SIZE; i++)
    {
    a[i] = i + 1;
    printf("%2d " , a[i]);
    }

    for (int i = 0; i< SIZE; i++)
    {
    int ind = rand() % 20; // vyberte libovolný index
    zatímco (a == -1) // když je prvek "vybraný"
    {
    ind++; // posun doprava
    ind %= 20; // pokud dosáhneme správné hranice, vrátíme se na začátek
    }
    b[i] = a; // zápis dalšího prvku pole b
    a = -1; // označte prvek pole a jako "vybraný"
    }
    printf("\n" );
    // Výstup výsledného pole
    for (int i = 0; i< SIZE; i++)
    printf("%2d " , b[i]);
    getchar();
    návrat 0;
    }


    Výsledek provedení

    Zdravím všechny, kteří se zastavili. Tato krátká poznámka bude obsahovat pár slov o generování pseudonáhodných čísel v C/C++. Zejména o tom, jak pracovat s nejjednodušší generovací funkcí - rand().

    funkce rand().

    Nalezeno ve standardní knihovně C++ (stdlib.h). Vygeneruje a vrátí pseudonáhodné číslo v rozsahu od 0 do RAND_MAX . Tato konstanta se může lišit v závislosti na architektuře kompilátoru nebo procesoru, ale obecně jde o maximální hodnotu datového typu unsigned int. Neakceptuje parametry a nikdy neakceptoval.

    Aby generování proběhlo, je potřeba nastavit seed pomocí pomocné funkce ze stejné knihovny - srand(). Vezme číslo a nastaví ho jako výchozí bod pro generování náhodného čísla. Pokud seed není nastaven, při každém spuštění programu obdržíme stejný náhodná čísla. Nejviditelnějším řešením je poslat tam aktuální systémový čas. To lze provést pomocí funkce time(NULL). Tato funkce je v knihovně time.h.

    Utřídili jsme teorii, našli všechny funkce pro generování náhodných čísel, teď je pojďme vygenerovat.

    Příklad použití funkce generování náhodných čísel rand().

    #zahrnout #zahrnout #zahrnout pomocí jmenného prostoru std; int main() ( srand(čas(NULL)); for(int i = 0; i< 10; i++) { cout << rand() << endl; } return 0; }

    Rand nám vygeneroval 10 náhodných čísel. Že jsou náhodné, můžete ověřit opakovaným spouštěním programu. Ale to nestačí, takže musíme mluvit o dosahu.

    Nastavit hranice rozsahu pro rand()

    Chcete-li vygenerovat číslo v rozsahu od A do B včetně, musíte napsat toto:

    A + rand() % ((B + 1) - A);

    Hraniční hodnoty mohou být také záporné, což vám umožňuje vygenerovat záporné náhodné číslo, podívejme se na příklad.

    #zahrnout #zahrnout #zahrnout pomocí jmenného prostoru std; int main() ( srand(čas(NULL)); int A = -2; int B = 8; for(int i = 0; i< 100; i++) { cout << A + rand() % ((B + 1) - A) << endl; } return 0; }