It's in the title.
What I have is the classic randomize()/randi() % some_number combo, which works fine with the tiny modulators I'm using for now.
HOWEVER
I tried bigger numbers and I don't need a high school diploma to see it's biased to smaller numbers, getting worse with bigger modulators.
Hopefully this post is ripe and bearing delicious, nutritious fruits of knowledge by the time it becomes a problem.

DaveTheCoder I start noticing around 15. Unless it's some weird luck on my end, every single test of 50 randi()s I've done has returned 0-4 most often. That's 1/3 chance for each generated number as far as I know.

I just ran this test (Godot 3.5.1). The numbers are distributed relatively evenly.

	# Compute 1000000 random integers in the range 0..99 and count the number of
	# ocurrences of each value.
	var counts: Array = []
	counts.resize(100)
	counts.fill(0)
	randomize()
	for _i in range(1000000):
		counts[randi() % 100] += 1
	print_debug("counts=", counts)

    DaveTheCoder Was the rng thingy changed since Godot 3.4.x? That was the last time I tested it more extensively. I only updated really recently. I'm not good about remembering to update my system.

    DaveTheCoder I've now run the code and you're definitely right. That's pretty wacky.
    Now how about weighted random number generation? You have any advice for that? That's something I could never get my head around.

    DaveTheCoder Traditional RPG loot is a pretty classic example. I'm talking the idea of using numbers to make some things show up more often than others.

    Here's one method:

    	match randi() % 10:
    		0,1,2,3: print("A selected")
    		4,5,6: print("B selected")
    		7,8: print("C selected")
    		9: print("D selected")

    A has a 40% probability of being selected, B 30%, C 20% and D 10%.

    (Pseudo-)random number generation (prng) is a wide field.

    In Godot, the prng is 'seeded' (initialized) once with "randomize()", usually when the program starts. The seed determines the sequence of numbers, meaning that when initialized with the same seed, subsequence calls to the (integer part of the) generator will produce the same sequence of numbers. These are evenly distributed over many calls (1000s). When only using few calls (like 10s or even 100s) there may be a local "unevenness" in the sequence of numbers. Should that happen, try a different seed. Just have in mind that the generated sequence based on a seed is reproducible.

    The rng-class cybereality mentioned offers, besides the equal distribution of generated numbers, a normal distribution through the "rand_fn()" method, in case you don't want to use float version of the rand_range(), or the module operator, and you need a float number.

    Note that you can replicate the behaviour of a normal distribution with multiple rolls of a random number and adding them up. In terms of good old table top role playing games, rolling 3 dice

    (randi() % 6) + 1

    or

    rand_range(1,6)

    the integer version of the latter! 3 times and summing up will produce numbers between 3 and 18 with higher probabilities for 9,10,11,12 and lower for the ends of the range. That was called 3D6 and there are a lot of other combinations, that's why there are so many dice. An even distribution for instance with a 20sided die (D20) can be used to determine a hit or a miss, a normal distribution like 3D4 or some such for the amount of damage dealt. Just additionally to @DaveTheCoder 's method (a D10 in that case). You can of course vary the ranges and the number of faces of a die.

    For a game that rolls dice you will want to use the prng methods that use integers. For a game where you let's say pick random directions, angles, ... you will want to use the float-methods.

    You can play with it if you need other distributions (or look them up somewhere), but even and normal distributions should be sufficient for most cases.

    The only thing that's changed between 3.2.3 and 3.5.1 (just picked 3.2.3 at random, no pun intended) is the randomize function used to use the microseconds since program start, but now uses that and system time combined. Otherwise the actual random function code is unchanged: it's PCG32.

    Assuming a random number generator is evenly distributed (PCG32 is supposed to be pretty good, so I'll assume it is), the modulo method of getting a smaller range will only be evenly distributed if it's a factor of the total range. Otherwise lower numbers will have a slight higher chance.
    For example, let's say randi() returned a number from 0-11 and we want to get a random from 0-9.
    0 % 10 = 0
    1 % 10 = 1
    2 % 10 = 2
    3 % 10 = 3
    4 % 10 = 4
    5 % 10 = 5
    6 % 10 = 6
    7 % 10 = 7
    8 % 10 = 8
    9 % 10 = 9
    10 % 10 = 0
    11 % 10 = 1
    Because it didn't neatly fit, we have a couple of values that got repeated. So getting a 0 or a 1 each have a chance of 2/12, while other values have 1/12 chance.

    (randi() actually has a range of 0-4,294,967,295)

    If I do Dave's code with a modulus of 10, the counts are:
    99529, 99580, 99840, 99838, 100370, 100154, 99828, 100328, 100489, 100044
    That's pretty even.
    But if I modify it to force randi() to be a 0-11 range (I rejected any value over 11 from the count) we get this:
    31328, 31192, 15538, 15655, 15431, 15581, 15548, 15731, 15565, 15747
    There it's clear that results of 0 or 1 are far more likely.
    The reason it's not visible in the first one is that the bigger the range of values, the less the effect is.
    I made a test in C++, the actual distribution of %10 values from a range of 0-4,294,967,295 is (this isn't using a random, just how the modulus is distributed):
    Values 0 - 5 occur 429,496,730 times.
    Values 6 - 9 occur 429,496,729 times.
    It's still slightly weighted towards the low half, but only by a microscopic amount. Plus the RNG might not have perfect distribution anyway.

    So if you aren't doing something critical like poker machines, the difference shouldn't really matter in general game dev. 🙂

    One case where it's going to be more noticeable is the old C/C++ rand() function. It returns a range of only 0-32767. Plus it just sucks as an algorithm overall.

    Sorry, been away from teaching for half a year, got to keep in shape. 🙂

      So in Godot randomize() is not the right method for providing a seed for reproducibility, but seed(x) is.

      C++ (since 11), besides the C library, also offers various engines/generators and options to combine them with various distributions. So there's C++ typical almost everything for any case. That's ofc too much for a game engine.

      But I'm currently getting away from C++ and towards Rust ...

      Values 0 - 5 occur 429,496,730 times.
      Values 6 - 9 occur 429,496,729 times.

      Looks like one of those had to be first ;-)

      Woops. I totally blacked out in my three day caffeine binger and forgot this thread existed.

      DaveTheCoder Thanks for this block. I never used a technique like this but I've modified it maybe 50 times now developing the next step in a project I'm working on. It's insanely helpful helping me decide if the output of a massive system of random numbers makes sense.

      Kojack This too is helpful.

      In fact everything here said is helpful. Thanks for being smarter than me, it leaves me so much more time for the art side of this. You all get Best Answers.

      4 months later