BlessRNG of kontrolearje de RNG foar earlikens

BlessRNG of kontrolearje de RNG foar earlikens

Yn spultsje-ûntwikkeling moatte jo faaks wat ferbine mei willekeurigens: Unity hat in eigen Random foar dit, en parallel mei it is System.Random. Eartiids krige ik by ien fan de projekten de yndruk dat beide oars wurkje koene (hoewol't se in lykweardige ferdieling hawwe moatte).

Doe gongen se net yn details - it wie genôch dat de oergong nei System.Random alle problemen korrizjearre. No hawwe wy besletten om it yn mear detail te besjen en in bytsje ûndersyk út te fieren: hoe "biased" of foarsisber RNG's binne, en hokker te kiezen. Boppedat haw ik mear as ien kear tsjinstridige mieningen heard oer har "earlikens" - lit ús besykje út te finen hoe't de echte resultaten fergelykje mei de ferklearre.

Koarte edukative programma as RNG is eins RNG

As jo ​​​​al bekend binne mei willekeurige getallengenerators, dan kinne jo fuortendaliks nei de seksje "Test" oerslaan.

Willekeurige nûmers (RN) binne in opienfolging fan nûmers oanmakke mei help fan guon willekeurich (chaotysk) proses, in boarne fan entropy. Dat is, dit is in folchoarder wêrfan de eleminten binne net mei-inoar ferbûn troch in wiskundige wet - se hawwe gjin oarsaak-en-gevolg relaasje.

Wat it willekeurige getal makket, wurdt in willekeurige getalgenerator (RNG) neamd. It liket derop dat alles elemintêr is, mar as wy fan teory nei praktyk ferpleatse, dan is it feitlik net sa ienfâldich om in softwarealgoritme te realisearjen foar it generearjen fan sa'n folchoarder.

De reden leit yn it ûntbrekken fan deselde gaos yn moderne konsuminteelektronika. Sûnder it stopje willekeurige getallen te wêzen willekeurich, en harren generator feroaret yn in gewoane funksje fan fansels definiearre arguminten. Foar in oantal spesjaliteiten op it mêd fan IT is dit in serieus probleem (bygelyks kryptografy), mar foar oaren is der in folslein akseptabel oplossing.

It is needsaaklik om in algoritme te skriuwen dat soe weromkomme, hoewol net wier willekeurige nûmers, mar sa ticht mooglik by har - de saneamde pseudo-willekeurige nûmers (PRN). It algoritme wurdt yn dit gefal in pseudorandom number generator (PRNG) neamd.

D'r binne ferskate opsjes foar it meitsjen fan in PRNG, mar it folgjende sil foar elkenien relevant wêze:

  1. De needsaak foar foarriedige inisjalisaasje.

    De PRNG hat gjin boarne fan entropy, dus it moat foar gebrûk in earste steat krije. It wurdt oantsjutte as in nûmer (of vector) en wurdt in sied neamd (willekeurich sied). Faak wurdt de prosessor-klokteller as it numerike ekwivalint fan systeemtiid brûkt as sied.

  2. Sequence reproducibility.

    De PRNG is folslein deterministysk, sadat it sied dat spesifisearre is tidens inisjalisaasje unyk de folsleine takomstige folchoarder fan nûmers bepaalt. Dit betsjut dat in aparte PRNG inisjalisearre mei itselde sied (op ferskillende tiden, yn ferskate programma's, op ferskate apparaten) deselde folchoarder sil generearje.

Jo moatte ek de kânsferdieling witte dy't de PRNG karakterisearret - hokker nûmers it sil generearje en mei hokker kâns. Meastentiids is dit of in normale ferdieling of in unifoarme ferdieling.
BlessRNG of kontrolearje de RNG foar earlikens
Normale ferdieling (lofts) en unifoarme ferdieling (rjochts)

Litte wy sizze dat wy in earlike stjer hawwe mei 24 kanten. As jo ​​​​it goaie, sil de kâns om ien te krijen gelyk wêze oan 1/24 (itselde as de kâns om in oar nûmer te krijen). As jo ​​meitsje in protte smyt en opnimme de resultaten, Jo sille fernimme dat alle rânen falle út mei likernôch deselde frekwinsje. Yn essinsje kin dizze die wurde beskôge as in RNG mei in unifoarme ferdieling.

Wat as jo smyt 10 fan dizze dobbelstiennen yn ien kear en telle de totale punten? Sil der unifoarmiteit foar behâlden wurde? Nee. Meastentiids sil it bedrach tichtby 125 punten wêze, dat is in gemiddelde wearde. En as gefolch, sels foardat jo in goaie meitsje, kinne jo it takomstige resultaat rûchwei skatte.

De reden is dat d'r it grutste oantal kombinaasjes binne om de gemiddelde skoare te krijen. Hoe fierder fan it, de minder kombinaasjes - en, sadwaande, hoe leger de kâns op in ferlies. As dizze gegevens fisualisearre binne, sille se vague op 'e foarm fan in klok lykje. Dêrom, mei wat stretch, kin in systeem fan 10 dobbelstiennen in RNG wurde neamd mei in normale ferdieling.

In oar foarbyld, allinich dizze kear op in fleantúch - sjitten op in doel. De skutter sil in RNG wêze dy't in pear nûmers genereart (x, y) dy't werjûn wurdt op 'e grafyk.
BlessRNG of kontrolearje de RNG foar earlikens
It iens dat de opsje oan de linkerkant is tichter by it echte libben - dit is in RNG mei in normale ferdieling. Mar as jo stjerren moatte ferspriede yn in tsjustere loft, dan is de juste opsje, krigen mei RNG mei in unifoarme ferdieling, better geskikt. Yn it algemien, kies in generator ôfhinklik fan de taak by de hân.

Litte wy no prate oer de entropy fan 'e PNG-sekwinsje. D'r is bygelyks in folchoarder dy't sa begjint:

89, 93, 33, 32, 82, 21, 4, 42, 11, 8, 60, 95, 53, 30, 42, 19, 34, 35, 62, 23, 44, 38, 74, 36, 52, 18, 58, 79, 65, 45, 99, 90, 82, 20, 41, 13, 88, 76, 82, 24, 5, 54, 72, 19, 80, 2, 74, 36, 71, 9, ...

Hoe willekeurich binne dizze nûmers op it earste each? Litte wy begjinne mei it kontrolearjen fan de ferdieling.
BlessRNG of kontrolearje de RNG foar earlikens
It liket tichtby unifoarm, mar as jo in folchoarder fan twa nûmers lêze en se ynterpretearje as koördinaten op in fleantúch, krije jo dit:
BlessRNG of kontrolearje de RNG foar earlikens
Patronen wurde dúdlik sichtber. En om't de gegevens yn 'e folchoarder op in bepaalde manier binne oardere (dat is, it hat in lege entropy), kin dit oanlieding jaan ta dat heul "bias". Op syn minst is sa'n PRNG net heul geskikt foar it generearjen fan koördinaten op in fleantúch.

In oare folchoarder:

42, 72, 17, 0, 30, 0, 15, 9, 47, 19, 35, 86, 40, 54, 97, 42, 69, 19, 20, 88, 4, 3, 67, 27, 42, 56, 17, 14, 20, 40, 80, 97, 1, 31, 69, 13, 88, 89, 76, 9, 4, 85, 17, 88, 70, 10, 42, 98, 96, 53, ...

Alles liket hjir goed te wêzen, sels op it fleantúch:
BlessRNG of kontrolearje de RNG foar earlikens
Litte wy yn folume sjen (lês trije nûmers tagelyk):
BlessRNG of kontrolearje de RNG foar earlikens
En wer de patroanen. It is net mear mooglik om in fisualisaasje yn fjouwer diminsjes op te bouwen. Mar patroanen kinne bestean op dizze diminsje en op gruttere.

Yn kryptografy, wêr't de strangste easken oan PRNG's oplein wurde, is sa'n situaasje kategoarysk net akseptabel. Dêrom binne spesjale algoritmen ûntwikkele om har kwaliteit te beoardieljen, dy't wy no net oanrekke. It ûnderwerp is wiidweidich en fertsjinnet in apart artikel.

Testing

As wy wat net wis witte, hoe dan mei te wurkjen? Is it de muoite wurdich om de dyk oer te stekken as jo net witte hokker ferkearsljocht it tastean? De gefolgen kinne oars wêze.

Itselde jildt foar de beruchte willekeur yn Unity. It is goed as de dokumintaasje de nedige details iepenbieret, mar it ferhaal neamd oan it begjin fan it artikel barde krekt fanwegen it ûntbrekken fan 'e winske spesifikaasjes.

En as jo net witte hoe't it ark wurket, kinne jo it net korrekt brûke. Yn 't algemien is de tiid kommen om in eksperimint te kontrolearjen en út te fieren om úteinlik op syn minst wis te wêzen oer de ferdieling.

De oplossing wie ienfâldich en effektyf - sammelje statistiken, krije objektive gegevens en besjoch de resultaten.

Underwerp fan stúdzje

D'r binne ferskate manieren om willekeurige nûmers te generearjen yn Unity - wy testen fiif.

  1. System.Random.Next(). Genereart heule getallen yn in bepaald berik fan wearden.
  2. System.Random.NextDouble(). Genereart dûbele presysnûmers yn it berik fan [0; 1).
  3. UnityEngine.Random.Range(). Genereart inkele presysnûmers (floaten) yn in bepaald berik fan wearden.
  4. UnityEngine.Random.value. Genereart inkele presysnûmers (floaten) yn it berik fan [0; 1).
  5. Unity.Mathematics.Random.NextFloat(). Part fan de nije Unity.Mathematics bibleteek. Genereart inkele presysnûmers (floaten) yn in bepaald berik fan wearden.

Hast oeral yn de dokumintaasje waard in unifoarme ferdieling oantsjutte, mei útsûndering fan UnityEngine.Random.value (dêr't de ferdieling net oantsjutte wie, mar nei analogy mei UnityEngine.Random.Range() waard ek unifoarm ferwachte) en Unity.Mathematics.Random .NextFloat () (dêr't yn De basis is de xorshift algoritme, dat betsjut dat wer moatte jo wachtsje op in unifoarm ferdieling).

Standert waarden de ferwachte resultaten nommen lykas oanjûn yn 'e dokumintaasje.

Methodology

Wy skreau in lytse applikaasje dy't generearre sekwinsjes fan willekeurige nûmers mei help fan elk fan de presintearre metoaden en bewarre de resultaten foar fierdere ferwurking.

De lingte fan elke sekwinsje is 100 nûmers.
It berik fan willekeurige getallen is [0, 100).

Gegevens waarden sammele fan ferskate doelplatfoarms:

  • Windows
    — Unity v2018.3.14f1, Bewurkingsmodus, Mono, .NET Standert 2.0
  • MacOS
    — Unity v2018.3.14f1, Bewurkingsmodus, Mono, .NET Standert 2.0
    - Unity v5.6.4p4, Bewurkingsmodus, Mono, .NET Standert 2.0
  • Android
    - Unity v2018.3.14f1, bouwe per apparaat, Mono, .NET Standert 2.0
  • iOS
    - Unity v2018.3.14f1, bouwe per apparaat, il2cpp, .NET Standard 2.0

Ymplemintaasje

Wy hawwe ferskate manieren om willekeurige getallen te generearjen. Foar elk fan har sille wy in aparte wrapperklasse skriuwe, dy't moatte leverje:

  1. Mooglikheid om it berik fan wearden yn te stellen [min / max). Sil wurde ynsteld fia de constructor.
  2. Metoade werom MF. Litte wy float kieze as it type, om't it algemiener is.
  3. De namme fan 'e generaasjemetoade foar it markearjen fan de resultaten. Foar it gemak sille wy as wearde de folsleine namme fan 'e klasse + de namme fan' e metoade dy't brûkt wurdt om de MF te generearjen weromjaan.

Litte wy earst in abstraksje ferklearje dy't sil wurde fertsjintwurdige troch de IRandomGenerator-ynterface:

namespace RandomDistribution
{
    public interface IRandomGenerator
    {
        string Name { get; }

        float Generate();
    }
}

Implementaasje fan System.Random.Next()

Dizze metoade lit jo in berik fan wearden ynstelle, mar it jout hiele getallen werom, mar driuwers binne nedich. Jo kinne gewoan in heule getal ynterpretearje as in float, of jo kinne it berik fan wearden útwreidzje mei ferskate oarders fan grutte, se kompensearje mei elke generaasje fan 'e midrange. It resultaat sil wat wêze as in fêst punt mei in opjûne folchoarder fan krektens. Wy sille dizze opsje brûke, om't it tichter by de echte floatwearde is.

using System;

namespace RandomDistribution
{
    public class SystemIntegerRandomGenerator : IRandomGenerator
    {
        private const int DefaultFactor = 100000;
        
        private readonly Random _generator = new Random();
        private readonly int _min;
        private readonly int _max;
        private readonly int _factor;


        public string Name => "System.Random.Next()";


        public SystemIntegerRandomGenerator(float min, float max, int factor = DefaultFactor)
        {
            _min = (int)min * factor;
            _max = (int)max * factor;
            _factor = factor;
        }


        public float Generate() => (float)_generator.Next(_min, _max) / _factor;
    }
}

Implementaasje fan System.Random.NextDouble()

Hjir is it fêste berik fan wearden [0; 1). Om it te projektearjen op de spesifisearre yn 'e konstruktor, brûke wy ienfâldige rekenen: X * (max - min) + min.

using System;

namespace RandomDistribution
{
    public class SystemDoubleRandomGenerator : IRandomGenerator
    {
        private readonly Random _generator = new Random();
        private readonly double _factor;
        private readonly float _min;


        public string Name => "System.Random.NextDouble()";


        public SystemDoubleRandomGenerator(float min, float max)
        {
            _factor = max - min;
            _min = min;
        }


        public float Generate() => (float)(_generator.NextDouble() * _factor) + _min;
    }
}

Implementaasje fan UnityEngine.Random.Range()

Dizze metoade fan 'e UnityEngine.Random statyske klasse lit jo in berik fan wearden ynstelle en jout in floattype werom. Jo hoege gjin ekstra transformaasjes te dwaan.

using UnityEngine;

namespace RandomDistribution
{
    public class UnityRandomRangeGenerator : IRandomGenerator
    {
        private readonly float _min;
        private readonly float _max;


        public string Name => "UnityEngine.Random.Range()";


        public UnityRandomRangeGenerator(float min, float max)
        {
            _min = min;
            _max = max;
        }


        public float Generate() => Random.Range(_min, _max);
    }
}

Útfiering fan UnityEngine.Random.value

De wearde-eigenskip fan 'e statyske klasse UnityEngine.Random jout in floattype werom út in fêste berik fan wearden [0; 1). Lit ús projektearje it op in bepaald berik op deselde wize as by it útfieren fan System.Random.NextDouble ().

using UnityEngine;

namespace RandomDistribution
{
    public class UnityRandomValueGenerator : IRandomGenerator
    {
        private readonly float _factor;
        private readonly float _min;


        public string Name => "UnityEngine.Random.value";


        public UnityRandomValueGenerator(float min, float max)
        {
            _factor = max - min;
            _min = min;
        }


        public float Generate() => (float)(Random.value * _factor) + _min;
    }
}

Implementaasje fan Unity.Mathematics.Random.NextFloat()

De metoade NextFloat() fan 'e klasse Unity.Mathematics.Random jout in driuwend punt fan it type float werom en lit jo in berik fan wearden opjaan. De ienige nuânse is dat elk eksimplaar fan Unity.Mathematics.Random sil moatte wurde inisjalisearre mei wat sied - op dizze manier sille wy foarkomme dat jo werheljende sekwinsjes generearje.

using Unity.Mathematics;

namespace RandomDistribution
{
    public class UnityMathematicsRandomValueGenerator : IRandomGenerator
    {
        private Random _generator;
        private readonly float _min;
        private readonly float _max;


        public string Name => "Unity.Mathematics.Random.NextFloat()";


        public UnityMathematicsRandomValueGenerator(float min, float max)
        {
            _min = min;
            _max = max;
            _generator = new Random();
            _generator.InitState(unchecked((uint)System.DateTime.Now.Ticks));
        }


        public float Generate() => _generator.NextFloat(_min, _max);
    }
}

Utfiering fan MainController

Ferskate ymplemintaasjes fan IRandomGenerator binne klear. Dêrnei moatte jo sekwinsjes generearje en de resultearjende dataset bewarje foar ferwurking. Om dit te dwaan, sille wy in sêne en in lyts MainController-skript yn Unity meitsje, dat alle nedige wurk sil dwaan en tagelyk ferantwurdlik wêze foar ynteraksje mei de UI.

Litte wy de grutte fan 'e dataset en it berik fan MF-wearden ynstelle, en ek in metoade krije dy't in array fan generators werombringt konfigureare en klear om te wurkjen.

namespace RandomDistribution
{
    public class MainController : MonoBehaviour
    {
        private const int DefaultDatasetSize = 100000;

        public float MinValue = 0f;
        public float MaxValue = 100f;

        ...

        private IRandomGenerator[] CreateRandomGenerators()
        {
            return new IRandomGenerator[]
            {
                new SystemIntegerRandomGenerator(MinValue, MaxValue),
                new SystemDoubleRandomGenerator(MinValue, MaxValue),
                new UnityRandomRangeGenerator(MinValue, MaxValue),
                new UnityRandomValueGenerator(MinValue, MaxValue),
                new UnityMathematicsRandomValueGenerator(MinValue, MaxValue)
            };
        }

        ...
    }
}

Litte wy no in dataset oanmeitsje. Yn dit gefal sil gegevensgeneraasje wurde kombinearre mei it opnimmen fan de resultaten yn in tekststream (yn csv-formaat). Om de wearden fan elke IRandomGenerator op te slaan, wurdt in eigen aparte kolom tawiisd, en de earste rigel befettet de namme fan 'e generator.

namespace RandomDistribution
{
    public class MainController : MonoBehaviour
    {
        ...
		
        private void GenerateCsvDataSet(TextWriter writer, int dataSetSize, params IRandomGenerator[] generators)
        {
            const char separator = ',';
            int lastIdx = generators.Length - 1;

            // write header
            for (int j = 0; j <= lastIdx; j++)
            {
                writer.Write(generators[j].Name);
                if (j != lastIdx)
                    writer.Write(separator);
            }
            writer.WriteLine();

            // write data
            for (int i = 0; i <= dataSetSize; i++)
            {
                for (int j = 0; j <= lastIdx; j++)
                {
                    writer.Write(generators[j].Generate());
                    if (j != lastIdx)
                        writer.Write(separator);
                }

                if (i != dataSetSize)
                    writer.WriteLine();
            }
        }

        ...
    }
}

Alles wat oerbliuwt is de GenerateCsvDataSet-metoade oan te roppen en it resultaat op te slaan yn in bestân, of de gegevens daliks oer it netwurk oermeitsje fan it einapparaat nei de ûntfangende tsjinner.

namespace RandomDistribution
{
    public class MainController : MonoBehaviour
    {
        ...
		
        public void GenerateCsvDataSet(string path, int dataSetSize, params IRandomGenerator[] generators)
        {
            using (var writer = File.CreateText(path))
            {
                GenerateCsvDataSet(writer, dataSetSize, generators);
            }
        }


        public string GenerateCsvDataSet(int dataSetSize, params IRandomGenerator[] generators)
        {
            using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
            {
                GenerateCsvDataSet(writer, dataSetSize, generators);
                return writer.ToString();
            }
        }

        ...
    }
}

De projekt boarnen binne by GitLab.

Resultaten

Gjin wûnder barde. Wat se ferwachten, is wat se krigen - yn alle gefallen, in evenredige ferdieling sûnder in hint fan konspiraasjes. Ik sjoch it punt net yn it pleatsen fan aparte grafiken foar platfoarms - se litte allegear sawat deselde resultaten sjen.

De realiteit is:
BlessRNG of kontrolearje de RNG foar earlikens

Fisualisaasje fan sekwinsjes op in fleantúch fan alle fiif generaasjemetoaden:
BlessRNG of kontrolearje de RNG foar earlikens

En fisualisaasje yn 3D. Ik lit allinnich it resultaat fan System.Random.Next () sa as net te produsearje in bosk identike ynhâld.
BlessRNG of kontrolearje de RNG foar earlikens

It ferhaal ferteld yn 'e ynlieding oer de normale ferdieling fan UnityEngine.Random werhelle himsels net: of it wie earst ferkeard, of wat is sûnt feroare yn 'e motor. Mar no binne wy ​​wis.

Boarne: www.habr.com

Add a comment