
Gamedev में, आपको अक्सर किसी यादृच्छिक घर पर कुछ बाँधने की आवश्यकता होती है: एकता के पास इसके लिए अपना रैंडम है, और System.Random इसके साथ समानांतर में मौजूद है। एक बार एक परियोजना पर यह लग रहा था कि दोनों अलग तरीके से काम कर सकते हैं (हालांकि उनका एक समान वितरण होना चाहिए)।
तब वे विवरण में नहीं गए - यह पर्याप्त था कि System.Random को संक्रमण ने सभी समस्याओं को ठीक कर दिया। अब हमने और अधिक विस्तार से समझने और थोड़ा शोध करने का निर्णय लिया: कैसे "पक्षपाती" या पूर्वानुमेय आरएनजी हैं, और किसे चुनना है। इसके अलावा, मैंने अक्सर उनके "ईमानदारी" के बारे में परस्पर विरोधी राय सुनी है - यह जानने की कोशिश करें कि वास्तविक परिणाम बताए गए लोगों से कैसे संबंधित हैं।
एक संक्षिप्त शैक्षिक कार्यक्रम या RNG वास्तव में एक PRNG है
यदि आप पहले से ही यादृच्छिक संख्या जनरेटर से परिचित हैं, तो आप तुरंत "परीक्षण" अनुभाग पर आगे बढ़ सकते हैं।रैंडम संख्या (एमएफ) कुछ यादृच्छिक (अराजक) प्रक्रिया का उपयोग करके उत्पन्न संख्याओं का एक क्रम है, जो एंट्रोपी का एक स्रोत है। यही है, यह एक ऐसा क्रम है, जिनमें से तत्व किसी गणितीय कानून द्वारा जुड़े नहीं हैं - उनके पास एक कारण संबंध नहीं है।
क्या एक midrange बनाता है एक यादृच्छिक संख्या जनरेटर (RNG) कहा जाता है। ऐसा लगता है कि सब कुछ प्राथमिक है, लेकिन अगर हम सिद्धांत से अभ्यास करने जाते हैं, तो वास्तव में इस तरह के अनुक्रम को उत्पन्न करने के लिए एक सॉफ्टवेयर एल्गोरिदम को लागू करना इतना सरल नहीं है।
इसका कारण आधुनिक उपभोक्ता इलेक्ट्रॉनिक्स में बहुत यादृच्छिकता के अभाव में है। इसके बिना, यादृच्छिक संख्या यादृच्छिक होना बंद हो जाती है, और उनका जनरेटर जानबूझकर निर्धारित तर्क के एक सामान्य कार्य में बदल जाता है। आईटी क्षेत्र में कई विशिष्टताओं के लिए, यह एक गंभीर समस्या है (उदाहरण के लिए, क्रिप्टोग्राफी के लिए), बाकी के लिए पूरी तरह से स्वीकार्य समाधान है।
हमें एक एल्गोरिथ्म लिखने की ज़रूरत है, जो भले ही वास्तव में यादृच्छिक संख्या नहीं है, लेकिन जितना संभव हो उतना करीब है - तथाकथित छद्म आयामी संख्या (पीएसएन)। इस मामले में एल्गोरिथ्म को छद्म यादृच्छिक संख्या जनरेटर (PRNG) कहा जाता है।
PRNG बनाने के लिए कई विकल्प हैं, लेकिन सभी के लिए, निम्नलिखित प्रासंगिक होंगे:
- प्रारंभिक-प्रारंभिककरण की आवश्यकता।
PRNG एन्ट्रापी के एक स्रोत से रहित है, इसलिए, इसका उपयोग करने से पहले, प्रारंभिक अवस्था को इंगित करना आवश्यक है। इसे एक संख्या (या वेक्टर) के रूप में निर्दिष्ट किया जाता है और इसे बीज (बीज, यादृच्छिक बीज) कहा जाता है। अक्सर, प्रोसेसर घड़ी काउंटर या सिस्टम समय के संख्यात्मक समकक्ष का उपयोग बीज के रूप में किया जाता है। - अनुक्रम की पुनरावृत्ति।
PRNG पूरी तरह से निर्धारक है, इसलिए आरंभिकता के दौरान निर्दिष्ट बीज विशिष्ट रूप से संख्याओं के पूरे भविष्य के क्रम को निर्धारित करता है। इसका मतलब यह है कि एक एकल पीआरएसपी, एक ही बीज के साथ (अलग-अलग समय पर, विभिन्न कार्यक्रमों में, विभिन्न उपकरणों पर) एक ही अनुक्रम उत्पन्न करेगा।
आपको PRNG को चिह्नित करने वाले प्रायिकता वितरण को भी जानना होगा - यह कौन सी संख्या उत्पन्न करेगा और किस संभावना के साथ होगा। ज्यादातर अक्सर, यह या तो एक सामान्य वितरण या एक समान वितरण होता है।
सामान्य वितरण (बाएं) और समान वितरण (दाएं)मान लीजिए कि हमारे पास 24 चेहरों के साथ एक ईमानदार पासा है। यदि आप इसे छोड़ देते हैं, तो एक इकाई के गिरने की संभावना 1/24 होगी (साथ ही साथ किसी अन्य संख्या के गिरने की संभावना)। यदि आप बहुत सारे थ्रो करते हैं और परिणाम रिकॉर्ड करते हैं, तो आप देखेंगे कि सभी चेहरे एक ही आवृत्ति पर गिरते हैं। वास्तव में, इस पासा को एक समान वितरण के साथ एक आरएनजी माना जा सकता है।
और अगर आप तुरंत 10 ऐसी हड्डियों को फेंक देते हैं और अंकों की कुल मात्रा की गणना करते हैं? क्या उसके लिए एकरूपता बनी रहेगी? नहीं। सबसे अधिक बार, राशि 125 अंकों के करीब होगी, अर्थात् कुछ औसत मूल्य तक। और परिणामस्वरूप - फेंक देने से पहले भी, आप भविष्य के परिणाम का अनुमान लगा सकते हैं।
कारण यह है कि अंकों की औसत राशि प्राप्त करने के लिए सबसे बड़ी संख्या संयोजन है। इससे दूर, कम संयोजन - और, तदनुसार, नुकसान की कम संभावना। यदि आप इस डेटा की कल्पना करते हैं, तो यह दूर से घंटी के आकार जैसा होगा। इसलिए, कुछ खिंचाव के साथ, 10 हड्डियों की प्रणाली को सामान्य वितरण के साथ एक आरएनजी कहा जा सकता है।
एक और उदाहरण, केवल पहले से ही विमान में - निशानेबाजी की शूटिंग। शूटर RNG होगा जो संख्या (x, y) की एक जोड़ी बनाता है, जिसे ग्राफ पर प्रदर्शित किया जाता है।

सहमति दें कि बाईं ओर का विकल्प वास्तविक जीवन के करीब है - यह एक सामान्य वितरण के साथ एक आरएनजी है। लेकिन अगर आपको अंधेरे आकाश में तारों को बिखेरने की जरूरत है, तो एक समान वितरण के साथ आरएनजी की मदद से प्राप्त सही विकल्प बेहतर है। सामान्य तौर पर, कार्य के आधार पर एक जनरेटर चुनें।
अब पीएसपी अनुक्रम के एन्ट्रॉपी के बारे में बात करते हैं। उदाहरण के लिए, एक क्रम है जो इस तरह शुरू होता है:
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, 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, 9 ...
पहली नज़र में ये संख्या कितनी यादृच्छिक है? चलो वितरण की जांच करके शुरू करते हैं।

यह वर्दी के करीब जैसा दिखता है, लेकिन यदि आप दो संख्याओं के अनुक्रम को पढ़ते हैं और उन्हें विमान पर निर्देशांक के रूप में व्याख्या करते हैं, तो आपको यह मिलता है:

पैटर्न स्पष्ट रूप से दिखाई दे रहे हैं। और चूंकि अनुक्रम में डेटा एक निश्चित तरीके से आदेश दिया जाता है (अर्थात, उनके पास कम एन्ट्रॉपी है), इससे बहुत "पूर्वाग्रह" पैदा हो सकते हैं। कम से कम, इस तरह के एक 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, 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, ...
लगता है सब कुछ यहाँ भी ठीक है:

आइए वॉल्यूम में देखें (हम तीन नंबर प्रत्येक पढ़ते हैं):

और फिर से पैटर्न। चार आयामों में दृश्य निर्माण से काम नहीं चलेगा। लेकिन पैटर्न इस आयाम पर और बड़े दोनों पर मौजूद हो सकते हैं।
उसी क्रिप्टोग्राफी में, जहां PRNG पर सबसे कठोर आवश्यकताओं को लगाया जाता है, ऐसी स्थिति स्पष्ट रूप से अस्वीकार्य है। इसलिए, उनकी गुणवत्ता का मूल्यांकन करने के लिए, विशेष एल्गोरिदम विकसित किए गए हैं, जिन्हें हम अब नहीं छूएंगे। विषय व्यापक है और एक अलग लेख पर आता है।
परीक्षण
यदि हम निश्चित रूप से कुछ नहीं जानते हैं, तो इसके साथ कैसे काम करें? क्या यह सड़क पार करने के लिए लायक है यदि आप नहीं जानते कि यह ट्रैफ़िक सिग्नल क्या अनुमति देता है? परिणाम अलग हो सकते हैं।
वही एकता में कुख्यात यादृच्छिकता के लिए जाता है। ठीक है, अगर दस्तावेज़ीकरण आवश्यक विवरणों को प्रकट करता है, लेकिन लेख की शुरुआत में उल्लिखित कहानी वांछित बारीकियों की कमी के कारण हुई।
और यह जाने बिना कि उपकरण कैसे काम करता है, आप इसे सही तरीके से लागू नहीं कर सकते। सामान्य तौर पर, वितरण की कीमत पर कम से कम अंत में सुनिश्चित करने के लिए एक प्रयोग की जांच और संचालन का समय आ गया है।
समाधान सरल और प्रभावी था - आंकड़े इकट्ठा करने, उद्देश्य डेटा प्राप्त करने और परिणामों को देखने के लिए।
शोध विषय
एकता में यादृच्छिक संख्या उत्पन्न करने के कई तरीके हैं - हमने पांच का परीक्षण किया।
- System.Random.Next ()। किसी दिए गए मान में पूर्णांक बनाता है।
- System.Random.NextDouble ()। [0 से सीमा में डबल सटीक संख्या (डबल) उत्पन्न करता है; 1)।
- UnityEngine.Random.Range ()। मूल्यों की दी गई श्रेणी में एकल सटीक संख्या (फ्लोट) उत्पन्न करता है।
- UnityEngine.Random.value। [0 से सीमा में एकल सटीक संख्या (फ्लोट) उत्पन्न करता है; 1)।
- यूनिटी.मैटमैटिक्स। नई यूनिटी.मैटमैटिक्स लाइब्रेरी का हिस्सा। मूल्यों की दी गई श्रेणी में एकल सटीक संख्या (फ्लोट) उत्पन्न करता है।
लगभग हर जगह प्रलेखन में, एक समान वितरण को इंगित किया गया था, जिसमें UnityEngine.Random.value (जहां वितरण निर्दिष्ट नहीं है, लेकिन UnityEngine.Random.Range () के समान है) यह भी एक समान होने की उम्मीद थी) और Unity.Mathematics। आधार xorshift एल्गोरिथ्म है, जिसका अर्थ है कि फिर से आपको एक समान वितरण की प्रतीक्षा करने की आवश्यकता है)।
डिफ़ॉल्ट रूप से, दस्तावेज़ीकरण में अपेक्षित लोग अपेक्षित परिणामों के लिए लिए गए थे।
तकनीक
हमने एक छोटा अनुप्रयोग लिखा है जो प्रस्तुत तरीकों में से प्रत्येक में यादृच्छिक संख्याओं के अनुक्रम उत्पन्न करता है और आगे की प्रक्रिया के लिए परिणाम बचाता है।
प्रत्येक अनुक्रम की लंबाई 100,000 संख्या है।
यादृच्छिक संख्याओं की सीमा [0, 100) है।
कई लक्ष्य प्लेटफार्मों से डेटा एकत्र किया गया था:
- विंडोज
- एकता v2018.3.14f1, संपादक मोड, मोनो, .NET मानक 2.0 - MacOS
- एकता v2018.3.14f1, संपादक मोड, मोनो, .NET मानक 2.0
- एकता v5.6.4p4, संपादक मोड, मोनो, .NET मानक 2.0 - एंड्रॉयड
- एकता v2018.3.14f1, डिवाइस पर असेंबली, मोनो, .NET मानक 2.0 - आईओएस
- एकता v2018.3.14f1, डिवाइस का निर्माण, il2cpp, .NET मानक 2.0
कार्यान्वयन
यादृच्छिक संख्या उत्पन्न करने के लिए हमारे पास कई अलग-अलग तरीके हैं। उनमें से प्रत्येक के लिए हम एक अलग आवरण वर्ग लिखेंगे, जो प्रदान करना चाहिए:
- मूल्यों की सीमा निर्धारित करने की क्षमता [न्यूनतम / अधिकतम)। इसे कंस्ट्रक्टर के जरिए सेट किया जाएगा।
- विधि लौटने की व्यवस्था। हम फ्लोट को प्रकार के रूप में चुनेंगे, अधिक सामान्य के रूप में।
- परिणामों को चिह्नित करने के लिए पीढ़ी विधि का नाम। सुविधा के लिए, हम मूल्य के रूप में midrange को उत्पन्न करने के लिए उपयोग की जाने वाली विधि का पूरा नाम + नाम वापस करेंगे।
सबसे पहले, एक अमूर्त की घोषणा करें, जो कि 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)। कंस्ट्रक्टर में निर्दिष्ट एक पर इसे प्रोजेक्ट करने के लिए, हम साधारण अंकगणित का उपयोग करते हैं: एक्स * (अधिकतम - मिनट) + मिनट।
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 () कार्यान्वयन
यूनिटीजाइन.ऑर्गैनिक स्टैटिक क्लास की यह विधि आपको कई मानों को निर्दिष्ट करने की अनुमति देती है और प्रकार के फ्लोट की एक मध्य व्यवस्था लौटाती है। कोई अतिरिक्त परिवर्तन आवश्यक नहीं हैं।
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 की मूल्य संपत्ति मानों की एक निश्चित श्रेणी से फ्लोट प्रकार की एक midrange देता है [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; } }
यूनिटी.मैटमैटिक्स
नेक्स्टफ्लोट () यूनिटी.मैटमैटिक्स.रैंडम क्लास का तरीका एक प्रकार की फ्लोट की मध्य व्यवस्था देता है और आपको कई मानों को निर्दिष्ट करने की अनुमति देता है। एकमात्र बारीकियों यह है कि एकता के प्रत्येक उदाहरण।मैटमैटिक्स।रैंडम को कुछ बीज के साथ आरंभीकृत करना होगा - इस तरह हम दोहराया अनुक्रम उत्पन्न करने से बचेंगे।
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); } }
मेनकंट्रोलर कार्यान्वयन
कई IRandomGenerator कार्यान्वयन तैयार हैं। अगला, आपको अनुक्रम उत्पन्न करने और प्रसंस्करण के लिए परिणामी डाटासेट को बचाने की आवश्यकता है। ऐसा करने के लिए, एकता में एक दृश्य बनाएं और एक छोटी स्क्रिप्ट मेनकंट्रोलर, जो सभी आवश्यक कार्य करेगा और साथ ही साथ यूआई के साथ बातचीत के लिए जिम्मेदार होगा।
हम डेटासेट के आकार और मिडरेंज मानों की सीमा निर्धारित करते हैं, और एक विधि भी प्राप्त करते हैं जो ट्यून और रेडी-टू-यूज़ जेनरेटरों की एक सरणी देता है।
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) }; } ... } }
और अब हम एक डेटासेट बना रहे हैं। इस स्थिति में, डेटा पीढ़ी को पाठ स्ट्रीम (सीएसवी प्रारूप में) में परिणामों की रिकॉर्डिंग के साथ जोड़ा जाएगा। प्रत्येक 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;
यह 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(); } } ... } }
परियोजना के स्रोत
गिटलैब पर हैं।
परिणाम
कोई चमत्कार नहीं हुआ। उन्हें क्या उम्मीद थी, उन्हें यह मिल गया - सभी मामलों में साजिश के संकेत के बिना एक समान वितरण। मैं प्लेटफार्मों पर अलग ग्राफिक्स लगाने का बिंदु नहीं देखता - वे सभी लगभग एक ही परिणाम दिखाते हैं।
वास्तविकता यह है:

सभी पांच पीढ़ी के तरीकों से एक विमान पर दृश्यों का दृश्य:

और 3 डी में दृश्य। मैं केवल System.Random.Next () का परिणाम छोड़ दूंगा ताकि समान सामग्री का एक गुच्छा तैयार न कर सकूं।

कहानी में यूनिटीजाइन के सामान्य वितरण के बारे में परिचय में बताया गया था। आयामी ने दोहराया नहीं था: या तो यह शुरुआत में गलत था, या तब से इंजन में कुछ बदल गया है। लेकिन अब हमें यकीन है।