BlessRNG эсвэл RNG-г шударга эсэхийг шалгах

BlessRNG эсвэл RNG-г шударга эсэхийг шалгах

Тоглоом хөгжүүлэхдээ та ямар нэг зүйлийг санамсаргүй байдлаар холбох хэрэгтэй болдог: Unity нь өөрийн гэсэн Random-тай бөгөөд үүнтэй зэрэгцээд System.Random байдаг. Нэгэн удаа төслүүдийн нэг дээр би хоёулаа өөр өөрөөр ажиллах боломжтой гэсэн сэтгэгдэл төрсөн (хэдийгээр тэд жигд хуваарилалттай байх ёстой).

Дараа нь тэд нарийвчилсан мэдээлэл өгөөгүй - System.Random руу шилжих нь бүх асуудлыг засахад хангалттай байсан. Одоо бид үүнийг илүү нарийвчлан судалж, бага зэрэг судалгаа хийхээр шийдсэн: RNG нь хэр "хязгаарлагдмал" эсвэл урьдчилан таамаглах боломжтой, алийг нь сонгох вэ. Түүгээр ч барахгүй тэдний "шударга байдлын" талаар зөрчилтэй санал бодлыг би нэгээс олон удаа сонссон - бодит үр дүн нь зарласан үр дүнгүүдтэй хэрхэн харьцуулж байгааг олж мэдэхийг хичээцгээе.

Боловсролын товч хөтөлбөр буюу RNG нь үнэндээ RNG юм

Хэрэв та санамсаргүй тоо үүсгэгчийг аль хэдийн мэддэг бол "Туршилт" хэсгийг нэн даруй алгасаж болно.

Санамсаргүй тоо (RN) нь энтропийн эх үүсвэр болох зарим санамсаргүй (эмх замбараагүй) процессыг ашиглан үүсгэгдсэн тоонуудын дараалал юм. Өөрөөр хэлбэл, энэ бол элементүүд нь ямар ч математик хуулиар хоорондоо холбогдоогүй дараалал юм - тэдгээр нь шалтгаан-үр дагаврын холбоогүй байдаг.

Санамсаргүй тоог үүсгэдэг зүйлийг санамсаргүй тоо үүсгэгч (RNG) гэж нэрлэдэг. Бүх зүйл энгийн зүйл мэт санагдах боловч хэрэв бид онолоос практик руу шилжих юм бол ийм дарааллыг бий болгох програм хангамжийн алгоритмыг хэрэгжүүлэх нь тийм ч энгийн зүйл биш юм.

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

Энэ нь үнэхээр санамсаргүй тоо биш ч гэсэн буцаж ирэх алгоритмыг бичих шаардлагатай, гэхдээ тэдгээрт аль болох ойрхон - псевдо санамсаргүй тоо (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-г шударга эсэхийг шалгах
Мөн дахин загварууд. Дөрвөн хэмжээст дүрслэлийг бүтээх боломжгүй болсон. Гэхдээ энэ хэмжээс дээр болон илүү том хэмжээтэй загварууд байж болно.

PRNG-д хамгийн хатуу шаардлага тавьдаг криптографийн хувьд ийм нөхцөл байдлыг хүлээн зөвшөөрөх боломжгүй юм. Тиймээс тэдний чанарыг үнэлэх тусгай алгоритмуудыг боловсруулсан бөгөөд бид одоо үүнийг хөндөхгүй. Энэ сэдэв нь өргөн цар хүрээтэй бөгөөд тусдаа нийтлэл байх ёстой.

Тест хийх

Хэрэв бид ямар нэг зүйлийг тодорхой мэдэхгүй бол үүнтэй хэрхэн ажиллах вэ? Аль гэрлэн дохио зөвшөөрч байгааг мэдэхгүй бол зам хөндлөн гарах нь зүйтэй болов уу? Үр дагавар нь өөр байж болно.

Unity дахь алдартай санамсаргүй байдлын хувьд ч мөн адил. Баримт бичиг нь шаардлагатай нарийн ширийн зүйлийг илчилсэн бол сайн хэрэг, гэхдээ нийтлэлийн эхэнд дурдсан түүх нь хүссэн нарийн ширийн зүйл дутмаг байсан тул яг ийм болсон.

Хэрэв та уг хэрэгсэл хэрхэн ажилладагийг мэдэхгүй бол та үүнийг зөв ашиглах боломжгүй болно. Ерөнхийдөө, ядаж тархалтын талаар баталгаажуулахын тулд шалгаж, туршилт хийх цаг болжээ.

Шийдэл нь энгийн бөгөөд үр дүнтэй байсан - статистик цуглуулах, бодитой мэдээлэл олж авах, үр дүнг харах.

Судалгааны сэдэв

Unity-д санамсаргүй тоо үүсгэх хэд хэдэн арга байдаг - бид тавыг туршиж үзсэн.

  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.Математикийн шинэ номын сангийн нэг хэсэг. Өгөгдсөн утгын мужид нэг нарийвчлалтай тоо (хөвөгч) үүсгэдэг.

UnityEngine.Random.value (тархалтыг заагаагүй боловч UnityEngine.Random.Range()-тай адилтгаж байсан) болон Unity.Mathematics.Random-ыг эс тооцвол баримт бичгийн бараг хаа сайгүй жигд хуваарилалтыг зааж өгсөн болно. .NextFloat() (үүнд суурь нь xorshift алгоритм бөгөөд энэ нь та дахин жигд тархалтыг хүлээх хэрэгтэй гэсэн үг юм).

Анхдагч байдлаар, хүлээгдэж буй үр дүнг баримт бичигт заасантай адилаар авсан.

Арга зүй

Бид танилцуулсан арга тус бүрийг ашиглан санамсаргүй тоонуудын дарааллыг үүсгэсэн жижиг програм бичиж, үр дүнг цаашдын боловсруулалтад зориулж хадгалсан.

Дараалал бүрийн урт нь 100 тоо юм.
Санамсаргүй тоонуудын хүрээ нь [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, Editor mode, 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. Утгын мужийг тохируулах боломж [min/max). Бүтээгчээр дамжуулан тохируулна.
  2. MF буцаах арга. Илүү ерөнхий учраас хөвөгчийг төрлөөр нь сонгоцгооё.
  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 * (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;
    }
}

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-ийн хэвийн тархалтын тухай танилцуулгад өгүүлсэн түүх давтагдсангүй: энэ нь эхэндээ алдаатай байсан эсвэл хөдөлгүүрт ямар нэг зүйл өөрчлөгдсөн байна. Харин одоо бид итгэлтэй байна.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх