BlessRNG yoki RNGni adolat uchun tekshirish

BlessRNG yoki RNGni adolat uchun tekshirish

O'yinni ishlab chiqishda siz ko'pincha tasodifiylik bilan biror narsani bog'lashingiz kerak bo'ladi: Unity buning uchun o'z Randomiga ega va unga parallel ravishda System.Random mavjud. Bir vaqtlar, loyihalardan birida men ikkalasi ham boshqacha ishlashi mumkinligi haqida taassurot qoldirdim (garchi ular bir tekis taqsimlanishi kerak).

Keyin ular tafsilotlarga kirmadilar - System.Random-ga o'tish barcha muammolarni to'g'irlashi kifoya edi. Endi biz buni batafsilroq ko'rib chiqishga va kichik tadqiqot o'tkazishga qaror qildik: RNGlar qanchalik "noxolis" yoki bashorat qilinadigan va qaysi birini tanlash kerak. Bundan tashqari, men ularning "halolligi" haqida bir necha bor qarama-qarshi fikrlarni eshitganman - keling, haqiqiy natijalar e'lon qilingan natijalar bilan qanday solishtirishini aniqlashga harakat qilaylik.

Qisqacha ta'lim dasturi yoki RNG aslida RNG

Agar siz tasodifiy sonlar generatorlari bilan tanish bo'lsangiz, darhol "Test" bo'limiga o'tishingiz mumkin.

Tasodifiy sonlar (RN) - bu qandaydir tasodifiy (xaotik) jarayon yordamida hosil qilingan raqamlar ketma-ketligi, entropiya manbai. Ya'ni, bu ketma-ketlik bo'lib, uning elementlari hech qanday matematik qonun bilan bog'lanmagan - ularda sabab-natija aloqasi yo'q.

Tasodifiy sonni yaratadigan narsa tasodifiy sonlar generatori (RNG) deb ataladi. Hamma narsa oddiy bo'lib tuyuladi, lekin agar biz nazariyadan amaliyotga o'tadigan bo'lsak, unda bunday ketma-ketlikni yaratish uchun dasturiy ta'minot algoritmini amalga oshirish unchalik oson emas.

Buning sababi zamonaviy maishiy elektronikada xuddi shunday tartibsizlikning yo'qligidadir. Busiz tasodifiy sonlar tasodifiy bo'lishni to'xtatadi va ularning generatori aniq belgilangan argumentlarning oddiy funktsiyasiga aylanadi. IT sohasidagi bir qator mutaxassisliklar uchun bu jiddiy muammo (masalan, kriptografiya), ammo boshqalar uchun mutlaqo maqbul echim mavjud.

Haqiqatan ham tasodifiy raqamlar emas, balki ularga imkon qadar yaqin bo'lgan - psevdo-tasodifiy raqamlar (PRN) deb ataladigan algoritmni yozish kerak. Bu holda algoritm pseudorandom number generator (PRNG) deb ataladi.

PRNG yaratish uchun bir nechta variant mavjud, ammo quyidagilar hamma uchun tegishli bo'ladi:

  1. Dastlabki ishga tushirish zarurati.

    PRNG entropiya manbasiga ega emas, shuning uchun foydalanishdan oldin unga dastlabki holat berilishi kerak. U raqam (yoki vektor) sifatida ko'rsatilgan va urug' (tasodifiy urug') deb ataladi. Ko'pincha, protsessor soati hisoblagichi yoki tizim vaqtining raqamli ekvivalenti urug' sifatida ishlatiladi.

  2. Ketma-ket takrorlanishi.

    PRNG butunlay deterministikdir, shuning uchun ishga tushirish paytida ko'rsatilgan urug' kelajakdagi raqamlar ketma-ketligini yagona tarzda aniqlaydi. Bu shuni anglatadiki, bir xil urug' bilan ishga tushirilgan alohida PRNG (turli vaqtlarda, turli dasturlarda, turli qurilmalarda) bir xil ketma-ketlikni yaratadi.

Shuningdek, siz PRNGni tavsiflovchi ehtimollik taqsimotini bilishingiz kerak - u qanday raqamlarni yaratadi va qanday ehtimollik bilan. Ko'pincha bu oddiy taqsimot yoki bir xil taqsimotdir.
BlessRNG yoki RNGni adolat uchun tekshirish
Oddiy taqsimot (chapda) va bir xil taqsimot (o'ngda)

Aytaylik, bizda 24 tomonli adolatli o'lim bor. Agar siz uni tashlasangiz, bittani olish ehtimoli 1/24 ga teng bo'ladi (boshqa raqamni olish ehtimoli bilan bir xil). Agar siz ko'p otishlarni qilsangiz va natijalarni yozsangiz, barcha qirralarning taxminan bir xil chastotada tushishini sezasiz. Aslida, bu o'limni yagona taqsimotga ega RNG deb hisoblash mumkin.

Agar siz birdaniga 10 ta zarni tashlab, umumiy ochkolarni hisoblasangiz-chi? Buning uchun bir xillik saqlanib qoladimi? Yo'q. Ko'pincha, miqdor 125 ballga, ya'ni o'rtacha qiymatga yaqin bo'ladi. Va natijada, hatto otishdan oldin, siz kelajakdagi natijani taxminan taxmin qilishingiz mumkin.

Buning sababi shundaki, o'rtacha ball olish uchun eng ko'p kombinatsiyalar mavjud. Undan qanchalik uzoq bo'lsa, kombinatsiyalar kamroq bo'ladi - va shunga mos ravishda yo'qotish ehtimoli shunchalik past bo'ladi. Agar bu ma'lumotlar vizual tarzda tasvirlangan bo'lsa, u noaniq tarzda qo'ng'iroq shakliga o'xshaydi. Shuning uchun, biroz cho'zilgan holda, 10 zardan iborat tizimni normal taqsimlangan RNG deb atash mumkin.

Yana bir misol, faqat bu safar samolyotda - nishonga otish. Otishma grafikda ko'rsatilgan juft raqamlarni (x, y) hosil qiluvchi RNG bo'ladi.
BlessRNG yoki RNGni adolat uchun tekshirish
Chapdagi variant haqiqiy hayotga yaqinroq ekanligiga rozi bo'ling - bu oddiy taqsimotga ega RNG. Ammo agar siz yulduzlarni qorong'i osmonda sochishingiz kerak bo'lsa, unda bir xil taqsimlangan RNG yordamida olingan to'g'ri variant yaxshiroq mos keladi. Umuman olganda, vazifaga qarab generatorni tanlang.

Endi PNG ketma-ketligining entropiyasi haqida gapiraylik. Misol uchun, shunday boshlanadigan ketma-ketlik mavjud:

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

Bu raqamlar birinchi qarashda qanchalik tasodifiy? Keling, taqsimotni tekshirishdan boshlaylik.
BlessRNG yoki RNGni adolat uchun tekshirish
Bu bir xil ko'rinishga yaqin ko'rinadi, lekin agar siz ikkita raqam ketma-ketligini o'qib, ularni tekislikdagi koordinatalar sifatida talqin qilsangiz, siz quyidagilarni olasiz:
BlessRNG yoki RNGni adolat uchun tekshirish
Naqshlar aniq ko'rinadigan bo'ladi. Va ketma-ketlikdagi ma'lumotlar ma'lum bir tarzda tartibga solinganligi sababli (ya'ni, u past entropiyaga ega), bu "tarafsizlik" ni keltirib chiqarishi mumkin. Hech bo'lmaganda, bunday PRNG samolyotda koordinatalarni yaratish uchun juda mos kelmaydi.

Boshqa ketma-ketlik:

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

Bu erda, hatto samolyotda ham hamma narsa yaxshi ko'rinadi:
BlessRNG yoki RNGni adolat uchun tekshirish
Keling, hajmni ko'rib chiqaylik (bir vaqtning o'zida uchta raqamni o'qing):
BlessRNG yoki RNGni adolat uchun tekshirish
Va yana naqshlar. Vizualizatsiyani endi to'rt o'lchovda qurish mumkin emas. Lekin naqshlar bu o'lchamda va kattaroqlarida mavjud bo'lishi mumkin.

PRNG-larga eng qattiq talablar qo'yilgan kriptografiyada bunday holat mutlaqo qabul qilinishi mumkin emas. Shuning uchun ularning sifatini baholash uchun maxsus algoritmlar ishlab chiqilgan, biz hozir bu haqda gapirmaymiz. Mavzu keng va alohida maqolaga loyiqdir.

Viktorina

Agar biror narsani aniq bilmasak, u bilan qanday ishlash kerak? Qaysi svetofor ruxsat berishini bilmasangiz, yo'lni kesib o'tishga arziydimi? Buning oqibatlari boshqacha bo'lishi mumkin.

Xuddi shu narsa Unity-dagi mashhur tasodifiylikka ham tegishli. Hujjatlar kerakli tafsilotlarni ochib bersa yaxshi bo'ladi, lekin maqolaning boshida aytib o'tilgan voqea kerakli xususiyatlarning yo'qligi sababli sodir bo'ldi.

Va agar siz vosita qanday ishlashini bilmasangiz, uni to'g'ri ishlata olmaysiz. Umuman olganda, hech bo'lmaganda tarqatish haqida ishonch hosil qilish uchun tajriba o'tkazish va tekshirish vaqti keldi.

Yechim oddiy va samarali edi - statistik ma'lumotlarni to'plash, ob'ektiv ma'lumotlarni olish va natijalarga qarash.

O'rganish mavzusi

Unity-da tasodifiy raqamlarni yaratishning bir necha yo'li mavjud - biz beshtasini sinab ko'rdik.

  1. System.Random.Next(). Belgilangan qiymatlar oralig'ida butun sonlarni hosil qiladi.
  2. System.Random.NextDouble(). [0 dan] oralig'ida ikki baravar aniqlikdagi raqamlarni hosil qiladi; 1).
  3. UnityEngine.Random.Range(). Belgilangan qiymatlar oralig'ida yagona aniq raqamlarni (suzuvchi) hosil qiladi.
  4. UnityEngine.Random.value. [0 dan oraliqda bitta aniqlikdagi raqamlarni (suzuvchi) hosil qiladi; 1).
  5. Unity.Mathematics.Random.NextFloat(). Yangi Unity.Mathematics kutubxonasining bir qismi. Belgilangan qiymatlar oralig'ida yagona aniq raqamlarni (suzuvchi) hosil qiladi.

Hujjatlarning deyarli hamma joyida yagona taqsimot ko'rsatilgan, UnityEngine.Random.value (bu erda taqsimot ko'rsatilmagan, lekin UnityEngine.Random.Range() bilan o'xshashlik bo'yicha ham kutilgan edi) va Unity.Mathematics.Random bundan mustasno .NextFloat() (bu erda The asosda xorshift algoritmi joylashgan, ya'ni yana bir xil taqsimotni kutish kerak).

Odatiy bo'lib, kutilgan natijalar hujjatlarda ko'rsatilgandek qabul qilindi.

Metodologiya

Biz taqdim etilgan usullarning har biri yordamida tasodifiy sonlar ketma-ketligini yaratadigan va natijalarni keyingi qayta ishlash uchun saqlaydigan kichik dastur yozdik.

Har bir ketma-ketlikning uzunligi 100 000 raqamdan iborat.
Tasodifiy sonlar diapazoni [0, 100).

Ma'lumotlar bir nechta maqsadli platformalardan to'plangan:

  • Windows
    — Unity v2018.3.14f1, muharrir rejimi, Mono, .NET Standard 2.0
  • MacOS
    — Unity v2018.3.14f1, muharrir rejimi, Mono, .NET Standard 2.0
    — Unity v5.6.4p4, muharrir rejimi, Mono, .NET Standard 2.0
  • Android
    — Unity v2018.3.14f1, har bir qurilma uchun tuzilish, Mono, .NET Standard 2.0
  • iOS
    — Unity v2018.3.14f1, har bir qurilma uchun qurish, il2cpp, .NET Standard 2.0

Реализация

Tasodifiy raqamlarni yaratishning bir necha xil usullari mavjud. Ularning har biri uchun biz alohida o'rash sinfini yozamiz, unda quyidagilar ko'rsatilishi kerak:

  1. Qiymatlar oralig'ini o'rnatish imkoniyati [min/max). Konstruktor orqali o'rnatiladi.
  2. MFni qaytarish usuli. Tur sifatida floatni tanlaylik, chunki u umumiyroq.
  3. Natijalarni belgilash uchun generatsiya usulining nomi. Qulaylik uchun biz qiymat sifatida sinfning to'liq nomini + MFni yaratishda foydalaniladigan usulning nomini qaytaramiz.

Birinchidan, IRandomGenerator interfeysi bilan ifodalanadigan abstraksiyani e'lon qilaylik:

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

        float Generate();
    }
}

System.Random.Next() ni amalga oshirish

Bu usul sizga qiymatlar oralig'ini o'rnatish imkonini beradi, lekin u butun sonlarni qaytaradi, lekin float kerak. Siz shunchaki butun sonni float sifatida talqin qilishingiz mumkin yoki qiymatlar diapazonini o'rta diapazonning har bir avlodi bilan qoplagan holda bir necha kattalik tartiblari bilan kengaytirishingiz mumkin. Natijada ma'lum bir aniqlik tartibiga ega sobit nuqtaga o'xshash narsa bo'ladi. Biz ushbu parametrdan foydalanamiz, chunki u haqiqiy float qiymatiga yaqinroqdir.

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() ni amalga oshirish

Bu erda belgilangan qiymatlar diapazoni [0; 1). Uni konstruktorda ko'rsatilganiga loyihalash uchun biz oddiy arifmetikadan foydalanamiz: X * (maks - 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;
    }
}

UnityEngine.Random.Range() ni amalga oshirish

UnityEngine.Random statik klassining ushbu usuli sizga qiymatlar oralig'ini o'rnatishga imkon beradi va float turini qaytaradi. Hech qanday qo'shimcha o'zgarishlar qilishingiz shart emas.

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 dasturini amalga oshirish

UnityEngine.Random statik klassining qiymat xususiyati belgilangan qiymatlar diapazonidan float turini qaytaradi [0; 1). Uni berilgan diapazonga xuddi System.Random.NextDouble() ni amalga oshirishdagidek proyeksiya qilaylik.

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() ni amalga oshirish

Unity.Mathematics.Random sinfining NextFloat() usuli float tipidagi suzuvchi nuqtani qaytaradi va qiymatlar oralig‘ini belgilash imkonini beradi. Yagona nuance shundaki, Unity.Mathematics.Random ning har bir nusxasi qandaydir urug 'bilan ishga tushirilishi kerak bo'ladi - bu bilan biz takroriy ketma-ketliklarni yaratishdan qochamiz.

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 dasturini amalga oshirish

IRandomGeneratorning bir nechta ilovalari tayyor. Keyinchalik, ketma-ketliklarni yaratishingiz va olingan ma'lumotlar to'plamini qayta ishlash uchun saqlashingiz kerak. Buning uchun biz Unity-da barcha kerakli ishlarni bajaradigan va shu bilan birga UI bilan o'zaro aloqa uchun javobgar bo'lgan sahna va kichik MainController skriptini yaratamiz.

Keling, ma'lumotlar to'plamining o'lchamini va MF qiymatlari diapazonini o'rnatamiz, shuningdek, konfiguratsiya qilingan va ishlashga tayyor generatorlar qatorini qaytaradigan usulni olamiz.

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

        ...
    }
}

Endi ma'lumotlar to'plamini yarataylik. Bunday holda, ma'lumotlarni yaratish natijalarni matn oqimiga (csv formatida) yozish bilan birlashtiriladi. Har bir IRandomGenerator qiymatlarini saqlash uchun uning alohida ustuni ajratiladi va birinchi qatorda generator nomi mavjud.

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

        ...
    }
}

Faqat GenerateCsvDataSet usulini chaqirish va natijani faylga saqlash yoki tarmoq orqali ma'lumotlarni oxirgi qurilmadan qabul qiluvchi serverga darhol o'tkazish qoladi.

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

        ...
    }
}

Loyiha manbalari bu yerda GitLab.

Natijalar

Hech qanday mo''jiza sodir bo'lmadi. Ular kutgan narsaga erishdilar - barcha holatlarda, fitnalarsiz bir tekis taqsimlash. Men platformalar uchun alohida grafiklarni qo'yishning ma'nosini ko'rmayapman - ularning barchasi taxminan bir xil natijalarni ko'rsatadi.

Haqiqat shunday:
BlessRNG yoki RNGni adolat uchun tekshirish

Barcha besh avlod usullaridan tekislikdagi ketma-ketlikni vizualizatsiya qilish:
BlessRNG yoki RNGni adolat uchun tekshirish

Va 3D formatida vizualizatsiya. Men bir xil tarkibni yaratmaslik uchun faqat System.Random.Next() natijasini qoldiraman.
BlessRNG yoki RNGni adolat uchun tekshirish

Kirish qismida UnityEngine.Random-ning normal taqsimlanishi haqida aytilgan voqea takrorlanmadi: yo dastlab xato bo'lgan, yoki keyinchalik dvigatelda nimadir o'zgargan. Ammo endi biz aminmiz.

Manba: www.habr.com

a Izoh qo'shish