BlessRNG au kuangalia RNG kwa haki

BlessRNG au kuangalia RNG kwa haki

Katika maendeleo ya mchezo, mara nyingi unahitaji kuunganisha kitu na randomness: Umoja una Random yake kwa hili, na sambamba nayo kuna System.Random. Wakati mmoja, kwenye moja ya miradi, nilipata maoni kwamba zote mbili zinaweza kufanya kazi tofauti (ingawa zinapaswa kuwa na usambazaji sawa).

Kisha hawakuingia katika maelezo - ilikuwa ya kutosha kwamba mpito kwa System.Random ilirekebisha matatizo yote. Sasa tuliamua kuiangalia kwa undani zaidi na kufanya utafiti mdogo: jinsi RNG za "upendeleo" au kutabirika zilivyo, na ni ipi ya kuchagua. Kwa kuongezea, zaidi ya mara moja nimesikia maoni yanayokinzana juu ya "uaminifu" wao - wacha tujaribu kujua jinsi matokeo halisi yanalinganishwa na yale yaliyotangazwa.

Mpango mfupi wa elimu au RNG kwa hakika ni RNG

Ikiwa tayari unajua jenereta za nambari za nasibu, basi unaweza kuruka mara moja hadi sehemu ya "Majaribio".

Nambari za nasibu (RN) ni mlolongo wa nambari zinazozalishwa kwa kutumia mchakato fulani wa nasibu (chaotic), chanzo cha entropy. Hiyo ni, hii ni mlolongo ambao vipengele vyake haviunganishwa na sheria yoyote ya hisabati - hawana uhusiano wa sababu-na-athari.

Kinachounda nambari nasibu kinaitwa jenereta ya nambari nasibu (RNG). Inaweza kuonekana kuwa kila kitu ni cha msingi, lakini ikiwa tunahama kutoka kwa nadharia kwenda kwa mazoezi, basi kwa kweli sio rahisi sana kutekeleza algorithm ya programu ya kutengeneza mlolongo kama huo.

Sababu iko katika kutokuwepo kwa machafuko hayo hayo katika umeme wa kisasa wa watumiaji. Bila hivyo, nambari za nasibu huacha kuwa nasibu, na jenereta yao inageuka kuwa kazi ya kawaida ya hoja zilizofafanuliwa wazi. Kwa idadi ya utaalam katika uwanja wa IT, hii ni shida kubwa (kwa mfano, cryptography), lakini kwa wengine kuna suluhisho linalokubalika kabisa.

Inahitajika kuandika algorithm ambayo ingerudi, ingawa sio nambari za nasibu, lakini karibu iwezekanavyo kwao - nambari zinazoitwa pseudo-random (PRN). Algorithm katika kesi hii inaitwa jenereta ya nambari ya pseudorandom (PRNG).

Kuna chaguzi kadhaa za kuunda PRNG, lakini zifuatazo zitakuwa muhimu kwa kila mtu:

  1. Haja ya uanzishaji wa awali.

    PRNG haina chanzo cha entropy, kwa hivyo lazima ipewe hali ya awali kabla ya matumizi. Imebainishwa kama nambari (au vekta) na inaitwa mbegu (mbegu ya nasibu). Mara nyingi, kihesabu cha saa ya kichakataji au nambari inayolingana na wakati wa mfumo hutumiwa kama mbegu.

  2. Uzalishaji wa mlolongo.

    PRNG ni ya kuamua kabisa, kwa hivyo mbegu iliyobainishwa wakati wa uanzishaji huamua kipekee mlolongo mzima wa baadaye wa nambari. Hii inamaanisha kuwa PRNG tofauti iliyoanzishwa na mbegu sawa (kwa nyakati tofauti, katika programu tofauti, kwenye vifaa tofauti) itazalisha mlolongo sawa.

Pia unahitaji kujua usambazaji wa uwezekano unaoashiria PRNG - itazalisha nambari gani na kwa uwezekano gani. Mara nyingi hii ni usambazaji wa kawaida au usambazaji sawa.
BlessRNG au kuangalia RNG kwa haki
Usambazaji wa kawaida (kushoto) na usambazaji sare (kulia)

Wacha tuseme tunayo kifo cha haki na pande 24. Ukitupa, uwezekano wa kupata moja utakuwa sawa na 1/24 (sawa na uwezekano wa kupata nambari nyingine yoyote). Ikiwa utafanya kurusha nyingi na kurekodi matokeo, utaona kuwa kingo zote zinaanguka kwa takriban frequency sawa. Kimsingi, kifo hiki kinaweza kuzingatiwa kuwa RNG na usambazaji sare.

Je, ikiwa utatupa kete 10 kati ya hizi mara moja na kuhesabu jumla ya pointi? Usawa utadumishwa kwa ajili yake? Hapana. Mara nyingi, kiasi kitakuwa karibu na pointi 125, yaani, kwa thamani fulani ya wastani. Na matokeo yake, hata kabla ya kufanya kutupa, unaweza takribani kukadiria matokeo ya baadaye.

Sababu ni kwamba kuna idadi kubwa zaidi ya mchanganyiko kupata alama ya wastani. Mbali zaidi kutoka kwake, mchanganyiko mdogo - na, ipasavyo, uwezekano wa hasara ni mdogo. Ikiwa data hii itaonyeshwa, itafanana kabisa na umbo la kengele. Kwa hiyo, kwa kunyoosha fulani, mfumo wa kete 10 unaweza kuitwa RNG na usambazaji wa kawaida.

Mfano mwingine, wakati huu tu kwenye ndege - risasi kwenye lengo. Kipiga risasi kitakuwa RNG ambayo hutoa jozi ya nambari (x, y) inayoonyeshwa kwenye grafu.
BlessRNG au kuangalia RNG kwa haki
Kukubaliana kuwa chaguo upande wa kushoto ni karibu na maisha halisi - hii ni RNG yenye usambazaji wa kawaida. Lakini ikiwa unahitaji kueneza nyota katika anga ya giza, basi chaguo sahihi, lililopatikana kwa kutumia RNG na usambazaji sare, linafaa zaidi. Kwa ujumla, chagua jenereta kulingana na kazi iliyopo.

Sasa hebu tuzungumze juu ya entropy ya mlolongo wa PNG. Kwa mfano, kuna mlolongo unaoanza kama hii:

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, ...

Nambari hizi ni za nasibu kwa mtazamo wa kwanza? Wacha tuanze kwa kuangalia usambazaji.
BlessRNG au kuangalia RNG kwa haki
Inaonekana karibu na sare, lakini ukisoma mlolongo wa nambari mbili na kuzitafsiri kama kuratibu kwenye ndege, unapata hii:
BlessRNG au kuangalia RNG kwa haki
Sampuli zinaonekana wazi. Na kwa kuwa data katika mlolongo imeagizwa kwa njia fulani (yaani, ina entropy ya chini), hii inaweza kutoa "upendeleo" huo. Kwa kiwango cha chini, PRNG hiyo haifai sana kwa kuzalisha kuratibu kwenye ndege.

Mlolongo mwingine:

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, ...

Kila kitu kinaonekana kuwa sawa hapa hata kwenye ndege:
BlessRNG au kuangalia RNG kwa haki
Wacha tuangalie kwa kiasi (soma nambari tatu kwa wakati mmoja):
BlessRNG au kuangalia RNG kwa haki
Na tena mifumo. Haiwezekani tena kuunda taswira katika vipimo vinne. Lakini mifumo inaweza kuwepo kwenye mwelekeo huu na kwa kubwa zaidi.

Katika kriptografia, ambapo mahitaji magumu zaidi yanawekwa kwenye PRNG, hali kama hiyo haikubaliki kimsingi. Kwa hiyo, algorithms maalum imetengenezwa ili kutathmini ubora wao, ambayo hatutagusa sasa. Mada ni pana na inastahili makala tofauti.

Upimaji

Ikiwa hatujui kitu kwa hakika, basi jinsi ya kufanya kazi nayo? Je, ni thamani ya kuvuka barabara ikiwa hujui ni taa gani ya trafiki inaruhusu? Matokeo yanaweza kuwa tofauti.

Vile vile huenda kwa bahati mbaya mbaya katika Umoja. Ni vizuri ikiwa nyaraka zinaonyesha maelezo muhimu, lakini hadithi iliyotajwa mwanzoni mwa makala ilitokea kwa usahihi kwa sababu ya ukosefu wa maalum yaliyotakiwa.

Na ikiwa hujui jinsi chombo kinavyofanya kazi, hutaweza kuitumia kwa usahihi. Kwa ujumla, wakati umefika wa kuangalia na kufanya jaribio ili hatimaye kuhakikisha angalau kuhusu usambazaji.

Suluhisho lilikuwa rahisi na la ufanisi - kukusanya takwimu, kupata data ya lengo na kuangalia matokeo.

Somo la masomo

Kuna njia kadhaa za kutengeneza nambari nasibu katika Umoja - tulijaribu tano.

  1. System.Nasibu.Inayofuata(). Huzalisha nambari kamili katika anuwai fulani ya thamani.
  2. System.Nasibu.NextDouble(). Huzalisha nambari za usahihi maradufu katika safu kutoka [0; 1).
  3. UnityEngine.Range.Nasibu(). Huzalisha nambari moja za usahihi (huelea) katika anuwai fulani ya thamani.
  4. UnityEngine.Thamani.ya.Nasibu. Huzalisha nambari moja za usahihi (huelea) katika masafa kutoka [0; 1).
  5. Umoja.Hisabati.Nasibu.NextFloat(). Sehemu ya maktaba mpya ya Unity.Mathematics. Huzalisha nambari moja za usahihi (huelea) katika anuwai fulani ya thamani.

Takriban kila mahali katika hati usambazaji sare ulibainishwa, isipokuwa UnityEngine.Random.value (ambapo usambazaji haukubainishwa, lakini kwa mlinganisho na sare ya UnityEngine.Random.Range() pia ilitarajiwa) na Unity.Mathematics.Nasibu. .NextFloat() (ambapo katika Msingi ni algorithm ya xorshift, ambayo ina maana kwamba unahitaji kusubiri usambazaji sawa).

Kwa chaguo-msingi, matokeo yaliyotarajiwa yalichukuliwa kama yale yaliyoainishwa kwenye hati.

Mbinu

Tuliandika programu ndogo ambayo ilizalisha mlolongo wa nambari za nasibu kwa kutumia kila moja ya njia zilizowasilishwa na kuhifadhi matokeo kwa usindikaji zaidi.

Urefu wa kila mlolongo ni nambari 100.
Idadi ya nambari nasibu ni [0, 100).

Data ilikusanywa kutoka kwa majukwaa kadhaa lengwa:

  • Windows
    β€” Umoja v2018.3.14f1, hali ya Mhariri, Mono, .NET Standard 2.0
  • MacOS
    β€” Umoja v2018.3.14f1, hali ya Mhariri, Mono, .NET Standard 2.0
    - Umoja v5.6.4p4, Hali ya Mhariri, Mono, .NET Standard 2.0
  • Android
    β€” Umoja v2018.3.14f1, muundo kwa kila kifaa, Mono, .NET Standard 2.0
  • iOS
    β€” Umoja v2018.3.14f1, muundo kwa kila kifaa, il2cpp, .NET Standard 2.0

Utekelezaji

Tuna njia kadhaa tofauti za kutengeneza nambari za nasibu. Kwa kila mmoja wao, tutaandika darasa tofauti la wrapper, ambalo linapaswa kutoa:

  1. Uwezekano wa kuweka anuwai ya maadili [min/max). Itawekwa kupitia mjenzi.
  2. Njia ya kurudisha MF. Wacha tuchague kuelea kama aina, kwani ni ya jumla zaidi.
  3. Jina la njia ya kizazi ya kuashiria matokeo. Kwa urahisi, tutarudisha kama thamani jina kamili la darasa + jina la njia iliyotumiwa kutengeneza MF.

Kwanza, hebu tutangaze kifupisho ambacho kitawakilishwa na kiolesura cha IRandomGenerator:

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

        float Generate();
    }
}

Utekelezaji wa System.Random.Next()

Njia hii hukuruhusu kuweka anuwai ya maadili, lakini inarudisha nambari kamili, lakini kuelea kunahitajika. Unaweza kutafsiri nambari kamili kama kuelea, au unaweza kupanua anuwai ya maadili kwa maagizo kadhaa ya ukubwa, ukiyafidia kwa kila kizazi cha katikati. Matokeo yatakuwa kitu kama sehemu isiyobadilika na mpangilio fulani wa usahihi. Tutatumia chaguo hili kwa kuwa iko karibu na thamani halisi ya kuelea.

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

Utekelezaji wa System.Random.NextDouble()

Hapa safu maalum ya maadili [0; 1). Ili kuitayarisha kwenye ile iliyoainishwa katika mjenzi, tunatumia hesabu rahisi: 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;
    }
}

Utekelezaji wa UnityEngine.Range.Range()

Njia hii ya UnityEngine. Darasa tuli la nasibu hukuruhusu kuweka anuwai ya maadili na kurudisha aina ya kuelea. Huna haja ya kufanya mabadiliko yoyote ya ziada.

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

Utekelezaji wa UnityEngine.Thamani.ya.Nasibu

Sifa ya thamani ya darasa tuli ya UnityEngine.Nasibu hurejesha aina ya kuelea kutoka kwa anuwai isiyobadilika ya thamani[0; 1). Wacha tuiweke kwenye safu fulani kwa njia sawa na wakati wa kutekeleza 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;
    }
}

Utekelezaji wa Umoja.Hisabati.Nasibu.NextFloat()

Mbinu ya NextFloat() ya darasa la Unity.Mathematics.Nasibu hurejesha sehemu inayoelea ya aina ya kuelea na hukuruhusu kubainisha anuwai ya thamani. Nuance pekee ni kwamba kila mfano wa Unity.Mathematics.Nasibu itabidi ianzishwe na baadhi ya mbegu - kwa njia hii tutaepuka kuzalisha mfuatano unaorudiwa.

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

Utekelezaji wa Mdhibiti Mkuu

Utekelezaji kadhaa wa IRandomGenerator uko tayari. Ifuatayo, unahitaji kutoa mlolongo na kuhifadhi data iliyopatikana kwa usindikaji. Ili kufanya hivyo, tutaunda eneo na hati ndogo ya MainController katika Umoja, ambayo itafanya kazi zote muhimu na wakati huo huo kuwajibika kwa kuingiliana na UI.

Wacha tuweke saizi ya hifadhidata na anuwai ya maadili ya MF, na pia tupate njia ambayo inarudisha safu ya jenereta zilizosanidiwa na tayari kufanya kazi.

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

        ...
    }
}

Sasa wacha tuunde seti ya data. Katika kesi hii, uzalishaji wa data utaunganishwa na kurekodi matokeo kwenye mtiririko wa maandishi (katika umbizo la csv). Ili kuhifadhi maadili ya kila IRandomGenerator, safu yake tofauti imetengwa, na mstari wa kwanza una Jina la jenereta.

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

        ...
    }
}

Kilichosalia ni kupiga simu kwa njia ya GenerateCsvDataSet na kuhifadhi matokeo kwenye faili, au kuhamisha data mara moja kwenye mtandao kutoka kwa kifaa cha mwisho hadi kwa seva inayopokea.

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

        ...
    }
}

Vyanzo vya mradi viko GitLab.

Matokeo

Hakuna muujiza uliotokea. Walichotarajia ni kile walichopata - katika hali zote, usambazaji sawa bila dokezo la njama. Sioni umuhimu wa kuweka grafu tofauti kwa majukwaa - zote zinaonyesha takriban matokeo sawa.

Ukweli ni kwamba:
BlessRNG au kuangalia RNG kwa haki

Taswira ya mlolongo kwenye ndege kutoka kwa njia zote tano za kizazi:
BlessRNG au kuangalia RNG kwa haki

Na taswira katika 3D. Nitaacha tu matokeo ya System.Random.Next() ili nisitoe rundo la maudhui yanayofanana.
BlessRNG au kuangalia RNG kwa haki

Hadithi iliyoelezwa katika utangulizi kuhusu usambazaji wa kawaida wa UnityEngine.Random haikurudia yenyewe: ama ilikuwa ya awali makosa, au kitu kimebadilika katika injini. Lakini sasa tuna uhakika.

Chanzo: mapenzi.com

Kuongeza maoni