【问题标题】:Lua 4.0 random number generatorLua 4.0 随机数生成器
【发布时间】:2013-09-11 21:59:49
【问题描述】:

我无法访问 Lua 4.0 中的 random()(不要问),所以我需要推出自己的随机数生成器。或者更确切地说,我必须推出另一个一个,因为我几年前实施的那个现在让我失望了。 IE。我注意到重复值很糟糕。

我可以使用任何用 Lua 编写的建议或示例吗?仅供参考,这是我迄今为止一直在使用的:

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

[编辑]

后来的编辑被删除了。

【问题讨论】:

  • 如果您可以访问 os.clock() 这应该会为您提供合适的种子。
  • 所以我还是要问一下,为什么math.random 不可用?
  • 这是一款视频游戏,开发人员决定用一个每次游戏开始时获取新种子的函数替换/覆盖 random()。我需要一个脚本,每次运行脚本时,我都可以依靠它产生相同的结果和相同的种子。从时钟中获取种子对我没有帮助。
  • 有人用 Lua 重写了 Marsenne twister 脚本吗?
  • 如果你能找到一个用 Lua 编写的散列函数,你可以使用任何散列函数作为良好的伪随机数生成器。

标签: random lua prng


【解决方案1】:

这是另一个尝试(始终是 Lua 5.1 代码),它使用了 Knuth 对减法生成器的 C 的改编(当时不是线性同余的)。根据 Knuth 的说法,它应该适用于 FP 算术(甚至是单精度)。

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

【讨论】:

  • 这个似乎效果更好。这里和那里有一些重复的值,但它们是零星的。在 10000 次迭代之后,只有 12 个重复,而之前的尝试有数百或数千次。不过,我必须将所有其他脚本重写为 seedobj 的新格式。在我接受这个答案之前,我会做更多的测试。
  • @posfan12 抱歉,也许我不明白。您是否正在寻找在结束周期之前不重复 any 数字的生成器?如果是这种情况,您的要求可能有点太多了。诸如此类的伪随机生成器不会尝试重复数字,因为序列必须类似于实际随机序列(比如掷骰子的结果)。如果您在整个序列重复之前添加任何已经看到的数字都不能再次看到的附加约束,那么您正在扭曲序列的随机性并使其更可预测。
  • @posfan12 当然,您确实希望在某些应用程序中避免任何重复,并且有一些技术可以实现这一点,但是您确定需要它。避免重复并不会增加序列的随机性,如果这是你所追求的,那就恰恰相反。
  • 少量重复是可以的,只要它们不是连续的。我正在使用脚本绘制视频游戏对象的 3D 点,因此如果所有三个坐标重复并导致对象重叠,则可能会导致游戏出现问题。
  • LCG 功能会产生可见的伪影,所以我还是选择了这个。谢谢!
【解决方案2】:

我没有安装 Lua 4.0 并且从未使用过它,因此以下代码可能需要一些调整。

这适用于 Lua 5.1。它是对 Park 和 Miller 生成器(用 C 语言编写,使用 32 位整数)实现的粗略改编。我试图更接近 4.0 语法(我从你的 sn-p 中猜到了)。测试它,看看它的周期是否适合您的需要。原始版本的句号约为2e9,但转换为浮点运算可能会破坏某些东西(这些生成器是微妙的东西)。

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

【讨论】:

  • 我的代码也有同样的问题。数字开始重复。在这种情况下,它们开始重复大约 8500 次迭代,这是一种改进,但仍然很糟糕。在游戏的讨论板上,有人向我建议,其中包含的 Lua 版本被编译为使用单精度浮点而不是双精度。如果是这种情况,我不知道该怎么办。
  • @posfan12 如果 Lua 数字是单精度 FP,那么这很可能是 srandom 周期太短的原因,因为单精度浮点数只有 23 (IIRC) 位有效位,这也是很少有人在不进行舍入的情况下执行 32 位整数计算。有专门设计用于处理 16 位整数的算法,但我手头没有。调整其中之一可能会奏效。
  • @posfan12 要测试您的数字是否为单精度,请执行此行print(2^25)。双精度可以毫无问题地处理它并打印33554432。如果你得到不同的结果,你可能有单精度数字。
  • 我得到了同样的结果。
  • print(2^1000) 是否打印类似于 1.0715086071863e+301 的内容?
【解决方案3】:

我找到了解决方案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

我不知道统计属性,但即使经过数千次迭代,该函数也不会返回重复值。感谢大家的帮助!

【讨论】:

  • 该死。由 LCG 功能产生并在此线程中其他地方描述的“平面”非常明显。我不能使用这个功能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-03
  • 2013-12-07
  • 1970-01-01
  • 1970-01-01
  • 2015-03-14
  • 2022-01-26
相关资源
最近更新 更多