BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust

BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust

MĂ€ngude arenduses on sageli vajalik midagi juhuslikku: Unity'l on selle jaoks oma Random, kuid paralleelselt eksisteerib ka System.Random. Kaugelt muiste, ĂŒhel projektil, tekkis mul tunne, et need kaks vĂ”ivad töötada erinevalt (kuigi nad peaksid omama ĂŒhtlast jaotust).

Siis ei sĂŒvenenud me detailidesse — piisavalt, et ĂŒleminek System.Random'ile lahendas kĂ”ik probleemid. NĂŒĂŒd oleme otsustanud rohkem sĂŒveneda ja teha vĂ€ike uurimus: kui "poolitatud" vĂ”i ettearvatavad on juhuslikud numbrid ning millist valida. Eriti kuna olen kuulnud vastuolulisi arvamusi nende "aususe" kohta — pĂŒĂŒame vĂ€lja selgitada, kuidas reaalsed tulemused seonduvad deklareeritud tulemustega.

LĂŒhike sissejuhatus vĂ”i Juhuslikud arvud on tegelikult Pseudojuhuslikud arvud

Kui olete juba tuttav juhuslike numbrite generaatoritega, vÔite kohe minna "Testimise" osasse.

Juhuslikud numbrid (JN) on numbrite jĂ€rjestus, mis genereeritakse mĂ”ne juhusliku (kaootilise) protsessi, entropia allika abil. See tĂ€hendab, et see on selline jĂ€rjestus, mille elemendid ei ole omavahel mingisuguse matemaatilise seaduse kaudu seotud — neil puudub pĂ”hjuslik seos.

See, mis loob juhuslikke numbreid, on juhuslikke numbrite generaator (JNG). Tundub, et kÔik on elementaarne, kuid kui minna teooriast praktikasse, pole tegelikult sellise jÀrjestuse genereerimise programmeerimise algoritmi teostamine nii lihtne.

PĂ”hjus peitub kaasaegse tarbeelektroonika puudulikes kaootilisuse tasemes. Ilma selleta kaotavad juhuslikud numbrid oma juhuslikkuse ning nende generaator muutub tavaliseks funktsiooniks etteantud argumentide pĂ”hjal. IT-ala teatud erialade jaoks on see tĂ”sine probleem (nĂ€iteks krĂŒptograafia jaoks), kuid teiste jaoks on tĂ€iesti vastuvĂ”etav lahendus.

Peame kirjutama algoritmi, mis tagab, et kuigi need ei ole tÔeliselt juhuslikud numbrid, oleksid nad vÔimalikult lÀhedased - nii nimetatud pseudojuhuslikud numbrid (PJN). Antud juhul nimetatakse algoritmi pseudojuhuslike numbrite generaatoriks (PJNG).

Pseudojuhuslike numbrite genereerimiseks on mitmeid variante, kuid kÔigi jaoks kehtib jÀrgmine:

  1. Eeltingimusena on vajalik eelneva initsialiseerimise teostamine.

    GPSH on ilma entropiaallikat, seega tuleb enne kasutamist mÀÀrata algtingimused. Need esitatakse numbrina (vĂ”i vektorina) ja neid nimetatakse seemneks (seed, random seed). Sageli kasutatakse seemnena protsessori taktisageduse loendajat vĂ”i sĂŒsteemi aja numbrilist ekvivalenti.

  2. JĂ€rje taastatavus.

    GPSH on tĂ€ielikult deterministlik, seega mÀÀrab algatamisel antud seeme ĂŒhemĂ”tteliselt kogu tulevase numbrite jĂ€rjestuse. See tĂ€hendab, et ĂŒkski GPSH, mis on algatatud sama seemnega (erineval ajal, erinevates programmides, erinevates seadmetes), genereerib sama jĂ€rjestuse.

Samuti tuleb teada, milline on GPSH iseloomustav tĂ”enĂ€osusjaotus — milliseid numbreid see genereerib ja millise tĂ”enĂ€osusega. KĂ”ige sagedamini on see kas normaaljaotus (normal distribution) vĂ”i ĂŒhtlane jaotus (uniform distribution).
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust
Normaaljaotus (vasakul) ja ĂŒhtlane jaotus (paremal)

Oletame, et meil on keskmine mĂ€ngukaart 24 kĂŒljega. Kui seda heita, siis ĂŒhe tulemise tĂ”enĂ€osus on 1/24 (nagu ka mis tahes muu numbri tĂ”enĂ€osus). Kui teha palju heiteid ja tulemusi kirja panna, vĂ”ib mĂ€rgata, et kĂ”ik kĂŒljed tulevad vĂ€lja umbes sama sagedusega. Sisuliselt vĂ”ib seda mĂ€ngukaarti pidada ĂŒhtlase jaotusega RNG-ks.

Aga kui visata kohe 10 sellist kaarti ja arvestada saadud punktide kogusummat? Kas selle puhul jÀÀb ĂŒhtsus alles? Ei. KĂ”ige sagedamini on summa lĂ€hedal 125 punktile, st teatud keskmisele vÀÀrtusele. Seega vĂ”ib enne heite tegemist tulevast tulemust umbes hinnata.

PĂ”hjus on selles, et keskmise punktisumma saavutamiseks on kĂ”ige rohkem kombinatsioone. Mida kaugemal sellest, seda vĂ€hem on kombinatsioone — ja vastavalt ka vĂ€iksem tĂ”enĂ€osus tulemuste saamiseks. Kui neid andmeid visualiseerida, sarnaneksid need uduselt kellakujuga. SeetĂ”ttu vĂ”ib mĂ”ningasel mÀÀral 10 kaardist koosnevat sĂŒsteemi nimetada normaaljaotusega RNG-ks.

Veel ĂŒks nĂ€ide, ainult et tasandil — sihtmĂ€rgi tulistamine. KĂ” shooter on RNG, mis genereerib paari numbreid (x, y), mis kuvatakse graafikul.
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust
NĂ”ustuge, et vasakpoolne variant on tĂ”elisema elu lĂ€hedal — see on normaaljaotusega juhuslik arvugeneraatoreis. But if you need to scatter stars on a dark sky, the right option, generated using a uniformly distributed RNG, is more suitable. Seega valige generaator vastavalt esitatud ĂŒlesandele.

NĂŒĂŒd rÀÀgime PSČ jĂ€rjestuseentsĂ”t/ĂŒllatavust. NĂ€iteks on olemas jĂ€rjestus, mis algab nii:

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, 


Kui palju need numbrid esmapilgul juhuslikud on? Alustame jaotuse kontrollimisest.
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust
Tundub, et see on peaaegu ĂŒhtlane, kuid kui lugeda jĂ€rjestust kahe numbri kaupa ja tĂ”lgendada neid koordinaatidena tasandil, siis see tundub selline:
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust
Mustrid muutuvad selgelt nĂ€htavaks. Kui andmed jĂ€rjestuses on teatud viisil jĂ€rjestatud (st neil on madal entropia), vĂ”ib see tekitada nimelt "kallutatud" tulemusi. VĂ€hemalt selline PSČ ei sobi hĂ€sti tasandi koordinaatide genereerimiseks.

Teine jÀrjestus:

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, 


Siin tundub kÔik isegi tasandil hea:
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust
Vaadates kolmemÔÔtmeliselt (lugedes kolm numbrit korraga):
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust
Ja jÀlle mustrid. NeljamÔÔtmelise visualiseerimise loomine ei ole enam vÔimalik. Kuid mustrid vÔivad eksisteerida ka selles mÔÔtmes ja suuremates.

Samas krĂŒptograafias, kus RNG-dele esitatakse kĂ”ige rangemaid nĂ”udeid, on selline olukord kategooriliselt vastuvĂ”etamatu. SeetĂ”ttu on nende kvaliteedi hindamiseks vĂ€lja töötatud spetsiaalsed algoritmid, millele me praegu ei keskendu. Teema on ulatuslik ja vÀÀrib eraldi artiklit.

Testimine

Kui me midagi kindlasti ei tea, siis kuidas sellega töötada? Kas tasub teed ĂŒletada, kui sa ei tea, milline valgusfoori signaal seda lubab? TagajĂ€rjed vĂ”ivad olla erinevad.

See kehtib ka kuulsate juhuslikkuse kohta Unitys. Hea, kui dokumentatsioon annab vajalikud detailid, kuid artikli alguses mainitud lugu juhtus just soovitud konkreetika puudumise tÔttu.

Ilma et teada, kuidas tööriist töötab, ei saa seda Ă”igesti rakendada. Üldiselt on aeg teha kontroll ja eksperiment, et lĂ”puks veenduda vĂ€hemalt jaotuse osas.

Lahendus oli lihtne ja tÔhus - koguda statistikat, saada objektiivseid andmeid ja vaadata tulemusi.

Uuringu teema

Unitys on mitu viisi juhuslike arvude genereerimiseks - testisime viit.

  1. System.Random.Next(). Genereerib tÀisarve (integer) mÀÀratud vÀÀrtuste vahemikus.
  2. System.Random.NextDouble(). Genereerib kahefikaarse tÀpsusega arve (double) vahemikus [0; 1).
  3. UnityEngine.Random.Range(). Genereerib ĂŒheselgelt tĂ€psusega arve (float) mÀÀratud vÀÀrtuste vahemikus.
  4. UnityEngine.Random.value. Genereerib ĂŒheselgelt tĂ€psusega arve (float) vahemikus [0; 1).
  5. Unity.Mathematics.Random.NextFloat(). Osa uuest raamatukogust Unity.Mathematics. Genereerib ĂŒheselgelt tĂ€psusega arve (float) mÀÀratud vÀÀrtuste vahemikus.

Peaaegu igal pool dokumentatsioonis oli mĂ€rgitud ĂŒhtlane jaotus, vĂ€lja arvatud UnityEngine.Random.value (kus jaotust ei ole mĂ€rgitud, kuid sarnaselt UnityEngine.Random.Range() oodati ka ĂŒhtlast) ja Unity.Mathematics.Random.NextFloat() (kuna alus pĂ”hineb algoritmil xorshift, tuleb jĂ€lle oodata ĂŒhtlast jaotust).

Oletati, et oodatavad tulemused on need, mis on esitatud dokumentatsioonis.

Metoodika

Kirjutasime vÀikese rakenduse, mis genereeris juhuslike arvsede jÀrjendisid igal esitatud viisil ja salvestas tulemused edasise töötlemise jaoks.

Iga jÀrjendi pikkus - 100 000 arvu.
Juhuslike arvude vÀÀrtuste vahemik - [0, 100).

Andmeid koguti mitmetelt sihtplatvormidelt:

  • Windows
    — Unity v2018.3.14f1, toimetaja reĆŸiim, Mono, .NET Standard 2.0
  • macOS
    — Unity v2018.3.14f1, toimetaja reĆŸiim, Mono, .NET Standard 2.0
    — Unity v5.6.4p4, toimetaja reĆŸiim, Mono, .NET Standard 2.0
  • Android
    — Unity v2018.3.14f1, seade jaoks koostamine, Mono, .NET Standard 2.0
  • iOS
    — Unity v2018.3.14f1, seade jaoks koostamine, il2cpp, .NET Standard 2.0

Rakendamine

Meil on mitu erinevat viisi juhuslike arvude genereerimiseks. IgaĂŒhe jaoks kirjutame eraldi wrapper-klassi, mis peab pakkuma:

  1. VÔimalust mÀÀrata vÀÀrtuste vahemik [min/max). See mÀÀratakse konstruktoris.
  2. Meetod, mis tagastab RND. TĂŒĂŒbina valime float, kuna see on ĂŒldisem.
  3. Genereerimisviisi nimetus tulemuste mÀrgistamiseks. Mugavuse huvides tagastame vÀÀrtuse tÀieliku klassi nime + meetodi nime, mida RND genereerimiseks kasutatakse.

Alustame abstraktsiooni kuulutamisega, mis esindatakse IRandomGenerator liidese kaudu:

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

        float Generate();
    }
}

Teostus System.Random.Next()

See meetod vÔimaldab seada vÀÀrtuste vahemiku, kuid tagastab tÀisarve (integer), kuid vajame float. Saame lihtsalt tÔlgendada integerit floatiks vÔi laiendada vÀÀrtuste vahemikku mÔne jÀrgu vÔrra, kompenseerides neid igal juhuslike arvude genereerimisel. Tulemuseks on midagi sarnast fixed-point'iga antud tÀpsusega. Kasutame seda varianti, kuna see on lÀhemal tÔelisele float vÀÀrtusele.

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;
    }
}

Teostus System.Random.NextDouble()

Siin on fikseeritud vÀÀrtuste vahemik [0; 1). Selle projitseerimiseks antud konstruktoris kasutame lihtsat aritmeetikat: 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;
    }
}

Teostus UnityEngine.Random.Range()

See staatilise klassi UnityEngine.Random meetod vĂ”imaldab seada vÀÀrtuste vahemiku ja tagastab float tĂŒĂŒpi juhuslikud arvud. TĂ€iendavateks teisendusteks ei ole vaja.

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);
    }
}

Teostus UnityEngine.Random.value

Staatilise klassi UnityEngine.Random property value tagastab float tĂŒĂŒpi juhusliku arvu fikseeritud vÀÀrtuste vahemikust [0; 1). Projitseerime selle antud vahemikku sama viisi kasutades, nagu teostuses System.Random.NextDouble().

kasutades 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;
    }
}

Unity.Mathematics.Random.NextFloat() rakendamine

NextFloat() meetod klassis Unity.Mathematics.Random tagastab float tĂŒĂŒpi juhusliku arvu ning vĂ”imaldab mÀÀrata vÀÀrtuste vahemikku. Oluline on, et iga Unity.Mathematics.Random eksemplar tuleb initsialiseerida teatud seed'iga — nii vĂ€ltime korduvate jĂ€rjestuste genereerimist.

kasutades 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);
    }
}

MainController rakendamine

Mitmed IRandomGenerator rakendused on valmis. JÀrgmine samm on genereerida jÀrjestused ja salvestada saadud andmestik töötlemiseks. Selleks loome Unitys stseeni ja vÀikese MainController skripti, mis tÀidab kogu vajaliku töö ning vastutab UI-ga suhtlemise eest.

MÀÀrame andmestiku suuruse ja juhuslike arvude vahemiku, samuti loome meetodi, mis tagastab seadistatud ja tööks valmis generaatorite massiivi.

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)
            };
        }

        ...
    }
}

NĂŒĂŒd loome andmestiku. Antud juhul toimub andmete genereerimine koos tulemuste salvestamisega tekstivoogu (csv formaadis). Iga IRandomGenerator vÀÀrtuste jaoks mÀÀratakse eraldi veerg, samas kui esimene rida sisaldab generaatori nime.

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();
            }
        }

        ...
    }
}

On jÀÀnud kutsuda GenerateCsvDataSet meetod ja salvestada tulemus faili vÔi saata andmed otse vÔrgus lÔpuseadmest vastuvÔtjale. server.

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();
            }
        }

        ...
    }
}

Projekti lÀhtekoodid asuvad GitLab.

tulemused nÀitasid ainult nelja ebaolulise koodibloki kattuvust, mis olid tingitud POSIX ja ANSI C nÔuetest.

Imesid ei juhtunud. Saime seda, mida ootasime — igas olukorras ĂŒhtlane jaotus ilma igasuguste vandenĂ”udeta. Eraldi graafikute lisamine platvormide kaupa ei tundu mĂ”ttekas — need kĂ”ik nĂ€itavad enam-vĂ€hem samu tulemusi.

Tegelik olukord on jÀrgmine:
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust

Seeriate visualiseerimine tasandil kÔikidest viiest genereerimisviisist:
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust

Ja 3D visualiseerimine. JĂ€tsin alles ainult System.Random.Next() tulemuse, et mitte toota hulk sama sisu.
BlessRNG vÔi kontrollime juhuslikkuse generaatori ausust

Eelnevas sissejuhatuses jutustatud lugu UnityEngine.Random normaalsest jaotusest ei kordunud: kas see oli algusest peale vale vĂ”i on midagi mootoris muutunud. Kuid nĂŒĂŒd oleme kindlad.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster