BlessRNG neu wirio'r RNG i sicrhau tegwch

BlessRNG neu wirio'r RNG i sicrhau tegwch

Wrth ddatblygu gêm, yn aml mae angen i chi glymu rhywbeth i fyny gyda hap: Mae gan Unity ei Hap ei hun ar gyfer hyn, ac ochr yn ochr ag ef mae System.Random. Un tro, ar un o'r prosiectau, cefais yr argraff y gallai'r ddau weithio'n wahanol (er y dylent gael dosbarthiad gwastad).

Yna ni wnaethant fynd i fanylion - roedd yn ddigon bod y trawsnewid i System.Random wedi cywiro'r holl broblemau. Nawr fe benderfynon ni edrych i mewn iddo'n fanylach a gwneud ychydig o ymchwil: pa mor “rhagfarnllyd” neu ragweladwy yw RNGs, a pha un i'w ddewis. Ar ben hynny, rwyf wedi clywed mwy nag unwaith o farnau croes am eu “gonestrwydd” - gadewch i ni geisio darganfod sut mae'r canlyniadau go iawn yn cymharu â'r rhai datganedig.

Rhaglen addysgol gryno neu RNG yw RNG mewn gwirionedd

Os ydych chi eisoes yn gyfarwydd â generaduron rhif ar hap, yna gallwch chi fynd ar unwaith i'r adran “Profi”.

Mae haprifau (RN) yn ddilyniant o rifau a gynhyrchir gan ddefnyddio rhyw broses ar hap (anhrefnus), ffynhonnell entropi. Hynny yw, mae hwn yn ddilyniant nad yw ei elfennau wedi'u rhyng-gysylltu gan unrhyw gyfraith fathemategol - nid oes ganddynt unrhyw berthynas achos-ac-effaith.

Gelwir yr hyn sy'n creu'r haprif yn generadur haprif (RNG). Mae'n ymddangos bod popeth yn elfennol, ond os ydym yn symud o theori i ymarfer, yna mewn gwirionedd nid yw mor syml i weithredu algorithm meddalwedd ar gyfer cynhyrchu dilyniant o'r fath.

Mae'r rheswm yn gorwedd yn absenoldeb yr un anhrefn mewn electroneg defnyddwyr modern. Hebddo, mae haprifau yn peidio â bod ar hap, ac mae eu generadur yn troi'n swyddogaeth gyffredin o ddadleuon amlwg wedi'u diffinio. Ar gyfer nifer o arbenigeddau yn y maes TG, mae hon yn broblem ddifrifol (er enghraifft, cryptograffeg), ond i eraill mae datrysiad cwbl dderbyniol.

Mae angen ysgrifennu algorithm a fyddai'n dychwelyd, er nad yw'n wir rifau ar hap, ond mor agos â phosibl atynt - yr hyn a elwir yn ffug-rifau hap (PRN). Gelwir yr algorithm yn yr achos hwn yn gynhyrchydd rhif ffug-enwog (PRNG).

Mae sawl opsiwn ar gyfer creu PNG, ond bydd y canlynol yn berthnasol i bawb:

  1. Yr angen am gychwyniad rhagarweiniol.

    Nid oes gan y PRNG ffynhonnell entropi, felly mae'n rhaid rhoi cyflwr cychwynnol cyn ei ddefnyddio. Fe'i pennir fel rhif (neu fector) ac fe'i gelwir yn hedyn (hadau ar hap). Yn aml, defnyddir cownter cloc y prosesydd neu'r hyn sy'n cyfateb yn rhifiadol i amser system fel hedyn.

  2. Dilyniant atgynhyrchu.

    Mae'r PRNG yn gwbl benderfynol, felly mae'r hedyn a nodir yn ystod y cychwyn yn unigryw yn pennu'r dilyniant cyfan o rifau yn y dyfodol. Mae hyn yn golygu y bydd PRNG ar wahân wedi'i gychwyn gyda'r un hedyn (ar adegau gwahanol, mewn rhaglenni gwahanol, ar ddyfeisiau gwahanol) yn cynhyrchu'r un dilyniant.

Mae angen i chi hefyd wybod y dosraniad tebygolrwydd sy'n nodweddu'r PRNG - pa rifau y bydd yn eu cynhyrchu a chyda pha debygolrwydd. Yn fwyaf aml mae hyn naill ai'n ddosraniad normal neu'n ddosraniad unffurf.
BlessRNG neu wirio'r RNG i sicrhau tegwch
Dosbarthiad arferol (chwith) a dosbarthiad unffurf (dde)

Gadewch i ni ddweud ein bod yn cael marw teg gyda 24 ochr. Os byddwch yn ei daflu, bydd y tebygolrwydd o gael un yn hafal i 1/24 (yr un fath â'r tebygolrwydd o gael unrhyw rif arall). Os byddwch yn gwneud llawer o dafliadau ac yn cofnodi'r canlyniadau, byddwch yn sylwi bod yr ymylon i gyd yn cwympo allan tua'r un amlder. Yn y bôn, gellir ystyried y marw hwn yn RNG gyda dosbarthiad unffurf.

Beth os ydych chi'n taflu 10 o'r dis hyn ar unwaith ac yn cyfrif cyfanswm y pwyntiau? A fydd unffurfiaeth yn cael ei chynnal ar ei gyfer? Nac ydw. Yn fwyaf aml, bydd y swm yn agos at 125 o bwyntiau, hynny yw, i ryw werth cyfartalog. Ac o ganlyniad, hyd yn oed cyn taflu, gallwch chi amcangyfrif yn fras y canlyniad yn y dyfodol.

Y rheswm yw bod y nifer fwyaf o gyfuniadau i gael y sgôr cyfartalog. Po bellaf oddi wrtho, y lleiaf o gyfuniadau - ac, yn unol â hynny, yr isaf yw'r tebygolrwydd o golled. Os caiff y data hwn ei ddelweddu, bydd yn ymdebygu'n fras i siâp cloch. Felly, gyda rhywfaint o ymestyn, gellir galw system o 10 dis yn RNG gyda dosbarthiad arferol.

Enghraifft arall, dim ond y tro hwn ar awyren - saethu at darged. Bydd y saethwr yn RNG sy'n cynhyrchu pâr o rifau (x, y) sy'n cael eu harddangos ar y graff.
BlessRNG neu wirio'r RNG i sicrhau tegwch
Cytuno bod yr opsiwn ar y chwith yn agosach at fywyd go iawn - mae hwn yn RNG gyda dosbarthiad arferol. Ond os oes angen i chi wasgaru sêr mewn awyr dywyll, yna mae'r opsiwn cywir, a geir gan ddefnyddio RNG gyda dosbarthiad unffurf, yn fwy addas. Yn gyffredinol, dewiswch generadur yn dibynnu ar y dasg dan sylw.

Nawr, gadewch i ni siarad am entropi y dilyniant PNG. Er enghraifft, mae dilyniant sy'n dechrau fel hyn:

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

Pa mor hap yw'r rhifau hyn ar yr olwg gyntaf? Gadewch i ni ddechrau trwy wirio'r dosbarthiad.
BlessRNG neu wirio'r RNG i sicrhau tegwch
Mae'n edrych yn agos at lifrai, ond os ydych chi'n darllen dilyniant o ddau rif ac yn eu dehongli fel cyfesurynnau ar awyren, fe gewch chi hyn:
BlessRNG neu wirio'r RNG i sicrhau tegwch
Daw patrymau i'w gweld yn glir. A chan fod y data yn y dilyniant wedi'i drefnu mewn ffordd benodol (hynny yw, mae ganddo entropi isel), gall hyn arwain at yr union “duedd”. Ar y lleiaf, nid yw PRNG o'r fath yn addas iawn ar gyfer cynhyrchu cyfesurynnau ar awyren.

Dilyniant arall:

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

Mae'n ymddangos bod popeth yn iawn yma hyd yn oed ar yr awyren:
BlessRNG neu wirio'r RNG i sicrhau tegwch
Edrychwn mewn cyfaint (darllenwch dri rhif ar y tro):
BlessRNG neu wirio'r RNG i sicrhau tegwch
Ac eto y patrymau. Nid yw bellach yn bosibl llunio delweddiad mewn pedwar dimensiwn. Ond gall patrymau fodoli ar y dimensiwn hwn ac ar rai mwy.

Mewn cryptograffeg, lle mae'r gofynion mwyaf llym yn cael eu gosod ar PRNGs, mae sefyllfa o'r fath yn gwbl annerbyniol. Felly, mae algorithmau arbennig wedi'u datblygu i asesu eu hansawdd, na fyddwn yn cyffwrdd â nhw nawr. Mae'r pwnc yn helaeth ac yn haeddu erthygl ar wahân.

Profi

Os nad ydym yn gwybod rhywbeth yn sicr, yna sut i weithio gydag ef? A yw'n werth croesi'r ffordd os nad ydych chi'n gwybod pa oleuadau traffig sy'n caniatáu hynny? Gall y canlyniadau fod yn wahanol.

Mae'r un peth yn wir am yr hap drwg-enwog yn Unity. Mae'n dda os yw'r ddogfennaeth yn datgelu'r manylion angenrheidiol, ond digwyddodd y stori a grybwyllwyd ar ddechrau'r erthygl yn union oherwydd diffyg y manylion a ddymunir.

Ac os nad ydych chi'n gwybod sut mae'r offeryn yn gweithio, ni fyddwch yn gallu ei ddefnyddio'n gywir. Yn gyffredinol, mae'r amser wedi dod i wirio a chynnal arbrawf i wneud yn siŵr o'r diwedd o leiaf am y dosbarthiad.

Roedd yr ateb yn syml ac yn effeithiol - casglu ystadegau, cael data gwrthrychol ac edrych ar y canlyniadau.

Pwnc astudio

Mae yna sawl ffordd o gynhyrchu haprifau yn Unity - fe wnaethon ni brofi pump.

  1. System.Random.Nesaf(). Yn cynhyrchu cyfanrifau mewn ystod benodol o werthoedd.
  2. System.Random.NextDouble(). Yn cynhyrchu rhifau manwl dwbl yn yr ystod o [0; 1).
  3. UnityEngine.Random.Range(). Yn cynhyrchu rhifau trachywiredd sengl (flotiau) mewn ystod benodol o werthoedd.
  4. UnityEngine.Random.value. Yn cynhyrchu rhifau manylder sengl (flotiau) yn yr ystod o [0; 1).
  5. Unity.Mathematics.Random.NextFloat(). Rhan o lyfrgell newydd Unity.Mathematics. Yn cynhyrchu rhifau trachywiredd sengl (flotiau) mewn ystod benodol o werthoedd.

Bron ym mhobman yn y ddogfennaeth nodwyd dosbarthiad unffurf, ac eithrio UnityEngine.Random.value (lle nad oedd y dosbarthiad wedi'i nodi, ond trwy gyfatebiaeth â gwisg UnityEngine.Random.Range() hefyd yn ddisgwyliedig) ac Unity.Mathematics.Random .NextFloat() (lle yn Y sail yw'r algorithm xorshift, sy'n golygu eto bod angen i chi aros am ddosbarthiad unffurf).

Yn ddiofyn, cymerwyd y canlyniadau disgwyliedig fel y rhai a nodir yn y ddogfennaeth.

Methodoleg

Ysgrifennom gymhwysiad bach a gynhyrchodd ddilyniannau o haprifau gan ddefnyddio pob un o'r dulliau a gyflwynwyd ac a arbedwyd y canlyniadau i'w prosesu ymhellach.

Hyd pob dilyniant yw 100 o rifau.
Amrediad yr haprifau yw [0, 100).

Casglwyd data o sawl platfform targed:

  • ffenestri
    — Undod v2018.3.14f1, modd golygydd, Mono, .NET Standard 2.0
  • MacOS
    — Undod v2018.3.14f1, modd golygydd, Mono, .NET Standard 2.0
    — Undod v5.6.4p4, modd golygydd, Mono, .NET Safon 2.0
  • Android
    — Undod v2018.3.14f1, adeiladu fesul dyfais, Mono, .NET Standard 2.0
  • iOS
    — Undod v2018.3.14f1, adeiladu fesul dyfais, il2cpp, .NET Standard 2.0

Gweithredu

Mae gennym sawl ffordd wahanol o gynhyrchu haprifau. Ar gyfer pob un ohonynt, byddwn yn ysgrifennu dosbarth lapio ar wahân, a ddylai ddarparu:

  1. Posibilrwydd i osod yr ystod o werthoedd [min/uchafswm). Bydd yn cael ei osod trwy'r adeiladwr.
  2. Dull dychwelyd MF. Gadewch i ni ddewis arnofio fel y math, gan ei fod yn fwy cyffredinol.
  3. Enw'r dull cynhyrchu ar gyfer marcio'r canlyniadau. Er hwylustod, byddwn yn dychwelyd fel gwerth enw llawn y dosbarth + enw'r dull a ddefnyddiwyd i gynhyrchu'r MF.

Yn gyntaf, gadewch i ni ddatgan tyniad a fydd yn cael ei gynrychioli gan ryngwyneb IRandomGenerator:

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

        float Generate();
    }
}

Gweithredu System.Random.Nesaf()

Mae'r dull hwn yn caniatáu ichi osod ystod o werthoedd, ond mae'n dychwelyd cyfanrifau, ond mae angen fflotiau. Yn syml, gallwch ddehongli cyfanrif fel fflôt, neu gallwch ehangu'r ystod o werthoedd trwy nifer o orchmynion maint, gan eu digolledu gyda phob cenhedlaeth o'r midrange. Bydd y canlyniad yn rhywbeth fel pwynt sefydlog gyda threfn benodol o gywirdeb. Byddwn yn defnyddio'r opsiwn hwn gan ei fod yn agosach at y gwerth arnofio go iawn.

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

Gweithredu System.Random.NextDouble()

Yma mae'r ystod sefydlog o werthoedd [0; 1). Er mwyn ei daflunio ar yr un a nodir yn y lluniwr, rydym yn defnyddio rhifyddeg syml: X * (uchafswm − mun) + 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;
    }
}

Gweithredu UnityEngine.Random.Range()

Mae'r dull hwn o'r dosbarth statig UnityEngine.Random yn eich galluogi i osod ystod o werthoedd ac yn dychwelyd math arnofio. Nid oes rhaid i chi wneud unrhyw drawsnewidiadau ychwanegol.

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

Gweithredu UnityEngine.Random.value

Mae eiddo gwerth y dosbarth statig UnityEngine.Random yn dychwelyd math arnofio o ystod sefydlog o werthoedd [0; 1). Gadewch i ni ei daflunio ar ystod benodol yn yr un ffordd ag wrth weithredu 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;
    }
}

Gweithredu Unity.Mathematics.Random.NextFloat()

Mae'r dull NextFloat() o'r dosbarth Unity.Mathematics.Random yn dychwelyd fflôt pwynt arnawf o fath ac yn eich galluogi i nodi ystod o werthoedd. Yr unig naws yw y bydd yn rhaid i bob achos o Unity.Mathematics.Random gael ei gychwyn gyda rhywfaint o had - fel hyn byddwn yn osgoi cynhyrchu dilyniannau ailadroddus.

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

Gweithredu'r Prif Reolwr

Mae sawl gweithrediad o IRandomGenerator yn barod. Nesaf, mae angen i chi gynhyrchu dilyniannau ac arbed y set ddata canlyniadol i'w phrosesu. I wneud hyn, byddwn yn creu golygfa a sgript MainController bach yn Unity, a fydd yn gwneud yr holl waith angenrheidiol ac ar yr un pryd yn gyfrifol am ryngweithio â'r UI.

Gadewch i ni osod maint y set ddata a'r ystod o werthoedd MF, a hefyd cael dull sy'n dychwelyd amrywiaeth o gynhyrchwyr wedi'u ffurfweddu ac yn barod i weithio.

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

        ...
    }
}

Nawr, gadewch i ni greu set ddata. Yn yr achos hwn, bydd cynhyrchu data yn cael ei gyfuno â chofnodi'r canlyniadau i ffrwd testun (ar ffurf csv). I storio gwerthoedd pob IRandomGenerator, dyrennir ei golofn ar wahân ei hun, ac mae'r llinell gyntaf yn cynnwys Enw'r generadur.

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

        ...
    }
}

Y cyfan sydd ar ôl yw galw'r dull GenerateCsvDataSet ac arbed y canlyniad i ffeil, neu drosglwyddo'r data ar unwaith dros y rhwydwaith o'r ddyfais derfynol i'r gweinydd sy'n derbyn.

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

        ...
    }
}

Mae ffynonellau'r prosiect yn GitLab.

Canfyddiadau

Ni ddigwyddodd unrhyw wyrth. Yr hyn yr oeddent yn ei ddisgwyl yw'r hyn a gawsant - ym mhob achos, dosbarthiad gwastad heb awgrym o gynllwynion. Dydw i ddim yn gweld y pwynt mewn rhoi graffiau ar wahân ar gyfer platfformau - maen nhw i gyd yn dangos tua'r un canlyniadau.

Y gwir amdani yw:
BlessRNG neu wirio'r RNG i sicrhau tegwch

Delweddu dilyniannau ar awyren o bob un o'r dulliau pum cenhedlaeth:
BlessRNG neu wirio'r RNG i sicrhau tegwch

A delweddu mewn 3D. Dim ond canlyniad System.Random.Next() y byddaf yn ei adael er mwyn peidio â chynhyrchu criw o gynnwys union yr un fath.
BlessRNG neu wirio'r RNG i sicrhau tegwch

Nid oedd y stori a adroddwyd yn y cyflwyniad am ddosbarthiad arferol UnityEngine.Random yn ailadrodd ei hun: naill ai roedd yn wallus i ddechrau, neu mae rhywbeth wedi newid ers hynny yn yr injan. Ond yn awr yr ydym yn sicr.

Ffynhonnell: hab.com

Ychwanegu sylw