BlessRNG ή έλεγχος του RNG για δικαιοσύνη

BlessRNG ή έλεγχος του RNG για δικαιοσύνη

Στην ανάπτυξη παιχνιδιών, συχνά χρειάζεται να συνδέσετε κάτι με την τυχαιότητα: η Unity έχει τη δική της Random για αυτό, και η System.Random υπάρχει παράλληλα με αυτήν. Κάποτε, σε ένα από τα έργα, είχα την εντύπωση ότι και τα δύο μπορούν να λειτουργήσουν διαφορετικά (αν και θα έπρεπε να έχουν ομοιόμορφη κατανομή).

Εκείνη την εποχή, δεν μπήκαμε σε λεπτομέρειες - αρκούσε που η μετάβαση στο System.Random έλυσε όλα τα προβλήματα. Τώρα αποφασίσαμε να το εξετάσουμε λεπτομερέστερα και να διεξάγουμε μια μικρή μελέτη: πόσο "προκατειλημμένες" ή προβλέψιμες είναι οι RNG και ποια να επιλέξουμε. Επιπλέον, έχω ακούσει αντικρουόμενες απόψεις για την "ειλικρίνειά" τους περισσότερες από μία φορές - ας προσπαθήσουμε να καταλάβουμε πώς συγκρίνονται τα πραγματικά αποτελέσματα με τα δηλωμένα.

Σύντομο εκπαιδευτικό πρόγραμμα ή μήπως το RNG είναι στην πραγματικότητα ένα RNG;

Αν είστε ήδη εξοικειωμένοι με τις γεννήτριες τυχαίων αριθμών, μπορείτε να μεταβείτε στην ενότητα "Δοκιμές".

Οι τυχαίοι αριθμοί (ΤΑ) είναι μια ακολουθία αριθμών που παράγεται από κάποια τυχαία (χαοτική) διαδικασία, μια πηγή εντροπίας. Δηλαδή, είναι μια ακολουθία της οποίας τα στοιχεία δεν συνδέονται μεταξύ τους με κανέναν μαθηματικό νόμο - δεν έχουν σχέση αιτίας-αποτελέσματος.

Αυτό που δημιουργεί το SN ονομάζεται γεννήτρια τυχαίων αριθμών (RNG). Φαίνεται ότι όλα είναι στοιχειώδη, αλλά αν προχωρήσουμε από τη θεωρία στην πράξη, τότε στην πραγματικότητα, η εφαρμογή ενός αλγορίθμου λογισμικού για τη δημιουργία μιας τέτοιας ακολουθίας δεν είναι τόσο απλή.

Ο λόγος είναι η απουσία αυτού του χάους στις σύγχρονες ηλεκτρονικές συσκευές ευρείας κατανάλωσης. Χωρίς αυτό, οι τυχαίοι αριθμοί παύουν να είναι τυχαίοι και η γεννήτριά τους μετατρέπεται σε μια συνηθισμένη συνάρτηση προκαθορισμένων ορισμάτων. Για ορισμένες ειδικότητες στον τομέα της πληροφορικής, αυτό είναι ένα σοβαρό πρόβλημα (για παράδειγμα, για την κρυπτογραφία), αλλά για τις υπόλοιπες υπάρχει μια απολύτως αποδεκτή λύση.

Είναι απαραίτητο να γραφτεί ένας αλγόριθμος που θα επέστρεφε, αν όχι πραγματικά τυχαίους αριθμούς, τότε όσο το δυνατόν πιο κοντά σε αυτούς - τους λεγόμενους ψευδοτυχαίους αριθμούς (PRNG). Ο αλγόριθμος σε αυτήν την περίπτωση ονομάζεται γεννήτρια ψευδοτυχαίων αριθμών (PRNG).

Υπάρχουν αρκετές επιλογές για τη δημιουργία ενός PRNG, αλλά οι ακόλουθες θα είναι σχετικές με όλες:

  1. Απαιτείται προ-αρχικοποίηση.

    Το PRNG δεν έχει πηγή εντροπίας, επομένως πριν το χρησιμοποιήσετε, πρέπει να καθορίσετε την αρχική κατάσταση. Καθορίζεται ως αριθμός (ή διάνυσμα) και ονομάζεται σπόρος (seed). Συχνά, ο μετρητής κύκλου επεξεργαστή ή το αριθμητικό ισοδύναμο του χρόνου συστήματος χρησιμοποιείται ως σπόρος.

  2. Αναπαραγωγιμότητα της ακολουθίας.

    Το PRNG είναι εντελώς ντετερμινιστικό, επομένως ο σπόρος που καθορίζεται κατά την αρχικοποίηση καθορίζει μοναδικά ολόκληρη τη μελλοντική ακολουθία αριθμών. Αυτό σημαίνει ότι ένα μόνο PRNG που αρχικοποιείται με τον ίδιο σπόρο (σε διαφορετικές χρονικές στιγμές, σε διαφορετικά προγράμματα, σε διαφορετικές συσκευές) θα δημιουργήσει την ίδια ακολουθία.

Πρέπει επίσης να γνωρίζετε την κατανομή πιθανοτήτων που χαρακτηρίζει το PRNG - ποιους αριθμούς θα δημιουργήσει και με ποια πιθανότητα. Τις περισσότερες φορές, πρόκειται είτε για κανονική κατανομή είτε για ομοιόμορφη κατανομή.
BlessRNG ή έλεγχος του RNG για δικαιοσύνη
Κανονική κατανομή (αριστερά) και ομοιόμορφη κατανομή (δεξιά)

Ας υποθέσουμε ότι έχουμε ένα ζάρι με 24 πλευρές. Αν το ρίξουμε, η πιθανότητα να πετύχουμε 1 είναι 24/XNUMX (όπως και η πιθανότητα να πετύχουμε οποιοσδήποτε άλλος αριθμός). Αν κάνουμε πολλές ρίψεις και καταγράψουμε τα αποτελέσματα, θα παρατηρήσουμε ότι όλες οι πλευρές εμφανίζονται περίπου με την ίδια συχνότητα. Στην ουσία, αυτό το ζάρι μπορεί να θεωρηθεί ένα ομοιόμορφα κατανεμημένο RNG.

Και αν ρίξετε 10 τέτοια ζάρια ταυτόχρονα και υπολογίσετε το συνολικό σκορ; Θα παραμείνει ομοιόμορφο; Όχι. Τις περισσότερες φορές, το σκορ θα είναι κοντά στους 125 πόντους, δηλαδή, σε κάποια μέση τιμή. Και ως αποτέλεσμα, ακόμη και πριν από τη ρίψη, μπορείτε να εκτιμήσετε περίπου το μελλοντικό αποτέλεσμα.

Ο λόγος είναι ότι υπάρχει ο μεγαλύτερος αριθμός συνδυασμών για να ληφθεί η μέση βαθμολογία. Όσο πιο μακριά από αυτό, τόσο λιγότεροι συνδυασμοί - και, κατά συνέπεια, τόσο χαμηλότερη είναι η πιθανότητα να πέσουν έξω. Εάν αυτά τα δεδομένα οπτικοποιηθούν, θα μοιάζουν αμυδρά με το σχήμα μιας καμπάνας. Επομένως, με κάποια έκταση, το σύστημα των 10 ζαριών μπορεί να ονομαστεί RNG με κανονική κατανομή.

Ένα άλλο παράδειγμα, αλλά ήδη σε αεροπλάνο - σκοποβολή σε στόχο. Ο σκοπευτής θα είναι ένα RNG, που θα παράγει ένα ζεύγος αριθμών (x, y), το οποίο εμφανίζεται στο γράφημα.
BlessRNG ή έλεγχος του RNG για δικαιοσύνη
Συμφωνώ ότι η αριστερή παραλλαγή είναι πιο κοντά στην πραγματικότητα - είναι ένα RNG με κανονική κατανομή. Αλλά αν χρειάζεται να διασκορπίσετε αστέρια σε έναν σκοτεινό ουρανό, τότε η σωστή παραλλαγή, που λαμβάνεται χρησιμοποιώντας ένα RNG με ομοιόμορφη κατανομή, είναι πιο κατάλληλη. Γενικά, επιλέξτε μια γεννήτρια ανάλογα με την εργασία που έχετε να κάνετε.

Ας μιλήσουμε τώρα για την εντροπία μιας ακολουθίας τυχαίων αριθμών. Για παράδειγμα, υπάρχει μια ακολουθία που ξεκινά ως εξής:

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() Παράγει αριθμούς απλής ακρίβειας (float) σε ένα δεδομένο εύρος τιμών.
  4. Το UnityEngine.Random.value δημιουργεί αριθμούς απλής ακρίβειας (float) στην περιοχή [0; 1).
  5. Unity.Mathematics.Random.NextFloat(). Μέρος της νέας βιβλιοθήκης Unity.Mathematics. Δημιουργεί κινητήρες μονής ακρίβειας σε ένα δεδομένο εύρος τιμών.

Σχεδόν παντού στην τεκμηρίωση, καθορίστηκε μια ομοιόμορφη κατανομή, με εξαίρεση την 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, Λειτουργία επεξεργασίας, Mono, .NET Standard 2.0
  • Android
    — Unity v2018.3.14f1, έκδοση συσκευής, Mono, .NET Standard 2.0
  • iOS
    — Unity v2018.3.14f1, δομημένο σε συσκευή, il2cpp, .NET Standard 2.0

Реализация

Έχουμε αρκετούς διαφορετικούς τρόπους για να δημιουργήσουμε τυχαίους αριθμούς. Για καθέναν από αυτούς, θα γράψουμε μια ξεχωριστή κλάση περιτυλίγματος που θα πρέπει να παρέχει:

  1. Δυνατότητα ορισμού εύρους τιμών [ελάχ./μέγ.]. Θα οριστεί μέσω του κατασκευαστή.
  2. Μέθοδος που επιστρέφει το SC. Θα επιλέξουμε τον τύπο float, καθώς είναι πιο γενικός.
  3. Το όνομα της μεθόδου δημιουργίας για την επισήμανση των αποτελεσμάτων. Για ευκολία, θα επιστρέψουμε το πλήρες όνομα της κλάσης + το όνομα της μεθόδου που χρησιμοποιήθηκε για τη δημιουργία του SC ως τιμή.

Αρχικά, δηλώνουμε μια αφαίρεση που θα αναπαρασταθεί από τη διεπαφή IRandomGenerator:

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

        float Generate();
    }
}

Υλοποίηση του System.Random.Next()

Αυτή η μέθοδος σάς επιτρέπει να ορίσετε ένα εύρος τιμών, αλλά επιστρέφει ακέραιους αριθμούς και απαιτούνται floats. Μπορείτε απλώς να ερμηνεύσετε τον integer ως float ή μπορείτε να επεκτείνετε το εύρος τιμών κατά αρκετές τάξεις μεγέθους, αντισταθμίζοντάς τες σε κάθε γενιά του SC. Θα λάβετε κάτι σαν ένα σταθερό σημείο με μια καθορισμένη τάξη ακρίβειας. Θα χρησιμοποιήσουμε αυτήν την επιλογή, καθώς είναι πιο κοντά στην πραγματική τιμή float.

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 σάς επιτρέπει να καθορίσετε ένα εύρος τιμών και επιστρέφει έναν τυχαίο αριθμό τύπου float. Δεν απαιτούνται πρόσθετες μετατροπές.

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

Η ιδιότητα value της στατικής κλάσης UnityEngine.Random επιστρέφει έναν τυχαίο αριθμό τύπου float από ένα σταθερό εύρος τιμών [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()

Η μέθοδος NextFloat() της κλάσης Unity.Mathematics.Random επιστρέφει έναν τυχαίο αριθμό τύπου float και σας επιτρέπει να καθορίσετε ένα εύρος τιμών. Η μόνη προειδοποίηση είναι ότι κάθε στιγμιότυπο της Unity.Mathematics.Random θα πρέπει να αρχικοποιηθεί με κάποιο seed — με αυτόν τον τρόπο αποφεύγουμε τη δημιουργία επαναλαμβανόμενων ακολουθιών.

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 που θα εκτελέσει όλη την απαραίτητη εργασία και ταυτόχρονα θα είναι υπεύθυνο για την αλληλεπίδραση με το περιβάλλον χρήστη.

Θα ορίσουμε το μέγεθος του συνόλου δεδομένων και το εύρος των τιμών SC, και επίσης θα αποκτήσουμε μια μέθοδο που επιστρέφει έναν πίνακα από διαμορφωμένες και έτοιμες προς χρήση γεννήτριες.

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

Αγοράστε αξιόπιστη φιλοξενία για ιστότοπους με προστασία DDoS, διακομιστές VPS VDS 🔥 Αγοράστε αξιόπιστη φιλοξενία ιστοσελίδων με προστασία DDoS, διακομιστές VPS VDS | ProHoster