Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Bug Random.Value with Percentage is Bugged

Discussion in 'Scripting' started by TomTheMan59, Feb 10, 2022.

  1. TomTheMan59

    TomTheMan59

    Joined:
    Mar 8, 2021
    Posts:
    355
    I googles this a lot and it seems I have to be doing something wrong as I am doing what everyone else says.

    I want to spawn something with only 0.05% chance. However, I just played the game it it spawned twice in a row. Unless I am crazy, that seems insane and something isn't working.

    Code (CSharp):
    1.  if(Random.value < 0.05f)
    This should work, correct?
     
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,922
    Well, first of all
    if(Random.value < 0.05f)
    is not
    0.05%
    but
    5%
    . Second it's supposed to be random, so why do you think its crazy if it happens two times in a row? It seems you fall for the gambler's fallacy?
     
    _geo__ likes this.
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,612
    Have you tried testing it more that twice to make sure it wasn't just a fluke?

    How about Debug.Log-ing the value output by it (cache it first) to see what you're getting.

    And yeah, as noted, your maths is off.
     
  4. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    You don’t use unity engines random.range?

    If (Random.Range(0.0f,100.0f) < 0.05f)
    // Unity Engine Results 0.05%

    you can int it aswell for the whole chances. Say 1% 5% if the chance is between 1,100 if it’s less than your chance then from my experience operates just fine
     
  5. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,298
    A numberphile video mentioned in the forums. Love it :D

    @TomTheMan59 : Maybe what you want is more of pseudo random system. Imagine like a bowl full of marbles, 95 black ones and 5 white ones. Now you draw from them at random until it is empty and then you refill and restart picking. It still would not prevent you from getting two in a row but you would be guaranteed that this can only happen up to 5 times for the white marbles within once cycle (10 times globally).

    Here is a class I use for such instances. It is serializable so you can keep the "bowl of marbles" across multiple game sessions (beware of cheaters ;-).

    Usage:
    Code (CSharp):
    1.  
    2. var myPool = new PseudoRandomPool(new int[] { 9, 1 });
    3. // GetNext() will return 1 in one out of ten cases (10%).
    4. // In the back it uses an array looking like this: { 1,0,0,0,0,0,0,0,0,0 }.
    5. // It shuffles it once. If a value is picked it will be removed. Refilled if empty.
    6. var result = myPool.GetNext();
    7.  
    8. // 5% chance would be:
    9. var myPool = new PseudoRandomPool(new int[] { 95, 5 });
    10. var result = myPool.GetNext();
    11.  
    12. // 0.05% chance would be:
    13. var myPool = new PseudoRandomPool(new int[] { 9995, 5 });
    14. var result = myPool.GetNext();
    15.  
    Code:
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Linq;
    4.  
    5. namespace Kamgam.Helpers
    6. {
    7.     /// <summary>
    8.     /// Serializable pool of numbers. <br />
    9.     /// NOTICE: The serialized data may get very BIG for big ratios as for each possibility one Integer is stored.<br />
    10.     /// TODO: (n2h) change serialization from serializing the whole list into custom compressed serialization (RLE or similar).</ br>
    11.     /// </ br>
    12.     /// Usage:<br />
    13.     ///  We want a pool of seven 10s and one 3. Equals to 87.5% chance to get a 10 and 12.5% to get a 3.<br />
    14.     ///  var pool = new PseudoRandomPool(new int[] { 7, 1 }, new int[] { 10, 3 });<br />
    15.     ///
    16.     ///  If no values are given then the index of the ratios is used (starting with 0). E.G.: 95% chance for a 0 and 5% chance for a 1.
    17.     ///  var pool = new PseudoRandomPool(new int[] { 95, 5 });<br />
    18.     /// </summary>
    19.     [System.Serializable]
    20.     public class PseudoRandomPool
    21.     {
    22.         [SerializeField]
    23.         protected int[] pool;
    24.  
    25.         [System.NonSerialized]
    26.         protected int[] poolRatios;
    27.  
    28.         [System.NonSerialized]
    29.         protected int[] poolValues;
    30.  
    31.         public PseudoRandomPool()
    32.         {
    33.         }
    34.  
    35.         public PseudoRandomPool( int[] poolRatios, int[] poolOptions = null )
    36.         {
    37.             SetRatiosAndValues(poolRatios, poolOptions);
    38.         }
    39.  
    40.         /// <summary>
    41.         /// Sets the used ratios and options.<br />
    42.         /// NOTICE: The serialized data may get very BIG for big ratios as for each possibility one Integer is stored.<br />
    43.         /// Example: <br />
    44.         ///  We want a pool of seven 10s and one 3. Equals to 87.5% chance to get a 10 and 12.5% to get a 3.<br />
    45.         ///  pool.SetRatiosAndValues(new int[] { 7, 1 }, new int[] { 10, 3 });<br />
    46.         ///  <br />
    47.         ///  If no values are given then the index of the ratios is used (starting with 0). E.G.: 95% chance for a 0 and 5% chance for a 1.<br />
    48.         ///  pool.SetRatiosAndValues(new int[] { 95, 5 });<br />
    49.         /// </summary>
    50.         /// <param name="poolRatios"></param>
    51.         /// <param name="poolValues">The values are matched to the ratios by index (order in array).<br />If no values are provided then the index of the ratio will be returned as value, e.g.: 0, 1,2,3 ... poolRatios.Length - 1.</param>
    52.         public void SetRatiosAndValues(int[] poolRatios, int[] poolValues = null)
    53.         {
    54.             this.poolRatios = poolRatios;
    55.             this.poolValues = poolValues;
    56.  
    57.             RefillPoolIfNecessary();
    58.         }
    59.  
    60.         public int GetPoolSize()
    61.         {
    62.             if(this.pool != null)
    63.             {
    64.                 return this.pool.Length;
    65.             }
    66.  
    67.             return 0;
    68.         }
    69.  
    70.         /// <summary>
    71.         /// Refills the pool only if necessary.
    72.         /// </summary>
    73.         public void RefillPoolIfNecessary()
    74.         {
    75.             if (pool == null || pool.Length == 0)
    76.             {
    77.                 RefillPool();
    78.             }
    79.         }
    80.  
    81.         /// <summary>
    82.         /// Dumps the old pool data and fills it anew.
    83.         /// </summary>
    84.         public void RefillPool()
    85.         {
    86.             if (poolRatios != null)
    87.             {
    88.                 pool = new int[poolRatios.Sum()];
    89.  
    90.                 int index = 0;
    91.                 for (int i = 0; i < poolRatios.Length; ++i)
    92.                 {
    93.                     for (int r = 0; r < poolRatios[i]; ++r)
    94.                     {
    95.                         if (poolValues != null)
    96.                         {
    97.                             pool[index] = poolValues[i];
    98.                         }
    99.                         else
    100.                         {
    101.                             pool[index] = i;
    102.                         }
    103.                         index++;
    104.                     }
    105.                 }
    106.  
    107.                 Shuffle();
    108.             }
    109.             else
    110.             {
    111. #if UNITY_EDITOR
    112.                 if (UnityEditor.EditorApplication.isPlaying)
    113.                 {
    114. #endif
    115.                     Debug.LogWarning("Called refresh on a pool without ratios. Will do nothing!");
    116. #if UNITY_EDITOR
    117.                 }
    118. #endif
    119.             }
    120.         }
    121.  
    122.         public void Shuffle()
    123.         {
    124.             int n = pool.Length;
    125.             while (n > 1)
    126.             {
    127.                 int k = UnityEngine.Random.Range(0, n);
    128.                 n--;
    129.                 var value = pool[k];
    130.                 pool[k] = pool[n];
    131.                 pool[n] = value;
    132.             }
    133.         }
    134.  
    135.         /// <summary>
    136.         /// Returns the next value in the pool. Refills the pool if necessary.
    137.         /// </summary>
    138.         /// <returns></returns>
    139.         public int GetNext()
    140.         {
    141.             return GetNext(1, int.MinValue, null);
    142.         }
    143.  
    144.         /// <summary>
    145.         /// Checks if the next values is one of the allowed values.
    146.         /// If it is not then it shuffles the pool and tries "attempts" times.
    147.         /// If it still does not find one randomly then it will search
    148.         /// for it and return the first valid value (if one exists).
    149.         ///
    150.         /// If no valid value could be retrieved then failValue is returned.
    151.         /// </summary>
    152.         /// <param name="attempts"></param>
    153.         /// <param name="allowedValues">If it is null or empty then it assumes that all values are allowed.</param>
    154.         /// <returns></returns>
    155.         public int GetNext(int attempts, int failValue, params int[] allowedValues)
    156.         {
    157.             RefillPoolIfNecessary();
    158.  
    159.             while (attempts > 0)
    160.             {
    161.                 attempts--;
    162.                 if (pool != null && pool.Length > 0)
    163.                 {
    164.                     int nextValue = pool[pool.Length - 1];
    165.                     if(allowedValues == null || allowedValues.Length == 0 || allowedValues.Contains(nextValue))
    166.                     {
    167.                         Array.Resize(ref pool, pool.Length - 1);
    168.                         return nextValue;
    169.                     }
    170.                     else
    171.                     {
    172.                         if(attempts == 1)
    173.                         {
    174.                             // skrew randomness, let's search
    175.                             for (int i = 0; i < pool.Length; i++)
    176.                             {
    177.                                 if (allowedValues == null || allowedValues.Length == 0 || allowedValues.Contains(pool[i]))
    178.                                 {
    179.                                     nextValue = pool[i];
    180.                                     int[] newPool = new int[pool.Length - 1];
    181.                                     Array.Copy(pool, 0, newPool, 0, i);
    182.                                     if (pool.Length - i - 1 > 0)
    183.                                     {
    184.                                         Array.Copy(pool, i + 1, newPool, i, pool.Length - i - 1);
    185.                                     }
    186.                                     pool = newPool;
    187.                                     return nextValue;
    188.                                 }
    189.                             }
    190.                         }
    191.                         else
    192.                         {
    193.                             Shuffle();
    194.                         }
    195.                     }
    196.                 }
    197.             }
    198.  
    199.             Debug.LogWarning("PseudoRandomPool.GetNext() could not find a fitting value.");
    200.             return failValue;
    201.         }
    202.  
    203.         /// <summary>
    204.         /// A very bad "checksum" immplementation.
    205.         /// </summary>
    206.         /// <returns></returns>
    207.         public int Sum()
    208.         {
    209.             if (pool == null)
    210.                 return 0;
    211.             return pool.Sum();
    212.         }
    213.  
    214.         /// <summary>
    215.         /// A helper to convert enums to integer values. Use this to fill the values array.<br />
    216.         /// Example:<br />
    217.         ///  PseudoRandomPool.EnumToInts<YourEnumType>();
    218.         /// </summary>
    219.         /// <param name="type"></param>
    220.         /// <returns>An array of integers (the enum values).</returns>
    221.         public static int[] EnumToInts<T>() where T : Enum
    222.         {
    223.             var type = typeof(T);
    224.             var array = Enum.GetValues(type);
    225.             int[] enumValues = new int[array.Length];
    226.             for (int i = 0; i < array.Length; i++)
    227.             {
    228.                 enumValues[i] = (int) array.GetValue(i);
    229.             }
    230.             return enumValues;
    231.         }
    232.     }
    233. }
    234.  

    I find that quite often pure random numbers are the opposite of what I really want.
     
    Last edited: Feb 12, 2022
    exiguous likes this.
  6. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    You could always do a random runner


    RandomChance as int
    += 1 per frame

    if application frame rate > 100 fps and is consistent you can iterate your random chance in the same way as Doom or Duke Nukem 3D.

    Of course it wouldn’t be random if it resulted every frame. But if it was a function called when an AI does something for example( fires a projectile. Then it can be random enough for purpose

    in case you want half of 1% just do it with floats += 0.1f
     
  7. TomTheMan59

    TomTheMan59

    Joined:
    Mar 8, 2021
    Posts:
    355
    Wow! Thanks everyone for posting great insights, videos, and code! I really appreciate all the responses and am able to see what is wrong.

    @Bunny83 Thanks for the video! I just kind of thought, how is it possible something spawned twice in a row with such a low percentage.

    Also yes, it is 5% not 0.05%. :confused::(

    @_geo__ @AnimalMan Thanks for the code and different methods!
     
  8. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    Random.value is a shortcut for Random.Range(0.0f, 1.0f);. It's commonly used for percentages: Random.value<0.25f for 25%, and so on. You can even write it as 0.30f (for 30%) to make it easier to read.

    Once you get used to it, expressing percents in a 0-1 range is more natural. If you have an 80% chance of getting 6 gold, that's an average of 0.8*6 gold. Or 83% bonus crit damage means the crit multiplier is (1+0.83). If fog reduces range to 75% the real range is maxRange*0.75f. So in your game you've got float critBonus=1.83f, rangePctChange=0.75f;. Recognizing 0.05% is 0.0005f even feels natural after using this style for a bit -- you automatically see the first 2 places as the 0-100 percent and write in "0.00" to start.
     
    Bunny83 and TomTheMan59 like this.
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,922
    Right, when doing statistics and probability this is usually the norm anyways ^^.

    :) I've already linked quite a few over the years. I think the one I linked the most is the Quaternion video (by Dr. James Grime) I think it's one of the most concise. It leaves out quite a bit of details but just enough to go from zero up to a point where you can at least start to understand how they work internally and how the maths behind them work on a numeric level.

    Anyways we went a bit off topic ^^. I think it's important to note that computers can not really generate true random numbers. Some modern PCs may have a piece of hardware that is able to generate true random numbers (or at least unpredictable enough for cryptography), though for the most part computers can only generate sequences of numbers which appear random but actually are not. Those are called pseudo random numbers. Unity uses an implementation of the xorshift128 generator which is a quite fast and has a decent "quality" and a long period.
     
    _geo__ likes this.
  10. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,298
    Bunny83 likes this.