Lua 4.0 random number generator

2020-02-15 04:42发布

问题:

I don't have access to random() in Lua 4.0 (DON'T ASK), so I need to roll my own random number generator. Or rather I have to roll another one since the one I implemented several years ago is failing me now. I.e. I am noticing repeating values which is bad.

Any suggestions or examples written in Lua that I can use? FYI here's the one I've been using up until now:

seedobja = 1103515245
seedobjc = 12345
seedobjm = 4294967295 --0x100000000

function srandom(seedobj, fVal1, fVal2)
    seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
    local temp_rand = seedobj[1] / (seedobjm - 1)
    if (fVal2) then
        return floor(fVal1 + 0.5 + temp_rand * (fVal2 - fVal1))
    elseif (fVal1) then
        return floor(temp_rand * fVal1) + 1
    else
        return temp_rand
    end
end

[edit]

Later edit deleted.

回答1:

Here is another attempt (always Lua 5.1 code), using an adaptation from C of a subtractive generator by Knuth (not linear congruential then). According to Knuth it should work with FP arithmetic (even single precision).

local mod = math.fmod
local floor = math.floor
local abs = math.abs

local B =  4000000

-- rough adaptation of Knuth float generator
function srandom( seedobj, fVal1, fVal2 )
    local ma = seedobj.ma
    local seed = seedobj.seed
    local mj, mk
    if seed < 0 or not ma then
        ma = {}
        seedobj.ma = ma
        mj = abs( 1618033 - abs( seed ) )
        mj = mod( mj, B )
        ma[55] = mj
        mk = 1
        for i = 1, 54 do
            local ii = mod( 21 * i,  55 )
            ma[ii] = mk
            mk = mj - mk
            if mk < 0 then mk = mk + B end
            mj = ma[ii]
        end
        for k = 1, 4 do
            for i = 1, 55 do
                ma[i] = ma[i] - ma[ 1 + mod( i + 30,  55) ]
                if ma[i] < 0 then ma[i] = ma[i] + B end
            end
        end
        seedobj.inext = 0
        seedobj.inextp = 31
        seedobj.seed = 1
    end -- if
    local inext = seedobj.inext
    local inextp = seedobj.inextp
    inext = inext + 1
    if inext == 56 then inext = 1 end
    seedobj.inext = inext
    inextp = inextp + 1
    if inextp == 56 then inextp = 1 end
    seedobj.inextp = inextp
    mj = ma[ inext ] - ma[ inextp ]
    if mj < 0 then mj = mj + B end
    ma[ inext ] = mj
    local temp_rand = mj / B
    if fVal2 then
        return floor( fVal1 + 0.5 + temp_rand * ( fVal2 - fVal1 ) )
    elseif fVal1 then
        return floor( temp_rand * fVal1 ) + 1
    else
        return temp_rand
    end
end

-- test

-- Note: seedobj must be a table with a field named `seed`;
-- this field must be negative; after the first number has
-- been generated, the seedobj table will be populated with
-- additional state needed to generate numbers; changing its
-- `seed` field to a negative number will reinitialize the
-- generator and start a new pseudorandom sequence.
local seedobj = { seed = -232343 }
for i = 1, 100 do
    print( srandom( seedobj, 100, 1000 ) )
end


回答2:

I have not Lua 4.0 installed and never worked with it, thus the following code may need some tweaks.

This is something that works on Lua 5.1. It is a rough adaptation of an implementation of the Park and Miller generator (written in C, using 32 bit ints). I tried to come closer to the 4.0 syntax (that I guessed from your snippet). Test it and see if its period suits your needs. The original version has a period of about 2e9, but converting to float arithmetic may have broken something (these generators are delicate things).

local mod = math.fmod
local floor = math.floor

local B = 2^31
-- rough adaptation of Park-Miller generator
function srandom( seedobj, fVal1, fVal2 )
    local seed = seedobj[1]
    local k = mod( floor( seed / 127773 ), B )
    seed = mod( 16807 * ( seed - mod( k * 127773, B ) ), B )
    seed = seed - mod( 2836 * k, B )
    if seed < 0 then seed = mod( seed + B - 1, B ) end
    seedobj[1] = seed
    local temp_rand = seed / ( B - 1 )
    if fVal2 then
        return floor( fVal1 + 0.5 + temp_rand * ( fVal2 - fVal1 ) )
    elseif fVal1 then
        return floor( temp_rand * fVal1 ) + 1
    else
        return temp_rand
    end
end

-- test
local seedobj = { 2 }   -- first element is the seed and must not be 0
for i = 1, 100 do
    print( srandom( seedobj, 100, 1000 ) )
end


回答3:

I've found a solution here:

function mul16(a, b)
    local a_lo, b_lo = mod(a, 2^8), mod(b, 2^8)
    local a_hi, b_hi = a - a_lo, b - b_lo
    return mod(a_lo * b_lo + mod(a_lo * b_hi, 2^16) + mod(a_hi * b_lo, 2^16) + mod(a_hi * b_hi, 2^16), 2^16)
end

function lcg(s, r)
    local temp = {}
    function temp:random(a, b)
        local y = mod(mul16(self.a, self.x) + self.c, self.m)
        self.x = y
        if not a then
            return y / 65536
        elseif not b then
            if a == 0 then
                return y
            else
                return 1 + mod(y, a)
            end
        else
            return a + mod(y, b - a + 1)
        end
    end
    function temp:randomseed(s)
        if not s then
            s = seed()
        end
        self.x = mod(s, 2147483648)
    end
    -- 'Numerical Recipes' parameters
    temp.a = 26125
    temp.c = 62303
    temp.m = 65536
    temp:randomseed(s)
    return temp
end

local R = lcg(0974)
local rand_table = {}
for i = 1, 10000 do
    local new_value = R:random()
    rand_table[i] = new_value
    if (i > 1) then
        for j = 1, i - 1 do
            local old_value = rand_table[j]
            if (new_value == old_value) then
                print("\ti = " .. i .. "\n\tj = " .. j .. "\n\tnew_value = " .. new_value .. "\n\told_value = " .. old_value)
                break
            end
        end
    end
end

I don't know about the statistical properties, but the function is not returning duplicate values even after many thousands of iterations. Thanks for everyone's help regardless!



标签: random lua prng