BlessRNG же RNGди калыстык үчүн текшерүү

BlessRNG же RNGди калыстык үчүн текшерүү

Оюнду иштеп чыгууда сиз көп учурда кокустук менен бир нерсени байланыштырышыңыз керек: Биримдиктин бул үчүн өзүнүн Random бар жана ага параллелдүү System.Random бар. Бир жолу, долбоорлордун биринде мен экөө тең башкача иштеши мүмкүн деген ойдо калдым (бирок алар бирдей бөлүштүрүлүшү керек).

Андан кийин алар майда-чүйдөсүнө чейин киришкен жок - бул System.Random өтүү бардык көйгөйлөрдү оңдоо үчүн жетиштүү болду. Эми биз аны майда-чүйдөсүнө чейин карап чыгууну жана бир аз изилдөө жүргүзүүнү чечтик: RNG канчалык "бир тараптуу" же болжолдуу жана кайсынысын тандоо керек. Анын үстүнө, мен бир нече жолу алардын "чынчылдыгы" жөнүндө карама-каршы пикирлерди уктум - келгиле, чыныгы натыйжалар жарыялангандар менен кандай салыштырарын түшүнүүгө аракет кылалы.

Кыскача билим берүү программасы же RNG чындыгында RNG

Эгер сиз кокустук сан генераторлору менен тааныш болсоңуз, анда сиз дароо "Тесттөө" бөлүмүнө өтсөңүз болот.

Кокус сандар (RN) кандайдыр бир кокустук (баш аламан) процесстин жардамы менен түзүлгөн сандардын ырааттуулугу, энтропиянын булагы. Башкача айтканда, бул элементтери эч кандай математикалык мыйзам менен байланышпаган ырааттуулук - алардын себеп-натыйжа байланышы жок.

Кокус санды жараткан нерсе кокус сан генератору (RNG) деп аталат. Баары эле элементардуу окшойт, бирок теориядан практикага өтсөк, чындыгында мындай ырааттуулукту түзүү үчүн программалык алгоритмди ишке ашыруу анчалык деле оңой эмес.

Мунун себеби заманбап керектөө электроникасында ошол эле башаламандыктын жоктугунда. Ансыз кокус сандар кокустук болбой калат жана алардын генератору айкын аныкталган аргументтердин кадимки функциясына айланат. IT тармагындагы бир катар адистиктер үчүн бул олуттуу көйгөй (мисалы, криптография), бирок башкалар үчүн толугу менен алгылыктуу чечим бар.

Чынында эле кокус сандар эмес, бирок аларга мүмкүн болушунча жакын - псевдо-кокус сандар (PRN) деп аталгандар кайтып келе турган алгоритмди жазуу керек. Бул учурда алгоритм псевдордук сан генератору (PRNG) деп аталат.

PRNG түзүү үчүн бир нече варианттар бар, бирок төмөндөгүлөр бардыгына тиешелүү болот:

  1. Алдын ала инициализациялоонун зарылдыгы.

    PRNG энтропиянын булагы жок, ошондуктан колдонуудан мурун ага баштапкы абал берилиши керек. Ал сан (же вектор) катары көрсөтүлүп, урук (кокустук үрөн) деп аталат. Көп учурда процессордун саат эсептегичи же система убакытынын сандык эквиваленти үрөн катары колдонулат.

  2. Ырааттуулуктун кайталанышы.

    PRNG толугу менен детерминистикалык болуп саналат, ошондуктан инициализациялоо учурунда көрсөтүлгөн урук келечектеги сандар ырааттуулугун уникалдуу түрдө аныктайт. Бул бир эле үрөн менен инициализацияланган өзүнчө PRNG (ар кандай убакта, ар кандай программаларда, ар кандай түзмөктөрдө) бирдей ырааттуулукту жаратат дегенди билдирет.

Сиз ошондой эле PRNGди мүнөздөгөн ыктымалдык бөлүштүрүүнү билишиңиз керек - ал кандай сандарды жана кандай ыктымалдуулук менен жаратат. Көбүнчө бул нормалдуу бөлүштүрүү же бирдиктүү бөлүштүрүү.
BlessRNG же RNGди калыстык үчүн текшерүү
Кадимки бөлүштүрүү (солдо) жана бирдей бөлүштүрүү (оңдо)

Бизде 24 жагы бар адилеттүү өлүк бар дейли. Эгер сиз аны ыргытсаңыз, бирди алуу ыктымалдыгы 1/24кө барабар болот (башка санды алуу ыктымалдыгы менен бирдей). Эгерде сиз көп ыргытсаңыз жана натыйжаларды жазсаңыз, анда бардык четтери болжол менен бирдей жыштык менен түшүп жатканын байкайсыз. Негизинен, бул өлүп бирдиктүү бөлүштүрүү менен RNG каралышы мүмкүн.

Эгер сиз бир эле учурда 10 чөйчөк ыргытып, жалпы упайларды санасаңызчы? Ал үчүн бирдейлик сакталабы? Жок. Көпчүлүк учурда, сумма 125 упайга жакын болот, башкача айтканда, кандайдыр бир орточо мааниге. Натыйжада, ыргытуудан мурун, келечектеги натыйжаны болжолдуу түрдө баалай аласыз.

Себеби, орточо балл алуу үчүн комбинациялардын эң көп саны бар. Андан канчалык алыс болсо, комбинациялар ошончолук аз болот - жана, демек, жоготуу ыктымалдыгы ошончолук төмөн. Бул маалымат визуализацияланган болсо, ал коңгуроонун формасына окшошпой калат. Ошондуктан, бир аз сунуу менен, 10 сөөк системасы нормалдуу бөлүштүрүү менен RNG деп атоого болот.

Дагы бир мисал, учакта бул жолу гана - бутага атуу. Атышуучу графикте көрсөтүлгөн жуп санды (x, y) түзгөн RNG болот.
BlessRNG же RNGди калыстык үчүн текшерүү
Сол жактагы вариант реалдуу жашоого жакын экенине макул - бул нормалдуу бөлүштүрүү менен RNG. Бирок эгер сиз жылдыздарды караңгы асманга чачышыңыз керек болсо, анда RNG аркылуу бирдиктүү бөлүштүрүү менен алынган туура вариант ылайыктуу. Жалпысынан алганда, тапшырмага жараша генератор тандоо.

Эми PNG ырааттуулугунун энтропиясы жөнүндө сүйлөшөлү. Мисалы, мындай башталган ырааттуулук бар:

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

Бул сандар биринчи караганда канчалык кокустук? Бөлүштүрүүнү текшерүүдөн баштайлы.
BlessRNG же RNGди калыстык үчүн текшерүү
Бул бир калыпка жакын көрүнөт, бирок сиз эки сандын ырааттуулугун окуп, аларды учактагы координаттар катары чечмелесеңиз, сиз муну аласыз:
BlessRNG же RNGди калыстык үчүн текшерүү
Үлгүлөр даана көрүнүп калат. Ал эми ырааттуулуктагы маалыматтар белгилүү бир тартипте иреттелгендиктен (башкача айтканда, анын энтропиясы төмөн), бул дал ошол “бир тараптуулукту” пайда кылышы мүмкүн. Жок дегенде, мындай PRNG бир тегиздикте координаттарды түзүү үчүн абдан ылайыктуу эмес.

Дагы бир ырааттуулугу:

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

Бул жерде да учакта баары жакшы окшойт:
BlessRNG же RNGди калыстык үчүн текшерүү
Келгиле, көлөмүн карап көрөлү (бир убакта үч санды окуйбуз):
BlessRNG же RNGди калыстык үчүн текшерүү
Жана дагы үлгүлөр. Мындан ары төрт өлчөмдүү визуализацияны куруу мүмкүн эмес. Бирок үлгүлөр бул өлчөмдө жана чоңураактарында болушу мүмкүн.

PRNGs үчүн эң катуу талаптар коюлган криптографияда мындай абалга таптакыр жол берилгис. Ошондуктан, алардын сапатын баалоо үчүн атайын алгоритмдер иштелип чыккан, биз азыр аларга токтолбойбуз. Тема кенен жана өзүнчө макалага татыктуу.

тестирлөө

Эгер биз бир нерсени так билбесек, анда аны менен кантип иштөө керек? Кайсы светофор уруксат берерин билбесеңиз, жолду кесип өтүүнүн кереги барбы? кесепеттери ар кандай болушу мүмкүн.

Ошол эле Биримдиктеги белгилүү кокустукка да тиешелүү. Документте зарыл болгон деталдар ачылса жакшы, бирок макаланын башында айтылган окуя так керектүү спецификациянын жоктугунан улам болгон.

Ал эми курал кантип иштээрин билбесеңиз, аны туура колдоно албай каласыз. Жалпысынан алганда, убакыт текшерүү жана акыры бөлүштүрүү жөнүндө, жок эле дегенде, ынануу үчүн эксперимент жүргүзүү үчүн келди.

Чечим жөнөкөй жана натыйжалуу болду - статистиканы чогултуу, объективдүү маалыматтарды алуу жана натыйжаларды көрүү.

Окуу предмети

Биримдикте кокус сандарды түзүүнүн бир нече жолу бар - биз бешөөнү сынап көрдүк.

  1. System.Random.Next(). Берилген маанилер диапазонунда бүтүн сандарды жаратат.
  2. System.Random.NextDouble(). Кош тактыктагы сандарды [0ден; 1).
  3. UnityEngine.Random.Range(). Берилген маанилер диапазонунда бир тактык сандарды (сүзмөлөрдү) жаратат.
  4. UnityEngine.Random.value. [0ден баштап диапазондо бир тактык сандарды (сүзмөлөрдү) жаратат; 1).
  5. Unity.Mathematics.Random.NextFloat(). Жаңы Unity.Mathematics китепканасынын бир бөлүгү. Берилген маанилер диапазонунда бир тактык сандарды (сүзмөлөрдү) жаратат.

UnityEngine.Random.value (бул жерде бөлүштүрүү көрсөтүлгөн эмес, бирок UnityEngine.Random.Range() окшоштуктары боюнча да күтүлгөн) жана Unity.Mathematics.Randomдон башка документациянын дээрлик бардык жеринде бирдиктүү бөлүштүрүү көрсөтүлгөн. .NextFloat().

Демейки боюнча, күтүлгөн натыйжалар документтерде көрсөтүлгөндөй кабыл алынган.

техника

Биз ар бир сунушталган ыкмаларды колдонуу менен кокус сандардын ырааттуулугун түзгөн чакан тиркеме жаздык жана натыйжаларды андан ары иштетүү үчүн сактап койдук.

Ар бир катардын узундугу 100 000 санды түзөт.
Кокус сандардын диапазону [0, 100).

Маалыматтар бир нече максаттуу платформалардан чогултулган:

  • Windows
    — Unity v2018.3.14f1, Редактор режими, Mono, .NET Standard 2.0
  • MacOS
    — Unity v2018.3.14f1, Редактор режими, Mono, .NET Standard 2.0
    — Unity v5.6.4p4, Редактор режими, Mono, .NET Standard 2.0
  • Android
    — Unity v2018.3.14f1, ар бир түзүлүшкө куруу, Mono, .NET Standard 2.0
  • Белги
    — Unity v2018.3.14f1, ар бир түзүлүшкө куруу, il2cpp, .NET Standard 2.0

Реализация

Бизде кокус сандарды түзүүнүн бир нече ар кандай жолдору бар. Алардын ар бири үчүн биз өзүнчө орогуч классын жазабыз, ал төмөнкүлөрдү камсыз кылышы керек:

  1. Маанилердин диапазонун коюу мүмкүнчүлүгү [мин/макс). Конструктор аркылуу орнотулат.
  2. MF кайтаруу ыкмасы. Түрү катары float тандайлы, анткени ал жалпыраак.
  3. Натыйжаларды белгилөө үчүн муун ыкмасынын аталышы. Ыңгайлуу болуу үчүн, биз маани катары класстын толук аталышын + MF түзүү үчүн колдонулган ыкманын атын кайтарабыз.

Биринчиден, IRandomGenerator интерфейси менен көрсөтүлө турган абстракцияны жарыялайлы:

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

        float Generate();
    }
}

System.Random.Next() ишке ашыруу

Бул ыкма бир катар маанилерди коюуга мүмкүндүк берет, бирок ал бүтүн сандарды кайтарат, бирок калкып чыгуулар керек. Сиз жөн гана бүтүн санды сүзүүчү катары чечмелей аласыз же маанилердин диапазонун орто диапазондун ар бир мууну менен компенсациялоо менен чоңдуктун бир нече тартибине кеңейте аласыз. Натыйжа берилген тактык тартиби менен белгиленген чекит сыяктуу нерсе болот. Биз бул параметрди колдонобуз, анткени ал чыныгы калкыма мааниге жакыныраак.

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

System.Random.NextDouble() ишке ашыруу

Бул жерде маанилердин белгиленген диапазону [0; 1). Аны конструктордо көрсөтүлгөнгө проекциялоо үчүн биз жөнөкөй арифметиканы колдонобуз: X * (макс - мин) + мин.

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

UnityEngine.Random.Range() ишке ашыруу

UnityEngine.Random статикалык классынын бул ыкмасы баалуулуктардын диапазонун коюуга мүмкүндүк берет жана флот түрүн кайтарат. Эч кандай кошумча трансформацияларды жасоонун кереги жок.

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

UnityEngine.Random.value ишке ашыруу

UnityEngine.Random статикалык классынын баалуулук касиети [0; 1). Келгиле, аны 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;
    }
}

Unity.Mathematics.Random.NextFloat() ишке ашыруу

Unity.Mathematics.Random классынын NextFloat() ыкмасы float түрүндөгү калкыма чекитти кайтарат жана бир катар маанилерди көрсөтүүгө мүмкүндүк берет. Бир гана нюанс, Unity.Mathematics.Random программасынын ар бир инстанциясы кандайдыр бир урук менен инициализацияланышы керек болот - ушундай жол менен биз кайталануучу ырааттуулуктарды жаратуудан качабыз.

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

MainController ишке ашырылышы

IRandomGenerator бир нече ишке ашыруу даяр. Андан кийин, сиз ырааттуулукту жаратып, натыйжада алынган маалыматтар топтомун иштетүү үчүн сакташыңыз керек. Бул үчүн, биз Unity ичинде сахнаны жана кичинекей MainController скриптин түзөбүз, ал бардык керектүү иштерди аткарат жана ошол эле учурда UI менен өз ара аракеттенүү үчүн жооп берет.

Келгиле, берилиштер топтомунун өлчөмүн жана MF маанилеринин диапазонун коёлу, ошондой эле конфигурацияланган жана иштөөгө даяр генераторлордун массивдерин кайтарган ыкманы алалы.

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

        ...
    }
}

Эми берилиштер топтомун түзөлү. Бул учурда, маалыматтарды түзүү натыйжаларды текст агымына (csv форматында) жазуу менен айкалыштырылат. Ар бир IRandomGenerator баалуулуктарын сактоо үчүн анын өзүнчө тилкеси бөлүнүп, биринчи сапта генератордун аталышы болот.

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

        ...
    }
}

Болгону GenerateCsvDataSet ыкмасын чакырып, натыйжаны файлга сактап коюу же маалыматты дароо тармак аркылуу акыркы түзүлүштөн кабыл алуучу серверге өткөрүп берүү гана калды.

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

        ...
    }
}

Долбоордун булактары бул жерде GitLab.

натыйжалары

Эч кандай керемет болгон жок. Алар күткөн нерсе - бардык учурларда, эч кандай кутумдар жок бирдей бөлүштүрүү. Мен платформалар үчүн өзүнчө графиктерди коюунун маанисин көрбөй жатам - алардын бардыгы болжол менен бирдей натыйжаларды көрсөтөт.

Чындык мындай:
BlessRNG же RNGди калыстык үчүн текшерүү

Бардык беш муун ыкмаларынан бир учакта ырааттуулуктун визуализациясы:
BlessRNG же RNGди калыстык үчүн текшерүү

Жана 3D визуализациясы. Бирдей мазмунду чыгарбоо үчүн мен System.Random.Next() натыйжасын гана калтырам.
BlessRNG же RNGди калыстык үчүн текшерүү

UnityEngine.Randomдун нормалдуу бөлүштүрүлүшү жөнүндө кириш сөзүндө айтылган окуя кайталанган жок: же ал башында ката болгон, же кыймылдаткычта бир нерсе өзгөргөн. Бирок азыр биз ишенебиз.

Source: www.habr.com

Комментарий кошуу