How to generate random number between 0 & 1000 that on average is 1

Here is my current code:

function module.logUniform(min, max, skewPower)
	min = min or 1/1000
	max = max or 1000
	skewPower = skewPower or 12

	local logMin = math.log(min)
	local logMax = math.log(max)

	-- Skew the distribution: higher exponent = stronger skew toward low values
	local rand = math.random() ^ skewPower

	local value = math.exp(logMin + (logMax - logMin) * rand)

	-- Scale down to make expected value close to 1
	local scale = 1 / (2 * (logMax - logMin) / (skewPower + 1)) -- adjust scale for skew
	return value * scale
end

I want to make a RNG system for my game that generates a random number between 2 numbers that will always average out to 1. This is what I have right now, but I’m not sure it average out to 1 and the results are too scattered. I want the results to be way closer to 1.

1 Like

I recommend doing it like this
generate a number from 1-1000 then if it is around like 1-500 make another one that is like 0-2
yw

I’m kind of confused on what your issue is. I tested the code and it seems to be printing random numbers

They want to make a number that always averages out to one.

So for example what they don’t want is it generating
1, 10, 86 because that averages to 32.222, not one

What they do want (I think) is this
12.5, −7.8, -1.7

12.5+(-7.8)+(-1.7) =3 and 3/3=1

@UW4Z I have an idea. Generate 5 numbers with math.random inside a function maybee (-20,20) and then check if the sum divided by 5 is one using an if statment.

If it is, it prints. If it isn’t, it rerolls until it gets those numbers that average

JUST BY THE WAY, what do you wanna use this for, it sounds interesting and cool

I’ll make some code soon [THIRTYYYYY CHARACTERZZ]

If I understand what you want correctly, then this should help:

local function BiasedRNG()
    local RNG = Random.new(Seed)
    return math.floor(RNG:NextInteger(0, 1000000)^0.5) --will give 0-1000 range, but values will be closer to 1.
end

In case of you wanting to make it to be avarage of all selections it will be 1, like this:
20+0+0+...+0 (19 times +0) = 20/20 = 1
You need to build much more complex system.

1 Like

Doesn’t necessarily need to be divided. You can just check whether the sum IS 5 or not as the only number divided by 5 to equal 1 is 5.

Although from my understanding of OP’s post, this isn’t the intended behaviour. They are attempting to generate random numbers that are generally closer to 1, i.e in a test of 100 random numbers, 75 of them will be around the range of 1.

To test it, take the average of 100 random numbers multiple times. If the averages are close to 1, then your bias function is likely working as intended.

From my tests, your averages are more biased towards 3 rather than 1.

Ok we finna use a clamped f distribution and then scale it to get our desired range and mean (average)

local rng = Random.new()

local function generateF(min, max, mean)
    -- mean gotta be greater than 1
    if mean <= 1 then
        return warn('the mean gotta be >1')
    end
    
    -- Calculate d2 from the mean: mean = d2 / (d2 - 2)
    local d2 = (2 * mean) / (mean - 1)
    local d1 = 5  -- Fixed d1
    
    -- Generate 2 chi-squared random variables
    local function generate_chi_squared(df)
        local sum = 0
        for _ = 1, df do
            local u = rng:NextNumber(0, 1)
            local v = rng:NextNumber(0, 1)
            local normal = math.sqrt(-2 * math.log(u)) * math.cos(2 * math.pi * v)
            sum = sum + normal * normal
        end
        return sum
    end
    
    -- Generate F-distributed random variable: F = (X1/d1) / (X2/d2)
    local X1 = generate_chi_squared(d1)
    local X2 = generate_chi_squared(d2)
    local F_val = (X1 / d1) / (X2 / d2)
    
    -- Scale F_val to achieve the desired mean
    local theoretical_mean = d2 / (d2 - 2)
    local scaled_F = F_val * (mean / theoretical_mean)
    
    -- Truncate to [min, max]
    if scaled_F < min then
        return min
    elseif scaled_F > max then
        return max
    else
        return scaled_F
    end
end

:happy1:
Change the average to something close to and greater than 1 because the function cannot take it

Also, your distribution is way too extreme. There is almost no chance of getting anywhere near 50 let alone 1000 with an average of 1. Here are the results for 100000 generations with degree of freedom 1 set to 1 (when d1 approaches 1, results become more extreme):

average: 1.0116090184458038
highest: 19.334914530933823
lowest: 6.946124304037747e-13

Either change the mean or the maximum if you want any chance at getting close to the maximum

1 Like

Try this:

function module.logUniformNormalized(min, max, skewPower)
    min = min or 1/1000
    max = max or 1000
    skewPower = skewPower or 2  -- much lower skew power

    local logMin = math.log(min)
    local logMax = math.log(max)

    local rand = math.random() ^ skewPower
    local value = math.exp(logMin + (logMax - logMin) * rand)

    -- Compute expected value of this distribution for scaling
    -- Approximate: E[X] = (max^(1 - a) - min^(1 - a)) / ((1 - a) * (log(max) - log(min)))
    -- where a = skewPower

    local a = skewPower
    local expected

    if a == 1 then
        -- Avoid divide by zero: E[X] = (log(max) - log(min)) / (log(max/min))
        expected = (logMax - logMin) / math.log(max / min)
    else
        expected = (max^(1 - a) - min^(1 - a)) / ((1 - a) * (logMax - logMin))
    end

    return value / expected
end

When I tested this with the default values I got this:

 15:42:15.879  Lowest: 0.000013815544564754405  -  Server - Script:34
  15:42:15.879  Highest: 13.110942666013731  -  Server - Script:35
  15:42:15.879  Median: 0.00041721335298058686  -  Server - Script:36
  15:42:15.879  Average: 0.4045008960348116  -  Server - Script:37

I’m going to continue looking for a way to solve this.

Instead of creating an entirely new function, I did some more search and discovered instead of modifying the formula, I can use specific number pairs to generate responses that average out to a certain number.
Some of these pairs are:

.5 and .2
1 and 10,000
.1 and 1,000
.01 and 100
& etcetera...

With that logic in mind I wrote this code.

function randomPair(min, max, target)
	local rand = math.random() * (max - min) + min
	local anchor = 2 * target - rand
	
	return rand, anchor
end

print("target 1 (min)", randomPair(1, 1000, 1))
print("target 500 (avg)", randomPair(1, 1000, 500))
print("target 1000 (max)", randomPair(1, 1000, 1000))

It seems the further the target is from the mean (1/2 range since math.random is uniform) the more likely the pair is to be out of bounds. You can see this by running the code for yourself, typically one of the numbers is outside the range.
(Theoretically if we increase the number of terms in the mean above from a pair to a large tuple, we could take advantage of the growing denominator and allow the quotient to approach the target to some degree)

I’m confused when looking at your list of pairs because to me none of these average out to 1 (in the sense of (a+b)/2=1) unless you’re talking about a different kind of average?

With all this in mind I think modifying the math.random function into a skewed distribution is your best bet.

I was talking to batmansmells. He’s been spamming unuseful information among many posts.
Get pwned boi!

OH. Sorry, that came across wrong. I didn’t mean to say 'I’m reporting @UW4Z ’ I meant to say ‘@UW4Z I’m reporting batmansmells’

By average I meant the average of X results.

“Wait, so what does chocolate icecream taste like?”
“It tastes like chocolate.”

You asked if I was talking about a different kind of average. I must have misunderstood what you meant.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.