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 με κανονική κατανομή.

Άλλο παράδειγμα, μόνο αυτή τη φορά σε αεροπλάνο - βολή σε στόχο. Ο σκοπευτής θα είναι ένα RNG που δημιουργεί ένα ζεύγος αριθμών (x, y) που εμφανίζεται στο γράφημα.
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(). Δημιουργεί μεμονωμένους αριθμούς ακριβείας (floats) σε ένα δεδομένο εύρος τιμών.
  4. UnityEngine.Random.value. Δημιουργεί μεμονωμένους αριθμούς ακριβείας (floats) στην περιοχή από [0; 1).
  5. Unity.Mathematics.Random.NextFloat(). Μέρος της νέας βιβλιοθήκης Unity.Mathematics. Δημιουργεί μεμονωμένους αριθμούς ακριβείας (floats) σε ένα δεδομένο εύρος τιμών.

Σχεδόν παντού στην τεκμηρίωση καθοριζόταν μια ομοιόμορφη κατανομή, με εξαίρεση το UnityEngine.Random.value (όπου η κατανομή δεν προσδιορίστηκε, αλλά κατ' αναλογία με το UnityEngine.Random.Range() αναμενόταν επίσης ομοιόμορφη) και Unity.Mathematics.Random .NextFloat() (όπου στο The βάση είναι ο αλγόριθμος 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. Δυνατότητα ρύθμισης του εύρους τιμών [min/max). Θα ρυθμιστεί μέσω του κατασκευαστή.
  2. Μέθοδος επιστροφής MF. Ας επιλέξουμε ως τύπο το float, καθώς είναι γενικότερο.
  3. Το όνομα της μεθόδου δημιουργίας για την επισήμανση των αποτελεσμάτων. Για ευκολία, θα επιστρέψουμε ως τιμή το πλήρες όνομα της κλάσης + το όνομα της μεθόδου που χρησιμοποιήθηκε για τη δημιουργία του MF.

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

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

        float Generate();
    }
}

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

Αυτή η μέθοδος σάς επιτρέπει να ορίσετε ένα εύρος τιμών, αλλά επιστρέφει ακέραιους αριθμούς, αλλά χρειάζονται floats. Μπορείτε απλά να ερμηνεύσετε τον ακέραιο αριθμό ως float ή μπορείτε να επεκτείνετε το εύρος τιμών κατά πολλές τάξεις μεγέθους, αντισταθμίζοντάς τις με κάθε γενιά του μεσαίου εύρους. Το αποτέλεσμα θα είναι κάτι σαν ένα σταθερό σημείο με μια δεδομένη σειρά ακρίβειας. Θα χρησιμοποιήσουμε αυτήν την επιλογή αφού είναι πιο κοντά στην πραγματική τιμή 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

Η ιδιότητα τιμής της στατικής κλάσης 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 είναι έτοιμες. Στη συνέχεια, πρέπει να δημιουργήσετε ακολουθίες και να αποθηκεύσετε το σύνολο δεδομένων που προκύπτει για επεξεργασία. Για να γίνει αυτό, θα δημιουργήσουμε μια σκηνή και ένα μικρό σενάριο MainController στο Unity, το οποίο θα κάνει όλες τις απαραίτητες εργασίες και ταυτόχρονα θα είναι υπεύθυνο για την αλληλεπίδραση με το 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

Προσθέστε ένα σχόλιο