在Lua中,人们通常会使用math.random
&生成随机值和/或字符串。 math.randomseed
,其中os.time
用于math.randomseed
。
然而,这种方法有一个主要的缺点;返回的数字总是和当前时间一样随机, AND 每个随机数的间隔一秒,如果需要很多随机值,这个时间太长了很短的时间。
Lua用户wiki:http://lua-users.org/wiki/MathLibraryTutorial以及相应的RandomStringS receipe甚至指出了这个问题:http://lua-users.org/wiki/RandomStrings。
所以我坐下来写了一个不同的算法(如果它甚至可以被称为),它使用表的内存地址(错误)生成随机数:
math.randomseed(os.time())
function realrandom(maxlen)
local tbl = {}
local num = tonumber(string.sub(tostring(tbl), 8))
if maxlen ~= nil then
num = num % maxlen
end
return num
end
function string.random(length,pattern)
local length = length or 11
local pattern = pattern or '%a%d'
local rand = ""
local allchars = ""
for loop=0, 255 do
allchars = allchars .. string.char(loop)
end
local str=string.gsub(allchars, '[^'..pattern..']','')
while string.len(rand) ~= length do
local randidx = realrandom(string.len(str))
local randbyte = string.byte(str, randidx)
rand = rand .. string.char(randbyte)
end
return rand
end
起初,一切似乎都是完全随机的,我确信它们是......至少对于当前的程序而言。
所以我的问题是,realrandom
这些数字的随机性是多少?
或者是否有一种更好的方法可以在比一秒更短的时间间隔内生成随机数(这意味着不应该使用os.time
,如上所述),而不依赖于外部库,< em> AND ,如果可能的话,以完全跨平台的方式?
编辑:
关于RNG播种方式似乎存在重大误区;在生产代码中,对math.randomseed()
的调用仅发生一次,这只是一个选择不当的例子。
我的意思是随机值只是每秒一次,这个粘贴很容易证明:http://codepad.org/4cDsTpcD
由于这个问题无论我的编辑都会被忽视,我也取消了我以前接受的答案 - 希望有一个更好的答案,即使只是更好的意见。我理解有关随机值/数字的问题之前已经讨论了很多次,但我没有找到可能与Lua相关的问题 - 请记住这一点!
答案 0 :(得分:7)
每次调用随机时都不应该调用种子,在程序初始化时你应该只调用一次种子(除非你从某个地方获取种子,例如,复制)一些以前的“随机”行为)。
标准Lua随机生成器在统计意义上质量很差(事实上,它是标准C随机生成器),如果您关心它,请不要使用它。例如,使用lrandom
模块(在LuaRocks中可用)。
如果您需要更安全的随机数,请在Linux上从/dev/random
进行阅读。 (我认为Windows应该有相同的东西 - 但你可能需要用C编写代码才能使用它。)
依赖表指针值是一个坏主意。例如,考虑使用Java中的备用Lua实现 - 没有人知道它们会返回什么。 (此外,指针值可能是可预测的,并且在某些情况下,每次调用程序时它们都可能相同。)
如果你想要更精确的种子(并且只有当你启动程序时每隔一次时才会想要这个),你应该使用更好的计时器解析度。例如,来自LuaSocket的socket.gettime()
。将它乘以某个值,因为math.randomseed
仅使用整数部分,socket.gettime()
返回时间(浮点)秒。
require 'socket'
math.randomseed(socket.gettime() * 1e6)
for i = 1, 1e3 do
print(math.random())
end
答案 1 :(得分:3)
然而,这种方法有一个主要方法 弱点;返回的号码是 总是和当前一样随机 时间,和每个随机的间隔 数字是一秒,这也是一种方式 如果一个人需要很多随机值 在很短的时间内。
只有在你错误地实现它时才会有这些弱点。
应该谨慎地调用{p>math.randomseed
- 通常只在程序开始时调用一次,并且通常使用os.time
种子。设置种子后,您可以多次使用math.random
,它将产生随机值。
了解此示例会发生什么:
> math.randomseed(1)
> return math.random(), math.random(), math.random()
0.84018771715471 0.39438292681909 0.78309922375861
> math.randomseed(2)
> return math.random(), math.random(), math.random()
0.70097636929759 0.80967634907443 0.088795455214007
> math.randomseed(1)
> return math.random(), math.random(), math.random()
0.84018771715471 0.39438292681909 0.78309922375861
当我将种子从1更改为2时,我得到不同的随机结果。但是当我回到1时,“随机序列”被重置。我获得了与以前相同的值。
os.time()
会返回一个不断增加的数字。使用它作为种子是合适的;那么你可以永远调用math.random
并在每次调用时都有不同的随机数。
您必须对非随机性有点担心的唯一情况是您的程序应该每秒执行多次。在这种情况下,正如其他人所说,最简单的解决方案是使用更高清晰度的时钟。
换句话说:
os.time()
可以是99%的情况)math.random
。问候!
答案 2 :(得分:2)
关于问题第一部分的一些想法:
所以我的问题是,
realrandom
这些数字的随机性是多少?
您的函数正在尝试使用其默认实现tostring()
的怪癖来发现表的地址。我不相信tostring{}
返回的字符串具有指定的格式,或者该字符串中包含的值具有任何记录的含义。实际上,它源自与特定表相关的 something 的地址,因此不同的表转换为不同的字符串。但是,Lua的下一个版本可以自由地将其更改为方便的任何内容。更糟糕的是,它所采用的格式将高度依赖平台,因为它似乎使用%p
格式说明符sprintf()
,它只被指定为指针的合理表示。
还有一个更大的问题。虽然在一个进程中创建的第n个表的地址在您的平台上可能看起来是随机的,但tt可能根本不是随机的。或者它可能只有几位变化。例如,在我的win7盒子上只有几位变化,而且不是很随机:
C:...>for /L %i in (1,1,20) do @ lua -e "print{}" table: 0042E5D8 table: 0061E5D8 table: 0024E5D8 table: 0049E5D8 table: 0042E5D8 table: 0042E5D8 table: 0042E5D8 table: 0064E5D8 table: 0042E5D8 table: 002FE5D8 table: 0042E5D8 table: 0049E5D8 table: 0042E5D8 table: 0042E5D8 table: 0042E5D8 table: 0024E5D8 table: 0042E5D8 table: 0042E5D8 table: 0061E5D8 table: 0042E5D8
其他平台当然会有所不同。我甚至希望有一些平台,其中第一个分配的表的地址是完全确定的,因此在每次运行程序时都是相同的。
简而言之,过程映像中任意对象的地址不是一个非常好的随机源。
编辑:为了完整起见,我想补充一些其他想法,这些想法在夜间浮现在脑海中。
库存tostring()
函数由基本库提供,并由函数luaB_tostring()
实现。相关位是这个片段:
switch (lua_type(L, 1)) {
...
default:
lua_pushfstring(L, "%s: %p", luaL_typename(L, 1), lua_topointer(L, 1));
break;
如果您真的在调用此函数,那么字符串的结尾将是一个地址,由标准C sprintf()
格式%p
表示,与特定表格密切相关。一个观察是我见过%p
的几个不同的实现。 Windows MSVCR80.DLL(当前版本的Lua for Windows使用的C库版本)使其等同于%08X
。我的Ubuntu Karmic Koala框似乎相当于%#x
,它显着地降低了前导零。如果你要解析字符串的那一部分,那么你应该以一种在%p
的含义变化时更灵活的方式来做。
另请注意,在库代码中执行此类操作可能会让您遇到一些意外情况。
首先,如果传递给tostring()
的表有一个提供函数__tostring()
的元表,那么将调用该函数,并且根本不会执行上面引用的片段。在您的情况下,该问题不会出现,因为表具有单独的元表,并且您不会意外地将metatable应用于本地表。
其次,当您的模块加载时,其他一些模块或用户提供的代码可能已用其他内容替换了库存tostring()
。如果替换是良性的(例如memoization包装器),则可能与编写的代码无关。但是,这将是攻击的来源,并且完全不受模块的控制。如果目标是为您的随机种子材料提供某种改进的安全性,那么这并不是一个好主意。
第三,你可能根本没有加载一个库存Lua解释器,而较大的应用程序(Lightroom,WoW,Wireshark,...)可能会选择用自己的实现替换基本库函数。对于tostring()
来说这是一个不太可能发生的问题,但请注意,基本库的print()
是替代实施中替换或删除的常用目标,并且有一些模块(Lua Lanes)如果print
不是基础库中的实现,则会中断。
答案 3 :(得分:1)
我想到了一些重要的事情:
如果您需要更多信息,请参阅维基百科上的PRNG文章,并根据需要使用参考/链接。